Anthropic has moved structured outputs from beta to general availability. If you've been sending the structured-outputs-2025-11-13 beta header to get guaranteed JSON schema conformance, you can drop it. Structured outputs are now a first-class API feature with no beta flag required.
Note: The exam tests structured output patterns (Domain 4) — the move to GA status doesn't change the concepts tested, but it means you no longer need to pass the beta header in production.
What changed from beta to GA
Two things:
-
No more beta header. The
anthropic-betaheader is no longer required. Structured output parameters work directly in the standard API. -
A new parameter name. The beta
output_formatparameter has moved tooutput_config.format. The old name still works if you send the beta header, but it's deprecated and will be removed. Version 1.0 and later of the Python SDK raises aTypeErrorif you passoutput_formattoclient.beta.messages.create().
Two limits are worth knowing before you design a schema:
- Supported schema features. You can use
anyOf,allOf(thoughallOfcan't contain$ref),enum,constand internal$refdefinitions, and every object needsadditionalProperties: false. Recursive schemas, external$refand numeric or string-length constraints such asminimumandmaxLengtharen't supported. - First-request latency. The API compiles each new schema into a grammar that constrains token generation, so the first request with a given schema takes longer. Compiled grammars are cached for 24 hours from their last use.
How structured outputs work
Structured outputs guarantee that Claude's response conforms to a JSON schema you provide. This isn't prompt-based — it's not "please output JSON." The API constrains token generation at the decoding level so the output can't violate your schema, short of a refusal or a max_tokens cut-off. Every key, every type, every required field is enforced.
There are two ways to use this.
Direct JSON schema enforcement
Pass your schema in output_config.format:
import json
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Analyse this customer review and extract sentiment, key topics, and urgency level: 'The product arrived damaged and support hasn't responded in 3 days. Very frustrated.'"
}
],
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"sentiment": {
"type": "string",
"enum": ["positive", "negative", "neutral", "mixed"]
},
"topics": {
"type": "array",
"items": {"type": "string"}
},
"urgency": {
"type": "string",
"enum": ["low", "medium", "high", "critical"]
},
"summary": {
"type": "string"
}
},
"required": ["sentiment", "topics", "urgency", "summary"],
"additionalProperties": False
}
}
}
)
if response.stop_reason in ("refusal", "max_tokens"):
raise RuntimeError(f"Output may not match the schema: stop_reason={response.stop_reason}")
analysis = json.loads(response.content[0].text)The JSON arrives in the response's text block and matches that schema, unless the response stops for a refusal or at max_tokens, so check stop_reason before you parse it. On a normal stop you'll always get a sentiment from the enum, an array of topics, an urgency level, and a summary. No parsing failures, no missing fields, no creative reinterpretation of your structure.
Structured outputs via tool_use
The second approach uses tool definitions with input_schema. Set "strict": True on the tool and the input Claude generates is constrained to that schema, the same way output_config.format constrains a direct answer. Without strict, the schema guides the input but doesn't guarantee it. Tool definitions have been the standard pattern for structured extraction since the tool_use feature launched.
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=[
{
"name": "extract_order_details",
"description": "Extract structured order information from customer messages.",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Order number in format ORD-XXXXX"
},
"issue_type": {
"type": "string",
"enum": ["refund", "exchange", "tracking", "damage", "other"]
},
"amount": {
"type": "number",
"description": "Order amount if mentioned"
},
"requires_escalation": {
"type": "boolean"
}
},
"required": ["order_id", "issue_type", "requires_escalation"],
"additionalProperties": False
}
}
],
tool_choice={"type": "tool", "name": "extract_order_details"},
messages=[
{
"role": "user",
"content": "I need a refund for order ORD-44821, it was $129.99 and arrived completely smashed."
}
]
)Using tool_choice to force a specific tool turns the tool call into a structured extraction step. The model must call the tool, and with strict set the input must conform to the schema. This gives you the same guarantee as output_config.format but routes through the tool_use mechanism.
Current state (checked 29 September 2026): forced
tool_choicenow depends on the model. Claude Opus 5.5, Sonnet 5.5, Fable 5.1 and Mythos 5.1 reject{"type": "tool"}and{"type": "any"}with a 400 error, and manual extended thinking rejects both on any model. The example above uses Claude Sonnet 4.6, which isn't on that list. On the newer models, keeptool_choiceonautoand setstrict: trueon the tool, or use structured outputs. The exam guide still tests forced selection, so answer with it on the exam.
Which approach to use
Use output_config.format when you want Claude's direct answer in a structured format. Use tool_use with forced tool choice when you're building the extraction into an agentic workflow where the structured data feeds into subsequent tool calls or agent steps.
In practice, tool_use is more common in production systems because it integrates naturally with the agentic tool loop. output_config.format is cleaner for standalone extraction tasks where you just need structured data back.
Validation still matters
Structured outputs guarantee schema conformance — the JSON will be valid and match your types. They don't guarantee semantic correctness. If you ask Claude to extract an order number and it hallucinates one that looks plausible but doesn't exist, the output will still conform to your schema. It'll be a valid string in the right format. Just wrong.
Always validate extracted values against your source data:
# The SDK returns the tool input as a dict, already parsed
tool_input = response.content[0].input
# Schema conformance is guaranteed — but verify the data
order = db.lookup_order(tool_input["order_id"])
if not order:
# The model extracted a plausible but incorrect order ID
handle_extraction_error(tool_input)This is a critical distinction the exam tests. Schema enforcement and semantic validation are separate concerns. Structured outputs handle the first. Your application code handles the second.
What this means for the exam
Domain 4 tests structured output patterns — specifically how to use JSON schemas with the API, when to use tool_use for structured extraction, and how to validate outputs. The move from beta to GA doesn't change any of these concepts. The patterns are identical. You drop the beta header and pass the schema in output_config.format.
The exam-relevant concepts are:
- Schema design: choosing the right types, enums for constrained values, required vs optional fields
- tool_use as structured extraction: forcing tool calls with
tool_choiceto guarantee structured output - Validation layering: understanding that schema conformance and semantic correctness are different things
- When structured outputs break down: very large schemas with deep nesting can increase latency; extremely open-ended tasks don't benefit from rigid schemas
Work through Domain 4 — Prompt Engineering & Structured Output for the full treatment, or jump to Task 4.3 — Structured Output for the specific task statement.