
Use strict tool use when the output triggers an action in your system, a schema response format when code consumes the final answer, and JSON mode only when neither is available. Schema-constrained output removes most retry loops for malformed JSON, but you still check for refusals and max_tokens cutoffs before parsing, and you still validate the values against your business rules after parsing.
Three Mechanisms, Three Different Promises
All provider details below come from the official documentation as it stood on 2026-10-16. Parameter names and model support change, so re-check the linked pages before you copy a request shape.
| Mechanism | What the provider promises | How you turn it on |
|---|---|---|
| JSON mode (OpenAI) | Valid JSON, "except for in some edge cases". No schema. | text.format: { type: "json_object" } in the Responses API |
| Schema response format | Output adheres to your JSON Schema, within each provider's supported subset | OpenAI: text.format with type: "json_schema" and strict: true. Claude: output_config.format with type: "json_schema" |
| Strict tool use | Tool arguments follow the tool's schema; on Claude, the tool name is also valid | strict: true on the function or tool definition |
OpenAI's Structured Outputs guide puts the core difference in one line: both features produce valid JSON, but only Structured Outputs ensures schema adherence. JSON mode still needs your own schema validation and, as OpenAI advises, potentially retries. The guide recommends "always using Structured Outputs instead of JSON mode when possible". The json_schema format is supported from the gpt-4o-mini and gpt-4o-2024-08-06 snapshots onward; older models such as gpt-4-turbo have JSON mode only.
JSON mode has a trap of its own. You must tell the model to produce JSON somewhere in the conversation. If the string "JSON" does not appear in the context, the API returns an error; without a real instruction, the model may emit whitespace until it reaches the token limit.
Anthropic's structured outputs page marks the feature as generally available and describes two parts: JSON outputs through output_config.format, and strict tool use through strict: true on a tool definition. The page describes no schema-less JSON mode. If you still send the older output_format parameter, it is deprecated and needs the structured-outputs-2025-11-13 beta header, or the API returns a 400 error.
A Decision Tree You Can Apply in One Pass
- Does the output trigger an action in your system? A database query, a refund, a ticket update: use strict tool use. OpenAI's guide draws the same line: function calling when you connect the model to tools, functions or data in your system; a structured
text.formatwhen you structure the model's response to the user. Whether that tool should live in your process or behind a protocol server is a separate question, covered in MCP vs Function Calling. - Does code consume the final answer? Extraction, classification, a UI payload: use a schema response format.
- Can your schema be expressed in the provider's subset? If a keyword is rejected, first try to move that rule into your own validator (next section). Only if the model has no schema support at all, fall back to JSON mode on OpenAI, or to a prompt plus validation on Claude, and keep a bounded retry for that branch only.
- Do you need both? Anthropic documents using JSON outputs and strict tools in the same request, for example an agent that calls tools during the turn and returns a structured final answer.
One OpenAI default deserves an explicit check. If you omit strict on a function, the function calling guide says Responses requests try to normalize the schema into strict mode and fall back to best-effort calling when that fails, showing strict: false on the response tool. Chat Completions stays non-strict by default. Set the flag yourself and assert the echoed value in a test, so a schema edit cannot silently downgrade you. The same guide notes that with a fine-tuned model, strict mode is disabled for calls made in parallel in one turn; parallel_tool_calls: false limits a turn to zero or one call.
Schema Keywords Each Provider Rejects
The subsets differ enough that one schema can pass on one provider and fail with an error on the other. As documented on 2026-10-16:
| Feature | OpenAI Structured Outputs | Claude structured outputs |
|---|---|---|
| Optional fields | Every field must be in required; emulate optional with a null union type | Allowed; required properties are emitted first, then optional ones |
additionalProperties | Must be false on every object | Must be false on every object |
Numeric limits (minimum, maximum, multipleOf) | Supported, except on fine-tuned models | Not supported; 400 error |
String length (minLength, maxLength) | Not in the supported list; pattern and format are | Not supported; pattern works without backreferences, lookaround or \b |
| Array size | minItems and maxItems supported | Only minItems of 0 or 1 |
| Recursion | Supported | Not supported |
| Composition | anyOf only, and not at the root; allOf, not, if/then/else, dependentRequired, dependentSchemas rejected | anyOf and allOf, but allOf with $ref not supported |
| Size limits | Up to 5,000 object properties, 10 nesting levels, 1,000 enum values per schema | Per request: 20 strict tools, 24 optional parameters, 16 parameters with union types across all strict schemas |
This is not the complete keyword list for either provider; check both pages for format strings, regex details and character limits. Claude's limits add up across every strict tool and output schema in one request, so four strict tools with six optional parameters each already reach 24. A schema that passes the explicit checks can still fail with "Schema is too complex for compilation".
When Claude rejects a keyword, the official SDK helpers can strip it, append it to the field description, and validate the response against your original schema. That validation only happens in helpers that validate: Anthropic notes that the TypeScript jsonSchemaOutputFormat() helper transforms the schema but does not check the response, so a minimum that was moved into a description is a hint to the model, not a constraint.
Same Schema, Two Request Shapes
Both requests use one schema object. The only differences are the provider's wrapper and Claude's required max_tokens.
const ticketSchema = {
type: "object",
properties: {
category: { type: "string", enum: ["billing", "bug", "account"] },
order_id: { type: ["string", "null"] },
summary: { type: "string" }
},
required: ["category", "order_id", "summary"],
additionalProperties: false
};
// OpenAI Responses API
const openaiResponse = await openai.responses.create({
model: OPENAI_MODEL,
input: ticketText,
text: { format: { type: "json_schema", name: "ticket", strict: true, schema: ticketSchema } }
});
// Claude Messages API
const claudeResponse = await anthropic.messages.create({
model: CLAUDE_MODEL,
max_tokens: 1024,
messages: [{ role: "user", content: ticketText }],
output_config: { format: { type: "json_schema", schema: ticketSchema } }
});
Making order_id a required field with a null type keeps the schema valid on OpenAI, where every field must be required, and on Claude, which counts a ["string", "null"] type toward its 16 union-type parameters.
The Checks That Still Belong in Code
Both providers say the schema can break. OpenAI lists a safety refusal and an incomplete response at the max tokens limit. Anthropic says its structured outputs guarantee schema compliance "in most cases" and names the same two exceptions. Check the stop state before you parse:
- Refusal. OpenAI returns a content item of type
refusalinstead of your JSON. Claude returnsstop_reason: "refusal"with HTTP status 200, bills the generated tokens, and the output may not match the schema. - Truncation. OpenAI sets
status: "incomplete"withincomplete_details.reason: "max_output_tokens". Claude setsstop_reason: "max_tokens", and the output may be incomplete. Test Truncated Structured Outputs shows how to force this case in a contract test. - Enum casing on Claude. Anthropic documents that capitalization of string
enumandconstvalues is not guaranteed: "Conversation topic 3" can come back as "Conversation Topic 3" with a normal stop reason and no error. Compare enum values case-insensitively and avoid values that differ only in case. - Semantic validation. OpenAI's guide says outputs "can still contain mistakes", and that a model trying to fit an unrelated input to the schema can hallucinate. A valid
order_idstring is not an existing order. Check that values exist, belong to the current user and make sense together; AI Guardrails for Developers covers authorization and safe rendering of model output.
function readTicket(claudeResponse) {
if (claudeResponse.stop_reason === "refusal") throw new RefusedOutput();
if (claudeResponse.stop_reason === "max_tokens") throw new TruncatedOutput();
const block = claudeResponse.content.find((b) => b.type === "text");
const ticket = JSON.parse(block.text); // shape is constrained
ticket.category = ticket.category.toLowerCase(); // casing is not
if (ticket.order_id !== null) assertOrderBelongsToUser(ticket.order_id);
return ticket;
}
What you can usually delete is the retry loop that existed only because JSON failed to parse or a required key was missing. Keep retries for truncation, with a higher token limit, and decide separately what a refusal means for the user.
Cost and Latency Side Effects
Both providers document a slower first request with a new schema while it is processed or compiled; neither page gives a figure. Anthropic caches compiled grammars for 24 hours from last use and invalidates them when the schema structure or the set of tools changes. It also injects a system prompt that explains the format, which adds input tokens, and changing output_config.format invalidates the prompt cache for that conversation. On Claude, JSON outputs cannot be combined with Citations, which returns a 400 error, or with message prefilling. Generate schemas once, keep them stable between requests, and log the first-request latency per schema version.
- OpenAI API docs: Structured model outputs: JSON mode versus Structured Outputs, enabling parameters, supported models, supported schema subset, limits, refusal and incomplete handling.
- OpenAI API docs: Function calling: strict mode requirements, default when
strictis omitted, fine-tuned parallel-call caveat. - Claude Platform Docs: Structured outputs: GA status,
output_config.format, schema limitations, complexity limits, invalid outputs, enum casing, SDK transformation, cost and compatibility notes. - Claude Platform Docs: Strict tool use:
strict: trueplacement and guarantees for tool inputs and names.
The decision tree, the comparison tables and the readTicket sketch are this article's proposals. No benchmarks or reliability measurements were run, and the code was not executed.
Related Guides
Test Truncated Structured Outputs
Force an output-budget cutoff in a contract test and keep incomplete JSON away from your write path.
AI Guardrails for Developers
Validate model output for authorization, business rules and safe rendering after the shape is right.
MCP vs Function Calling
Decide where a tool runs and where consent lives once you have chosen strict tool use.