By Sean Donahoe · Published July 17, 2026 · accurate as of this date
The pipeline ran clean for three weeks. Then 3am, a pager, and a JSONDecodeError on a response that opened with "Sure! Here's the JSON:". Not a code bug. Not a bad model. Just the day the model felt chatty, and your parser met a sentence where it expected a brace.
Everyone who parses model output has that story. The fix people reach for is a better prompt. The fix that actually holds is moving the guarantee off the prompt entirely.
Why not just ask the model for JSON in the prompt?
Because the prompt is a request, not a contract. The model honors it most of the time and quietly defects the rest, and those defections are not random noise you can grep for. A schema-enforced output mode constrains what the decoder is even allowed to emit, so producing valid, correctly-shaped JSON stops being a coin flip and starts being a property of the call.
The three ways prompt-and-pray fails, and only one is loud
Here is the thing nobody tells you when they say "just tell it to return JSON." That instruction can fail in three different ways, and the loud one is the least of your problems.
Invalid JSON. The loud one. A markdown fence, a chatty preamble, a trailing comma. Your parser throws. Annoying, but at least you know it happened.
Valid JSON, wrong shape. Silent. A required key goes missing, a field you never asked for shows up, a nested object arrives where you expected a list. It parses fine. Then it corrupts whatever it feeds three functions downstream.
Valid JSON, right shape, wrong types. The quietest of all. "age": "36" as a string instead of the number 36. An enum value the model invented on the spot. Your try/except sees none of this, because nothing threw.
So the real tax on prompt-and-pray was never the crash. It's the two failures that don't crash, riding downstream into your data with a clean parse behind them.
What does a real structured-output mode actually change?
It moves the guarantee from the prompt, where it's probabilistic, into the output contract, where it's enforced. Two tiers do two different jobs. JSON mode guarantees you get valid JSON. JSON-schema mode goes further and constrains the output to your shape. OpenAI's own docs are blunt about the gap: "While both ensure valid JSON is produced, only Structured Outputs ensure schema adherence," and their comparison table lists JSON mode as "Adheres to schema: No."
That is the whole distinction, and it maps straight onto the failure modes above: valid-but-wrong-shape is the silent class. JSON mode buys you the loud failure and nothing else. Only schema mode closes the quiet ones.
The evidence, and how much to trust it
OpenAI reports a large reliability gap on its own schema-following eval. A newer model with Structured Outputs turned on scored near-perfect. A separate older model relying on prompting alone struggled with the same task. Two different models, one eval.
Frame that honestly. It's a vendor's number, on a vendor's eval, one source, not an independent benchmark. So don't quote it like gospel. But the direction is the story, and the direction matches what every team that's shipped this feels in production: the guarantee in the decoder holds where the guarantee in the prose slips.
The retry tax nobody budgets for
Every parse failure you do catch forces a retry. Same prompt, same input tokens, sent again. So a 5% format-failure rate on an endpoint isn't a 5% quality problem. It's a 5% surcharge on that endpoint, plus the added latency, plus the silent failures you never retried because they never threw.
Worth being precise here, because it's a different tax than the one people usually talk about. This isn't retrying because the model gave a wrong answer. It's retrying because the envelope came back broken while the answer inside might have been perfect. Format reliability shows up on the invoice, not only in correctness.
And on Flux you can actually price it. Per-request cost comes back on the X-Flux-Cost-Usd header on non-streaming responses on the OpenAI /v1 path, so your retry rate has a dollar figure attached instead of a shrug.
The router reality, said out loud
Here's where a lot of "just use structured outputs" advice goes quiet. We won't.
If you're already running your calls through a gateway, schema-enforced output is one of the guarantees you actually get to keep. So it's worth knowing exactly how far it travels.
Not every model enforces a strict schema. JSON mode is widely supported across models. Strict json_schema enforcement is honored by fewer of them, and it's a property of the model that serves the request, not something the gateway bolts on.
Two things follow from that, and neither is complicated:
- If you route across many models, the portable guarantee is JSON mode plus your own validation. Strict schema is a per-model capability, so when your app genuinely depends on it, pin a model you've verified and depend on that. Don't assume a mode travels.
- On the Anthropic-compatible path there's no
response_formatfield at all. The shape lives in a tool's input schema instead. Same principle, enforce the shape in the API, different lever to pull.
So is asking for JSON in the prompt ever fine?
Yes. Pretending otherwise is dishonest, and you'd catch me out the first time you shipped a throwaway script. Prototypes, one-off jobs, a human reading the output anyway, a model that doesn't support the mode you want: prompt-and-pray is genuinely fine when a failure is cheap and visible. It stops being fine the moment that output feeds another system with nobody watching.
What to do Monday
No code here, just the order to make the call in.
- If your model enforces a JSON schema, use it. That's the strongest guarantee on the table. Take it.
- If it doesn't, use a tool or function schema as the portable fallback. The shape still lives in the API contract, not in your prose.
- If neither is available, JSON mode plus explicit validation. Parse it, check the required keys, check the types. Don't trust the parse alone.
- Validate the output no matter which tier you used. Even a strict mode can be pinned to a model that later regresses. Trust the contract, verify the delivery.
- If you catch yourself writing a regex to strip
```jsonfences off a response, stop. That regex is a monument to a decision that belonged in the API call.
The whole argument collapses to one line: the guarantee belongs in the call, not the prompt. When you're ready to wire it up, the step-by-step how-to has the response_format modes, the schema, and how to pin a schema-capable model.
