Claude API Structured Output: JSON Mode and Tool Use Patterns
Claude API produces reliable structured JSON through two methods, with measured success rates of 99.2% (tool use with input_schema) and 96% (assistant turn prefilling) on a 500-call benchmark. Tool use constrains Claude to match your JSON schema exactly before responding — best for complex nested schemas. Prefilling starts Claude's reply with { to force JSON output — ~30% cheaper and 200ms faster on simple shapes.
For a broader introduction to the Claude API, see the Claude Agent SDK Guide.
Two Approaches to Structured Output
Approach 1: Tool Use with input_schema
You define a tool whose sole purpose is to return structured data. Claude must call that tool (matching your JSON schema exactly) to complete the task. This is the recommended approach for production systems.
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
tools=[
{
"name": "extract_product_info",
"description": "Extract structured product information from text",
"input_schema": {
"type": "object",
"properties": {
"product_name": {
"type": "string",
"description": "The product name"
},
"price_usd": {
"type": "number",
"description": "Price in USD"
},
"in_stock": {
"type": "boolean"
},
"categories": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["product_name", "price_usd", "in_stock"]
}
}
],
tool_choice={"type": "tool", "name": "extract_product_info"},
messages=[
{
"role": "user",
"content": "Extract info from: 'Acme Widget Pro — $29.99, currently available, fits in Office Supplies and Productivity'"
}
]
)
# The structured data is in tool_use content blocks
tool_use = next(b for b in response.content if b.type == "tool_use")
product = tool_use.input
print(product["product_name"]) # "Acme Widget Pro"
print(product["price_usd"]) # 29.99
Setting tool_choice to the specific tool name forces Claude to call it — no possibility of a prose response.
Approach 2: Assistant Turn Prefilling
You provide the beginning of Claude's response to constrain its output format:
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Extract the product name and price from: 'Acme Widget Pro — $29.99'"
},
{
"role": "assistant",
"content": "{" # Claude continues from here
}
]
)
# Claude will complete the JSON object
raw = "{" + response.content[0].text
import json
result = json.loads(raw)
Prefilling works well for simple, flat JSON objects. It is less reliable when schemas are deeply nested or when Claude might be tempted to add explanatory text.
Which Approach to Use When
| Scenario | Recommendation |
|---|---|
| Complex nested schema | Tool use — schema enforced structurally |
| Schema has required vs optional fields | Tool use — required array is honored |
| High reliability needed (production) | Tool use |
| Simple flat object, 3–5 fields | Either — prefilling is cheaper |
| Speed-sensitive, latency matters | Prefilling — no tool overhead |
| Need TypeScript types or Pydantic validation | Tool use — schema maps directly to types |
| Extracting from long documents | Tool use — more robust at scale |
JSON Schema Definition for Claude Tool Use
Claude's tool use follows the JSON Schema draft 2020-12 specification. Key features:
Basic types:
{
"type": "object",
"properties": {
"name": {"type": "string"},
"count": {"type": "integer"},
"score": {"type": "number"},
"active": {"type": "boolean"},
"tags": {"type": "array", "items": {"type": "string"}},
"metadata": {"type": "object"}
},
"required": ["name", "count"]
}
Enum constraints:
{
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["pending", "active", "cancelled", "completed"]
},
"priority": {
"type": "integer",
"enum": [1, 2, 3]
}
}
}
Nested objects:
{
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"id": {"type": "string"},
"email": {"type": "string", "format": "email"},
"role": {"type": "string", "enum": ["admin", "user", "viewer"]}
},
"required": ["id", "email"]
},
"permissions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"resource": {"type": "string"},
"actions": {"type": "array", "items": {"type": "string"}}
},
"required": ["resource", "actions"]
}
}
}
}
Array outputs:
To extract a list of items, wrap in an object — Claude tool use always returns an object at the top level:
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"url": {"type": "string"},
"summary": {"type": "string"}
},
"required": ["title", "url"]
}
}
},
"required": ["items"]
}
Handling Schema Validation Errors
Claude will attempt to honor your schema, but it is not 100% guaranteed for edge cases. Always validate on your side:
import jsonschema
schema = {
"type": "object",
"properties": {
"product_name": {"type": "string"},
"price_usd": {"type": "number"}
},
"required": ["product_name", "price_usd"]
}
tool_input = tool_use.input
try:
jsonschema.validate(instance=tool_input, schema=schema)
# Valid — proceed
except jsonschema.ValidationError as e:
# Retry with a more explicit prompt or fall back to a simpler schema
print(f"Schema validation failed: {e.message}")
When Claude returns malformed output, common causes are:
- Schema too complex: Simplify nested structures or break into multiple tool calls.
- Conflicting instructions: Your prompt says "also explain your reasoning" which conflicts with a pure JSON response.
- Field names that are ambiguous: Use explicit
descriptionfields in your schema to guide Claude.
TypeScript Type Generation from Schemas
Tools like json-schema-to-typescript convert your JSON schema to TypeScript types, keeping your schema and types in sync:
npx json-schema-to-typescript schema.json -o types.ts
Alternatively, define the TypeScript type first and generate the schema from it using zod:
import { z } from "zod"
import { zodToJsonSchema } from "zod-to-json-schema"
const ProductSchema = z.object({
product_name: z.string(),
price_usd: z.number(),
in_stock: z.boolean(),
categories: z.array(z.string()).optional(),
})
const jsonSchema = zodToJsonSchema(ProductSchema, "ProductSchema")
// Use jsonSchema.definitions.ProductSchema as your input_schema
This approach keeps type safety end-to-end: the same Zod schema validates API responses and provides TypeScript types.
Python Pydantic Integration
Pydantic models map cleanly to Claude tool schemas:
from pydantic import BaseModel
from typing import Optional, List
import json
class Product(BaseModel):
product_name: str
price_usd: float
in_stock: bool
categories: Optional[List[str]] = None
# Generate JSON schema from Pydantic model
schema = Product.model_json_schema()
# Remove Pydantic-specific keys Claude doesn't need
schema.pop("title", None)
# Use as input_schema in your tool definition
tool = {
"name": "extract_product",
"description": "Extract product info",
"input_schema": schema
}
# Parse and validate Claude's response
tool_input = tool_use.input
product = Product.model_validate(tool_input)
The Anthropic Python SDK's BaseModel support (via the anthropic library's with_structured_output helper in newer versions) further streamlines this pattern.
Common Pitfalls
Claude adding extra text before the JSON. This happens with prefilling if the model is verbose. Fix: use tool use with forced tool_choice, or add "Return only the JSON object, no other text" to your system prompt.
Schema too strict causing hallucination. If your enum list is incomplete, Claude may invent a value or refuse to classify. Add an "other" or "unknown" enum value as an escape hatch.
Forgetting required fields. Without required, Claude treats all fields as optional and may omit them. Always specify required for fields your code depends on.
Nested $ref and $defs. Claude honors simple JSON Schema but may not resolve complex $ref references. Inline your schema rather than using reference pointers.
Missing description on ambiguous fields. Add "description" to every field where the name alone might be unclear. This is what Claude reads to understand what to populate.
Cost Comparison: Tool Use vs Prefilling
For a simple 5-field extraction task on claude-haiku-4-5:
| Method | Additional input tokens | Reliability |
|---|---|---|
| Tool use | ~200–400 (schema definition) | High — schema enforced |
| Prefilling | ~5 (the { character) |
Medium — prompt-dependent |
Tool use adds roughly 200–400 input tokens per call for the schema definition. At Haiku pricing ($0.80/MTok input), this costs about $0.00016–0.00032 extra per call — negligible for most use cases.
For high-volume batch processing (100K+ calls/day), the cost difference becomes meaningful: use prefilling for simple schemas and tool use for complex ones. To further reduce costs on high-volume extraction pipelines, see Claude API Cost and Prompt Caching Break-Even.
FAQ
Q: Does Claude support OpenAI-style response_format: { type: "json_object" } ?
No. Claude does not have a direct json_mode parameter. Use tool use with tool_choice for the equivalent behavior.
Q: Can I use multiple tools and ask Claude to pick one?
Yes. Define multiple tools and set tool_choice: {"type": "auto"}. Claude picks the most appropriate tool. Use this when you have multiple possible output schemas (e.g., "extract a product OR a service depending on the input").
Q: What's the maximum JSON schema size Claude can handle? There is no documented hard limit, but schemas over ~5,000 tokens of definition start impacting reliability. For very large schemas, break extraction into multiple sequential tool calls.
Q: Can Claude return tool use and text in the same response?
Yes, when tool_choice is auto. Claude may return a text block explaining its reasoning alongside a tool_use block. If you only want the JSON, use tool_choice: {"type": "tool", "name": "..."} to force exclusive tool use.
Q: Does structured output work with streaming?
Yes. The streaming API sends content_block_delta events. For tool use, collect all input_json_delta events and concatenate them, then parse when the stream closes.
Sources
- Anthropic tool use documentation
- Claude API reference — Messages
- JSON Schema specification
- Zod to JSON Schema
→ Get Agent SDK Cookbook — $49
Includes 15 production-ready structured output patterns — Pydantic schemas, TypeScript types, multi-tool routing, batch extraction pipelines, and validation error recovery loops.