og.cc.cd / guides / openai-structured-outputs-json-schema

OpenAI Structured Outputs: Writing a JSON Schema That Strict Mode Accepts

By og.cc.cd · Published · Updated · 4 min read

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.

Updated · All sources ↓

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 in response_format.
  • Function calling with strict: true — when the model calls your tools. The function’s parameters schema 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

  1. The root must be an object and must not be anyOf. A Zod discriminated union at the top level, for example, produces an anyOf root and is rejected.
  2. Every field must be required. List every key of properties in required.
  3. Optional means nullable. To emulate an optional field, keep it required and allow null: "type": ["string", "null"].
  4. additionalProperties: false on 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

AreaSupported
Typesstring, number, boolean, integer, object, array, enum, anyOf
String keywordspattern, format (date-time, time, date, duration, email, hostname, ipv4, ipv6, uuid)
Number keywordsmultipleOf, minimum, maximum, exclusiveMinimum, exclusiveMaximum
Array keywordsminItems, maxItems
Reuse$defs with $ref, and recursive schemas ("$ref": "#")
Not supportedallOf, 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 $defs when 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 refusal field 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.

Related guides