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.
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.
Summary
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.
Recipe
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:
- Any time downstream code will parse Claude's response as data rather than display it as text.
- Extracting a fixed set of fields from unstructured input (emails, tickets, form text).
- Classification tasks where the response should be one of a known set of labels.
- Whenever you're currently relying on "please return only JSON" prompt wording.
Working Example
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 Schemadescriptionthe model reads when deciding what to put in each field.list[str]becomes anarrayofstringitems automatically - no manual array schema needed.response.parsed_outputreturns an actualBugReportinstance, not a dict, so attribute access and type checkers both work.- The schema and the runtime type are defined exactly once, in one class.
Deep Dive
How It Works
output_config.formattakes atype: "json_schema"wrapper around yourschemaobject; the schema itself is a standard JSON Schema document.- The API constrains token generation so the response can only be text that validates against the schema you provided - it isn't validating after the fact, it's constraining during generation.
requiredtells the API every property listed must be present in the response; omitting a property fromrequiredmakes it optional, which is rarely what you want for a fixed extraction task.additionalProperties: falsecloses 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.- Both
requiredandadditionalProperties: falseare needed on every object in the schema, including nested objects - setting them only at the top level does not cascade down.
Building the Schema by Hand vs. with Pydantic
| 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.
Nested Objects and Arrays
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.- Nesting is supported through ordinary JSON Schema composition (
properties,items,$ref/$def), which is exactly what Pydantic emits for nested models. - Keep nesting shallow. Two or three levels deep is reliable; very deep or recursive structures are not supported - see the field types reference.
- Every object at every level still needs its own
requiredlist andadditionalProperties: false; Pydantic handles this correctly on its own, but if you hand-write nested schemas, add both to each nested object yourself.
Python Notes
from pydantic import BaseModel
class Ticket(BaseModel):
subject: str
urgency: str
schema = Ticket.model_json_schema()
print(schema["required"]) # ['subject', 'urgency']
print(schema["additionalProperties"]) # False- Pydantic's
model_json_schema()setsrequiredandadditionalProperties: falseautomatically for a standardBaseModelwith no optional fields - this is one reason to prefer it over hand-writing the dict. - A field typed
Optional[str]or given a default value is excluded fromrequiredby Pydantic - if you want every field mandatory in the response, avoid defaults andOptionalon fields that matter. response.parsed_outputfromclient.messages.parse(...)is typed as an instance of the model you passed in, so IDEs and type checkers understand the shape without extra annotation.
Parameters & Return Values
| 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. |
Gotchas
- Forgetting
additionalProperties: falseon a nested object. Setting it only at the top level leaves inner objects open. Fix: verify every nested object in the schema also carriesadditionalProperties: false- if you're hand-writing schemas, this is easy to miss on a deeply nested field. - Listing a field in
propertiesbut not inrequired. 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 inrequired, and only make fields optional when the response genuinely may not have them. - Assuming every JSON Schema keyword works. Things like numeric range constraints or certain complex composition keywords aren't enforced by the API. Fix: check the field types and constraints reference before designing a schema beyond basic types,
enum, and simple nesting. - Reusing a Pydantic model with
Optionalfields for a "must have everything" extraction task. Optional/default-valued fields drop out ofrequiredautomatically, which weakens the guarantee you actually wanted. Fix: keep fields non-optional with no default when every value must be present in the response. - Treating a schema-valid response as guaranteed correct data. The schema enforces shape, not accuracy - a
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. - Writing an overly deep or recursive schema. Recursive schemas (an object referencing itself) are not supported, and very deep nesting increases the chance of an unsupported construct sneaking in. Fix: flatten the shape where possible, or split a deeply nested extraction into two sequential calls.
Alternatives
| 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 |
FAQs
Do I have to write required and additionalProperties by hand every time?
- Not if you use Pydantic -
model_json_schema()sets both automatically for aBaseModelwith no optional/default fields. - If you hand-write the schema as a dict, yes, you must add both explicitly on every object, including nested ones.
What happens if I forget additionalProperties: false?
- The object is left "open," meaning the enforcement guarantee around exactly which fields can appear is weaker than intended.
- Always set it explicitly to
Falseon every object in the schema.
Can a field be optional in my schema?
- Yes - simply omit it from
requiredand the model may or may not include it. - For extraction tasks where you truly need every field populated, keep everything in
requiredrather than making fields optional out of habit.
How do I express "one of these fixed values" in the schema?
{"type": "string", "enum": ["low", "medium", "high"]}enumis a well-supported construct and is the standard way to constrain a field to a fixed set of values.
Can I nest objects and arrays inside the schema?
- Yes -
propertiesfor nested objects anditemsfor arrays both work, and Pydantic generates this automatically for nestedBaseModeltypes andlist[...]fields. - Keep nesting shallow (two to three levels); very deep or recursive structures aren't supported.
Does model_json_schema() work with fields that have default values?
- It does, but a field with a default value (or typed
Optional) is excluded fromrequiredby Pydantic. - If you need every field guaranteed present, avoid defaults and
Optionaltyping on those fields.
Is a JSON Schema for output_config.format the same as a schema for tool use?
- They use the same JSON Schema syntax and the same
required/additionalProperties: falseconvention. - The difference is what they constrain:
output_config.formatconstrains the whole message response; a tool'sinput_schema(withstrict: true) constrains a single tool call's parameters.
Will an unsupported schema keyword cause an error?
- Behavior varies by construct - some unsupported constraints are simply not enforced rather than rejected outright.
- Check the field types and constraints reference rather than assuming a keyword works because it's valid JSON Schema in general.
Should I validate the parsed response again after getting it back?
- The schema enforces structure, not factual correctness, so if the values themselves matter (not just their shape), keep your own validation or review step.
- This is separate from - and doesn't replace - the API's shape guarantee.
What's the minimum valid schema I can pass to output_config.format?
{
"type": "object",
"properties": {"answer": {"type": "string"}},
"required": ["answer"],
"additionalProperties": False,
}- This is the smallest well-formed shape: one object, one required string property, closed to extras.
Can I generate the schema from a dataclass instead of Pydantic?
- Pydantic's
BaseModelis the common convenience path because.model_json_schema()produces the schema directly. - A plain
dataclassdoesn't have this built in - you'd hand-write the schema dict yourself if you don't want a Pydantic dependency.
Related
- Structured Outputs Basics - request a JSON schema response and parse it with client.messages.parse.
- Validating and Parsing Structured Responses in Python - what happens to this schema once you request a response with it.
- Structured Output Field Types and Constraints Reference - the full list of supported and unsupported schema constructs referenced throughout this page.
- Handling Malformed or Truncated JSON Output - what to do when a well-formed schema still comes back incomplete.
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.