JSON Schemas

What a Schema Is

A JSON Schema is itself a JSON document that describes what other JSON documents must look like. It is like a form template with labelled boxes: "order_id: text, required", "rating: whole number", "issue: tick one of late / damaged / wrong item". A schema has a few building blocks:

KeywordMeaningExample
typeThe kind of value"string", "integer", "boolean", "object", "array"
propertiesThe fields of an object and the rule for each{"rating": {"type": "integer"}}
requiredFields that must be present["order_id", "issue"]
enumA fixed list of allowed values["late", "damaged", "other"]
additionalProperties: falseNo extra fields allowed (required on every object for Claude structured outputs)
itemsThe rule for each element of an array{"type": "string"}

How Schema Enforcement Works

  1. You send the schema with the request in output_config.format.
  2. The API prepares the schema once (a one-time cost on the first request; the same schema is then cached for about 24 hours).
  3. While the model generates, its choices are constrained so that every token keeps the output valid for the schema. It cannot open a field that does not exist or write a word where a number belongs.
  4. You receive text that is guaranteed to parse and match the schema, except in two cases: the model refused (stop_reason is "refusal") or ran out of tokens ("max_tokens"). Always check stop_reason.

Describing the Shape

JSON Schema is a standard way to describe JSON: which fields, their types, which are required, allowed values. With the Claude API, pass it in output_config={"format": {"type": "json_schema", "schema": ...}} and the reply is guaranteed to be valid JSON matching the schema.

import json

schema = {
    "type": "object",
    "properties": {
        "order_id": {"type": "string"},
        "issue": {"type": "string", "enum": ["late", "damaged", "wrong_item", "refund", "other"]},
        "rating": {"type": "integer"},
        "wants_callback": {"type": "boolean"},
    },
    "required": ["order_id", "issue", "rating", "wants_callback"],
    "additionalProperties": False,
}

response = client.messages.create(
    model="claude-haiku-5-5",
    max_tokens=500,
    output_config={"format": {"type": "json_schema", "schema": schema}},
    messages=[{"role": "user", "content": "Order SE-4410 came with a cracked screen guard. 2 stars. Call me back!"}],
)
data = json.loads(next(b.text for b in response.content if b.type == "text"))
print(data)

Run it

python schema_extract.py

Example output

{'order_id': 'SE-4410', 'issue': 'damaged', 'rating': 2, 'wants_callback': True}

A Mini Schema Validator

Example

Libraries like jsonschema and Pydantic validate for you. Underneath, validation is simple checks like these:

TYPES = {"string": str, "integer": int, "boolean": bool, "number": (int, float)}

def validate(data, schema):
    errors = []
    for field in schema.get("required", []):
        if field not in data:
            errors.append(f"missing '{field}'")
    for field, value in data.items():
        rule = schema["properties"].get(field)
        if rule is None:
            if schema.get("additionalProperties") is False:
                errors.append(f"unexpected '{field}'")
            continue
        expected = TYPES[rule["type"]]
        if not isinstance(value, expected) or (rule["type"] == "integer" and isinstance(value, bool)):
            errors.append(f"'{field}' should be {rule['type']}")
        if "enum" in rule and value not in rule["enum"]:
            errors.append(f"'{field}' must be one of {rule['enum']}")
    return errors

schema = {"type": "object", "additionalProperties": False, "required": ["order_id", "issue", "rating"],
          "properties": {"order_id": {"type": "string"}, "rating": {"type": "integer"},
                         "issue": {"type": "string", "enum": ["late", "damaged", "other"]}}}

print(validate({"order_id": "SE-4410", "issue": "damaged", "rating": 2}, schema))
print(validate({"order_id": "SE-4410", "issue": "broken", "rating": "2", "mood": "angry"}, schema))

Output

[]
["'issue' must be one of ['late', 'damaged', 'other']", "'rating' should be integer", "unexpected 'mood'"]

The first object passes with an empty error list. The second collects three errors at once: an issue outside the enum, a rating sent as a string, and an unexpected field. Returning all errors together, not just the first, matters when you send them back to the model for a retry.

Limits and Summary

Note: Not every JSON Schema feature is supported by Claude structured outputs. Recursive schemas, numeric limits (minimum, maximum) and string length limits (minLength, maxLength) are not enforced by the API; the Python and TypeScript SDKs remove such constraints from the schema they send and check them on your side instead. Structured outputs also cannot be combined with citations. Keep schemas flat and simple: deep nesting and dozens of optional fields make results worse, not better.

  • A JSON Schema describes fields, types, required fields and allowed values.
  • Enums are the best way to stop the model inventing categories.
  • Schema enforcement guarantees shape; check stop_reason for refusals and truncation.
  • Rules a schema cannot express still need your own validation code.