ArticleAI Architecture

Structured Outputs vs JSON Mode: Which One to Use, and Where Strict Tool Use Fits

Asking a model for JSON now has three answers: a mode that returns parseable JSON, a response format that follows your schema, and tool definitions with a strict flag. They promise different things, reject different schema keywords, and none of them makes a parsed object correct.

Last reviewed: 2026-10-16

A glowing cyan cube rests on a dark platform between an open metal frame and a row of black cabinet modules, one with an amber circuit-patterned panel and one closed with a padlock.
Three ways to ask a model for JSON, each with a different promise about what comes back.
TL;DR

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.

MechanismWhat the provider promisesHow 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 formatOutput adheres to your JSON Schema, within each provider's supported subsetOpenAI: text.format with type: "json_schema" and strict: true. Claude: output_config.format with type: "json_schema"
Strict tool useTool arguments follow the tool's schema; on Claude, the tool name is also validstrict: 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

  1. 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.format when 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.
  2. Does code consume the final answer? Extraction, classification, a UI payload: use a schema response format.
  3. 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.
  4. 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:

FeatureOpenAI Structured OutputsClaude structured outputs
Optional fieldsEvery field must be in required; emulate optional with a null union typeAllowed; required properties are emitted first, then optional ones
additionalPropertiesMust be false on every objectMust be false on every object
Numeric limits (minimum, maximum, multipleOf)Supported, except on fine-tuned modelsNot supported; 400 error
String length (minLength, maxLength)Not in the supported list; pattern and format areNot supported; pattern works without backreferences, lookaround or \b
Array sizeminItems and maxItems supportedOnly minItems of 0 or 1
RecursionSupportedNot supported
CompositionanyOf only, and not at the root; allOf, not, if/then/else, dependentRequired, dependentSchemas rejectedanyOf and allOf, but allOf with $ref not supported
Size limitsUp to 5,000 object properties, 10 nesting levels, 1,000 enum values per schemaPer 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:

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.

Primary sources checked 2026-10-16

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.