Engineering Guide

Claude Structured Output: JSON That Always Parses

"Reply only with JSON" is a prompt, not a guarantee — Claude can still wrap it in prose, trail a comma, or drop a field, and your json.loads() throws. Anthropic's Structured Outputs feature removes the guesswork: pass a JSON Schema in the output_config.format request field and Claude's response is constrained at sampling time to match it, or use strict: true on a tool so its parameters always validate. The SDKs add a client.messages.parse() helper that takes a Pydantic or Zod model and hands back a typed, validated object. This guide covers the three approaches — prompting vs strict tool use vs structured outputs — with working code, the JSON Schema limits you must respect, and when each one is the right call.

Claude structured output — guaranteed-valid JSON via output_config.format and messages.parse

How to get valid JSON from Claude, every time

Stop prompting for JSON and start constraining it. Anthropic's Structured Outputs feature gives you two enforcement mechanisms, not a hopeful instruction:

Both are generally available — no beta header required — and the SDKs wrap them in a client.messages.parse() helper that takes a Pydantic model (Python) or a Zod schema (TypeScript) and returns a typed, already-validated object. The rest of this guide shows the code, the schema limits, and when each mechanism is the right tool.

Why 'reply only in JSON' keeps breaking

Because a prompt is a request, and generation is still free — nothing stops the model from drifting off-format. When you ask Claude to "respond with a JSON object and nothing else," every token is still sampled from the full vocabulary, so any of these can land in production:

You end up bolting on regex extraction, try/except guards, and retry loops — and still get pages that occasionally crash. Structured outputs move the constraint from the prompt into the sampler, so the failure mode disappears by construction rather than being caught after the fact.

How Claude structured outputs actually work

Claude compiles your schema into a grammar and only samples tokens that keep the output valid. When you send a schema, Anthropic's platform turns it into a formal grammar that constrains generation token by token — the model literally cannot emit a character that would break the schema. Two operational details follow from that, both from the primary docs:

Secondary write-ups put the first-request overhead at roughly 100–300ms; the primary docs confirm there is added latency but do not state that exact figure, so treat it as an estimate rather than a spec.

Working example: a schema-constrained JSON response

The whole feature is one extra field on the request you already send. Here Claude extracts structured data from a support ticket and returns JSON that is valid against the schema by construction. Note additionalProperties: false — it is required, not optional:

import anthropic

client = anthropic.Anthropic(
    base_url="https://www.datallmlab.com/v1",   # DataLLM Lab gateway
    api_key="YOUR_DATALLMLAB_KEY",
)

schema = {
    "type": "object",
    "properties": {
        "category": {"type": "string", "enum": ["billing", "bug", "feature"]},
        "priority": {"type": "string", "enum": ["low", "med", "high"]},
        "summary":  {"type": "string"},
    },
    "required": ["category", "priority", "summary"],
    "additionalProperties": False,           # must be false
}

msg = client.messages.create(
    model="claude-opus-4.8",
    max_tokens=512,
    messages=[{"role": "user", "content": "Card declined three times, cannot check out."}],
    output_config={"format": {"type": "json_schema", "schema": schema}},
)

# msg.content is now valid JSON matching the schema — no parsing guards needed
import json
data = json.loads(msg.content[0].text)      # {"category": "billing", "priority": "high", ...}

The gateway base URL keeps the exact same OpenAI/Anthropic-compatible shape — see our OpenAI-compatible API guide for how one endpoint fronts many providers. The output_config.format field is passed straight through to Claude.

The messages.parse() helper does the validation for you

If you already model your data with Pydantic or Zod, skip the raw schema — hand the model class in and get a typed object back. The SDKs expose client.messages.parse(), which auto-transforms your schema (adding additionalProperties: false for you), sends it as output_config.format, and validates the response against your original model before returning a parsed_output:

from pydantic import BaseModel
from typing import Literal
import anthropic

class Ticket(BaseModel):
    category: Literal["billing", "bug", "feature"]
    priority: Literal["low", "med", "high"]
    summary:  str

client = anthropic.Anthropic(base_url="https://www.datallmlab.com/v1")

msg = client.messages.parse(
    model="claude-opus-4.8",
    max_tokens=512,
    messages=[{"role": "user", "content": "Card declined three times, cannot check out."}],
    output_format=Ticket,           # Pydantic model in, typed object out
)

ticket: Ticket = msg.parsed_output   # already validated — ticket.priority == "high"

In TypeScript the same helper takes zodOutputFormat(YourZodSchema, "name") or jsonSchemaOutputFormat(...) instead of a Pydantic model. The Python output_format= convenience param is translated internally to output_config.format, so you never touch the raw request shape.

Strict tool use vs JSON output — pick by what you are constraining

JSON outputs control what Claude says; strict tool use controls how Claude calls your functions. They are the two halves of the same feature, and the right one depends on whether the JSON is your answer or your function arguments:

