Structured Outputs Basics
10 examples to get you started with Structured Outputs - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Structured Outputs - 7 basic and 3 intermediate.
pip install anthropic.export ANTHROPIC_API_KEY=sk-ant-....pip install pydantic.from anthropic import Anthropic and client = Anthropic() unless shown otherwise.The smallest possible output_config.format call - one string field, guaranteed back as valid JSON.
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {"greeting": {"type": "string"}},
"required": ["greeting"],
"additionalProperties": False,
},
}
},
messages=[{"role": "user", "content": "Say hello in French."}],
)
print(response.content[0].text)output_config.format is what enforces the shape - the prompt itself needs no "return JSON" instruction.required and additionalProperties: False are required parts of a well-formed schema, not optional extras.json.loads() on it here.Skip the manual json.loads() step by using the SDK's parsing helper.
from anthropic import Anthropic
client = Anthropic()
schema = {
"type": "object",
"properties": {
"language": {"type": "string"},
"greeting": {"type": "string"},
},
"required": ["language", "greeting"],
"additionalProperties": False,
}
response = client.messages.parse(
model="claude-sonnet-5",
max_tokens=1024,
output_config={"format": {"type": "json_schema", "schema": schema}},
messages=[{"role": "user", "content": "Say hello in French."}],
)
print(response.parsed_output)client.messages.parse validates the response against your schema and hands back a parsed value on response.parsed_output.json.loads() call is needed anywhere in this snippet.Use Pydantic to define the target shape once, and reuse it for both the schema and the parsed result type.
from pydantic import BaseModel
from anthropic import Anthropic
class Greeting(BaseModel):
language: str
greeting: str
client = Anthropic()
response = client.messages.parse(
model="claude-sonnet-5",
max_tokens=1024,
output_config={
"format": {"type": "json_schema", "schema": Greeting.model_json_schema()}
},
messages=[{"role": "user", "content": "Say hello in French."}],
)
greeting: Greeting = response.parsed_output
print(greeting.language, greeting.greeting)Greeting.model_json_schema() generates the JSON Schema from the Pydantic model, so you don't hand-write it twice.response.parsed_output here is a real Greeting instance, not a plain dict.parse) is the most common pattern for structured extraction in Python.Schemas aren't limited to a single flat object - request a list of structured items in one call.
from anthropic import Anthropic
client = Anthropic()
schema = {
"type": "object",
"properties": {
"todos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"task": {"type": "string"},
"done": {"type": "boolean"},
},
"required": ["task", "done"],
"additionalProperties": False,
},
}
},
"required": ["todos"],
"additionalProperties": False,
}
response = client.messages.parse(
model="claude-sonnet-5",
max_tokens=1024,
output_config={"format": {"type": "json_schema", "schema": schema}},
messages=[{"role": "user", "content": "Give me 3 sample todo items."}],
)
for item in response.parsed_output["todos"]:
print(item["task"], item["done"])required and additionalProperties: False, not just the outer one.Restrict a field to a fixed set of values instead of accepting any string.
from anthropic import Anthropic
client = Anthropic()
schema = {
"type": "object",
"properties": {
"sentiment": {"type": "string", "enum": ["positive", "neutral", "negative"]},
},
"required": ["sentiment"],
"additionalProperties": False,
}
response = client.messages.parse(
model="claude-sonnet-5",
max_tokens=1024,
output_config={"format": {"type": "json_schema", "schema": schema}},
messages=[{"role": "user", "content": "Classify: 'This product exceeded my expectations!'"}],
)
print(response.parsed_output["sentiment"])enum guarantees the returned value is one of the listed strings - no post-hoc validation of free text needed.Always confirm the response actually finished before treating it as complete JSON.
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=256,
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {"summary": {"type": "string"}},
"required": ["summary"],
"additionalProperties": False,
},
}
},
messages=[{"role": "user", "content": "Summarize the plot of a 300-page novel in detail."}],
)
if response.stop_reason == "max_tokens":
print("Response was truncated - retry with a larger max_tokens.")
else:
print(response.content[0].text)stop_reason == "max_tokens" means the model ran out of room before finishing - the JSON may be cut off mid-structure.stop_reason before parsing is cheap insurance against a confusing JSONDecodeError downstream.Reduce truncation risk for larger structured responses by sizing max_tokens to the expected output.
from anthropic import Anthropic
client = Anthropic()
schema = {
"type": "object",
"properties": {
"bullet_points": {"type": "array", "items": {"type": "string"}},
},
"required": ["bullet_points"],
"additionalProperties": False,
}
response = client.messages.parse(
model="claude-sonnet-5",
max_tokens=4096, # generous headroom for a multi-item array response
output_config={"format": {"type": "json_schema", "schema": schema}},
messages=[{"role": "user", "content": "List 10 detailed release notes for a v2.0 launch."}],
)
print(response.parsed_output["bullet_points"])max_tokens headroom than a single short string.max_tokens that's too tight is the most common cause of truncated structured output in practice.stop_reason check from Example 6 in production code.Combine multiple field types, including a nested object, in a single extraction schema.
from pydantic import BaseModel
from anthropic import Anthropic
class Address(BaseModel):
city: str
country: str
class Contact(BaseModel):
name: str
email: str
is_customer: bool
address: Address
client = Anthropic()
response = client.messages.parse(
model="claude-sonnet-5",
max_tokens=1024,
output_config={"format": {"type": "json_schema", "schema": Contact.model_json_schema()}},
messages=[{
"role": "user",
"content": (
"Extract contact info: Jane Doe, jane@example.com, existing "
"customer, lives in Austin, USA."
),
}],
)
contact: Contact = response.parsed_output
print(contact.name, contact.address.city)model_json_schema() flattens that into $ref/$def entries the API understands.response.parsed_output returns a fully nested, typed Contact object - including the nested Address.Detect a truncated structured response and automatically retry with more room.
from anthropic import Anthropic
client = Anthropic()
schema = {
"type": "object",
"properties": {"report": {"type": "string"}},
"required": ["report"],
"additionalProperties": False,
}
def get_report(max_tokens: int = 512) -> str:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=max_tokens,
output_config={"format": {"type": "json_schema", "schema": schema}},
messages=[{"role": "user", "content": "Write a detailed incident report."}],
)
if response.stop_reason == "max_tokens" and max_tokens < 4096:
return get_report(max_tokens=max_tokens * 2)
return response.content[0].text
print(get_report())max_tokens each time, bounded by a ceiling, rather than retrying forever.Pair output_config.format with strict: true on a tool so both the final response and any tool call parameters are guaranteed valid.
from anthropic import Anthropic
client = Anthropic()
tools = [{
"name": "log_ticket",
"description": "Log a support ticket in the tracking system.",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"priority": {"type": "string", "enum": ["low", "medium", "high"]},
"summary": {"type": "string"},
},
"required": ["priority", "summary"],
"additionalProperties": False,
},
}]
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "Server is down, customers can't check out. Log this."}],
)
for block in response.content:
if block.type == "tool_use":
print(block.input) # guaranteed to match the strict input_schemastrict: True on a tool definition is the tool-use equivalent of output_config.format on a message response.strict schemas follow the same required / additionalProperties: false rules as output_config.format schemas.Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
anthropicPython SDK (latest 0.x release). Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.
Reviewed by Chris St. John·Last updated Jul 19, 2026