Read Request IDs

Su Suriya Ravichandran Updated 10 Nov 2026
7 min read ·Lesson 49 of 200

What a Request ID Is

Every HTTP request to the Claude API receives a unique request ID, a string that starts with req_:

req_018EeWyXxfu5pfWkrYcMdjWG

The API returns it in the request-id response header for every response, both successful and failed. It identifies the exact request on Anthropic's side, which makes it the single most useful value when you investigate a problem or contact support.

Lesson 29 compared request IDs with message IDs. This lesson focuses on reading request IDs in every situation (success, raw responses, and errors) and on making them part of your logging.


Why Request IDs Matter

SituationHow the request ID helps
A user reports a strange or wrong answerFind the exact request in your logs, with its prompt, model, and usage
A request failed with an errorGive the request ID to Anthropic support so they can investigate that request
Intermittent failuresGroup and compare failing requests by ID, time, and error type
Tracing across systemsConnect your application's logs, monitoring, and user reports to one specific API call

A message ID (msg_…) exists only for successful responses. A request ID exists for every request that reached the API, including failures. That is why it is the better value for error investigation.


Reading the Request ID from a Successful Response

The SDK attaches the request ID to response objects as _request_id:

from anthropic import Anthropic

client = Anthropic()

message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}],
)

print(message._request_id)   # req_018EeWyXxfu5pfWkrYcMdjWG

Although the name begins with an underscore, _request_id is a public, documented property. The underscore only avoids clashing with field names from the API response.


Reading It from Raw Responses

with_raw_response gives access to all HTTP headers (Lesson 14), including the request ID and other useful metadata:

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

print(raw.headers.get("request-id"))                 # req_...
print(raw.headers.get("anthropic-organization-id"))  # Your organization
print(raw.headers.get("anthropic-workspace-id"))     # The workspace that ran the request

message = raw.parse()                                # The usual Message object
HeaderMeaning
request-idUnique ID of this request
anthropic-organization-idThe organization the API key belongs to
anthropic-workspace-idThe workspace the request ran in

Rate-limit headers are also available here. Lesson 142 uses them.


Reading It from Errors

Failed requests do not return a Message, but errors that came from the API still carry the request ID. For APIStatusError and its subclasses, such as RateLimitError, BadRequestError, and AuthenticationError, read it from the HTTP response attached to the exception:

import anthropic

try:
    client.messages.create(
        model="claude-does-not-exist",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hello"}],
    )
except anthropic.APIStatusError as error:
    request_id = error.response.headers.get("request-id")
    print(f"HTTP {error.status_code}, request ID: {request_id}")
HTTP 404, request ID: req_011CSHoEeqs5C35K2UUqR7Fy

The JSON error body also contains a request_id field (Lesson 2), so the ID is available even if you only have the body.

Errors Without a Request ID

APIConnectionError and APITimeoutError mean the request never received a response, for example because of a network failure. They have no HTTP response and therefore no request ID. For these errors, log the time, the operation, and the error message instead.

Error typeRequest ID available?Where
Successful responseYesmessage._request_id
APIStatusError and subclasses (4xx/5xx)Yeserror.response.headers.get("request-id")
APIConnectionError, APITimeoutErrorNo—

Request IDs in Logs

Request IDs are most valuable when they are automatically written to your logs with every API call. A good log line for an API call includes:

FieldExample
Time2026-11-05T10:15:02Z
Your own operation or feature namesummarize_ticket
Your user or session identifier (non-sensitive)user=4821
Request IDreq_018EeWyX…
Outcomeok, error 429, connection_error
Model, stop reason, and usage (on success)claude-opus-5-5, end_turn, in=320 out=145
Duration2.4s

With these fields, any problem report can be traced to the exact request, and any request can be traced back to the feature and user involved.


Practical Example: A Wrapper That Logs Every Request ID

This wrapper sends a request and writes one structured log line for every outcome, whether success, API error, or connection error, always including the request ID when one exists. It also produces a ready-to-send support summary for failed requests.

"""request_logging.py: log request IDs for every API call outcome."""

import json
import logging
import time

import anthropic
from anthropic import Anthropic

logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger("claude")
client = Anthropic()


def log_event(**fields) -> None:
    logger.info(json.dumps(fields, ensure_ascii=False))


def call_claude(operation: str, user_id: str, **request):
    start = time.perf_counter()
    base = {"operation": operation, "user": user_id, "model": request.get("model")}

    try:
        message = client.messages.create(**request)
    except anthropic.APIStatusError as error:
        request_id = error.response.headers.get("request-id")
        log_event(**base, outcome=f"error_{error.status_code}", request_id=request_id,
                  error=error.message, seconds=round(time.perf_counter() - start, 2))
        raise
    except anthropic.APIConnectionError as error:
        log_event(**base, outcome="connection_error", request_id=None,
                  error=str(error), seconds=round(time.perf_counter() - start, 2))
        raise

    log_event(**base, outcome="ok", request_id=message._request_id, message_id=message.id,
              stop_reason=message.stop_reason, input_tokens=message.usage.input_tokens,
              output_tokens=message.usage.output_tokens,
              seconds=round(time.perf_counter() - start, 2))
    return message


