Claude Certification Guides
CCDV-F/Domain 69 min read

Lesson 6.2

Structured Output and Schemas

Getting guaranteed JSON from Claude with output_config.format and strict tools, validating it with Pydantic or Zod, and knowing which features cannot be combined.

Most applications do not want prose from Claude; they want a value they can put in a database or pass to the next function. Structured output constrains the response to a JSON Schema so you stop writing regexes against free text. The exam tests the two mechanisms (response format and strict tool parameters), the SDK helpers that validate for you, and the schema rules that cause 400 errors.

Two mechanisms

MechanismConstrainsSet via
JSON output formatThe assistant's response textoutput_config: {"format": {"type": "json_schema", "schema": ...}}
Strict tool useThe input of a tool_use block"strict": true on the tool definition

Use the output format when the answer itself is the data. Use strict tools when Claude is calling a function and you need the arguments to match the signature exactly. They can be combined in one request.

JSON output format

resp = client.messages.create(
    model=MODEL, max_tokens=512,
    messages=[{"role": "user", "content": f"Extract the order details:\n<email>{email}</email>"}],
    output_config={"format": {"type": "json_schema", "schema": {
        "type": "object",
        "properties": {
            "order_id": {"type": "string"},
            "items": {"type": "array", "items": {"type": "string"}},
            "urgent": {"type": "boolean"},
        },
        "required": ["order_id", "items", "urgent"],
        "additionalProperties": False,
    }}},
)
data = json.loads(next(b.text for b in resp.content if b.type == "text"))

The response is a text block containing JSON that conforms to the schema. You still parse it, but you no longer need to defend against markdown fences, preambles or missing fields. The older output_format request parameter is deprecated in favour of output_config.format.

Let the SDK validate: parse()

The Python and TypeScript SDKs offer messages.parse(), which takes a Pydantic model or Zod schema, generates the JSON Schema, sends the request, and returns a validated object.

from pydantic import BaseModel

class Order(BaseModel):
    order_id: str
    items: list[str]
    urgent: bool

resp = client.messages.parse(
    model=MODEL, max_tokens=512,
    messages=[{"role": "user", "content": f"Extract the order details:\n<email>{email}</email>"}],
    output_format=Order,
)
order: Order = resp.parsed_output
import { z } from "zod";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";

const Order = z.object({ order_id: z.string(), items: z.array(z.string()), urgent: z.boolean() });
const resp = await client.messages.parse({
  model: MODEL, max_tokens: 512,
  messages: [{ role: "user", content: `Extract the order details:\n<email>${email}</email>` }],
  output_format: zodOutputFormat(Order),
});
const order = resp.parsed_output;   // typed as z.infer<typeof Order>

parse() is the recommended path: one source of truth for the schema, and a typed result.

Strict tool use

{
  "name": "create_ticket",
  "description": "Create a support ticket",
  "strict": true,
  "input_schema": {
    "type": "object",
    "properties": {
      "priority": {"type": "string", "enum": ["low", "medium", "high"]},
      "summary": {"type": "string"}
    },
    "required": ["priority", "summary"],
    "additionalProperties": false
  }
}

With strict: true, tool_use.input is guaranteed to validate against the schema, so your handler can trust types and enums without a validation layer. strict is a top-level field on the tool, not on tool_choice.

Schema rules to remember

  • Objects should set additionalProperties: false and list every property in required; strict mode and output formats are stricter than general JSON Schema.
  • Supported keywords are a subset: types, enum, properties, items, required, basic formats and $ref within the schema. Numeric range constraints and some string patterns are not enforced; validate those in code.
  • Keep schemas reasonably sized; very large or deeply recursive schemas may be rejected.
  • Descriptions on properties help Claude fill fields correctly, exactly as tool descriptions do.
  • The first use of a new schema incurs a small compile cost; repeated use is cached.

What you cannot combine

Structured output formats cannot be used together with citations. They also require a model that supports the feature; check the structured outputs page on docs.anthropic.com. If a request hits max_tokens the JSON may be cut off, so check stop_reason before parsing and raise max_tokens if it is max_tokens.

Fallback: extraction via a tool

Before native output formats existed, the standard trick was to define a tool whose input_schema is the shape you want and force Claude to call it. That still works where forced tool_choice is supported, and strict: true gives it the same guarantee. On models that reject forced tool choice, prefer output_config.format.

Exam focus

  • output_config: {format: {type: "json_schema", schema}} constrains the response text; strict: true on a tool constrains tool_use.input.
  • messages.parse() with Pydantic or Zod builds the schema and returns a validated, typed parsed_output.
  • Schemas should declare required for all properties and additionalProperties: false; unsupported keywords are ignored or rejected, so validate ranges in code.
  • Structured output is incompatible with citations; check stop_reason for max_tokens before parsing.
  • output_format as a request parameter is deprecated; output_config.format is current.
  • Tool-based extraction is the historical fallback and still valid where forced tool_choice is supported.

Check yourself

5 questions

  1. 1.What is the most reliable way to obtain JSON from Claude that conforms to a known schema?

  2. 2.An extraction request defines a tool named record_entities with an input_schema. The response has stop_reason "tool_use" and a tool_use content block. Where is the extracted JSON?

  3. 3.A plain-text prompt asks for JSON and the model often replies with: ```json {"name": "Acme"} ``` JSON.parse fails on the fences. Which fix is most robust?

  4. 4.A structured-output schema sent via raw HTTP includes "minimum": 0 on a number and "maxLength": 50 on a string, and the request is rejected. Why?

  5. 5.What does setting strict: true on a tool definition do?