response_format and the model’s reply is parsed, pruned to your schema’s
own keys, validated against it, repaired at most once, and returned as a json
output item — or refused with a 422 that names the paths that did not match.
json and the value is under the json key. output is
still an array — a run can produce prose and a structured object.
Three formats
You may send the short string form (
"response_format": "json_schema") or the
object form. The object form takes name (up to 64 characters), schema
(required for json_schema) and strict.
strict is accepted only as true. There is no mode in which a schema is
requested and not enforced. A caller told their schema was honoured when it was
not is the single outcome this whole feature exists to prevent, and a flag that
turns validation off is that outcome with a name.The schema subset
The validator is a closed keyword set, not a full JSON Schema engine. Accepting a keyword and then ignoring it is how an API quietly returns something other than what was asked for. Accepted:object, array, string, number, integer, boolean, null.
Formats: email, date, date-time, uri, uuid.
Refused, each with a 400 naming the keyword and the reason:
Bounds: 16 KB of schema, 6 levels deep, 200 nodes, 100 properties per object, 200
enum values. Property names match
[A-Za-z_][A-Za-z0-9_-]{0,63} — the characters
that are the evidence-path grammar (. and []) cannot also appear in a name,
or an entry for a.b would be indistinguishable from one for b inside a.
Two deliberate departures from JSON Schema
format is asserted, not annotated
format is asserted, not annotated
In JSON Schema,
format is a hint a validator may ignore. Here it is
checked. A caller who writes "format": "email" is telling us what they will
do with the value, and handing them "n/a" because the spec permits it would
be the wrong kind of correct.A required field that came back null is missing
A required field that came back null is missing
JSON Schema counts the key as present.
{"full_name": null} is not an
answer, it is the absence of one wearing a key.If you genuinely want “present, possibly null”, write
"type": ["string", "null"]. The subset supports it and it then passes.additionalProperties prunes rather than fails
It defaults to false, and the effect is to remove keys you did not ask for
before validation runs. A model volunteering an extra field is the commonest form
of plausible-looking noise, and neither silently returning it nor failing the
whole run over it is the right answer.
Cost is knowable before the run starts
One repair pass by default; two at the absolute most. So one structured run costs at most1 + max_repairs model calls, and a bad schema cannot turn into an
open-ended spend. There are no unlimited repair loops.
A model that produced the wrong shape twice, given the exact failing paths, is
not going to produce the right one on the third try — and each attempt is a full
turn, with knowledge, memory and tools.
When it fails
1
A schema this runtime cannot enforce is a 400, before any spend
The schema is checked when the request is parsed — before authorization,
before the idempotency claim, before a single model call. So an unsupported
keyword costs you nothing.
2
Output that will not validate is a 422
Streaming a structured run
Fragments arrive asresponse.output_json.delta rather than
response.output_text.delta. They are still advisory — read the validated object
off response.completed. A partial JSON string is not JSON.
Reports
POST /reports accepts "format": "json" with a schema and honours it the same
way. See Reports.
