Claude Corner: Force JSON Schema on Tool-Free Replies

  • Lock a tool-free Messages reply to a JSON Schema with output_config.format, so parsers stop stripping markdown fences.
  • Keep the request free of tools. JSON outputs shape the text block. They do not stand in for a tool_use call.
  • Parse that text only when stop_reason is end_turn. Refusal and token cutoff drop the schema guarantee.

You asked Claude for a record and got a fenced blob, a missing key, or a string where the next service expects a boolean. “Return only JSON” is a wish, not a decoder. The Messages API can compile the schema into the sampler so the reply is already the object.

Why it matters

Record extraction breaks when Claude wraps JSON in prose, drops a required key, or changes a type. Asking for valid JSON in the prompt does not constrain the sampler. Structured outputs compile your schema into a grammar and sample only tokens that fit it. Put that schema on output_config.format with type set to json_schema. Do not attach a dummy tool, and do not set tool_choice. The object comes back in a text content block. The first request for a schema pays compile latency. Later calls reuse a grammar cached for 24 hours from last use. Keep additionalProperties false, list fields under required, and size max_tokens for the full object. Gate the parser on stop_reason before you trust the text.

How to force a JSON schema

A tool-free extraction call needs a model the structured outputs page lists, output_config.format set to json_schema, and no tools array. The snippet uses claude-opus-5, the model id in the current docs examples. Close every object, list required fields, and leave room in max_tokens for the whole object.

  1. 1. Define a closed object schema. Put every field under properties and again under required. Set additionalProperties to false on the root and on any nested object.
  2. 2. Send the schema on output_config.format with type json_schema. Do not send the beta header structured-outputs-2025-11-13, and do not put output_format at the top level.
  3. 3. Omit tools and tool_choice. If tools are present, Claude may emit tool_use before any JSON text.
  4. 4. After HTTP 200, branch on stop_reason. Parse the text block only when it is end_turn.
#!/usr/bin/env bash
set -euo pipefail
: "${ANTHROPIC_API_KEY:?Set ANTHROPIC_API_KEY in your environment}"

curl -sS https://api.anthropic.com/v1/messages \
  -H "content-type: application/json" \
  -H "x-api-key: ${ANTHROPIC_API_KEY}" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-opus-5",
    "max_tokens": 1024,
    "messages": [
      {
        "role": "user",
        "content": "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
      }
    ],
    "output_config": {
      "format": {
        "type": "json_schema",
        "schema": {
          "type": "object",
          "properties": {
            "name": {"type": "string"},
            "email": {"type": "string"},
            "plan_interest": {"type": "string"},
            "demo_requested": {"type": "boolean"}
          },
          "required": ["name", "email", "plan_interest", "demo_requested"],
          "additionalProperties": false
        }
      }
    }
  }'

Gotchas

  • Unsupported schema keywords return 400: minimum, maximum, multipleOf, minLength, maxLength, recursive schemas, external $ref, and additionalProperties set to anything but false. Raw HTTP will not strip them. Some SDKs drop those constraints, append them to descriptions, and validate locally.
  • stop_reason of refusal is still HTTP 200 and still billed. The text may ignore the schema. max_tokens can cut the object mid-stream. Raise the cap and retry. Do not parse a partial JSON prefix.
  • String enum and const values are not case-guaranteed, often on the first letter after a space. Compare case-insensitively. Avoid labels that differ only by capitalization.
  • JSON outputs conflict with citations and with assistant message prefilling (400). An injected system prompt explains the format and adds input tokens. Changing output_config.format invalidates prompt cache for that thread.
  • A new schema compiles on first use, then the grammar caches for 24 hours from last use. Do not put PHI in property names, enum, const, or pattern. That cache is separate from message content.
  • Required keys come out first, then optional keys. If order matters, mark every property required.
  • Python SDK 1.0 and later rejects output_format={...} on client.beta.messages.create() and count_tokens(). client.messages.parse() still accepts output_format and maps it to output_config.format.

Copy this paragraph into ChatGPT, Claude, Gemini, Grok, or whatever you use.

Sources: Structured outputs · Messages API · Handling stop reasons

Leave a Comment