The Claude API is one endpoint, POST /v1/messages. Tools, thinking, caching, structured output and streaming are all fields on that one request, and the response is a list of typed blocks plus a stop_reason. Read the blocks by type and branch on stop_reason, and most production bugs never happen.
Pick a model by ID
List prices per million tokens, read from the pricing page on 21 October 2026. Every current model has a 1M-token context window and up to 128K output tokens, and output costs five times input on all of them.
| Model | ID | In / out |
|---|---|---|
| Fable 5.1 | claude-fable-5-1 | $10 / $50 |
| Opus 5.5 | claude-opus-5-5 | $4 / $20 |
| Sonnet 5.5 | claude-sonnet-5-5 | $2 / $10 |
| Haiku 5.5 | claude-haiku-5-5 | $0.10 / $0.50 |
Haiku 5.5's price holds for prompts up to 100,000 tokens; above that every rate is five times higher. IDs are complete as written: no date suffix.
Start with Opus 5.5, which the models page recommends for most workloads. Move up to Fable 5.1 only when evals at higher effort still fall short, and down to Haiku 5.5 for classification, extraction and routing (see tiered model routing). Two older names are on their way out: Haiku 4.5 ($1 / $5, 200K context) is still active, and Sonnet 4.5's retirement date is 30 November 2026, with claude-sonnet-5-5 as its replacement.
The tokenizer point matters when you compare bills: the 4.7-and-later models count about 30% more tokens than Sonnet 4.6 and earlier for the same text, so a per-token price comparison across generations understates the difference.
Make the first request, and read it by type
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=16000,
system="Answer support questions from the policy text only.",
messages=[{"role": "user", "content": "Can I return an opened item?"}],
)
if response.stop_reason == "end_turn":
answer = "".join(b.text for b in response.content if b.type == "text")
print(answer)
else:
print("stopped early:", response.stop_reason)
response.content is a list, not a string. On current models adaptive thinking is on by default, so the first block is often a thinking block and content[0].text raises. Join the text blocks, and only after you have checked why the model stopped:
stop_reason | What your code does |
|---|---|
end_turn | finished: read the text blocks |
tool_use | run every tool_use block, send the results, call again |
max_tokens | cut off: raise the cap and stream; never run a half-written tool call |
pause_turn | a server tool paused: send the assistant turn back unchanged |
refusal | declined: surface it and read stop_details |
The field-by-field reference, errors and retries are in the Messages API.
Know what returns a 400 now
If you learned this API in 2025, several shapes that used to work are now rejected. Each has a direct replacement:
temperature,top_p,top_k- 400 on current models. Steer with
output_config.effortand say what you want in the prompt. budget_tokens- 400. Use
thinking: {"type": "adaptive"}plus effort. - Assistant prefill
- 400. Use structured output, or an instruction.
- Forced
tool_choice anyor a named tool is a 400 on Opus 5.5, Sonnet 5.5 and Fable 5.1. Useauto, astricttool, and say when to call it.output_format- Deprecated at the top level. Use
output_config.format. - Thinking
disabled - 400 on Sonnet 5.5 (send
between_tools); Opus 5.5 always thinks.
Haiku 5.5 still accepts a forced tool_choice, but the response then starts with the tool call and has no thinking block. Sources: the Sonnet 5.5 and Haiku 5.5 migration guides.
Set effort instead of temperature
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=16000,
thinking={"type": "adaptive", "display": "summarized"},
output_config={"effort": "high"}, # low | medium | high | xhigh | max
messages=messages,
)
for block in response.content:
if block.type == "thinking":
log.debug("summary: %s", block.thinking) # empty unless display is set
elif block.type == "text":
print(block.text)
Where you once turned temperature, you now choose how hard the model works. effort lives inside output_config, not at the top level. The default is high on most models, medium on Opus 5.5 and Haiku 5.5.
low- routing, extraction, subagents that look things up
medium/high- most product traffic; measure before moving
xhigh- the usual pick for coding and long agent runs
max- when a wrong answer costs more than the tokens
Thinking text is omitted by default: thinking blocks arrive with an empty thinking field and a signature. On Opus 5.5 the short notes the model writes between tool calls also come back as thinking blocks, so an app that streams progress goes quiet until it sets display: "summarized". Pass thinking blocks back unchanged when you continue the conversation. More in extended thinking.
Follow one tool call on the wire
A tool is a name, a description and a JSON schema. The model never calls your API: it asks, your code calls, and you send the result back. These are the six messages, with their real payloads:
- Requesttools + messages
- tool_usestop_reason pauses
- Your codecalls the real API
- 200 OKthe real API answers
- tool_resultsent back in one user turn
- end_turnthe final text
Interactive: step through the six messages of one tool call and read each payload.
Put strict: true on the tool definition, with required and additionalProperties: false, and the input is guaranteed to match the schema. That is also the replacement for forced tool_choice on the 5.5 models. The description does most of the selection work, especially the sentence that says when not to use the tool (see tool surface design).
Run the loop, and return every result in one message
def run(messages: list, max_steps: int = 20):
for _ in range(max_steps): # a budget, never while True
resp = client.messages.create(
model="claude-opus-5-5", max_tokens=16000,
tools=TOOLS, messages=messages)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason != "tool_use": # end_turn, max_tokens, refusal...
return resp
results = []
for b in resp.content:
if b.type != "tool_use":
continue
try:
out = str(TOOL_FNS[b.name](**b.input))
results.append({"type": "tool_result", "tool_use_id": b.id, "content": out})
except Exception as e: # errors go back as results
results.append({"type": "tool_result", "tool_use_id": b.id,
"content": f"{type(e).__name__}: {e}", "is_error": True})
messages.append({"role": "user", "content": results}) # ONE message
raise RuntimeError("max_steps reached")
One assistant turn can hold several tool_use blocks, because parallel tool use is on by default. Send all their results back in a single user message. Split them across messages and the model learns to stop calling in parallel: the agent gets slower and nothing errors. A failed tool still gets a tool_result, with is_error: true; a missing one makes the next request fail.
You rarely need to hand-write this. The SDK's Tool Runner (client.beta.messages.tool_runner) runs the same loop for decorated functions, and the Claude Agent SDK gives you the whole Claude Code harness. Which one to pick is who owns the loop; the loop itself, with every guard, is the agent loop.
Stream anything long
- message_startid, model, empty content
- content_block_deltatext, tool input JSON, thinking
- content_block_stopone block done
- message_deltastop_reason and usage
- message_stopthe end
with client.messages.stream(
model="claude-opus-5-5",
max_tokens=64000,
messages=messages,
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
final = stream.get_final_message() # the assembled Message
print(final.stop_reason, final.usage.output_tokens)
Up to 128K output tokens will not finish inside an HTTP timeout, so the SDK expects you to stream large max_tokens values. A sensible default is about 16K non-streaming and 64K streaming. A low cap does not save money: you pay for what is generated, and a truncated answer costs a retry. Tool inputs stream as input_json_delta fragments; parse them once the block stops. See streaming.
Cache the stable prefix
response = client.messages.create(
model="claude-sonnet-5-5", max_tokens=16000,
system=[{"type": "text", "text": FROZEN_INSTRUCTIONS,
"cache_control": {"type": "ephemeral"}}], # breakpoint after system
messages=messages,
)
u = response.usage
print(u.cache_read_input_tokens) # served from cache
print(u.cache_creation_input_tokens) # written to cache this call
print(u.input_tokens) # after the breakpoint, full price
The cache matches a prefix in the fixed order tools, system, messages. Any byte that changes before your breakpoint invalidates everything after it. Take a 20,000-token system prompt on Sonnet 5.5, called 1,000 times inside the cache lifetime:
Cache hits are not 0.1x everywhere: Sonnet 5.5 and Opus 5.5 read at 0.05x of input, Fable 5.1 at 0.025x, Haiku 5.5 at 0.1x. The 5-minute write costs 1.25x and the 1-hour write 2x. The minimum cacheable prefix on the 5.5 models is 512 tokens; a shorter one is silently not cached. Usage is the only honest check. Details in prompt caching and the interview answer how prompt caching works.
Constrain the output, don't ask nicely
schema = {
"type": "object",
"properties": {
"category": {"type": "string", "enum": ["billing", "bug", "other"]},
"summary": {"type": "string"},
},
"required": ["category", "summary"],
"additionalProperties": False,
}
response = client.messages.create(
model="claude-sonnet-5-5", max_tokens=4000,
output_config={"format": {"type": "json_schema", "schema": schema}},
messages=[{"role": "user", "content": ticket_text}],
)
text = next(b.text for b in response.content if b.type == "text")
ticket = json.loads(text) # valid shape; still check the values
A JSON schema in output_config.format guarantees the shape: schemas need additionalProperties: false, and the SDKs strip numeric and length constraints and check them client-side. It does not guarantee the values, so a well-formed ticket can still carry the wrong category. Keep validating. client.messages.parse() does the parsing and validation for you. See structured output.
Reading content[0].text. Appending only the answer text instead of the full content, which drops tool calls and thinking blocks. Splitting parallel tool results across messages. Setting max_tokens low to save money. A timestamp or a reordered tool list in front of the cache breakpoint. None of these raise an error; all of them show up in stop_reason and usage.
“Walk me through what happens when Claude calls a tool.” Name the six messages: request with tools, tool_use with stop_reason: "tool_use", your code calls the real system with its own credentials, one user message of tool_result blocks keyed by tool_use_id, then end_turn. Then say what you guard: a step budget, errors as results, strict schemas, and approvals before anything irreversible.
A classifier used tool_choice: {"type": "tool"} to force JSON. After moving from Opus 5 to Opus 5.5 every call returns 400. What do you change?
Forced tool choice is rejected on Opus 5.5. For a classifier, switch to structured output (output_config.format with a JSON schema), which guarantees the shape without a tool. If you keep the tool, use tool_choice: auto, mark the tool strict: true and say in the prompt that it must be called. Haiku 5.5 would accept the forced choice, and is cheaper for classification anyway.
Your system prompt is 300 tokens, marked with cache_control, and cache_creation_input_tokens is always 0. Why?
It is below the 512-token minimum cacheable prefix, so the API silently does not cache it. Move the breakpoint later (after the tools and a longer stable block), or accept that a prefix this small is not worth caching.
The model asks for three tools in one turn and you send each result in its own user message. What goes wrong, and when do you notice?
Nothing errors. Over the following turns the model learns to call tools one at a time, so runs get slower. Send all three tool_result blocks in one user message, ahead of any text, and include failed calls with is_error: true.
- One endpoint; tools, thinking, caching, structured output and streaming are request fields.
- Read
contentby block type and branch onstop_reasonbefore you read anything. - Sampling parameters,
budget_tokens, prefill and (on Opus 5.5, Sonnet 5.5, Fable 5.1) forcedtool_choicereturn 400; use effort, adaptive thinking, structured output andstricttools. - All tool results for a turn go back in one user message, errors included.
- Cache a stable prefix; on Sonnet 5.5 a hit costs 0.05x of input, and
usageproves it works.