GuideModels & inference
LLM structured outputs with JSON schema in the OpenAI and Claude APIs
Get schema-valid JSON from OpenAI and Claude with LLM structured outputs, JSON schema and strict tool use, and know what each API guarantees.
Both the OpenAI and Claude APIs offer LLM structured outputs with JSON schema: you send a schema, and the model's response is constrained to match it. On OpenAI you set text.format to a json_schema with strict: true. On Claude you set output_config.format to a json_schema, and for tool calls you add strict: true to the tool definition.
Why asking for JSON in the prompt fails
A prompt that says "reply in JSON" is a request, and the model can ignore parts of it. Anthropic's structured outputs page lists what goes wrong without constrained decoding: invalid JSON syntax, missing required fields, inconsistent data types and schema violations that need retries.
OpenAI's JSON mode only partly fixes this. The OpenAI guide says JSON mode guarantees valid JSON but not adherence to any schema. JSON mode also needs the word "JSON" somewhere in the context, or the API returns an error. Without that instruction, the guide warns, the model may emit whitespace until it hits the token limit.
Structured outputs go further. Both providers compile your schema and restrict which tokens the model can sample, so the output parses and has the fields and types you declared.
OpenAI structured outputs with a JSON schema
OpenAI's Structured Outputs guide (opens in a new tab) says the feature is available on models starting with GPT-4o, and recommends gpt-6-astra for new projects. With response_format of type json_schema, the supported snapshots are gpt-4o-mini, gpt-4o-mini-2024-07-18, gpt-4o-2024-08-06 and later. The feature works in the Responses, Chat Completions, Assistants, Fine-tuning and Batch APIs.
Use the SDK helper with a Pydantic model
The Python SDK turns a Pydantic model into a schema and parses the reply:
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI()
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
response = client.responses.parse(
model="gpt-6-astra",
input=[
{"role": "system", "content": "Extract the event information."},
{"role": "user", "content": "Alice and Bob are going to a science fair on Friday."},
],
text_format=CalendarEvent,
)
event = response.output_parsed
The JavaScript SDK does the same with a Zod schema and zodTextFormat() from openai/helpers/zod.
Send a raw JSON schema
Without a helper, the Responses API takes the schema under text.format:
response = client.responses.create(
model="gpt-6-astra",
input="Extract the event: Alice and Bob are going to a science fair on Friday.",
text={
"format": {
"type": "json_schema",
"name": "event",
"strict": True,
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"date": {"type": "string"},
"participants": {"type": "array", "items": {"type": "string"}},
},
"required": ["name", "date", "participants"],
"additionalProperties": False,
},
}
},
)
OpenAI strict function calling
For tools, OpenAI applies the same engine through strict: true on a function definition. The function calling guide says strict mode needs additionalProperties: false on every object and every property listed in required. If you omit strict, Responses requests try to normalize the schema into strict mode and fall back to best-effort calling when they cannot. Chat Completions requests stay non-strict by default.
Claude structured outputs and strict tool use
Anthropic's structured outputs documentation (opens in a new tab) describes two features that can be used separately or in the same request:
- JSON outputs, set with
output_config.format, control the shape of Claude's response. - Strict tool use, set with
strict: trueon a tool, guarantees that tool names and inputs match the tool's schema.
Supported models listed on that page include claude-opus-5-5, claude-sonnet-5-5, claude-opus-4-6, claude-sonnet-4-5-20250929 and claude-haiku-4-5-20251001.
Claude JSON outputs with a Pydantic model
from anthropic import Anthropic
from pydantic import BaseModel
class ContactInfo(BaseModel):
name: str
email: str
plan_interest: str
demo_requested: bool
client = Anthropic()
response = client.messages.parse(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Extract the key information from this email: John Smith "
"([email protected]) wants a demo of the Enterprise plan next Tuesday.",
}],
output_format=ContactInfo,
)
print(response.parsed_output)
The SDK sends the schema as output_config.format, validates the response and returns the parsed model in parsed_output. In TypeScript, zodOutputFormat() from @anthropic-ai/sdk/helpers/zod plays the same role. A raw schema goes in output_config: {"format": {"type": "json_schema", "schema": {...}}}.
Anthropic notes that the older output_format request parameter has moved to output_config.format. The raw parameter now needs the structured-outputs-2025-11-13 beta header, or the API returns a 400 error.
Strict tool use and forced tool calls
The strict tool use page (opens in a new tab) gives an example of what strict mode prevents: a booking tool expecting passengers: 2 receiving "two" or "2" instead.
A common older pattern was to define one tool and force Claude to call it with tool_choice, then read the tool input as your JSON. Check your model before relying on that.
Anthropic's tool definition docs (opens in a new tab) say Claude Opus 5.5, Claude Sonnet 5.5, Claude Fable 5.1 and Claude Mythos 5.1 return a 400 error for tool_choice of type any or tool. For those models, Anthropic points to auto with strict tool use, or to JSON outputs when you need a fixed response shape. Manual extended thinking also blocks forced tool use.
Which JSON schema features each API supports
The two providers accept different subsets of JSON Schema. Sending an unsupported keyword with strict mode on produces an error in both APIs, so check this before you port a schema.
| Schema feature | OpenAI Structured Outputs | Claude structured outputs |
|---|---|---|
| Optional fields | Every field must be in required; emulate optional with a null union type | Allowed; at most 24 optional parameters across all strict schemas in a request |
additionalProperties | Must be false | Must be false |
| Root object | Must be an object, not anyOf | Not stated |
anyOf | Supported | Supported, with limits |
allOf | Not supported | Supported, except with $ref |
$ref and definitions | Supported | Supported; external $ref not supported |
| Recursive schemas | Supported | Not supported |
minimum, maximum, multipleOf | Supported (not for fine-tuned models) | Not supported; SDK helpers move them into descriptions |
minLength, maxLength | Not in the supported keyword list | Not supported |
minItems, maxItems | Supported | minItems of 0 or 1 only |
pattern | Supported | Supported, without backreferences, lookarounds or \b |
| String formats | date-time, time, date, duration, email, hostname, ipv4, ipv6, uuid | Same list plus uri |
| Size limits | 5,000 properties, 10 nesting levels, 1,000 enum values, 120,000 characters of names and values | 20 strict tools, 24 optional parameters, 16 union-type parameters, 180-second compile timeout |
| Key order | Same as schema | Schema order, but required properties come before optional ones |
Both providers also add latency the first time they see a schema. OpenAI says later requests with the same schema avoid it. Anthropic caches compiled grammars for 24 hours from last use. Changing only name or description fields does not reset Anthropic's cache.
Validate structured output anyway
A schema guarantee covers structure. It does not cover whether the values are right, and both providers document cases where the output does not match the schema at all.
Check these before you trust a parsed object:
- Refusals. OpenAI returns a
refusalcontent item instead of your schema. Claude setsstop_reason: "refusal"with a 200 status, and the output may not match the schema. - Truncation. An OpenAI response with
status: "incomplete"and reasonmax_output_tokens, or a Claude response withstop_reason: "max_tokens", can hold partial JSON. - Enum casing on Claude. Anthropic says Claude may return an enum or
constvalue that differs only in capitalization, with no error. Compare enum values case-insensitively. - Constraints the API dropped. Claude's SDK helpers strip unsupported constraints such as
minimumbefore sending, then check them on the response. A raw schema skips that check. - Wrong but valid values. OpenAI's guide says Structured Outputs can still contain mistakes, and that unrelated input can lead the model to invent values to fill the schema.
Running the response through your own model class costs one line in Python:
event = CalendarEvent.model_validate_json(raw_text)
OpenAI's guide also recommends generating schemas from your types with SDK helpers, so the schema and your code do not drift apart.
Decision table: LLM structured outputs with JSON schema
Use this as a starting point. The rows reflect the documented behavior of each API as of October 2026.
| You need | OpenAI | Claude |
|---|---|---|
| The final answer as JSON matching a schema | text.format with type: "json_schema", strict: true | output_config.format with type: "json_schema" |
| Arguments for your own functions, always typed correctly | Function calling with strict: true | Tool definition with strict: true |
| Both typed tool calls and a JSON final answer | Strict functions plus text.format | Strict tools plus output_config.format in one request |
| Valid JSON on an older model without schema support | JSON mode (type: "json_object") plus your own validation | Not documented as a separate mode |
| Optional fields without null placeholders | Not available; use null unions | Supported, within the 24-parameter limit |
| Recursive data such as trees | Supported | Not supported; flatten the structure |
| Numeric ranges or string length enforced | Numeric ranges supported; length limits not listed; validate in code | Validate in code, or let the SDK helper validate |
| Citations in the same response | Not covered in the OpenAI guide | Not available: citations with output_config.format return a 400 error |
Frequently asked questions
Do structured outputs cost more tokens?
On Claude, yes, slightly: Anthropic adds a system prompt that explains the expected format, and those tokens are billed like any other input. Changing `output_config.format` also invalidates any prompt cache for that conversation. The OpenAI guide reviewed here does not describe an extra prompt.
Can I stream a structured response?
Both providers support it. OpenAI's guide shows streaming responses and function call arguments while they are generated, and Anthropic lists streaming as compatible with structured outputs. Partial chunks will not parse as complete JSON until the stream ends, so validate after the final chunk.
Does using structured outputs affect data retention on Claude?
Anthropic says prompts and responses are processed with zero data retention, but the schema itself is cached for up to 24 hours after last use. For HIPAA workloads, Anthropic says protected health information must not appear in schema property names, enum values, const values or patterns.
Should a schema field ask the model to explain its reasoning?
Anthropic warns that a field asking for the model's thinking or step-by-step reasoning may trigger a `reasoning_extraction` refusal on Claude. Ask for a short explanation or supporting evidence instead.
Can I use structured outputs with message prefilling on Claude?
No. Anthropic lists message prefilling as incompatible with JSON outputs. Put formatting guidance in the schema and its field descriptions instead.
Sources
- 1.Structured model outputs | OpenAI APIOfficial docsPrimarydevelopers.openai.com
- 2.Structured outputs - Claude Platform DocsOfficial docsPrimaryplatform.claude.com
- 3.Tool use with Claude - Claude Platform DocsOfficial docsPrimaryplatform.claude.com
- 4.developers.openai.com/api/docs/guides/function-callingOfficial docsPrimary
- 5.platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-useOfficial docsPrimary
- 6.platform.claude.com/docs/en/agents-and-tools/tool-use/define-toolsOfficial docsPrimary
Spotted an error? See our corrections policy.
Enjoyed this? Get the weekly brief.
New guides, Project & Tool of the Week and the AI developments that matter. One email, no spam.
Read next
- TutorialModels & inference
Fine tune LLM LoRA adapters: data, training and a first test on one GPU
Fine tune LLM LoRA adapters on one GPU: check that fine-tuning fits, prepare data, train with PEFT and TRL or Unsloth, and compare against the base model.
6 min read
- GuideModels & inference
Prompt caching with the OpenAI, Claude and Gemini APIs: what is cached and what breaks it
Compare prompt caching API rules for OpenAI, Claude and Gemini, order prompts so the cache hits, avoid silent misses and measure savings from usage fields.
8 min read
NewsModels & inferenceIntel details the GPU D270, an air-cooled inference card with up to 480 GB
Intel detailed the GPU D270 inference card: 480 GB LPDDR5X on partner cards, 160 GB on its own, 350 W PCIe. No price or ship date yet for buyers.
4 min read