“Reply with JSON only” is a hope, not a guarantee. Structured outputs are a request parameter — the API constrains generation to your schema.
Raw JSON schema
import json
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Extract: John Smith (john@example.com) wants the Enterprise plan.",
}],
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"plan": {"type": "string"},
},
"required": ["name", "email", "plan"],
"additionalProperties": False,
},
}
},
)
data = json.loads(next(b.text for b in response.content if b.type == "text"))
print(data["email"]) # john@example.com
Forces the whole reply to be one JSON object matching the schema, so json.loads cannot fail on a stray “Here you go:”. List every field in required and set additionalProperties: false — that’s the shape the docs use and it keeps the output tight.
The parameter is output_config.format. An older top-level output_format field is deprecated; don’t reach for it in new code.
Python with Pydantic
from pydantic import BaseModel
import anthropic
class Contact(BaseModel):
name: str
email: str
plan: str
client = anthropic.Anthropic()
response = client.messages.parse(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Extract: Jane Doe (jane@co.com), Enterprise."}],
output_format=Contact,
)
print(response.parsed_output.email) # jane@co.com
messages.parse() derives the schema from the model class and hands back a validated Contact instance on parsed_output — no manual json.loads.
TypeScript with Zod
import Anthropic from "@anthropic-ai/sdk";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { z } from "zod";
const Contact = z.object({
name: z.string(),
email: z.string(),
plan: z.string(),
});
const client = new Anthropic(); // reads ANTHROPIC_API_KEY
const response = await client.messages.parse({
model: "claude-opus-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Extract: Jane Doe (jane@co.com), Enterprise." }],
output_config: { format: zodOutputFormat(Contact) },
});
console.log(response.parsed_output.email);
Same guarantee, and parsed_output is typed from the Zod schema.
Schema-valid tool arguments
{
"name": "book_flight",
"description": "Book a flight to a destination.",
"strict": true,
"input_schema": {
"type": "object",
"properties": {
"destination": { "type": "string" },
"passengers": { "type": "integer" }
},
"required": ["destination", "passengers"],
"additionalProperties": false
}
}
strict: true is the tool-side twin: it constrains the arguments Claude sends you, not the reply text. Structured outputs and strict tools are independent and can be used in the same request.
Next: tool use · OpenAI structured outputs.