Anthropic API

Structured output

Anthropic API

Guaranteed-valid JSON is not guaranteed-valid data. Half your schema is stripped before the model ever sees it, and your code enforces the rest.

Applies to
Claude Fable 5.1 Claude Opus 5 Claude Sonnet 5 Claude Haiku 4.5
Last verified
Reviewed by
Timothy Fehr

Pass a JSON Schema in output_config.format and the response conforms to it. Constrained decoding does the work, so the documentation's promises are strong and worth reading precisely: "No more JSON.parse() errors", guaranteed field types and required fields, and "No retries needed for schema violations".

Everything on this page is about the gap between that promise and what people assume it covers.

Two features, two different problems

They get conflated constantly.

JSON outputs (output_config.format) control the shape of what Claude says back to you.

Strict tool use (strict: true on a tool) guarantees the arguments Claude passes when it calls your function.

Different directions, and combinable. If you are doing extraction, JSON outputs is the one you want. If you are worried about invented tool arguments, strict is the one, and note it constrains shape rather than truthfulness.

The constraint you wrote may not be the one enforced

This is the fact to carry away. A large part of JSON Schema is unsupported:

  • Recursive schemas, external $ref
  • Numerical constraints — minimum, maximum, multipleOf
  • String constraints — minLength, maxLength
  • Array constraints beyond minItems of 0 or 1
  • additionalProperties set to anything other than false

And the SDKs do not reject a schema containing them. They transform it: strip the unsupported constraints, fold the information into descriptions, add additionalProperties: false, filter string formats to the supported list, then validate the response against your original schema.

The documentation states the consequence plainly: "Claude receives a simplified schema, but your code still enforces all constraints through validation."

Express the constraints that matter structurally where you can. An enum is enforced; a minimum is advisory. A field that must be one of four values should be an enum and not a string with a range described in prose.

What it costs

Three costs, none large, all of them real.

A grammar is compiled on first use, adding latency to that request. Compiled grammars are cached for 24 hours, and the cache invalidates when you change the schema structure or the set of tools.

Claude also receives an extra system prompt explaining the output format, so input token counts rise slightly.

And changing output_config.format invalidates the prompt cache for that conversation thread — the same "pick a configuration and hold it" rule that governs everything else in the caching hierarchy.

Use the SDK helpers

Most SDKs derive the schema from a type you already have: messages.parse() with Pydantic in Python, zodOutputFormat() in TypeScript, a class in Java and C#. They handle the transformation and validate for you.

Hand-writing the schema is where the unsupported-constraint trap is easiest to fall into, because nothing complains.

Try this

Define a schema with a minimum on a numeric field, then prompt for a case that should violate it. Watch what comes back before your validation layer sees it.

Whatever you get is the honest picture of which half of your schema the model is actually working under.

What goes wrong

Reading the guarantee as validation. Shape and types are guaranteed. Ranges, lengths and business rules are yours to enforce.

Hand-writing schemas with unsupported keywords. They are silently removed, so the schema you debug against is not the schema in force.

Reaching for it when tool use fits better. Extraction wants JSON outputs; deciding which function to call wants tools. Using the wrong one produces a working system with an awkward seam.

Changing the format mid-conversation. It throws the prompt cache for that thread, which on a long conversation costs more than the structure saved.

How to check it worked

Log every response that passes constrained decoding but fails your own validation. On a healthy schema that count is near zero. Anything above it is telling you which constraints exist only in your file and not in the model's grammar — and those are the ones that need restating as enums, required fields, or a check you actually run.

Sources

  1. Structured outputs — Claude Platform Docs Tier 1 2026-09-04
  2. Tool use with Claude — Claude Platform Docs Tier 1 2026-09-04