Read a Response

Su Suriya Ravichandran Updated 10 Nov 2026
9 min read ·Lesson 14 of 200

What messages.create() Returns

When you call client.messages.create(...), the SDK returns a Message object. It is a typed Python object that represents Claude's complete reply and everything the API reported about it.

from anthropic import Anthropic

client = Anthropic()

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

print(type(message))
<class 'anthropic.types.message.Message'>

Reading a response correctly means more than printing the text. A robust application also checks why Claude stopped, how many tokens were used, and which request produced the reply. This lesson explains every part of the Message object and how to use each one.


The Structure of a Response

Converted to JSON, a typical response looks like this. Values will differ for you, and some optional fields are omitted.

{
  "id": "msg_01XFDUDYJgAACzvnptvVoYEL",
  "type": "message",
  "role": "assistant",
  "model": "claude-opus-5-5",
  "content": [
    {
      "type": "thinking",
      "thinking": "",
      "signature": "EuYBCkQYAiJA..."
    },
    {
      "type": "text",
      "text": "Three popular Python web frameworks are Django, Flask, and FastAPI."
    }
  ],
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "stop_details": null,
  "usage": {
    "input_tokens": 15,
    "output_tokens": 88,
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 0
  }
}

The fields fall into four groups:

GroupFieldsAnswers the question
Identityid, type, role, modelWhat is this, and who produced it?
ContentcontentWhat did Claude say?
Completionstop_reason, stop_sequence, stop_detailsWhy did Claude stop, and is the answer complete?
UsageusageHow many tokens did this cost?

Identity Fields

print(message.id)      # msg_01XFDUDYJgAACzvnptvVoYEL
print(message.type)    # message
print(message.role)    # assistant
print(message.model)   # claude-opus-5-5
FieldMeaningPractical use
idA unique identifier for this message, starting with msg_Store it with your records and logs to refer to a specific reply
typeAlways "message" for a successful Messages API responseDistinguishing object types when processing mixed data
roleAlways "assistant", because the reply comes from ClaudeAppending the reply to a conversation history (Lesson 24)
modelThe model that generated the replyConfirming which model answered, especially when the model is configurable

The Content Field

message.content is a list of content blocks. Each block has a type that tells you what kind of content it is:

for block in message.content:
    print(block.type)
thinking
text

The two block types you will see in ordinary requests are:

Block typeContainsShow it to users?
textClaude's answer, in block.textYes
thinkingClaude's internal reasoning. The thinking text is empty by default; a signature field is also present.No, unless you deliberately request and display summarized thinking

Other block types appear when you use tools and other features. Lesson 16 covers all of them.

Reading the Text

Read text by type, never by position:

answer = "".join(block.text for block in message.content if block.type == "text")
print(answer)

Why not message.content[0].text? On Claude Opus 5.5, thinking is always on, so the first block is often a thinking block, which has no text attribute. Position-based access fails intermittently, depending on whether the model chose to think. Type-based access always works (Lesson 11).


Completion Fields: Is the Answer Complete?

stop_reason

stop_reason explains why generation ended. Check it before using the content.

ValueMeaningIs the content a complete answer?
end_turnClaude finished naturallyYes
max_tokensThe max_tokens limit was reachedNo: truncated (Lesson 13)
stop_sequenceOne of your custom stop sequences appearedYes, ending where you asked (Lesson 48)
tool_useClaude wants your application to run a toolNot yet: your code must act (Lesson 85)
pause_turnA long server-side tool operation pausedNot yet: send the content back to continue
refusalClaude declined the requestNo: do not treat it as a normal answer
model_context_window_exceededThe context window filled upNo: truncated

Lesson 20 (Handle Stop Reasons) shows how to handle each value.

stop_sequence

When stop_reason is "stop_sequence", this field contains the exact sequence that ended generation. Otherwise, it is None.

stop_details

When stop_reason is "refusal", stop_details describes the refusal, for example the policy category that triggered it. For every other stop reason, it is None.

if message.stop_reason == "refusal":
    print("Refused:", message.stop_details)

Usage Fields: What Did It Cost?

message.usage reports token counts for this request:

usage = message.usage

print(usage.input_tokens)                  # Tokens in your request
print(usage.output_tokens)                 # Tokens generated, including thinking
print(usage.cache_creation_input_tokens)   # Tokens written to the prompt cache
print(usage.cache_read_input_tokens)       # Tokens read from the prompt cache
FieldMeaning
input_tokensInput tokens processed normally: your system prompt, messages, and so on
output_tokensAll generated tokens, including thinking tokens that are not shown
cache_creation_input_tokensInput tokens written to the prompt cache (Lessons 121–130)
cache_read_input_tokensInput tokens read from the cache, which are billed at a much lower rate

Some features add further fields. For example, server tools report how many times they were used.

Practical use: multiply these counts by your model's prices to get the exact cost of the request (Lesson 12). Logging usage for every request is the foundation of cost monitoring (Lesson 186).


The Request ID

Every response carries the ID the API assigned to the HTTP request:

