← Back to blog
Analysis

After Claude API switched to 2026-10-21 on 21 October 2026, which layer of output_config holds the structured-output JSON Schema?

Published 2026-10-22Updated 2026-10-22About 11 minJSON Toolbox

Where this note goes

As of 2026-10-22, the Claude platform release notes date API version 2026-10-21 to 21 October 2026. Python SDK 2.0.0 and TypeScript SDK 1.0.0 send that version header by default. Structured outputs are GA: the schema sits on the request root under output_config.format, type is json_schema, and the object contract is the schema object. The old output_format field without a beta header returns 400. Why a local check still closes the object: why AI models like JSON. The Bedrock path: which text block Haiku 5.5 JSON sits in.

You bump the Messages client to one that sends anthropic-version: 2026-10-21, and ticket extraction returns 400. Three common causes: the request root still has output_format; you still send temperature to “stabilize JSON”; the schema includes minimum. All three match the same docs as the version date. The model did not suddenly forget how to write JSON.

What 21 October actually switched

As of 2026-10-22, from the Claude platform release notes and Migrating to 2026-10-21:

  • Version header: without an SDK, set anthropic-version: 2026-10-21 yourself. Python 2.0.0, TypeScript 1.0.0, Go 2.0.0, Java 3.0.0, C# 13.0.0, PHP 1.0.0, Ruby 2.0.0, and CLI 2.0.0 send this version by default. The SDKs and CLI moved to Apache 2.0.
  • Three Messages 400s right away: a document placed directly on an assistant message; non-empty citations on a user text block; temperature, top_p, or top_k on the request, including default values.
  • Do not mix in Bedrock: the migration page says 2026-10-21 on Bedrock is only for Claude 4.7 and later. Earlier models keep the current version. Yesterday’s Bedrock structured-output note follows the AWS user guide, not this version string.

Structured outputs themselves were not first GA on 21 October. The docs say output_format moved to output_config.format and no longer needs a beta header. New SDKs send the new version by default, so the old field, sampling parameters, and schema subset all fail on the same day.

One line. After the version header is 2026-10-21, the contract sits in output_config.format.schema. Do not send output_format or temperature.

The schema is in format.schema, not output_format

As of 2026-10-22, the cURL on structured outputs pins this shape. The docs sample still uses version header 2023-06-01; after the upgrade day, change it to 2026-10-21. The field nesting stays the same:

{
  "model": "claude-opus-5-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
      }
    }
  }
}

Keep three layers: output_config is the request field, format.type is json_schema, and schema is the object contract. additionalProperties must be false. The beta header structured-outputs-2025-11-13 is only for a transition that still sends output_format. Send the old field without that header and the API returns 400.

The docs also say changing output_config.format busts prompt cache for that thread. If the contract changes, the cache key changes with it.

parse()'s output_format is not a wire field

The recommended Python path is client.messages.parse(). The argument is still named output_format, and the value is a Pydantic model. The SDK converts it to output_config.format, validates, then puts the result in parsed_output. That is a client helper, not the old REST key.

The same page says: from Python SDK 1.0, client.beta.messages.create() and count_tokens() no longer accept output_format={...}; they raise TypeError. A raw schema uses client.messages.create(..., output_config={...}). TypeScript parse() takes output_config.format plus zodOutputFormat(), matching the wire names.

In beta SDK types, output_config hangs only on BetaSystemMessageParam. Put it on a user or assistant message and type checking fails; Ruby raises NoMethodError. Request-root output_config is still the GA Messages location.

The JSON is in a text block

The docs say schema-conforming JSON is in a text content block of the response. The raw-schema Python sample is not content[0].text. It is:

text = next(block.text for block in response.content if block.type == "text")
contact = json.loads(text)

Grammar constraints only cover what the model emits directly. Feature compatibility says the grammar does not cover tool calls, tool results, or thinking tags; grammar state resets between those segments. Thinking can come first. The final JSON still has to sit in a block whose type is text. Streaming needs the full string before deserialize.

Citations together with output_config.format return 400. Message prefilling is also incompatible with JSON output.

Keywords outside the subset return 400

JSON output and strict tools share one restriction list. Supported: basic types, enum (not complex types), const, anyOf / allOf (allOf plus $ref is not), $ref, $def, definitions (external $ref is not). Objects must include required, and additionalProperties can only be false. Array minItems only accepts 0 and 1.

Not supported: recursive schemas, external $ref, numeric constraints (minimum / maximum / multipleOf), string minLength / maxLength, and any other value for additionalProperties. Include them and you get 400.

SDK helpers first strip unsupported constraints into the field description, then run a local check against your original schema. Raw output_config has no such step: minimum: 100 is rejected. The keyword on the page is $def, not the more common JSON Schema draft keyword $defs.

String enum / const do not guarantee case. The first letter after a space may not match the list. The request can still finish with no special stop_reason. Compare locally case-insensitively, and do not ship two enum values that differ only by case.

strict tools constrain inputs

output_config.format constrains what Claude says. tools[].strict: true constrains how it calls a function. You can use both in one request. The docs example puts a trip summary in the output schema, and destination / date for search_flights in input_schema with strict: true.

Both schemas must pass the same subset. With many tools, the docs suggest turning strict on only for the ones that can go wrong; optional parameters roughly double the grammar state space.

How to check it in JSON Toolbox

Split “request shape” from “contact object”. All four samples should parse. Only one pair is a contract.

{
  "ok_format": {
    "type": "json_schema",
    "schema": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "email": { "type": "string" }
      },
      "required": ["name", "email"],
      "additionalProperties": false
    }
  },
  "legacy_output_format": {
    "output_format": {
      "type": "json_schema",
      "schema": { "type": "object", "additionalProperties": false }
    }
  },
  "ok_contact": { "name": "John Smith", "email": "john@example.com" },
  "extra_field": { "name": "John Smith", "email": "john@example.com", "phone": "1" }
}
  1. Paste the four samples into JSON format. All four parse. The key on legacy_output_format is the old field name. Do not send it on the wire.
  2. Use JSON → Schema from ok_contact, then add required and additionalProperties: false. Do not put minimum in the copy you send to the API.
  3. Drop ok_format.schema into Schema validator against ok_contact. It should pass. extra_field should fail.
  4. Tool inputs go to Function Calling. Notes for the final object go to structured output.

The data stays in the browser. First reject the extra phone, then bump the SDK major version.

FAQ

Is parse()'s output_format the same field that returns 400?

No. parse() takes a model class. The SDK converts it to output_config.format. The exploding one is output_format={...} on beta create / count_tokens.

After upgrading to 2026-10-21, can you still set temperature to 0 to stabilize JSON?

No. The three sampling parameters were removed from Messages requests. Structured output uses grammar, not temperature.

Is the first content block the JSON?

Read the text block. A thinking segment can come first.

If the schema includes minimum, will the API enforce the range?

A raw schema returns 400. Helpers strip the constraint. Ranges stay local.

Is this the same note as yesterday's Bedrock structured-output piece?

No. That one is the Bedrock user guide on 2026-10-20. This one is the Claude API version on 2026-10-21 and output_config.

Summary

As of 2026-10-22, API version 2026-10-21 was published on 2026-10-21. New SDKs send that header by default. Put the JSON Schema in output_config.format.schema, with additionalProperties only false on the object. Sending output_format without the transition beta header, or sending temperature, returns 400. parse()’s output_format is only a Python helper argument. Read JSON from a block whose type is text. minimum is outside the subset. Strict tools constrain inputs; the output schema constrains the last sentence. Next, reject extra fields and the old key name, then upgrade to Python 2.0.0.