OpenAI Structured Outputs: Writing a JSON Schema That Strict Mode Accepts
TL;DR: OpenAI strict mode accepts only a subset of JSON Schema: object root, additionalProperties: false on every object, every property required, and optional fields expressed as a union with null.
Structured Outputs makes an OpenAI model’s reply match your JSON Schema instead of merely being valid JSON. The catch is that strict mode accepts only a subset of JSON Schema. These are the rules, where to put the schema, and how to handle the cases where the model still does not return it.
Generate a strict JSON Schema →All guides
Structured Outputs vs JSON mode
OpenAI describes Structured Outputs as the evolution of JSON mode: both guarantee valid JSON, but only Structured Outputs guarantees adherence to your schema, and OpenAI recommends it whenever possible (Structured model outputs). It is available on GPT-4o and later models; with response_format it needs the gpt-4o-mini / gpt-4o-2024-08-06 snapshots or later.
Two ways to use it
- Response format — when you want the model’s answer to the user in a fixed shape (a UI card, an extraction result). In the Responses API it goes in
text.format; in Chat Completions inresponse_format. - Function calling with
strict: true— when the model calls your tools. The function’sparametersschema is enforced the same way (Function calling — strict mode).
// Responses API
{
"model": "gpt-6-astra",
"input": "...",
"text": {
"format": {
"type": "json_schema",
"name": "order",
"strict": true,
"schema": { "type": "object", "properties": { ... }, "required": [ ... ], "additionalProperties": false }
}
}
}
// Chat Completions
"response_format": { "type": "json_schema", "json_schema": { "name": "order", "strict": true, "schema": { ... } } }
The strict-mode rules
- The root must be an object and must not be
anyOf. A Zod discriminated union at the top level, for example, produces ananyOfroot and is rejected. - Every field must be required. List every key of
propertiesinrequired. - Optional means nullable. To emulate an optional field, keep it required and allow null:
"type": ["string", "null"]. additionalProperties: falseon every object, including nested ones and objects inside arrays.
If you send strict: true with a schema that breaks these rules, the API returns an error. For function calling, OpenAI notes a difference between APIs when you omit strict: the Responses API tries to normalise your schema into strict mode and falls back to best-effort if it cannot (the tool then shows strict: false), while Chat Completions stays non-strict by default.
What is supported
| Area | Supported |
|---|---|
| Types | string, number, boolean, integer, object, array, enum, anyOf |
| String keywords | pattern, format (date-time, time, date, duration, email, hostname, ipv4, ipv6, uuid) |
| Number keywords | multipleOf, minimum, maximum, exclusiveMinimum, exclusiveMaximum |
| Array keywords | minItems, maxItems |
| Reuse | $defs with $ref, and recursive schemas ("$ref": "#") |
| Not supported | allOf, not, dependentRequired, dependentSchemas, if / then / else |
Fine-tuned models support less: the docs list string length/pattern/format, number ranges, patternProperties and array length keywords as unsupported for them.
Size limits
- Up to 5,000 object properties in total and up to 10 levels of nesting.
- Total string length of property names, definition names, enum values and const values: up to 120,000 characters.
- Up to 1,000 enum values across the schema; for a single string enum with more than 250 values, their total length may not exceed 15,000 characters.
One more detail worth knowing: outputs are produced in the same order as the keys in your schema. Put a reasoning or steps field before final_answer if you want the model to work through the problem first.
A worked example
Say you want to extract orders. Start from one realistic sample — the fastest way is to paste it into the JSON Schema generator in strict mode:
{
"type": "object",
"properties": {
"order_id": { "type": "string" },
"coupon": { "type": ["string", "null"] },
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sku": { "type": "string" },
"qty": { "type": "integer" }
},
"required": ["sku", "qty"],
"additionalProperties": false
}
}
},
"required": ["order_id", "coupon", "items"],
"additionalProperties": false
}
Then improve it by hand: add a description to keys whose meaning is not obvious, turn fixed vocabularies into enums, and use clear key names — the guide’s own tips are to name keys clearly, add titles and descriptions for important keys, and use evals to pick the structure that works best.
Schema design tips that pay off
- Prefer enums to free text for anything with a fixed vocabulary (status, category, currency). The model can only pick a listed value, and your code no longer has to normalise spellings.
- Keep nesting shallow. The 10-level limit is generous, but flatter objects are easier for the model to fill correctly and easier for you to validate.
- Reuse with
$defswhen the same sub-object appears in several places, instead of copying it. - Use null deliberately. A nullable field tells the model “unknown is an acceptable answer”, which is often better than letting it invent a value.
Edge cases you still have to handle
- Refusals. If the model refuses for safety reasons, the reply may not follow your schema; the API flags this with a
refusalfield so your code can show it instead of parsing. - Truncation. If the response hits the output-token limit it is incomplete and will not parse. Check the response status and leave enough
max_output_tokens. - Unrelated input. The model always tries to fit the schema, which OpenAI warns can produce hallucinations when the input has nothing to do with the task. Tell it in the prompt what to return in that case (for example empty fields or a fixed message).
- Schema drift. OpenAI strongly recommends the SDKs’ native schema helpers (such as the Zod and Pydantic integrations) so your types and schema cannot silently diverge; otherwise add a CI step that regenerates or checks the schema.
Strict mode is about shape, not truth: a schema guarantees a price field is a number, not that the number is correct. Validate business rules in code.
Sources
Vendor features and prices were checked against these official pages on 2026-10-20. Products change; if something here no longer matches, the official page is right.
- OpenAI — Structured model outputs
https://developers.openai.com/api/docs/guides/structured-outputs - OpenAI — Function calling (strict mode)
https://developers.openai.com/api/docs/guides/function-calling - JSON Schema — specification overview
https://json-schema.org/specification