OpenAI vs Anthropic Structured Outputs: Strict-Mode Schema Rules
Both OpenAI and Anthropic can force a model to return JSON that matches a JSON Schema exactly — but each accepts only a subset of JSON Schema, and the subsets differ. A schema that works against OpenAI's strict mode can be rejected outright by Anthropic, and vice versa. This guide compares the two strict-mode rule sets keyword by keyword, shows the failure modes, and gives you one portable schema that passes both. Check your schema against the structured outputs schema validator before you wire it into an API call. Rules change as both providers iterate — confirm anything load-bearing against the official OpenAI and Anthropic docs.
Short Answer
Structured outputs constrain a model to emit JSON that conforms exactly to a JSON Schema. OpenAI does this with a strict json_schema response format (and strict: true on tools) — now the production default; JSON mode is legacy. Anthropic does it with Structured Outputs and strict tool use, generally available in 2026 (the old structured-outputs-2025-11-13 beta header is no longer required). Both accept only a subset of JSON Schema, and the two subsets differ:
- Shared by both: every object must set
additionalProperties: false, and every property must effectively be declared. Optional fields are expressed as nullable ("type": ["string", "null"]), not by omission. - OpenAI is permissive but silent: it accepts range/pattern keywords like
minimum,maximum, andpatternbut does not enforce them. RootanyOfis forbidden; max nesting is 5 levels. - Anthropic is strict and loud: it rejects
minimum,maximum,multipleOf,minLength, andmaxLengthwith an API error. It has no recursive schemas, and caps tools and parameters per request.
The portable move is to write to the intersection: declare every property, mark optionals nullable, set additionalProperties: false everywhere, and do all value-range and pattern validation in your own code after the call. Confirm specifics against the official docs — both providers iterate on these rules.
Side by Side
The rule sets at a glance. Where they disagree is exactly where schemas break.
| JSON Schema feature | OpenAI strict mode | Anthropic structured outputs |
|---|---|---|
additionalProperties: false |
Required on every object | Required on every object |
| Required vs optional | All properties must be in required; mark optional by making it nullable |
Every non-nullable property is required; add "null" to make optional |
minimum / maximum / multipleOf |
Accepted but not enforced (silently ignored) | Rejected with an API error |
minLength / maxLength |
Accepted but not enforced | Rejected with an API error |
pattern |
Accepted but not enforced | Supported, but no backreferences, lookahead/lookbehind, or \b |
format |
Accepted but not enforced | Limited support; don''t rely on it for validation |
minItems |
Accepted but not enforced | May only be 0 or 1 |
Root anyOf |
Not allowed — root must be an object | Allowed, but max 16 union-typed parameters |
| Nesting depth | Max 5 levels | No fixed published depth limit |
$ref / recursion |
$defs supported (internal refs) |
Internal #/$defs only; no recursive schemas, no external $ref |
| Tool / parameter limits | No published hard caps of this kind | Max 20 strict tools, 24 optional params, 16 union-typed params per request |
| Allowed types | string, number, integer, boolean, array, object, null | Standard JSON Schema types within the supported subset |
Structured Outputs vs JSON Mode vs Tool Calling
Three related features get conflated constantly. They are not the same thing:
- JSON mode guarantees the response is valid JSON — but not a specific shape. The model could return
{}, an array, or any keys at all. On OpenAI this is the legacy mechanism, superseded by structured outputs. - Structured outputs guarantee the response matches your exact JSON Schema — the right keys, types, enums, and nesting. This is what you reach for when downstream code depends on the shape.
- Function / tool calling is the model deciding to call a tool and producing arguments for it. With strict tool use, those arguments are constrained by the same JSON Schema rules described here — so a tool''s
parametersschema must obey the strict-mode subset, exactly like a response schema.
In short: JSON mode is "valid JSON," structured outputs is "this schema," and strict tool calling applies "this schema" to a tool''s arguments. The schema rules in this guide govern the latter two on both providers.
The Shared Rules
Start with what both providers demand, because most of a portable schema lives here. Two rules dominate:
- Every object must set
additionalProperties: false. No exceptions, at every level of nesting. - Every property must effectively be declared, and optionality is expressed by making a field nullable — not by leaving it out of
required.
OpenAI states this directly: list every property in required, and to make one optional, add "null" to its type. Anthropic arrives at the same place from the other direction — it treats every non-nullable property as required, so a nullable type is exactly how you opt a field out of being mandatory. The upshot is one shared idiom:
{
"type": "object",
"additionalProperties": false,
"properties": {
"title": { "type": "string" },
"priority": { "type": "string", "enum": ["low", "medium", "high"] },
"assignee": { "type": ["string", "null"] },
"due_date": { "type": ["string", "null"] }
},
"required": ["title", "priority", "assignee", "due_date"]
}
Here title and priority are genuinely required; assignee and due_date are "optional," expressed as nullable unions and still listed in required. This object validates against both providers'' strict modes.
OpenAI''s Rules
OpenAI''s strict mode is structurally rigid but value-permissive. The structural rules it enforces:
- All properties in
required. Mark a field optional by making it nullable ("type": ["string", "null"]). additionalProperties: falseon every object.- Root cannot be
anyOf. The top level must be an object.anyOfis fine on nested properties. - Max nesting depth of 5 levels. Deeper schemas are rejected.
$defssupported for shared/internal definitions.- Types limited to string, number, integer, boolean, array, object, null.
The trap is value constraints. OpenAI accepts pattern, format, minLength, maxLength, minimum, maximum, and multipleOf — but silently ignores them. They make your schema look validated when it isn''t. A schema like this compiles fine and enforces none of the numeric or string bounds:
{
"type": "object",
"additionalProperties": false,
"properties": {
"quantity": { "type": "integer", "minimum": 1, "maximum": 100 },
"sku": { "type": "string", "pattern": "^[A-Z]{3}-[0-9]{4}$" }
},
"required": ["quantity", "sku"]
}
The model is constrained to emit an integer and a string, but nothing stops quantity: 999 or a malformed sku. If those bounds matter, enforce them in your own code after the call.
Anthropic''s Rules
Anthropic''s structured outputs are the mirror image: it rejects what OpenAI ignores, so unsupported keywords surface loudly as API errors rather than quietly doing nothing.
- Rejected outright (API error):
minimum,maximum,multipleOf,minLength,maxLength. minItemsmay only be0or1— any other value is rejected.patternis supported, but cannot use backreferences, lookahead/lookbehind, or\b.- No recursive schemas and no external
$ref— only internal#/$defsreferences. - Per-request hard limits: at most 20 strict tools, at most 24 optional parameters, at most 16 union-typed parameters.
- Required properties are reordered before optional ones in the schema the API uses.
So the OpenAI example above fails on Anthropic — minimum and maximum trigger an error. The Anthropic-safe version drops them and keeps a backreference-free pattern:
{
"type": "object",
"additionalProperties": false,
"properties": {
"quantity": { "type": "integer" },
"sku": { "type": "string", "pattern": "^[A-Z]{3}-[0-9]{4}$" }
},
"required": ["quantity", "sku"]
}
The quantity bounds now live in your application code. The pattern is allowed because it uses no backreferences or lookaround — but remember it is only enforced on Anthropic, since OpenAI ignores pattern entirely.
One Schema That Passes Both
Write to the intersection of the two subsets and you get a schema you can send to either provider unchanged. The portable rules:
- Make the root an object (never
anyOfat the root — OpenAI forbids it). - Set
additionalProperties: falseon every object. - List every property in
required; express optional as nullable (["T", "null"]). - Drop all numeric and length keywords —
minimum,maximum,multipleOf,minLength,maxLength(Anthropic rejects them, OpenAI ignores them, so they buy you nothing portable). - Use
patternonly if it has no backreferences, lookahead/lookbehind, or\b— and treat it as advisory, since OpenAI won''t enforce it. - Keep nesting at or under 5 levels and use only internal
$defs(no recursion, no external$ref). - Keep
minItemsat0or1if you use it at all.
A portable schema for an extracted support ticket:
{
"type": "object",
"additionalProperties": false,
"properties": {
"subject": { "type": "string" },
"category": {
"type": "string",
"enum": ["billing", "bug", "feature_request", "other"]
},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"]
},
"tags": {
"type": "array",
"items": { "type": "string" }
},
"assignee": { "type": ["string", "null"] }
},
"required": ["subject", "category", "priority", "tags", "assignee"]
}
Every value constraint that matters here is expressed through enum and type, which both providers enforce. The "optional" assignee is nullable. There are no range keywords to trip Anthropic and nothing for OpenAI to silently ignore. This schema is safe to send to either API as-is.
Common Rejection Causes
When a schema that "looks fine" gets rejected — or quietly under-validates — it is almost always one of these:
- A missing
additionalProperties: falseon a nested object. Both providers want it on every object, not just the root. - A property left out of
requiredon the assumption that omission makes it optional. It doesn''t — use a nullable type instead. minimum/maximum/minLength/maxLengthsent to Anthropic. These are hard rejections, not warnings.- Expecting OpenAI to enforce
patternorminimum. No error, but no enforcement either — a silent correctness bug. - A root
anyOfon OpenAI. Wrap the union in an object property instead. - Nesting deeper than 5 levels on OpenAI. Flatten the schema.
- Recursive or external
$refon Anthropic. Inline the definition or restructure to internal#/$defsonly. - More than 20 strict tools, 24 optional params, or 16 union params on Anthropic. Split the request or reduce the surface.
- A
patternusing\b, lookahead, lookbehind, or backreferences on Anthropic. Rewrite it without those constructs.
The painful part is timing: both providers validate at request time, so these surface as opaque API errors — or, on OpenAI, as constraints that just never fire — after you''ve built and shipped the integration. Catch them before the API does.
Validate Before You Ship
The whole class of failure above is avoidable by checking the schema locally before it ever hits an API call. The workflow that prevents the late-night rejection:
- Generate or write the schema targeting the portable subset (object root,
additionalProperties: falseeverywhere, nullable optionals, no range keywords). - Validate it against both providers'' strict-mode rules locally — checking for a root
anyOf, missingadditionalProperties, properties absent fromrequired, rejected keywords, illegal regex constructs, and nesting depth. - Only then wire it into the API call. A schema that passes the local check passes the provider''s check.
Because both providers iterate on these rules, re-confirm anything that breaks against the current OpenAI and Anthropic documentation — the strict-mode subset is a moving target, and a keyword that''s ignored today may be enforced (or rejected) tomorrow.
Try It Live
The structured outputs schema validator checks a JSON Schema against both OpenAI and Anthropic strict-mode rules in your browser — flagging a root anyOf, missing additionalProperties: false, properties left out of required, and keywords each provider rejects or ignores, so you catch problems before the API does. Need a schema to start from? The JSON Schema generator infers a draft schema from a sample JSON payload — then tighten it to the portable subset described above and validate the result.
Frequently Asked Questions
What is the difference between structured outputs and JSON mode?
JSON mode only guarantees that the model returns syntactically valid JSON — any shape, any keys. Structured outputs guarantee the JSON matches a specific JSON Schema exactly: the right keys, types, enums, and nesting. On OpenAI, JSON mode is the legacy feature and strict json_schema response format is the production default; on Anthropic, structured outputs reached general availability in 2026. If you care about the shape of the output and not just that it parses, use structured outputs, not JSON mode.
Does OpenAI strict mode enforce minimum, maximum, and pattern constraints?
No — and this is the most common surprise. OpenAI's strict json_schema mode accepts keywords like pattern, format, minLength, maxLength, minimum, maximum, and multipleOf without error, but it does not enforce them — they silently do nothing. The model is constrained to the right types and structure, not to value ranges or string patterns. If you need a number bounded or a string to match a regex, validate it yourself after the call. Anthropic takes the opposite stance and rejects most of these keywords outright with an API error.
How do I mark a field optional in OpenAI vs Anthropic structured outputs?
Both providers make optionality a property of the type, not of a required list. On OpenAI, every property must appear in required; you make a field optional by making it nullable — "type": ["string", "null"] — so the model may emit null. Anthropic treats every non-nullable property as required, so you make a field optional the same way: add "null" to its type. The portable rule is therefore identical: list every property, and express "optional" as a nullable union rather than by omitting it from required.
Can the root of a structured-outputs schema be an anyOf or a union?
On OpenAI, no — the root schema cannot be anyOf. The top level must be an object (you can use anyOf on nested properties, just not at the root). Anthropic has its own union limits: at most 16 union-typed parameters across a tool's schema. If you need the model to pick between several top-level shapes, wrap them: make the root an object with a single property whose value is the anyOf, or add a discriminator field plus per-variant nested objects.
Why does my schema get rejected after I already built the tool?
Because both providers validate the schema at request time, not at design time — so a single unsupported keyword surfaces as an opaque API error (or, on OpenAI, as a silently ignored constraint) only after you've written and deployed the integration. Common causes: using minimum/maximum with Anthropic (rejected), expecting OpenAI to enforce pattern (ignored), a root anyOf on OpenAI, nesting deeper than 5 levels on OpenAI, recursive $ref on Anthropic, or more than 20 strict tools per request on Anthropic. Validate the schema locally first with the structured outputs validator so you catch these before they reach the API.