Skip to content

OpenAI Structured Output Schema Builder

Check a JSON Schema against the structured-outputs strict subset, or build one from a sample response. Finds why the API returns a 400.

Your JSON Schema

Verdict

would be rejected

Properties

6 / 100

Nesting

2 / 5

Enum values

3 / 500

Name characters

49 / 15,000

Issues

6 shown
  • error"additionalProperties": false is required on every object.

    It is absent here. Strict mode requires it explicitly — the JSON Schema default of true is not enough.

    /additionalProperties

  • errorEvery property must be listed in "required"; 3 are not: product, urgency, reporter.

    Strict mode has no optional fields. To make one optional, keep it required and give it type ["string", "null"].

    /required

  • error"minLength" is not supported in strict mode (string length).

    Strict mode enforces structure, not constraints. Move the requirement into "description" and validate it yourself after the response arrives.

    /properties/issue/minLength

  • error"additionalProperties": false is required on every object.

    It is absent here. Strict mode requires it explicitly — the JSON Schema default of true is not enough.

    /properties/reporter/additionalProperties

  • errorEvery property must be listed in "required"; 1 is not: email.

    Strict mode has no optional fields. To make one optional, keep it required and give it type ["string", "null"].

    /properties/reporter/required

  • error"format" is not supported in strict mode (string format).

    Strict mode enforces structure, not constraints. Move the requirement into "description" and validate it yourself after the response arrives.

    /properties/reporter/properties/email/format

Fixed automatically

6 changes
  • / — added "additionalProperties": false.
  • / — added product, urgency, reporter to "required".
  • /properties/issue — moved 1 unsupported keyword into the description.
  • /properties/reporter — added "additionalProperties": false.
  • /properties/reporter — added email to "required".
  • /properties/reporter/properties/email — moved 1 unsupported keyword into the description.

Rejected keywords are moved into the description rather than deleted, so the constraint still reaches the model — as a request, not a guarantee. Depth and property-count violations are left alone, because fixing those means deciding what to remove.

Not checked

the limits of a local validator
  • Whether the schema describes what you actually want back. Strict mode enforces shape, not correctness.
  • Whether the model can satisfy it. A schema can be strict-valid and still be hard to fill sensibly.
  • Per-model differences. The subset checked here is the documented one; a specific model may be narrower.
  • The 15,000-character total is checked, but a single enum above 250 values has a tighter separate limit that is not.

Checked and generated in your browser — nothing is uploaded. For validating data against a schema rather than checking the schema itself, use the JSON Schema Validator.

About the OpenAI Structured Output Schema Builder

This OpenAI schema builder checks a JSON Schema against the structured-outputs strict subset — the narrower set of JSON Schema that response_format with strict: true actually accepts. It exists because a schema can be entirely valid JSON Schema, pass every ordinary validator, and still be rejected by the API with a 400 at request time.

The rules that catch people are not obvious. additionalProperties: false is required on every object, and the JSON Schema default of true is not good enough — it has to be written down. Every property must appear in required, because strict mode has no optional fields at all; the way to express an optional value is a type union with null. And most constraint keywords — minLength, pattern, format, minimum — are rejected outright rather than ignored.

Every keyword that would be refused is named individually, with the category it belongs to and what to do instead. That follows the same rule as the JSON Schema validator on this site: a tool that reports "valid" while quietly skipping half the document has converted your ignorance into a false assurance, which is worse than having no tool.

The second mode works from the other end. Paste a sample of the response you want and get a schema that is already inside the subset — every object closed, every property required, sometimes-absent fields expressed as nullable rather than dropped. It comes with the full request body, ready to paste.

Where a fix is mechanical, it is offered: missing additionalProperties added, required filled in, and rejected keywords moved into the description so the constraint still reaches the model as a request. Depth and property-count violations are left alone, because fixing those means deciding what to remove, and that is not a decision a tool should make for you.

  • Checks the documented strict subset: closed objects, full required arrays, supported types and keywords
  • Counts the four global limits — 100 properties, 5 levels of nesting, 500 enum values, 15,000 characters of names
  • Names every rejected keyword with its category, rather than reporting one generic failure
  • Unrecognised keywords reported as unchecked, so a clean verdict means the whole document was read
  • Automatic fixes for what is mechanical, with each change listed before you apply it
  • Builds a strict schema from a sample response, including the complete request body
  • Walks $defs, so a definition is checked as thoroughly as the root

How to use it

  1. Paste the JSON Schema you plan to send as response_format.json_schema.schema — the inner schema, not the whole request body.
  2. Read the verdict. Errors are what the API will reject; warnings are things worth knowing that will not fail the request.
  3. Check the four counters. These are the limits people grow into gradually and hit without warning.
  4. Apply the automatic fixes if you agree with them — every change is listed first.
  5. Switch to "Build from a sample response" if you would rather start from the JSON you want back than from a schema.
  6. Copy the request body to see how the schema sits inside the API call.

Real-world use cases

Backend & API developers

Catch the additionalProperties and required rejections before shipping response_format with strict: true, rather than discovering them as a 400 in production.

AI & LLM application engineers