def support_summary(error: anthropic.APIStatusError) -> str:
    """Text to include when contacting Anthropic support about a failed request."""
    return (f"Request ID: {error.response.headers.get('request-id')}\n"
            f"HTTP status: {error.status_code}\n"
            f"Error: {error.message}")


if __name__ == "__main__":
    call_claude("explain_term", "user-4821",
                model="claude-opus-5-5", max_tokens=1024,
                messages=[{"role": "user", "content": "What is a REST API? One sentence."}])

    try:
        call_claude("explain_term", "user-4821",
                    model="claude-does-not-exist", max_tokens=1024,
                    messages=[{"role": "user", "content": "Hello"}])
    except anthropic.APIStatusError as error:
        print("\n--- Support summary ---")
        print(support_summary(error))

Example output:

{"operation": "explain_term", "user": "user-4821", "model": "claude-opus-5-5", "outcome": "ok", "request_id": "req_018EeWyXxfu5pfWkrYcMdjWG", "message_id": "msg_01Q8Faay6S7QPTvEUUQARt7h", "stop_reason": "end_turn", "input_tokens": 17, "output_tokens": 64, "seconds": 2.1}
{"operation": "explain_term", "user": "user-4821", "model": "claude-does-not-exist", "outcome": "error_404", "request_id": "req_011CSHoEeqs5C35K2UUqR7Fy", "error": "...", "seconds": 0.3}

--- Support summary ---
Request ID: req_011CSHoEeqs5C35K2UUqR7Fy
HTTP status: 404
Error: ...

How It Works

  1. log_event() writes each event as one line of JSON. Structured logs like this can be searched and filtered by field in log tools, for example "all events where outcome starts with error_".
  2. call_claude() wraps messages.create() and logs exactly one event per call, whatever the outcome.
  3. API errors (APIStatusError) are logged with the HTTP status and the request ID from the response headers, then re-raised with raise, so the caller can still handle the error. Logging must not swallow errors.
  4. Connection errors are logged with request_id=None, which correctly reflects that no request ID exists.
  5. Successful calls log the request ID, message ID, stop reason, usage, and duration (time.perf_counter()), everything needed to investigate a reply later.
  6. support_summary() formats the information support needs. The request ID comes first because it identifies the request.

Note: be careful about what you log. Do not log API keys, and think carefully before logging full prompts or responses, which may contain personal data. Request IDs, message IDs, and usage numbers are safe and usually sufficient.


Common Mistakes

Logging Only Message IDs

What happens: failed requests leave no usable identifier.

Fix: log the request ID for every request, successful or not.

Swallowing Errors While Logging Them

What happens: a wrapper logs an error and returns None, so callers continue as if nothing happened.

Fix: re-raise after logging, or return an explicit error result.

Expecting a Request ID for Connection Errors

What happens: code crashes trying to read headers from an error that has no response.

Fix: read request IDs only from APIStatusError and its subclasses. Handle connection errors separately.

Unstructured Log Messages

What happens: "Error calling Claude: …" lines are hard to search and correlate.

Fix: log structured fields, including request_id, operation, and outcome.

Logging Sensitive Content

What happens: logs become a store of personal data or secrets.

Fix: log identifiers and metadata by default, and only log content deliberately, with access controls and retention limits.


Best Practices

  • Log the request ID for every API call, along with the operation, outcome, and duration.
  • Read it from _request_id on success and from the response headers on API errors.
  • Use structured (JSON) logs so request IDs can be searched.
  • Re-raise errors after logging them.
  • Include the request ID in support requests, and in internal bug reports.
  • Keep logs free of secrets, and minimize personal data.

Exercise

  1. Use with_raw_response to print the request-id, anthropic-organization-id, and anthropic-workspace-id headers for a request.
  2. Extend call_claude() to also catch anthropic.APITimeoutError separately, logging outcome="timeout". Where should that except block go, and why?
  3. Add a feature_version field to every log event, for example the prompt template version from Lesson 34.
  4. Write a function that reads a log file of JSON lines and prints every request ID whose outcome is not "ok".
Show answers for steps 2 and 4

Step 2: APITimeoutError is a subclass of APIConnectionError, so its except block must come before the APIConnectionError block. Otherwise the more general block would catch timeouts first.

except anthropic.APITimeoutError as error:
    log_event(**base, outcome="timeout", request_id=None,
              error=str(error), seconds=round(time.perf_counter() - start, 2))
    raise
except anthropic.APIConnectionError as error:
    ...

Step 4:

import json


def failed_request_ids(path: str) -> list[str]:
    ids = []
    with open(path, encoding="utf-8") as file:
        for line in file:
            event = json.loads(line)
            if event.get("outcome") != "ok":
                ids.append(event.get("request_id") or "(no request ID)")
    return ids

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 Request IDs and get answers drawn from it.

Signed-in readers only.