Strict tool use is what you want inside agent loops; a whole class of "the model called my tool with a string where I needed an integer" bugs simply stops happening. If you are building agents, our best LLM for AI agents guide covers where reliable tool-calling sits in the wider decision. Just like JSON outputs, the tool's input_schema must include additionalProperties: false.

Prompting vs tool use vs structured outputs

Three ways to get JSON out of Claude, in ascending order of guarantee. The honest trade-offs:

ApproachHowGuaranteeBest forWatch out for
Reply-in-JSON prompting"Respond only with JSON" in the promptNone — best-effortQuick prototypes, unsupported modelsProse wrappers, invalid JSON, missing keys
Strict tool usestrict: true on a toolArgs match input_schemaAgents, function callingSchema needs additionalProperties: false
JSON outputsoutput_config.format / messages.parse()Response matches schemaExtraction, classification, structured repliesFirst-request compile latency; JSON Schema subset

The two strict paths differ only in what they constrain — Claude's spoken response versus its tool-call arguments. Prompting is the fallback, not the default: reach for it only when a model or endpoint does not support structured outputs.

Need JSON from Claude Calling functions? Want a JSON answer? Model unsupported? strict: true tool use output_config.format / parse() Prompt for JSON + guards
Which structured-output path to use. Source: Anthropic Structured Outputs docs, July 2026.

JSON Schema limits you have to respect

Claude supports a subset of JSON Schema — knowing the boundary saves a confusing error. Because the schema compiles to a sampling grammar, some keywords cannot be expressed. From the primary Structured Outputs reference:

SupportedNot supported
Basic types: object, array, string, integer, number, boolean, nullRecursive schemas
enum (scalars only), constComplex enum types
anyOf / allOf (with limits; allOf + $ref unsupported)External $ref
$ref / $def / definitionsNumeric constraints: minimum, maximum, multipleOf
default, required, additionalProperties: falseString length: minLength, maxLength
String format: date-time, date, time, duration, email, hostname, uri, ipv4, ipv6, uuidArray constraints beyond minItems 0/1
Array minItems of 0 or 1 onlyadditionalProperties set to anything but false

The practical rule: express constraints the grammar cannot enforce — value ranges, string lengths, regex — as post-validation in your own code or in your Pydantic/Zod model, and keep the schema you send to Claude within the supported set. The messages.parse() helper validates the response against your original model, so those richer constraints are still checked client-side.

When not to use structured outputs

Enforcement is not free, and not every task needs it. Skip it when:

There is also a separate Agent SDK path for structured output from agents, distinct from the Messages-API output_config.format feature — reach for that when your structure is the output of a whole agent run, not a single completion.

Structured outputs on 300+ models, one key

DataLLM Lab fronts Claude's output_config.format and strict tool use through an OpenAI-compatible endpoint — so you get schema-valid JSON from Claude and fail over to an equivalent model without changing your code.

FAQ

How do I get guaranteed valid JSON from Claude?

Use Structured Outputs: pass a JSON Schema in output_config.format (type json_schema) so the response is constrained at sampling time, or use client.messages.parse() with a Pydantic/Zod model to get a typed, validated object. Far more reliable than prompting for JSON.

output_config.format vs strict tool use — what is the difference?

output_config.format controls Claude's response format (what Claude says as JSON). strict: true on a tool validates tool-call arguments against the tool's input_schema. Use the first for JSON answers, the second inside agent/function-calling loops.

Do structured outputs need a beta header?

No — they are generally available and need no beta header. The old anthropic-beta: structured-outputs-2025-11-13 header and the output_format parameter still work during a transition period, but the parameter has moved to output_config.format.

What JSON Schema features are supported?

A subset: basic types, enum/const, anyOf/allOf with limits, $ref/$def, default/required, several string formats, and additionalProperties: false. Not supported: recursion, external $ref, numeric bounds, string-length constraints.

Which Claude models support structured outputs?

Per July 2026 docs: Opus 4.8/4.7/4.6/4.5, Sonnet 5/4.6/4.5, Haiku 4.5, plus Fable 5, Mythos 5 and Mythos Preview. The list is broader than the earlier beta (Sonnet 4.5 + Opus 4.1); check the docs before pinning a model.

Are structured outputs slower?

Only the first request with a new schema, while the grammar compiles. Compiled grammars are cached for 24 hours from last use; the cache is invalidated only if the schema structure or tool set changes, not on name/description edits.

Can I still just prompt Claude to reply in JSON?

Yes, but it is the least reliable option — no enforcement, so you get occasional prose, type drift, or invalid JSON and need guards and retries. Reserve it for prototypes or models that do not support structured outputs.

Written by
Kevin Fan

Founder of DataLLM Lab, the unified LLM gateway. Kevin tests models the boring way — same prompts, real costs, unedited outputs — and writes up what the runs actually show.

One API for every model

One API, every model.

Get a single API key for Claude Opus 4.7, GPT-5.4, and 300+ more — with automatic price comparison and routing to the best model for every request.