Build a structured-output schema directly from a sample response when extracting entities, tickets or records from unstructured text, instead of writing the JSON Schema by hand.

Platform & SDK engineers

Run every schema in a shared library through the checker whenever a nested object or $defs entry is added — that is where additionalProperties: false is most often missed.

QA & eval engineers

Reproduce a real 'invalid schema for response_format' error from production logs by pasting the schema here, getting the exact keyword and JSON Pointer named rather than guessing which part failed.

Solutions engineers

Migrate a JSON Schema originally written for validation — with minLength, pattern or format constraints — into the strict subset, using the automatic fix to relocate rejected keywords into descriptions.

Examples

The single most common rejection

Input
{ "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"] }
Output
"additionalProperties": false is required on every object.

The schema looks complete. Strict mode requires the keyword explicitly, and JSON Schema's default of true does not satisfy it.

There are no optional fields

Input
{ "properties": { "a": {...}, "b": {...} }, "required": ["a"] }
Output
Every property must be listed in "required"; 1 is not: b.

The hint says what to do: keep b required and give it type ["string", "null"]. That is how strict mode expresses optionality.

Constraints are rejected, not ignored

Input
{ "type": "string", "minLength": 5, "pattern": "^[A-Z]" }
Output
"minLength" is not supported in strict mode (string length).
"pattern" is not supported in strict mode (string pattern).

Applying the automatic fix moves both into the description, so the model still sees the requirement — as guidance, which is all a prompt ever was.

Built from a sample response

Input
{"name": "Dana", "tags": ["auth"]}
Output
{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "tags": { "type": "array", "items": { "type": "string" } }
  },
  "required": ["name", "tags"],
  "additionalProperties": false
}

Closed and fully required by construction, so it passes the checker in this tool's other mode without any editing.

Common errors

MessageCauseFix
Invalid schema for response_format 'json_schema'The API's own rejection message. Almost always a missing additionalProperties: false, or a property not listed in required.Paste the schema here — both are reported by name and location, and both are fixed automatically.
'additionalProperties' is required to be supplied and to be falseAn object somewhere in the schema does not set it. Nested objects and $defs entries are the ones usually missed.The checker walks every level including $defs and reports each one with a JSON Pointer, so you can find the nested object rather than guessing.
The API rejects the schema but every keyword looks supportedA global limit rather than a keyword — over 100 properties across the whole schema, or more than 5 levels of nesting.Check the four counters. Nesting counts objects only, so a flat schema of scalars is one level, not two.
The model returns null for fields that should have valuesThe schema types them as nullable, which is what happens when a sample-built schema saw the field missing from some records.Remove "null" from those types. Nullable was chosen because strict mode has no other way to express "sometimes absent", but it is a floor, not a recommendation.
Root schema must be an objectThe schema's root is an array or a scalar. Strict mode does not allow either, and does not allow anyOf at the root.Wrap it: { "type": "object", "properties": { "items": <your array schema> }, "required": ["items"], "additionalProperties": false }.

Frequently asked questions

Why does my schema work without strict mode but fail with it?

Because strict mode is a much smaller language. Without it, the schema is a hint and unsupported keywords are ignored; with it, the API validates the schema itself against a documented subset and refuses anything outside it. That refusal is the price of the guarantee that the response will match.

How do I make a field optional?

You cannot, in the way you mean. Every property must be in required. The documented approach is to keep it required and make the type a union with null — ["string", "null"] — so the model always emits the key and can signal absence with null. Dropping it from required makes the schema invalid.

What happens to minLength, pattern and format?

They are rejected, not ignored, so the request fails rather than silently under-validating. Applying the automatic fix moves them into that property's description, which means the model still sees the constraint as an instruction. It is no longer enforced, so validate it yourself after the response arrives.

Does this send my schema to OpenAI to check it?

No. Nothing leaves your browser, and no API key is needed or accepted. The rules are the documented ones, implemented locally — which is also why you can check a schema for an endpoint you have not built yet.

Is oneOf really not supported?

Correct — anyOf is the only composition keyword strict mode accepts, and allOf, oneOf, not, if/then/else are all rejected. In most schemas oneOf can be replaced with anyOf directly, since the exclusivity oneOf adds is rarely what was wanted.

How is nesting depth counted?

Objects only. A root object whose properties are all strings is one level deep, not two — a scalar leaf is not a level. This matters because the number is compared against a hard limit of 5, so an off-by-one would reject a legal schema or accept an illegal one.

Why report keywords it does not recognise?

So that a clean result means the whole document was read. If an unknown keyword were passed over silently, "no issues" would mean "no issues in the parts I understood", and you would have no way to tell the difference. Unrecognised keywords are warnings, not errors, because they are usually ignored rather than rejected.

Does it support $ref and $defs?

Yes, with one restriction: only local refs into #/$defs/ or #/definitions/ are accepted — anything pointing elsewhere is reported as an error, since strict mode has no other resolution mechanism. Each $defs entry is walked and checked on its own with the same rules as the root, and $ref is followed for the global property, nesting and character-count counters so a schema that fans out through definitions is still measured accurately.

Last updated