Read Raw Responses

Su Suriya Ravichandran Updated 10 Nov 2026
5 min read ·Lesson 159 of 200

Looking Beneath the Parsed Message

Every example so far has received a parsed message: an object with content blocks, a stop reason, and usage. That object is the convenient view of a response, and it hides the envelope the response arrived in. The envelope holds the HTTP status, the headers, and the request ID, along with the body. Most applications never need those parts. Some need them badly: a rate-limit dashboard reads headers, a support workflow records request IDs, and a debugging session wants to see exactly what the server sent.

The SDK provides a raw response interface for this. It returns the same data as a normal call, wrapped so the envelope is available, and the parsed message is produced only when you ask for it. This lesson explains how to use that wrapper, which headers are worth reading, and how to record them without leaking anything sensitive.


The Raw Response Wrapper

A normal call returns the parsed message directly. A raw call goes through a different entry point, with_raw_response, and returns a wrapper:

raw = client.messages.with_raw_response.create(
    model="claude-opus-5-5",
    max_tokens=256,
    messages=[{"role": "user", "content": "Say hello."}],
)

message = raw.parse()                       # The parsed message, as usual
request_id = raw.headers.get("request-id")  # The envelope, available before or after parsing

The wrapper exposes headers, which behave like a dictionary, and parse(), which returns the message object. The same data is available either way, so the choice is about what the code needs to read. Calling parse() is what gives you the typed message, and reading headers does not require parsing first.

The documentation's naming for these entry points may differ slightly between SDK versions, and the SDK reference should be checked for the exact methods of the version in use. The pattern, a wrapper with headers and a parse step, is the same across versions.


Which Headers Matter

A response carries many headers, and only a few help with reliability and support:

HeaderUse
request-idThe identifier support teams need (Lesson 148)
Rate-limit headersRemaining allowance, for pacing and dashboards (Lesson 142)
retry-afterThe wait the service asks for, on rate-limited responses (Lesson 142)
Model or version headersConfirming which model version served the request

The exact names of the rate-limit headers are listed in the rate limits documentation, and they should be copied from there. A dashboard should read them from every response, not only from failures, because the trend in remaining allowance is what warns of trouble before a 429 arrives.


Recording Headers Safely

Headers can contain values that must never be logged. Authentication information, session cookies, and other credentials may appear in some responses or in requests, and a log that records every header is a leak waiting to happen. The safe approach records an explicit allowlist of headers, and drops everything else:

"""raw_headers.py: read a raw response, and record only an allowlist of headers."""

SAFE_HEADERS = {"request-id", "retry-after", "content-type"}
SENSITIVE_MARKERS = ("authorization", "cookie", "set-cookie", "api-key")


def safe_header_summary(headers: dict) -> dict:
    summary = {}
    for name, value in headers.items():
        key = name.lower()
        if any(marker in key for marker in SENSITIVE_MARKERS):
            continue
        if key in SAFE_HEADERS:
            summary[key] = value
    return summary


class FakeRawResponse:
    """Stands in for the SDK's wrapper, so the example runs offline."""

    def __init__(self, headers: dict, body: dict):
        self.headers = headers
        self._body = body

    def parse(self) -> dict:
        return self._body


if __name__ == "__main__":
    raw = FakeRawResponse(
        headers={"request-id": "req_0198", "content-type": "application/json",
                 "retry-after": "2", "set-cookie": "session=abc123",
                 "x-api-key-echo": "sk-demo-not-real"},
        body={"id": "msg_01", "stop_reason": "end_turn"},
    )
    print("summary:", safe_header_summary(raw.headers))
    print("parsed stop reason:", raw.parse()["stop_reason"])

Output:

summary: {'request-id': 'req_0198', 'content-type': 'application/json', 'retry-after': '2'}
parsed stop reason: end_turn

How It Works

  1. The summary is an allowlist. Only the three headers named in SAFE_HEADERS can appear in the output. Anything not on the list is dropped, including headers that a future version of the service might add.
  2. Sensitive names are removed even if they are allowed by mistake. The second filter drops any header whose name contains a credential marker, such as cookies or key material. Two layers protect against two kinds of error: an unknown sensitive header, and an allowed header that turns out to be sensitive.
  3. The header names are compared in lowercase. HTTP header names are case-insensitive, so the check must be too, or Request-ID would slip past a comparison against request-id.
  4. The parsed body is still available after the headers have been read. The wrapper separates the two, so logging does not require the body, and the body does not need to be logged to find the request ID.
  5. The fake key value in the example shows that a header carrying key-like material is excluded. The example uses an obviously fake value, so no real credential appears in the material.

When Raw Access Is Worth It

Raw access is a tool for specific jobs, and most code does not need it. It is worth using when a dashboard reads rate-limit headers from every call, when a support workflow must store the request ID before parsing could fail, or when an investigation requires the exact status and headers of a problematic response. For everyday application code, the parsed message and the error object already carry what is needed.

A subtle point follows from this. Reading the raw response is itself a choice that affects what gets logged, and the allowlist discipline from Lesson 147 applies with extra force, because the envelope contains more than the body does.


Streaming Responses

Streaming has its own envelope. The equivalent entry point, with_streaming_response, provides headers before the stream begins, which lets an application read the request ID and rate-limit state early, and then consume the events. Lesson 53 covered the events themselves. The documentation for streaming gives the exact context-manager form, and that form should be copied from the SDK reference, since it involves resource handling that must close properly.


Summary

The raw response wrapper exposes the HTTP envelope around a message: the headers, and a parse step that produces the typed object. Headers carry the request ID, the rate-limit state, and the retry instruction, which are valuable for reliability and support. Recording them requires an allowlist, because the envelope can contain credentials that must never reach a log. The next lesson completes the section with the other side of the request: setting headers that the application sends.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Read Raw Responses and get answers drawn from it.

Signed-in readers only.