Structured output you can build on
The moment Claude's output feeds a program instead of a person, "usually valid JSON" becomes a bug class: markdown fences around the payload, a trailing comment, a missing field — each one a 3 a.m. parser exception. Structured outputs close the class: you hand the API a JSON Schema and the response is guaranteed to validate against it. Not prompted to, guaranteed to.
The old trick is gone; the real feature replaced it
For years the workaround was prefilling — starting the assistant turn with {"name": " to force JSON. Prefills return an error on current models, and the replacement is strictly better because it is enforced by the API rather than suggested to the model. The canonical form puts the schema in output_config.format:
response = client.messages.create(
model="claude-opus-5",
max_tokens=16000,
messages=[{"role": "user", "content": "Extract: Jane Doe (jane@co.com) wants the Enterprise plan, demo requested."}],
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"plan": {"type": "string"},
"demo_requested": {"type": "boolean"},
},
"required": ["name", "email", "plan", "demo_requested"],
"additionalProperties": False,
},
}
},
)The response's text block contains JSON that parses and validates, every time.
Let your type system write the schema
Hand-writing JSON Schema is busywork with failure modes. The SDKs generate it from the types you already have — Pydantic in Python, Zod in TypeScript — and hand you a validated object instead of a string:
from pydantic import BaseModel
class ContactInfo(BaseModel):
name: str
email: str
plan: str
demo_requested: bool
response = client.messages.parse(
model="claude-opus-5",
max_tokens=16000,
messages=[{"role": "user", "content": "Extract: Jane Doe (jane@co.com) wants Enterprise, demo requested."}],
output_format=ContactInfo,
)
contact = response.parsed_output # a validated ContactInfo instance
print(contact.email)This is the recommended shape for every extraction pipeline: one model class serves as prompt contract, wire schema, and parsed return type — three things that can no longer drift apart.
Strict tool inputs: the same guarantee, other direction
Structured outputs constrain what Claude says; the sibling feature constrains what Claude passes to your functions. Add strict: true to a tool definition (with additionalProperties: false and a required list) and the tool's input is guaranteed to validate against your schema — no more defensive parsing of half-right arguments inside every tool handler. When you reach the tool-use lesson, treat strict: true as the default for any tool whose arguments matter.
Know the schema dialect's edges
The guarantee covers a defined subset of JSON Schema, and the edges are worth memorising before you design around them:
- Supported: the basic types,
enum,const,anyOf/allOf,$ref, common string formats (date-time,email,uuid,uri), andadditionalProperties: false— which is required on every object. - Not supported: recursive schemas, numeric bounds (
minimum/maximum), and string length constraints. The Python and TypeScript SDKs quietly strip unsupported constraints from what they send and validate them client-side instead — convenient, but know that the API-level guarantee doesn't include yourminLength.
Three operational notes. A new schema pays a one-time compilation cost on first use, then caches for 24 hours — so don't generate schemas dynamically per request. If the response reports stop_reason: "max_tokens", the JSON may be cut off mid-structure — size the cap generously. And a safety refusal can override the schema, which is one more reason the habit from the streaming lesson — check stop_reason before parsing — is non-negotiable.
Schema design is prompt design
One more lever most teams miss: the schema itself steers the model. Field names and descriptions are read as instructions — "summary": {"type": "string", "description": "One sentence, plain language, no jargon"} shapes the value, not just its type. Enums turn open-ended judgment into forced choice, which is exactly what you want for classification. And field order nudges reasoning: putting an evidence field before a verdict field makes the model justify before it concludes — a miniature chain of thought, enforced by structure instead of prose.
What to take into the next lesson
Schema in, guaranteed-valid data out; types generate the schema; strict: true extends the same promise to tool arguments; the schema itself is a steering surface. Extraction, classification, and routing — the bread-and-butter production tasks — all collapse into this one pattern. Next, the feature that turns Claude from a text generator into a system that does things: tools.