print(message._request_id)   # req_018EeWyXxfu5pfWkrYcMdjWG
message.idmessage._request_id
IdentifiesThe message (Claude's reply)The HTTP request that produced it
Prefixmsg_req_
Main useReferring to replies in your applicationSupport tickets and debugging with Anthropic

Although _request_id starts with an underscore, it is a public, documented property.


Converting a Response

To a Dictionary or JSON

data = message.to_dict()      # Python dict
text = message.to_json()      # JSON-formatted string (indented by default)

When to use them:

  • to_dict() when you need plain data, for example to return from a web API, store in a document database, or compare in tests.
  • to_json() for logging, saving to a file, or reading the full structure while debugging.

Missing Fields vs. None

Some fields can be absent from a response, or present with the value null. Both appear as None in Python. When the difference matters, check model_fields_set:

if message.stop_sequence is None:
    if "stop_sequence" in message.model_fields_set:
        print("The API returned stop_sequence = null")
    else:
        print("The API did not include stop_sequence at all")

model_fields_set is a Pydantic feature. It lists the fields that were actually present in the JSON the API returned. Most applications never need this distinction, but it is useful when debugging unexpected data.


Reading HTTP Headers with with_raw_response

The Message object contains the response body. To read HTTP headers as well, such as rate-limit information or the workspace that handled the request, use .with_raw_response:

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"))
print(raw.headers.get("anthropic-workspace-id"))

message = raw.parse()   # The same Message object that create() would have returned
  • with_raw_response returns an APIResponse wrapper instead of the Message.
  • raw.headers holds the HTTP response headers.
  • raw.parse() gives you the usual typed Message.

Lesson 159 (Read Raw Responses) covers this in depth.


Practical Example: A Response Inspector

During development, it helps to see every part of a response in a readable form. This utility prints a structured report for any Message.

"""inspect_response.py: print a readable report of a Message object."""

from anthropic import Anthropic
from anthropic.types import Message

COMPLETE_REASONS = {"end_turn", "stop_sequence"}


def get_text(message: Message) -> str:
    return "".join(block.text for block in message.content if block.type == "text")


def inspect(message: Message) -> None:
    usage = message.usage
    complete = message.stop_reason in COMPLETE_REASONS

    print("=" * 60)
    print(f"Message ID : {message.id}")
    print(f"Request ID : {message._request_id}")
    print(f"Model      : {message.model}")
    print(f"Blocks     : {[block.type for block in message.content]}")
    print(f"Stop reason: {message.stop_reason}" + ("" if complete else "  <-- check this"))
    if message.stop_details is not None:
        print(f"Details    : {message.stop_details}")
    print(f"Tokens     : in={usage.input_tokens}, out={usage.output_tokens}")
    print("-" * 60)
    print(get_text(message) or "(no text in this response)")
    print("=" * 60)


if __name__ == "__main__":
    client = Anthropic()
    reply = client.messages.create(
        model="claude-opus-5-5",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Name three Python web frameworks, one line each."}],
    )
    inspect(reply)

Example output:

============================================================
Message ID : msg_01XFDUDYJgAACzvnptvVoYEL
Request ID : req_018EeWyXxfu5pfWkrYcMdjWG
Model      : claude-opus-5-5
Blocks     : ['thinking', 'text']
Stop reason: end_turn
Tokens     : in=19, out=96
------------------------------------------------------------
Django: a full-featured framework with an ORM, admin, and authentication built in.
Flask: a lightweight microframework you extend with libraries as needed.
FastAPI: a modern async framework with automatic validation and API docs.
============================================================

How It Works

  • COMPLETE_REASONS defines which stop reasons mean "this is a finished answer". Any other value gets a visible marker, so problems such as truncation stand out immediately.
  • Blocks shows the block types, which makes thinking blocks and tool calls visible while you learn.
  • stop_details is printed only when present, which is when Claude refused.
  • get_text(message) or "(no text...)" prints a clear placeholder when there is no text, for example when a small max_tokens was used up by thinking. An empty line would be confusing.
  • Both IDs are shown, because you will need the request ID if you ever contact support.

Common Mistakes

Treating the Response Like a Dictionary

message["content"]   # TypeError: 'Message' object is not subscriptable

Fix: use attributes, as in message.content, or convert with message.to_dict() first.

Reading Text by Position

What happens: message.content[0].text raises AttributeError whenever the first block is a thinking block.

Fix: filter blocks by type.

Ignoring stop_reason

What happens: truncated answers, refusals, and pending tool calls are treated as normal, complete answers.

Fix: check stop_reason first, every time it matters.

Estimating Cost from the Visible Text

What happens: costs are underestimated, because thinking tokens are counted in output_tokens but not visible.

Fix: always use message.usage.

Confusing id and _request_id

What happens: support cannot locate the request because the message ID was provided instead of the request ID.

Fix: log both, and give support the req_... value.


Best Practices

  • Check stop_reason before using content.
  • Extract text by block type with a helper function.
  • Log id, _request_id, model, stop_reason, and usage for every request in production.
  • Use to_dict() or to_json() when storing or returning responses, never str(message).
  • Keep response handling in one place, such as a helper or service module, so changes to response handling are made once.

Exercise

  1. Send any request and print message.to_json(). Identify each of the four field groups.
  2. Use the inspect() function on a request with max_tokens=40 and a question that needs a long answer. What does the inspector show?
  3. Write a function summarize_usage(message) that returns a string like "in=19 out=96 total=115".
  4. Use with_raw_response to print the request-id header, and confirm that it matches message._request_id from the parsed message.
Show answers

Step 2: the stop reason is max_tokens, marked with <-- check this. The text is either cut off, or replaced by (no text in this response) if thinking used the whole budget.

Step 3:

def summarize_usage(message) -> str:
    usage = message.usage
    total = usage.input_tokens + usage.output_tokens
    return f"in={usage.input_tokens} out={usage.output_tokens} total={total}"

Step 4:

raw = client.messages.with_raw_response.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}],
)
message = raw.parse()
print(raw.headers.get("request-id"))
print(message._request_id)   # The same value

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 a Response and get answers drawn from it.

Signed-in readers only.