Defining a JSON Schema for output_config.format
output_config.format only guarantees a schema-valid response if the schema you hand it is well-formed.
Search across all documentation pages
output_config.format only guarantees a schema-valid response if the schema you hand it is well-formed.
This page covers exactly how to write that schema: the two fields every object needs, how to build it by hand versus generating it from a Pydantic model, and how nesting, arrays, and enums fit together.
The schema you pass to output_config.format is standard JSON Schema, with two conventions the Claude API requires on every object: a required array listing every property, and additionalProperties: false.
Getting these two right on every nested object is the single most common source of schema errors.
You can write the schema as a plain Python dict, or generate it automatically from a Pydantic BaseModel with .model_json_schema().
Both approaches produce the same wire format; Pydantic just saves you from keeping a dict and a parsed type in sync by hand.
Not every JSON Schema keyword is supported, so a schema that looks reasonable can still be rejected or silently ignored in places - see the field types and constraints reference for the full list.
Quick-reference recipe card - copy-paste ready.
from anthropic import Anthropic
client = Anthropic()
schema = {
"type": "object",
"properties": {
"title": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "medium", "high"]},
},
"required": ["title", "priority"],
"additionalProperties": False,
}
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
output_config={"format": {"type": "json_schema", "schema": schema}},
messages=[{"role": "user", "content": "Summarize this bug report as a ticket."}],
)When to reach for this:
from pydantic import BaseModel, Field
from anthropic import Anthropic
class BugReport(BaseModel):
title: str = Field(description="A short, one-line summary of the bug")
priority: str = Field(description="One of: low, medium, high")
steps_to_reproduce: list[str] = Field(description="Ordered list of repro steps")
affected_component: str
client = Anthropic()
response = client.messages.parse(
model="claude-sonnet-5",
max_tokens=1024,
output_config={
"format": {"type": "json_schema", "schema": BugReport.model_json_schema()}
},
messages=[{
"role": "user",
"content": (
"Turn this into a bug report: The checkout page crashes when a user "
"applies a discount code with special characters. Happens every time "
"in the payments module. Login, add item to cart, apply a code like "
"'SAVE#10', click checkout. This blocks all purchases so it's high priority."
),
}],
)
report: BugReport = response.parsed_output
print(report.title)
print(report.priority)
for step in report.steps_to_reproduce:
print("-", step)What this demonstrates:
Field(description=...) on a Pydantic model becomes the JSON Schema description the model reads when deciding what to put in each field.list[str] becomes an array of string items automatically - no manual array schema needed.response.parsed_output returns an actual BugReport instance, not a dict, so attribute access and type checkers both work.output_config.format takes a type: "json_schema" wrapper around your schema object; the schema itself is a standard JSON Schema document.required tells the API every property listed must be present in the response; omitting a property from required makes it optional, which is rarely what you want for a fixed extraction task.additionalProperties: false closes the object - without it, the API (and any strict validator you layer on top) can't guarantee the model won't add extra, unrequested fields.required and additionalProperties: false are needed on every object in the schema, including nested objects - setting them only at the top level does not cascade down.| Approach | What you write | What you get |
|---|---|---|
| Hand-written dict | A plain Python dict matching JSON Schema syntax | Full control, but you maintain the schema and any downstream type separately |
Pydantic model_json_schema() | A BaseModel subclass with typed fields | Schema generated automatically; response.parsed_output returns an instance of your model |
Hand-written schemas are useful for very simple, one-off shapes, or when you don't want a Pydantic dependency. For anything with more than two or three fields, a Pydantic model keeps the schema and your application's data type from drifting apart as the shape evolves.
from pydantic import BaseModel
class LineItem(BaseModel):
sku: str
quantity: int
class Order(BaseModel):
customer_name: str
items: list[LineItem]
# Order.model_json_schema() produces a top-level object with an "items" array
# property, whose "items" keyword points at the nested LineItem object schema -
# each level still needs its own required + additionalProperties: false, which
# Pydantic generates for you automatically.properties, items, $ref/$def), which is exactly what Pydantic emits for nested models.required list and additionalProperties: false; Pydantic handles this correctly on its own, but if you hand-write nested schemas, add both to each nested object yourself.from pydantic import BaseModel
class Ticket(BaseModel):
subject: str
urgency: str
schema = Ticket.model_json_schema()
print(schema["required"]) # ['subject', 'urgency']
print(schema["additionalProperties"]) # Falsemodel_json_schema() sets required and additionalProperties: false automatically for a standard BaseModel with no optional fields - this is one reason to prefer it over hand-writing the dict.Optional[str] or given a default value is excluded from required by Pydantic - if you want every field mandatory in the response, avoid defaults and Optional on fields that matter.response.parsed_output from client.messages.parse(...) is typed as an instance of the model you passed in, so IDEs and type checkers understand the shape without extra annotation.| Field | Type | Description |
|---|---|---|
output_config.format.type | str | Always "json_schema" for this feature. |
output_config.format.schema | dict | The JSON Schema document describing the required response shape. |
schema.required | list[str] | Every property name that must be present in the response, per object. |
schema.additionalProperties | bool | Must be False on every object in the schema. |
additionalProperties: false on a nested object. Setting it only at the top level leaves inner objects open. Fix: verify every nested object in the schema also carries additionalProperties: false - if you're hand-writing schemas, this is easy to miss on a deeply nested field.properties but not in required. The field becomes optional, so the model may omit it, and your code has to defensively check for its presence. Fix: include every field you actually need in required, and only make fields optional when the response genuinely may not have them.enum, and simple nesting.Optional fields for a "must have everything" extraction task. Optional/default-valued fields drop out of required automatically, which weakens the guarantee you actually wanted. Fix: keep fields non-optional with no default when every value must be present in the response.priority: "high" field can still be schema-valid and factually wrong. Fix: keep your own downstream validation or review step for correctness, independent of schema conformance.| Alternative | Use When | Don't Use When |
|---|---|---|
| Hand-written schema dict | The shape is small, fixed, and you don't want a Pydantic dependency | The shape has more than a few fields or evolves often |
Pydantic model_json_schema() | You already have (or want) a typed Python class for the data | You need the schema in a non-Python context and can't share the model |
Strict tool input_schema (strict: true) | You want to constrain a tool call's parameters rather than the final message response | You're constraining the assistant's overall text response, not a tool invocation |
| Prompt-based JSON instructions only | Quick prototyping where occasional malformed output is acceptable | Any production pipeline that parses the response programmatically |
model_json_schema() sets both automatically for a BaseModel with no optional/default fields.False on every object in the schema.required and the model may or may not include it.required rather than making fields optional out of habit.{"type": "string", "enum": ["low", "medium", "high"]}enum is a well-supported construct and is the standard way to constrain a field to a fixed set of values.properties for nested objects and items for arrays both work, and Pydantic generates this automatically for nested BaseModel types and list[...] fields.Optional) is excluded from required by Pydantic.Optional typing on those fields.required / additionalProperties: false convention.output_config.format constrains the whole message response; a tool's input_schema (with strict: true) constrains a single tool call's parameters.{
"type": "object",
"properties": {"answer": {"type": "string"}},
"required": ["answer"],
"additionalProperties": False,
}BaseModel is the common convenience path because .model_json_schema() produces the schema directly.dataclass doesn't have this built in - you'd hand-write the schema dict yourself if you don't want a Pydantic dependency.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 13, 2026