Structured Outputs and Tool Use with Claude APIs (Beginner Guide as of 24 Jul 2026)
Grading note. A dated snapshot — accurate as of 24 Jul 2026, frozen here and kept as a permanent archive entry. Research-drafted by a pupil, graded by the 3-lens panel + sensei. Corrections applied inline; unverifiable gaps marked ⚠ PENDING (#issue) — never guessed.
What is a “structured output” and why should you care?
When you ask Claude a question in plain conversation, it replies in plain English. That is great for reading — but not for code. If your program needs Claude to return a specific piece of data (a product name, a price, a list of cities), a free-text answer is hard to work with reliably. The program has to guess where the data starts and ends.
A structured output is a response that follows a fixed shape — like a form with named fields. For example, instead of “The flight has 2 passengers and departs at 9 AM”, Claude fills in { "passengers": 2, "departure_time": "09:00" }. Your code can read those fields directly, every time, without any guesswork.
This guide shows you how to ask Claude for structured outputs reliably. It uses the Anthropic Claude API — the programmable interface that lets software talk to Claude.
How to read the labels
- ✅ independently-corroborated — 2+ independent publishers
- 📄 vendor-documented — official docs only (authoritative, single source)
- ⚠️ WARNING — a default that can cost money, break the machine, or remove a safety net
- 🕒 verify live — fast-moving (versions/prices/quotas); check the current value
Prerequisites: An Anthropic account and API key (console.anthropic.com), and the anthropic SDK installed (pip install anthropic or npm install @anthropic-ai/sdk). API calls cost money — check current pricing at anthropic.com/pricing before building a loop.
Practice: Use tool use (tool_use blocks) as the primary method for structured output
Do: Define tools with a name, description, and input_schema (JSON Schema — a standard way to describe the shape of a JSON object) in the tools array of your API request. When Claude responds, check for content blocks with type: "tool_use" and read the input field — that is your structured data. Do not try to parse free-text JSON out of a plain assistant message as your first approach.
Why (beginner): When you ask Claude to “return JSON”, it may add conversational text before or after the JSON, wrap it in markdown code fences (```json ... ```), or format it differently than you expect. All of that breaks a simple JSON.parse() or json.loads() call.
Tool use tells Claude to fill in a specific form — your schema — and return it in a separate, machine-readable block, isolated from any explanatory text. That block is always valid and always in the same place. It is much easier to parse reliably.
Caveat / contested: Tool use does add a small overhead: the API injects a hidden system prompt that costs extra tokens (290–500+ tokens depending on the model and tool_choice setting — 🕒 verify live). For very simple single-value extraction where this overhead matters, the prefill technique described later is an alternative — but it has its own tradeoffs and model restrictions.
Sources: platform.claude.com — Tool use overview (2026-07-24) · platform.claude.com — Handle tool calls (2026-07-24)
Confidence: 📄 vendor-documented
Practice: Add strict: true to tool definitions to guarantee schema conformance
⚠️ WARNING (Privacy/Legal): Never put real patient names, diagnoses, or other PHI (protected health information — personal medical data covered by privacy law) in schema field names, enum values, const values, or pattern fields. Tool schema definitions are cached for schema-compilation purposes for up to 24 hours and do not receive the same PHI protections as message content.
Do: Set "strict": true alongside name, description, and input_schema in your tool definition. Also add "additionalProperties": false to your schema. This uses grammar-constrained sampling — the model’s token generation is locked so it can only produce output that fits your schema exactly.
Why (beginner): Without strict mode, Claude might return a string "2" where you declared an integer, or quietly skip an optional field. Your code then crashes or gives wrong answers in a subtle way that is hard to debug.
Strict mode eliminates these type mismatches. You receive passengers: 2 instead of passengers: "two". You never need to write retry logic to handle malformed responses.
Caveat / contested: Strict mode supports a subset of JSON Schema. Supported features include anyOf (with limitations), $ref, and $def/definitions. Unsupported features include: oneOf, external HTTP $ref (a URL used as a schema reference), recursive schemas, numerical range constraints (minimum, maximum, multipleOf), and string length constraints (minLength, maxLength). 🕒 Verify the current supported/unsupported list at platform.claude.com — Structured outputs as this list evolves.
Sources: platform.claude.com — Strict tool use (2026-07-24) · platform.claude.com — Structured outputs (2026-07-24)
Confidence: 📄 vendor-documented
Practice: Force a specific tool call with tool_choice: {type: "tool", name: "..."}
Do: When you need Claude to always fill in a particular schema — not decide on its own whether to use it — set tool_choice: {"type": "tool", "name": "my_tool"} in the request. For structured extraction tasks, this replaces the need to prompt “please respond as JSON”.
Why (beginner): By default (tool_choice: "auto"), Claude decides on its own whether to call a tool or just answer in plain text. If you define an extraction schema but Claude feels confident enough to answer directly, it skips the tool call and returns prose. Your code then crashes because there is no tool_use block to read.
Forcing the tool call guarantees you always get the tool_use block with a filled-in input field — every time, no matter what the user says.
⚠️ WARNING: tool_choice: {"type": "tool"} and tool_choice: {"type": "any"} are incompatible with extended thinking (thinking mode — see the later practice). Sending these together in one request returns a 400 error (the API refuses the request). Use tool_choice: "auto" when thinking is active. Additionally, forced tool use is not supported on Claude Mythos Preview (claude-mythos-preview), which was retired 2026-07-21 — do not use that model alias. When using forced tool choice, Claude will not emit a natural-language explanation before the tool_use block.
Sources: platform.claude.com — Define tools (2026-07-24) · platform.claude.com — Extended thinking (2026-07-24)
Confidence: 📄 vendor-documented
Practice: Handle all parallel tool_use blocks before sending tool_result blocks back
Do: A single Claude response can contain multiple tool_use blocks in one turn (parallel tool calls — Claude asking for several pieces of information at once). Collect all of them. Execute them (at the same time if they are independent read-only operations; one after another if they have side effects or ordering dependencies). Then return all tool_result blocks together in a single user message. Each tool_result must reference the correct tool_use_id. Put all tool_result blocks before any text content in the content array.
Why (beginner): If you only return results for some of the tool calls and skip others, the API will error or lose track of the conversation. Claude is waiting for all the information it asked for before it can continue. Think of it like asking a friend two questions at once — you need to answer both before they can reply meaningfully.
⚠️ WARNING: Text content placed before tool_result blocks in the user message causes a 400 error (the API rejects the request). The API also requires tool results to come immediately after the assistant turn that made the calls — you cannot insert any other messages in between. If you choose not to execute one of the calls (for example, because a preceding call failed), still return a tool_result for it with is_error: true and an explanation — or the conversation will break. To disable parallel tool calls entirely and force Claude to ask for one tool at a time, set tool_choice: {"type": "auto", "disable_parallel_tool_use": true}.
Sources: platform.claude.com — Parallel tool use (2026-07-24) · platform.claude.com — Handle tool calls (2026-07-24)
Confidence: 📄 vendor-documented
Practice: Accumulate streaming tool_use blocks via input_json_delta events before parsing
Background — what is streaming? Normally the API waits until Claude finishes its entire response and then sends it all at once. Streaming ("stream": true) delivers the response as a series of small packets called events as Claude generates them. Each event is a small JSON object with a type field that tells you what kind of update it is.
Do: When streaming is enabled, for tool use watch for these event types in order:
content_block_start— a new content block is beginning. Check whethercontent_block.type == "tool_use"to know a tool call is starting.content_block_deltawithdelta.type: "input_json_delta"— carries adelta.partial_jsonstring: one small fragment of the tool’s input JSON.content_block_stop— the tool call is complete.
Concatenate (join together) all partial_json strings for a given block index until you receive content_block_stop, then parse the full assembled string as JSON. Do not attempt to parse partial JSON mid-stream unless you use a partial-JSON library designed for that purpose.
Why (beginner): Streaming delivers the tool input one chunk at a time — like receiving a letter one word at a time. Each individual chunk is not valid JSON on its own. You must assemble the full string before parsing. The good news: the official SDKs (Python, TypeScript, Go, etc.) handle this accumulation automatically, so if you use the SDK you do not need to write this logic yourself.
Caveat / contested: The official docs note that current models only emit one complete key-value pair from input at a time, so there can be pauses between input_json_delta events while the model works on the next field. This is expected behavior, not a network error. For lower latency on individual parameter values, the eager_input_streaming per-tool flag is available — 🕒 verify current availability at platform.claude.com — Fine-grained tool streaming.
Sources: platform.claude.com — Streaming (2026-07-24)
Confidence: 📄 vendor-documented
Practice: Cache tool definitions by placing cache_control: {type: "ephemeral"} on the last tool in the list
Do: If you send the same set of tool definitions on many requests (for example, a customer-service agent that always has the same tools), add "cache_control": {"type": "ephemeral"} to the last tool in the tools array. This caches all tools up to and including that point.
Cache reads cost approximately 10% of normal input token prices. Cache writes cost 125% (5-minute TTL — the cache expires after 5 minutes) or 200% (1-hour TTL). 🕒 Verify live pricing before relying on these numbers.
Why (beginner): Every time you call the API, your tool definitions go through the model as input tokens and cost money. If your tools never change between calls, you are paying full price each time for the same text. Caching stores the compiled result so subsequent calls read from cache at a fraction of the cost. For an agent that makes hundreds of calls per hour, this saving adds up quickly.
⚠️ WARNING: Changing any tool definition, or changing the tool_choice parameter, invalidates the cache for message-level blocks. Tool and system-prompt caches survive a tool_choice parameter change; message-content caches do not. Also, changing the thinking configuration (budget_tokens or mode) invalidates cache breakpoints.
The minimum prompt length required before caching kicks in varies by model — 🕒 verify the current per-model thresholds at platform.claude.com — Prompt caching, as the table changes with new model releases. As of 2026-07-24: 512 tokens for Fable 5 and Mythos 5; 1,024 tokens for Opus 4.8, Sonnet 5, Sonnet 4.6, and Sonnet 4.5; 2,048 tokens for Opus 4.7; 4,096 tokens for Haiku 4.5 and older Opus generations.
Sources: platform.claude.com — Prompt caching (2026-07-24) · platform.claude.com — Define tools (2026-07-24)
Confidence: 📄 vendor-documented
Practice: Use search_result blocks with citations: {enabled: true} for attributed RAG responses
Background — what is RAG? RAG stands for retrieval-augmented generation. It means your program first searches a database or the web for relevant documents, then passes those documents to Claude along with the user’s question. Claude uses the documents to give a more accurate, up-to-date answer.
Do: For RAG where you want Claude to cite its sources, pass retrieved documents as search_result content blocks — not as plain text pasted into the message. Each block requires type: "search_result", a source string (a URL or an internal identifier), a title, and a content array of text blocks. Set "citations": {"enabled": true} on each block. All search results in one request must use the same citations setting.
Why (beginner): When you paste retrieved text into a plain user message, Claude can use it but will not tell you which sentence came from which source. Using search_result blocks with citations enabled causes Claude to automatically annotate its response with source attributions — showing users exactly which document supported each claim. This is essential for fact-checking, legal, or medical applications where users need to verify what they are reading.
Caveat / contested: Citations are disabled by default — you must opt in on every search result block. The feature supports text content only: images and other media are not supported inside search_result content arrays. No special “beta” header is required as of the fetch date; it is part of the standard Messages API. Supported on all active Claude models except Claude Haiku 3. 🕒 Verify model support live.
Sources: platform.claude.com — Search results and citations (2026-07-24)
Confidence: 📄 vendor-documented
Practice: When using extended thinking with tool use, pass thinking blocks back unmodified and handle both block types
Background — what is extended thinking? Extended thinking (also called “thinking mode”) lets Claude reason step-by-step through a problem before answering. You enable it by adding thinking: {type: "enabled"} or thinking: {type: "adaptive"} to your request. The API returns thinking content blocks alongside the normal response.
Do: When thinking is active and Claude calls a tool, the response contains both thinking content blocks and tool_use blocks. When you return the tool result, include the full original assistant message — including all thinking blocks — in the conversation history. Pass thinking blocks back exactly as received. Do not edit, reorder, or drop them (including any redacted_thinking blocks). Parse tool inputs from tool_use blocks, not from thinking blocks.
Why (beginner): Claude’s thinking blocks contain the encrypted record of its reasoning. When you return the tool result and ask Claude to continue, it needs its earlier reasoning to pick up where it left off. Dropping the thinking blocks is like erasing someone’s notes mid-problem — the model can continue, but it silently loses reasoning continuity and quality, giving you worse answers.
⚠️ WARNING: Modified thinking blocks are rejected with a 400 error. If you filter content blocks by type (for example, keeping only blocks where block.type == "thinking") when building the conversation history, also include redacted_thinking blocks — filtering on "thinking" alone silently drops them and breaks the conversation. Also, thinking is incompatible with tool_choice: {"type": "any"} and tool_choice: {"type": "tool", ...} — using those together returns a 400 error.
Extended thinking (type: "enabled") is currently supported only on Claude Haiku 4.5 among active models. It is deprecated on Opus 4.6 and Sonnet 4.6, and unavailable on Opus 4.7, Opus 4.8, Sonnet 5, Fable 5, or Mythos 5. Use adaptive thinking (type: "adaptive") on those models instead. 🕒 Verify which models support which thinking mode at the live models overview.
Sources: platform.claude.com — Extended thinking (2026-07-24) · platform.claude.com — Extended thinking (detailed) (2026-07-24) · platform.claude.com — Models overview (2026-07-24)
Confidence: 📄 vendor-documented
Practice: Use assistant-turn prefilling ({") to coerce plain JSON — only on supported models, and only when tool use is impractical
Background — what is the messages array and what is “prefilling”? The Claude API takes a messages array where each entry has a role ("user" or "assistant") and a content field. Normally you add user messages and Claude fills in the assistant messages. Prefilling means you add an assistant message yourself — with just {" as the content — so Claude continues from that character. It skips any conversational opener and goes straight into a JSON object.
Do: On models that support it, open the assistant turn with the first character of your expected JSON (for example, {") to bypass Claude’s conversational preamble and force a JSON opening. Claude then continues from that character.
Why (beginner): Without prefilling, Claude typically opens with “Sure, here is the JSON: …” which you then have to strip before parsing. Starting the assistant turn with {" commits Claude to producing a JSON object from the very first token — nothing to strip.
⚠️ WARNING: Prefilling is not supported on Claude Fable 5, Claude Mythos 5, Claude Opus 4.8, Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6. Attempting it on those models will fail or be ignored — use tool use instead. On models where prefilling is supported, it carries a refusal risk: if the requested content falls into a category Claude would normally decline, the output can stop mid-generation, leaving you with broken JSON. For any production pipeline that uses prefilling, always validate that the output is complete and parseable before using it. The Anthropic documentation explicitly recommends using structured outputs or tool use on models that support them, instead of prefilling.
Sources: platform.claude.com — Increase consistency (2026-07-24)
Confidence: 📄 vendor-documented
Practice: Keep tool result content terse — return only what Claude needs to decide the next step
Do: When formatting tool_result blocks, return the minimum information Claude needs to reason about the next step and produce a final answer. Prefer semantic identifiers — slugs (short readable IDs like acme-invoice-2026) and UUIDs (machine-generated unique identifiers like a3f2c...) — over raw database dumps. Omit fields Claude does not need. If a result is large (for example, a full web page), summarize or truncate before sending it back.
Why (beginner): Claude re-reads every tool_result block in the context window on every subsequent API call. A 10,000-token tool result that could be 200 tokens costs 50 times more in input tokens per call — and these costs compound across every turn in a multi-step conversation. Verbose results also make it harder for Claude to find the signal it needs, leading to worse answers.
Caveat / contested: There is a real tension here: truncating results too aggressively can cause Claude to miss key information and make wrong decisions. The right balance depends on your use case. For content from untrusted external sources (web pages, user uploads, third-party APIs), the official docs also warn to keep that content inside tool_result blocks rather than in the system prompt or plain user text, to reduce indirect prompt injection risk (an attack where malicious content in an external source tries to hijack Claude’s behavior). Never paste untrusted external content into the system prompt.
Sources: platform.claude.com — Define tools (2026-07-24) · platform.claude.com — Handle tool calls (2026-07-24)
Confidence: 📄 vendor-documented
Practice: Choose the right model for your tool-use workload
Do: Match the model to the job:
- Claude Haiku 4.5 (
claude-haiku-4-5) — use for high-throughput, low-latency tool calling with straightforward schemas. Fastest and cheapest. - Claude Sonnet 5 (
claude-sonnet-5) — use for the best speed-intelligence balance in production tool pipelines. Good default for most work. - Claude Opus 4.8 (
claude-opus-4-8) — use for complex multi-step reasoning, ambiguous queries, or when required parameters are often missing and you need the model to ask for clarification. The official docs note that Opus models are “much more likely to recognize that a parameter is missing and ask for it”, while Sonnet models “might ask, especially when prompted to think before outputting a tool request, but might also infer a reasonable value.”
Why (beginner): Paying for Opus when Haiku would do the same job correctly wastes money and adds latency. Paying for Haiku when Opus is needed means tool calls that quietly guess at missing parameters — producing subtly wrong results that are hard to debug.
Caveat / contested: 🕒 Model names, pricing, and capability rankings change frequently — always verify against the live model table. As of 2026-07-24: Claude Fable 5 ($10/$50 per MTok — million tokens), Claude Opus 4.8 ($5/$25 per MTok), Claude Sonnet 5 ($3/$15 per MTok with introductory pricing of $2/$10 through August 31, 2026), Claude Haiku 4.5 ($1/$5 per MTok).
Forcing a specific tool call (tool_choice: "tool") adds system prompt overhead compared to auto — approximately 120 extra tokens on Opus 4.8 and Sonnet 5, approximately 92 extra on Haiku 4.5. 🕒 Verify model-specific overhead at platform.claude.com — Pricing (tool use section). Extended thinking adds latency; plan for this in latency-sensitive pipelines. Claude Haiku 4.5 does not support interleaved thinking (adaptive mode), though it does support extended thinking (type: "enabled").
Sources: platform.claude.com — Models overview (2026-07-24) · platform.claude.com — Define tools (2026-07-24) · platform.claude.com — Pricing (2026-07-24)
Confidence: 📄 vendor-documented
Held pending fixes (not publish-ready)
- None.
CHANGELOG (grading → this entry)
- Timekeeper KILL (P2): Removed
anyOfand$reffrom unsupported list — both ARE supported in strict mode. Corrected unsupported list to:oneOf, external HTTP$ref, recursive schemas, numeric/string range constraints. Added link to structured-outputs page. - Timekeeper KILL (P3): Noted
claude-mythos-previewwas retired 2026-07-21; updated caveat to reflect this. - Timekeeper KILL (P7): Replaced wrong two-bucket caching-threshold split with accurate per-generation values from live prompt-caching doc. Corrected Sonnet 4.5 threshold (1,024, not 4,096); added Fable 5/Mythos 5 (512) and Opus 4.7 (2,048) which were omitted.
- Timekeeper KILL (P8): Rewrote extended thinking caveat to explicitly state Haiku 4.5 is the one current model supporting
type: "enabled". Omission implied the feature was gone from all models; fixed. - Beginner KILL: Moved PHI warning to a standalone ⚠️ block at the top of the strict-mode practice, before caveat text.
- Timekeeper FIX: Removed November 2025 “beta” framing — Anthropic structured outputs is now GA. Updated caveat in P2 to link to the live structured-outputs page.
- Timekeeper FIX (P11): Added model-specific tool-use overhead note (92–120+ extra tokens by model) with link to pricing page.
- Beginner FIX (streaming): Added plain-English explanation of the streaming event types (
content_block_start,content_block_delta,input_json_delta,content_block_stop) with per-event descriptions before the technical do-instruction. - Beginner FIX (prefilling): Added explanation of the messages array structure and what “assistant turn” means before the prefill technique.
- Beginner FIX (prerequisites): Added prerequisites box at top of article (API key, SDK install, pricing pointer).
- Beginner FIX (terse results): Added plain-English definitions of “slugs” and “UUIDs” on first use.
- Beginner re-level by rings-beginner-author, 2026-07-24.