← Back to daily report

build-with-claude/structured-outputs.md

Changed on 2026-10-01 20:26:48 EST

+1 lines added
-0 lines removed
Visual Diff
---¶
title: Structured outputs¶
url: https://platform.claude.com/docs/en/build-with-claude/structured-outputs¶
description: Get validated JSON results from agent workflows¶
featureMetadata:¶
status: ga¶
zdr:¶
eligibility: eligible¶
note: Excludes [Covered Models](https://platform.claude.com/docs/en/manage-claude/api-and-data-retention#model-specific-data-retention-requirements).¶
supportedModels:¶
- claude-fable-5-1¶
- claude-mythos-5-1¶
- claude-fable-5¶
- claude-mythos-5¶
- claude-mythos-preview¶
- claude-opus-5-5¶
- claude-opus-5¶
- claude-opus-4-8¶
- claude-opus-4-7¶
- claude-opus-4-6¶
- claude-sonnet-5-5¶
- claude-sonnet-5¶
- claude-sonnet-4-6¶
- claude-sonnet-4-5-20250929¶
- claude-opus-4-5-20251101¶
- claude-haiku-4-5-20251001¶
supportedPlatforms:¶
Claude API: ga¶
Claude Platform on AWS: ga¶
Amazon Bedrock:¶
availability: ga¶
note: On Amazon Bedrock, structured outputs are available for Claude Opus 4.6, Claude Sonnet 4.6, Claude Sonnet 4.5, Claude Opus 4.5, and Claude Haiku 4.5.¶
Google Cloud: ga¶
Microsoft Foundry: ga¶
---¶

Structured outputs constrain Claude's responses to follow a specific schema, ensuring valid, parseable output for downstream processing. Structured outputs provide two complementary features:¶

* **JSON outputs** (`output_config.format`): Get Claude's response in a specific JSON format, for example to extract data from images or text, generate structured reports, or format API responses. This page covers JSON outputs.¶
* **Strict tool use** (`strict: true`): Guarantee schema validation on tool names and inputs. See [Strict tool use](https://platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use).¶

You can use these features independently or [together in the same request](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#using-both-features-together).¶

## Why use structured outputs¶

Without structured outputs, Claude can generate malformed JSON responses or invalid tool inputs that break your applications. Even with careful prompting, you may encounter:¶

* Parsing errors from invalid JSON syntax¶
* Missing required fields¶
* Inconsistent data types¶
* Schema violations requiring error handling and retries¶

Structured outputs guarantee schema-compliant responses through constrained decoding:¶

* **Always valid:** No more `JSON.parse()` errors¶
* **Type safe:** Guaranteed field types and required fields¶
* **Reliable:** No retries needed for schema violations¶

## How it works¶

<Steps>¶
<Step title="Define your schema">¶
Describe the structure you want as a JSON schema or as a type in your language. The schema follows JSON Schema, with some [limitations](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-schema-limitations).¶
</Step>¶

<Step title="Send it in output_config.format">¶
The request carries the schema in `output_config.format` with `type: "json_schema"`. SDK helpers set this for you.¶
</Step>¶

<Step title="Read the response">¶
Claude returns valid JSON that matches your schema in the response's text content block. SDK helpers parse it into your type.¶
</Step>¶
</Steps>¶

## Usage¶

<Tabs>¶
<Tab title="cURL">¶
```bash¶
curl 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-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¶
}¶
}¶
}¶
}'¶
```¶

Claude returns JSON like this in the response's text content block:¶

```json Output¶
{¶
"name": "John Smith",¶
"email": "john@example.com",¶
"plan_interest": "Enterprise",¶
"demo_requested": true¶
}¶
```¶
</Tab>¶

<Tab title="CLI">¶
```bash¶
ant messages create \¶
--transform 'content.#(type=="text").text|@fromstr' \¶
--format jsonl <<'YAML'¶
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¶
YAML¶
```¶

To print only some properties of the JSON output, pass a [GJSON path](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/using#transform-output-with-gjson) to `--transform`.¶

The example above outputs:¶

```text Output wrap¶
{"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}¶
```¶
</Tab>¶

<Tab title="Python">¶
```python¶
from pydantic import BaseModel¶
# ...¶


class ContactInfo(BaseModel):¶
name: str¶
email: str¶
plan_interest: str¶
demo_requested: bool¶


client = Anthropic()¶

response = client.messages.parse(¶
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_format=ContactInfo,¶
)¶

print(response.parsed_output)¶
```¶

Pass a [Pydantic](https://docs.pydantic.dev/) model to `client.messages.parse()`, the recommended method, as `output_format`. The SDK transforms the model's schema, sends it as `output_config.format`, validates the response, and returns the parsed model in `parsed_output`.¶

The example above outputs:¶

```text Output wrap¶
name='John Smith' email='john@example.com' plan_interest='Enterprise' demo_requested=True¶
```¶
</Tab>¶

<Tab title="TypeScript">¶
```typescript¶
import { z } from "zod";¶
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";¶

const ContactInfoSchema = z.object({¶
name: z.string(),¶
email: z.string(),¶
plan_interest: z.string(),¶
demo_requested: z.boolean()¶
});¶

const client = new Anthropic();¶

const response = await client.messages.parse({¶
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: zodOutputFormat(ContactInfoSchema) }¶
});¶

// Automatically parsed and validated¶
console.log(response.parsed_output);¶
```¶

Wrap a [Zod](https://zod.dev/) schema in `zodOutputFormat()` and pass it to `client.messages.parse()` as `output_config.format`. `zodOutputFormat()` transforms the schema, and `parse()` validates the response and returns the parsed result in `parsed_output`, typed to match the schema.¶

The example above outputs:¶

```javascript Output¶
{¶
name: 'John Smith',¶
email: 'john@example.com',¶
plan_interest: 'Enterprise',¶
demo_requested: true¶
}¶
```¶
</Tab>¶

<Tab title="C#">¶
```csharp¶
using Anthropic;¶
using Anthropic.Models.Messages;¶
using Anthropic.Services;¶

var client = new AnthropicClient();¶

var response = await client.Messages.Create<ContactInfo>(new MessageCreateParams¶
{¶
Model = Model.ClaudeOpus5_5,¶
MaxTokens = 1024,¶
Messages = [new() {¶
Role = 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."¶
}],¶
});¶

if (response.Content.Select(block => block.Parsed()).OfType<ContactInfo>().FirstOrDefault() is { } contact)¶
{¶
Console.WriteLine(contact);¶
}¶

public record ContactInfo¶
{¶
public string Name { get; set; } = "";¶
public string Email { get; set; } = "";¶
public string PlanInterest { get; set; } = "";¶
public bool DemoRequested { get; set; }¶
}¶
```¶

Pass a plain C# class or record to the generic `Create<T>()` overload. The SDK derives a JSON schema from the type, transforms it, and parses each text block back into it.¶

The example above outputs:¶

```text Output wrap¶
ContactInfo { Name = John Smith, Email = john@example.com, PlanInterest = Enterprise, DemoRequested = True }¶
```¶
</Tab>¶

<Tab title="Go">¶
```go¶
type ContactInfo struct {¶
Name string `json:"name"`¶
Email string `json:"email"`¶
PlanInterest string `json:"plan_interest"`¶
DemoRequested bool `json:"demo_requested"`¶
}¶

func main() {¶
client := anthropic.NewClient()¶

var contact ContactInfo¶
_, err := client.Beta.Messages.New(context.Background(), anthropic.BetaMessageNewParams{¶
Model: anthropic.ModelClaudeOpus5_5,¶
MaxTokens: 1024,¶
Messages: []anthropic.BetaMessageParam{¶
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(¶
"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.",¶
)),¶
},¶
OutputConfig: anthropic.BetaOutputConfigParam{¶
Format: anthropic.BetaJSONOutputFormatParam{Schema: &contact},¶
},¶
})¶
if err != nil {¶
panic(err)¶
}¶

fmt.Printf("%#v\n", contact)¶
}¶
```¶

<Note>¶
Passing a struct as the schema is in beta in Go, so this example uses `client.Beta.Messages.New`. To use the GA `client.Messages.New`, pass a JSON schema as shown in [Using a raw JSON schema](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#using-a-raw-json-schema).¶
</Note>¶

The SDK generates and transforms the JSON schema from the struct and unmarshals the response into it.¶

The example above outputs:¶

```text Output wrap¶
main.ContactInfo{Name:"John Smith", Email:"john@example.com", PlanInterest:"Enterprise", DemoRequested:true}¶
```¶
</Tab>¶

<Tab title="Java">¶
```java¶
static class ContactInfo {¶
public String name;¶
public String email;¶
public String plan_interest;¶
public boolean demo_requested;¶
}¶

void main() throws Exception {¶
AnthropicClient client = AnthropicOkHttpClient.fromEnv();¶

StructuredMessageCreateParams<ContactInfo> params = MessageCreateParams.builder()¶
.model(Model.CLAUDE_OPUS_5_5)¶
.maxTokens(1024)¶
.addUserMessage("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.")¶
.outputConfig(ContactInfo.class)¶
.build();¶

StructuredMessage<ContactInfo> response = client.messages().create(params);¶
ContactInfo contact = response.content().stream()¶
.flatMap(block -> block.text().stream())¶
.findFirst().orElseThrow().text();¶
IO.println(new ObjectMapper().writeValueAsString(contact));¶
}¶
```¶

When you pass a Java class to `outputConfig()`, the SDK derives a JSON schema from it and validates the schema. The builder then produces a `StructuredMessageCreateParams<T>`. Rather than transforming the schema, the SDK's local validation rejects [constraints not supported by the API](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-schema-limitations). Read the parsed result with `response.content().stream().flatMap(block -> block.text().stream()).findFirst().orElseThrow().text()`.¶

The example above outputs:¶

```text Output wrap¶
{"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}¶
```¶

<Note>¶
Declare your schema classes as top-level classes or `static` nested classes. This requirement comes from the Jackson Databind library (`com.fasterxml.jackson.databind`), which the SDK uses to deserialize JSON responses into your class instances and cannot instantiate non-static inner classes.¶
</Note>¶

<Accordion title="Generic type erasure">¶
Java retains generic type information for fields in the class's metadata, but generic type erasure applies in other scopes. While a JSON schema can be derived from a `BookList.books` field with type `List<Book>`, a valid JSON schema cannot be derived from a local variable of that same type.¶

If an error occurs while converting a JSON response to a Java class instance, the error message includes the JSON response to assist in diagnosis. If your JSON response may contain sensitive information, avoid logging it directly, or ensure that you redact any sensitive details from the error message.¶
</Accordion>¶

<Accordion title="Local schema validation">¶
Structured outputs support a [subset of the JSON Schema language](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-schema-limitations). The SDK generates schemas automatically from classes to align with this subset. The `outputConfig(Class<T>)` method performs a validation check on the schema derived from the specified class.¶

Key points:¶

* **Local validation** occurs without sending requests to the remote AI model.¶
* **Remote validation** is also performed by the AI model upon receiving the JSON schema.¶
* **Version compatibility:** Local validation may fail while remote validation succeeds if the SDK version is outdated.¶
* **Disabling local validation:** Pass `JsonSchemaLocalValidation.NO` if you encounter compatibility issues:¶

```java¶
import com.anthropic.core.JsonSchemaLocalValidation;¶
// ...¶

static class BookList {¶
public List<String> books;¶
}¶

void main() {¶
StructuredMessageCreateParams<BookList> createParams = MessageCreateParams.builder()¶
.model(Model.CLAUDE_OPUS_5_5)¶
.maxTokens(2048)¶
.outputConfig(BookList.class, JsonSchemaLocalValidation.NO)¶
.addUserMessage("List some famous late twentieth century novels.")¶
.build();¶
}¶
```¶
</Accordion>¶

<Accordion title="Streaming">¶
Structured outputs also work with streaming. As responses arrive in stream events, you need to accumulate the full response before deserializing the JSON.¶

Use `MessageAccumulator` to collect the JSON strings from the stream. Once accumulated, call `MessageAccumulator.message(Class<T>)` to convert the accumulated `Message` into a `StructuredMessage`, which automatically deserializes the JSON into your Java class.¶
</Accordion>¶

<Accordion title="JSON schema properties">¶
When the SDK derives a JSON schema from your Java classes, it includes all properties represented by `public` fields or `public` getter methods by default and excludes non-`public` fields and getter methods.¶

You can control visibility with annotations:¶

* `@JsonIgnore` excludes a `public` field or getter method¶
* `@JsonProperty` includes a non-`public` field or getter method¶

If you define `private` fields with `public` getter methods, the SDK derives the property name from the getter (for example, `private` field `myValue` with `public` method `getMyValue()` produces a `"myValue"` property). To use a non-conventional getter name, annotate the method with `@JsonProperty`.¶

Each class must define at least one property for the JSON schema. A validation error occurs if no fields or getter methods can produce schema properties, such as when:¶

* There are no fields or getter methods in the class¶
* All `public` members are annotated with `@JsonIgnore`¶
* All non-`public` members lack `@JsonProperty` annotations¶
* A field uses a `Map` type, which produces an empty `"properties"` field¶
</Accordion>¶

<Accordion title="Composition and inheritance">¶
Your Java classes can use composition and inheritance to share structure when defining JSON schemas. Each pattern affects the output structure differently.¶

**Composition** produces nested JSON output. Deriving a schema from class `Composed` that composes `A` and `B`:¶

```java¶
static class A {¶
public String a;¶
}¶

static class B {¶
public String b;¶
}¶

static class Composed {¶
public A composedA;¶
public B composedB;¶
}¶
```¶

The JSON output has this nested structure:¶

```json¶
{¶
"composedA": { "a": "hello" },¶
"composedB": { "b": "world" }¶
}¶
```¶

**Inheritance** produces flat JSON output. Deriving a schema from class `Derived` that extends `Base`:¶

```java¶
static class Base {¶
public String a;¶
}¶

static class Derived extends Base {¶
public String b;¶
}¶
```¶

The JSON output has this flat structure:¶

```json¶
{¶
"a": "hello",¶
"b": "world"¶
}¶
```¶
</Accordion>¶

<Accordion title="Annotations (Jackson and Swagger)">¶
You can use Jackson Databind annotations to enrich the JSON schema derived from your Java classes:¶

```java¶
import com.fasterxml.jackson.annotation.JsonClassDescription;¶
import com.fasterxml.jackson.annotation.JsonIgnore;¶
import com.fasterxml.jackson.annotation.JsonPropertyDescription;¶

static class Person {¶

@JsonPropertyDescription("The first name and surname of the person")¶
public String name;¶

public int birthYear;¶

@JsonPropertyDescription("The year the person died, or 'present' if the person is living.")¶
public String deathYear;¶
}¶

@JsonClassDescription("The details of one published book")¶
static class Book {¶

public String title;¶
public Person author;¶

@JsonPropertyDescription("The year in which the book was first published.")¶
public int publicationYear;¶

@JsonIgnore¶
public String genre;¶
}¶

static class BookList {¶
public List<Book> books;¶
}¶
```¶

Annotation summary:¶

* `@JsonClassDescription`: Add a description to a class¶
* `@JsonPropertyDescription`: Add a description to a field or getter method¶
* `@JsonIgnore`: Exclude a `public` field or getter from the schema¶
* `@JsonProperty`: Include a non-`public` field or getter in the schema¶

If you use `@JsonProperty(required = false)`, the SDK ignores the `false` value. Class-derived schemas always mark all properties as required.¶

You can also use Swagger Core (OpenAPI 3) `@Schema` and `@ArraySchema` annotations for type-specific constraints:¶

```java¶
import io.swagger.v3.oas.annotations.media.ArraySchema;¶
import io.swagger.v3.oas.annotations.media.Schema;¶

static class Article {¶

@ArraySchema(minItems = 1)¶
public List<String> authors;¶

public String title;¶

@Schema(format = "date")¶
public String publicationDate;¶

public int pageCount;¶
}¶
```¶

Local validation checks that you haven't used any unsupported constraint keywords, but constraint values aren't validated locally. For example, an unsupported `"format"` value may pass local validation but cause a remote error.¶

If you use both Jackson and Swagger annotations to set the same schema field, the Jackson annotation takes precedence.¶
</Accordion>¶
</Tab>¶

<Tab title="PHP">¶
```php¶
use Anthropic\Lib\Concerns\StructuredOutputModelTrait;¶
use Anthropic\Lib\Contracts\StructuredOutputModel;¶

$client = new Client();¶

class ContactInfo implements StructuredOutputModel¶
{¶
use StructuredOutputModelTrait;¶

public string $name;¶
public string $email;¶
public string $plan_interest;¶
public bool $demo_requested;¶
}¶

$message = $client->messages->create(¶
maxTokens: 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.',¶
],¶
],¶
model: 'claude-opus-5-5',¶
outputConfig: ['format' => ContactInfo::class],¶
);¶

$contact = $message->parsedOutput();¶
if ($contact instanceof ContactInfo) {¶
var_dump($contact);¶
}¶
```¶

Define a class that implements `StructuredOutputModel` and uses `StructuredOutputModelTrait`, and pass its name in `outputConfig: ['format' => MyClass::class]`. The SDK derives a JSON schema from the class's PHP 8 property types, transforms it, and returns a typed instance from `$message->parsedOutput()`.¶

`parsedOutput()` returns your model instance on success, or `null` (or an error array) if parsing fails. Use `instanceof` to narrow the type before accessing fields.¶

The example above outputs:¶

```text Output wrap¶
object(ContactInfo)#42 (4) {¶
["name"]=>¶
string(10) "John Smith"¶
["email"]=>¶
string(16) "john@example.com"¶
["plan_interest"]=>¶
string(10) "Enterprise"¶
["demo_requested"]=>¶
bool(true)¶
}¶
```¶

<Accordion title="Type inference">¶
The SDK maps native PHP 8 property types to JSON Schema:¶

| PHP type | JSON Schema |¶
| ------------------------------------------ | ---------------------------------- |¶
| `string` | `"string"` |¶
| `int` | `"integer"` |¶
| `float` | `"number"` |¶
| `bool` | `"boolean"` |¶
| `array` | `"array"` (see the following note) |¶
| `?type` (nullable) | Optional field |¶
| Class implementing `StructuredOutputModel` | Nested object |¶

For `array` properties, the SDK adds an `items` schema only when the element type is a nested `StructuredOutputModel`, declared with `#[Constrained(itemClass: MyModel::class)]` or a `/** @var MyModel[] */` docblock. Arrays of scalars (`string[]`, `int[]`) emit an unconstrained `{"type":"array"}`.¶

All non-nullable properties become required fields.¶
</Accordion>¶

<Accordion title="Constraints with the #[Constrained] attribute">¶
Add constraints with the `#[Constrained]` attribute:¶

```php¶
use Anthropic\Lib\Attributes\Constrained;¶
use Anthropic\Lib\Concerns\StructuredOutputModelTrait;¶
use Anthropic\Lib\Contracts\StructuredOutputModel;¶

class Address implements StructuredOutputModel { use StructuredOutputModelTrait; public string $street; }¶

class Profile implements StructuredOutputModel¶
{¶
use StructuredOutputModelTrait;¶

#[Constrained(description: 'Age in years', minimum: 0, maximum: 150)]¶
public int $age;¶

#[Constrained(format: 'email')]¶
public string $email;¶

#[Constrained(itemClass: Address::class, minItems: 1)]¶
public array $addresses;¶
}¶
```¶

**API-enforced constraints** (sent in the schema): `description`, `format`, `const`, `itemClass`, `minItems` (0 or 1 only).¶

**SDK-validated constraints** (stripped from the wire schema, appended to the description, and validated against the response): `minimum`, `maximum`, `multipleOf`, `minLength`, `maxLength`.¶
</Accordion>¶
</Tab>¶

<Tab title="Ruby">¶
```ruby¶
client = Anthropic::Client.new¶

class ContactInfo < Anthropic::BaseModel¶
required :name, String¶
required :email, String¶
required :plan_interest, String¶
required :demo_requested, Anthropic::Boolean¶
end¶

message = client.messages.create(¶
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: ContactInfo}¶
)¶

contact = message.parsed_output¶
puts contact¶
```¶

Define a class that extends `Anthropic::BaseModel` and pass it to `messages.create()` as `output_config: {format: Model}`. The SDK derives a JSON schema from the class, transforms it, and assigns the parsed result to the response's `parsed_output` attribute as a typed Ruby object.¶

The example above outputs:¶

```text Output wrap¶
{name: "John Smith", email: "john@example.com", plan_interest: "Enterprise", demo_requested: true}¶
```¶

<Accordion title="Advanced model features">¶
The Ruby SDK supports additional model definition features for richer schemas:¶

* **`doc:` keyword:** Add descriptions to fields for more informative schema output¶
* **`Anthropic::ArrayOf[T]`:** Typed arrays. Pass array-level constraints (`min_items:`, `max_items:`) as keywords on `required`/`optional`, not on `ArrayOf` itself¶
* **`Anthropic::EnumOf[:a, :b]`:** Enum fields with constrained values¶
* **`Anthropic::UnionOf[T1, T2]`:** Union types mapped to `anyOf`¶

```ruby¶
class FamousNumber < Anthropic::BaseModel¶
required :value, Float¶
optional :reason, String, doc: "why is this number mathematically significant?"¶
end¶

class Output < Anthropic::BaseModel¶
required :numbers, Anthropic::ArrayOf[FamousNumber], min_items: 3, max_items: 5¶
end¶

message = client.messages.create(¶
model: "claude-opus-5-5",¶
max_tokens: 1024,¶
messages: [{role: "user", content: "give me some famous numbers"}],¶
output_config: {format: Output}¶
)¶

message.parsed_output¶
# => #<Output numbers=[#<FamousNumber value=3.14159... reason="Pi is...">...]>¶
```¶
</Accordion>¶
</Tab>¶
</Tabs>¶

## How SDK transformation works¶

Most SDK helpers transform schemas that use unsupported features. The transformation steps:¶

1. **Remove unsupported constraints** (for example, `minimum`, `maximum`, `minLength`, `maxLength`)¶
2. **Update descriptions** by adding each unsupported constraint to the field's description (for example, `{minimum: 100}`)¶
3. **Add `additionalProperties: false`** to all objects¶
4. **Filter string formats** to supported list only¶
5. **Validate responses** against your original schema and all its constraints, if the helper validates responses¶

Claude receives a simplified schema, but a helper that validates responses still enforces every constraint in your code.¶

**Example:** A field with `minimum: 100` becomes a plain integer in the sent schema, and the SDK adds `{minimum: 100}` to the field's description. A helper that validates responses still checks the response against `minimum: 100`.¶

## Using a raw JSON schema¶

To use a JSON schema from a file, an OpenAPI spec, or code that builds it at runtime, pass it in `output_config.format`.¶

<Tabs>¶
<Tab title="cURL">¶
```bash¶
curl 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-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¶
}¶
}¶
}¶
}'¶
```¶

Claude returns JSON like this in the response's text content block:¶

```json Output¶
{¶
"name": "John Smith",¶
"email": "john@example.com",¶
"plan_interest": "Enterprise",¶
"demo_requested": true¶
}¶
```¶
</Tab>¶

<Tab title="CLI">¶
```bash¶
ant messages create \¶
--transform 'content.#(type=="text").text|@fromstr' \¶
--format jsonl <<'YAML'¶
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¶
YAML¶
```¶

The example above outputs:¶

```text Output wrap¶
{"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}¶
```¶
</Tab>¶

<Tab title="Python">¶
```python¶
client = anthropic.Anthropic()¶

response = client.messages.create(¶
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,¶
},¶
}¶
},¶
)¶
text = next(block.text for block in response.content if block.type == "text")¶
contact = json.loads(text)¶
print(contact)¶
```¶

The example above outputs:¶

```text Output wrap¶
{'name': 'John Smith', 'email': 'john@example.com', 'plan_interest': 'Enterprise', 'demo_requested': True}¶
```¶

To move [constraints not supported by the API](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-schema-limitations) into field descriptions, pass the schema through `transform_schema()` from the `anthropic` package before sending it, and edit the result if you need to. `transform_schema()` also accepts a Pydantic model. Unlike `client.messages.parse()`, it returns the transformed schema rather than sending it.¶
</Tab>¶

<Tab title="TypeScript">¶
```typescript¶
import { jsonSchemaOutputFormat } from "@anthropic-ai/sdk/helpers/json-schema";¶

const client = new Anthropic();¶

const response = await client.messages.parse({¶
model: "claude-opus-5-5",¶
max_tokens: 1024,¶
messages: [¶
{¶
role: "user",¶
content: "Extract contact info: John Smith, john@example.com, interested in the Pro plan"¶
}¶
],¶
output_config: {¶
format: jsonSchemaOutputFormat({¶
type: "object",¶
properties: {¶
name: { type: "string" },¶
email: { type: "string" },¶
planInterest: { type: "string" }¶
},¶
required: ["name", "email", "planInterest"],¶
additionalProperties: false¶
} as const)¶
}¶
});¶

// response.parsed_output is typed as { name: string; email: string; planInterest: string } | null¶
console.log(response.parsed_output);¶
```¶

Use `jsonSchemaOutputFormat()` to pass a plain JSON schema to `parse()`, without installing Zod. The API returns a response that conforms to the schema, and if you declare the schema with `as const`, the type for `parsed_output` is automatically inferred by TypeScript according to your schema. For an imported or generated schema, `parsed_output` is typed as `unknown`.¶

By default, the helper [transforms the schema](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#how-sdk-transformation-works) the same way `zodOutputFormat()` does. Pass `{ transform: false }` as the second argument to send it unchanged. Unlike `zodOutputFormat()`, it doesn't validate the response, so it won't catch violations of [constraints not supported by the API](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-schema-limitations), such as `minimum`.¶

The example above outputs:¶

```javascript Output¶
{ name: 'John Smith', email: 'john@example.com', planInterest: 'Pro' }¶
```¶
</Tab>¶

<Tab title="C#">¶
```csharp¶
using System.Text.Json;¶
using Anthropic;¶
using Anthropic.Models.Messages;¶

var client = new AnthropicClient();¶

var response = await client.Messages.Create(new MessageCreateParams¶
{¶
Model = Model.ClaudeOpus5_5,¶
MaxTokens = 1024,¶
Messages = [new() {¶
Role = 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."¶
}],¶
OutputConfig = new OutputConfig¶
{¶
Format = new JsonOutputFormat¶
{¶
Schema = new Dictionary<string, JsonElement>¶
{¶
["type"] = JsonSerializer.SerializeToElement("object"),¶
["properties"] = JsonSerializer.SerializeToElement(new¶
{¶
name = new { type = "string" },¶
email = new { type = "string" },¶
plan_interest = new { type = "string" },¶
demo_requested = new { type = "boolean" },¶
}),¶
["required"] = JsonSerializer.SerializeToElement(¶
new[] { "name", "email", "plan_interest", "demo_requested" }),¶
["additionalProperties"] = JsonSerializer.SerializeToElement(false),¶
},¶
},¶
},¶
});¶

if (response.Content.Select(b => b.Value).OfType<TextBlock>().FirstOrDefault() is { } textBlock)¶
{¶
// JSON is guaranteed to match the schema¶
var contact = JsonSerializer.Deserialize<JsonElement>(textBlock.Text);¶
Console.WriteLine(contact);¶
}¶
```¶

The example above outputs:¶

```text Output wrap¶
{"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}¶
```¶
</Tab>¶

<Tab title="Go">¶
```go¶
type ContactInfo struct {¶
Name string `json:"name"`¶
Email string `json:"email"`¶
PlanInterest string `json:"plan_interest"`¶
DemoRequested bool `json:"demo_requested"`¶
}¶

func main() {¶
client := anthropic.NewClient()¶

message, _ := client.Messages.New(context.TODO(), anthropic.MessageNewParams{¶
Model: anthropic.ModelClaudeOpus5_5,¶
MaxTokens: 1024,¶
Messages: []anthropic.MessageParam{¶
anthropic.NewUserMessage(anthropic.NewTextBlock(¶
"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.",¶
)),¶
},¶
OutputConfig: anthropic.OutputConfigParam{¶
Format: anthropic.JSONOutputFormatParam{¶
Schema: map[string]any{¶
"type": "object",¶
"properties": map[string]any{¶
"name": map[string]string{"type": "string"},¶
"email": map[string]string{"type": "string"},¶
"plan_interest": map[string]string{"type": "string"},¶
"demo_requested": map[string]string{"type": "boolean"},¶
},¶
"required": []string{"name", "email", "plan_interest", "demo_requested"},¶
"additionalProperties": false,¶
},¶
},¶
},¶
})¶

for _, block := range message.Content {¶
switch variant := block.AsAny().(type) {¶
case anthropic.TextBlock:¶
var contact ContactInfo¶
json.Unmarshal([]byte(variant.Text), &contact)¶
fmt.Printf("%#v\n", contact)¶
}¶
}¶
}¶
```¶

The example above outputs:¶

```text Output wrap¶
main.ContactInfo{Name:"John Smith", Email:"john@example.com", PlanInterest:"Enterprise", DemoRequested:true}¶
```¶

To have the SDK transform a map schema, pass it through `BetaJSONSchemaOutputFormat()` on the beta API.¶
</Tab>¶

<Tab title="Java">¶
```java¶
import com.anthropic.core.JsonValue;¶
import com.anthropic.models.messages.JsonOutputFormat;¶
// ...¶
import com.anthropic.models.messages.OutputConfig;¶
import com.fasterxml.jackson.databind.JsonNode;¶
import com.fasterxml.jackson.databind.ObjectMapper;¶

void main() throws Exception {¶
AnthropicClient client = AnthropicOkHttpClient.fromEnv();¶

JsonOutputFormat.Schema schema = JsonOutputFormat.Schema.builder()¶
.putAdditionalProperty("type", JsonValue.from("object"))¶
.putAdditionalProperty("properties", JsonValue.from(Map.of(¶
"name", Map.of("type", "string"),¶
"email", Map.of("type", "string"),¶
"plan_interest", Map.of("type", "string"))))¶
.putAdditionalProperty("required", JsonValue.from(¶
List.of("name", "email", "plan_interest")))¶
.putAdditionalProperty("additionalProperties", JsonValue.from(false))¶
.build();¶

OutputConfig outputConfig = OutputConfig.builder()¶
.format(JsonOutputFormat.builder().schema(schema).build())¶
.build();¶

MessageCreateParams createParams = MessageCreateParams.builder()¶
.model(Model.CLAUDE_OPUS_5_5)¶
.maxTokens(1024)¶
.outputConfig(outputConfig)¶
.addUserMessage(¶
"John Smith (john@example.com) is interested in our Enterprise plan.")¶
.build();¶

String json = client.messages().create(createParams).content().stream()¶
.flatMap(contentBlock -> contentBlock.text().stream())¶
.findFirst()¶
.orElseThrow()¶
.text();¶

JsonNode contact = new ObjectMapper().readTree(json);¶
IO.println(contact);¶
}¶
```¶

The example above outputs:¶

```text Output wrap¶
{"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise"}¶
```¶

For a more extensive example that builds a nested schema with arrays and descriptions, see [`StructuredOutputsRawExample.java`](https://github.com/anthropics/anthropic-sdk-java/blob/main/anthropic-java-example/src/main/java/com/anthropic/example/StructuredOutputsRawExample.java) in the SDK repository.¶
</Tab>¶

<Tab title="PHP">¶
```php¶
use Anthropic\Messages\OutputConfig;¶
use Anthropic\Messages\JSONOutputFormat;¶

$client = new Client();¶

$message = $client->messages->create(¶
maxTokens: 1024,¶
messages: [¶
[¶
'role' => 'user',¶
'content' => 'Extract the key information from this email: '¶
. 'John Smith (john@example.com) is interested in our Enterprise plan.',¶
],¶
],¶
model: 'claude-opus-5-5',¶
outputConfig: OutputConfig::with(format: JSONOutputFormat::with(schema: [¶
'type' => 'object',¶
'properties' => [¶
'name' => ['type' => 'string'],¶
'email' => ['type' => 'string'],¶
'plan_interest' => ['type' => 'string'],¶
],¶
'required' => ['name', 'email', 'plan_interest'],¶
'additionalProperties' => false,¶
])),¶
);¶

$textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');¶
$contact = json_decode($textBlock->text, associative: true);¶
var_dump($contact);¶
```¶

Pass the schema as an associative array to `OutputConfig::with()`, and decode the response with `json_decode()`.¶

The example above outputs:¶

```text Output wrap¶
array(3) {¶
["name"]=>¶
string(10) "John Smith"¶
["email"]=>¶
string(16) "john@example.com"¶
["plan_interest"]=>¶
string(10) "Enterprise"¶
}¶
```¶
</Tab>¶

<Tab title="Ruby">¶
```ruby¶
client = Anthropic::Client.new¶

response = client.messages.create(¶
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¶
}¶
}¶
}¶
)¶

text = response.content.find { it.type == :text }.text¶
contact = JSON.parse(text)¶
p contact¶
```¶

The example above outputs:¶

```text Output wrap¶
{"name" => "John Smith", "email" => "john@example.com", "plan_interest" => "Enterprise", "demo_requested" => true}¶
```¶
</Tab>¶
</Tabs>¶

## Common use cases¶

<AccordionGroup>¶
<Accordion title="Data extraction">¶
Extract structured data from unstructured text:¶

<CodeGroup>¶
```bash cURL¶
curl 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-5",¶
"max_tokens": 4096,¶
"messages": [¶
{¶
"role": "user",¶
"content": "Extract invoice data from: Invoice #12345, Date: 2024-01-15, Total: $500.00"¶
}¶
],¶
"output_config": {¶
"format": {¶
"type": "json_schema",¶
"schema": {¶
"type": "object",¶
"properties": {¶
"invoice_number": {"type": "string"},¶
"date": {"type": "string"},¶
"total_amount": {"type": "number"},¶
"line_items": {¶
"type": "array",¶
"items": {"type": "object", "additionalProperties": false}¶
},¶
"customer_name": {"type": "string"}¶
},¶
"required": ["invoice_number", "date", "total_amount", "line_items", "customer_name"],¶
"additionalProperties": false¶
}¶
}¶
}¶
}'¶
```¶

```bash CLI¶
ant messages create \¶
--transform 'content.#(type=="text").text|@fromstr' \¶
--format jsonl <<'YAML'¶
model: claude-opus-5-5¶
max_tokens: 4096¶
messages:¶
- role: user¶
content: "Extract invoice data from: Invoice #12345, Date: 2024-01-15, Total: $500.00"¶
output_config:¶
format:¶
type: json_schema¶
schema:¶
type: object¶
properties:¶
invoice_number: {type: string}¶
date: {type: string}¶
total_amount: {type: number}¶
line_items:¶
type: array¶
items: {type: object, additionalProperties: false}¶
customer_name: {type: string}¶
required: [invoice_number, date, total_amount, line_items, customer_name]¶
additionalProperties: false¶
YAML¶
```¶

```python Python¶
from pydantic import BaseModel¶


class Invoice(BaseModel):¶
invoice_number: str¶
date: str¶
total_amount: float¶
line_items: list[dict]¶
customer_name: str¶


client = anthropic.Anthropic()¶
invoice_text = "Invoice #12345, Date: 2024-01-15, Total: $500.00"¶

response = client.messages.parse(¶
model="claude-opus-5-5",¶
max_tokens=4096,¶
output_format=Invoice,¶
messages=[¶
{"role": "user", "content": f"Extract invoice data from: {invoice_text}"}¶
],¶
)¶

print(response.parsed_output)¶
```¶

```typescript TypeScript¶
import { z } from "zod";¶
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";¶

const client = new Anthropic();¶

const InvoiceSchema = z.object({¶
invoice_number: z.string(),¶
date: z.string(),¶
total_amount: z.number(),¶
line_items: z.array(z.record(z.string(), z.any())),¶
customer_name: z.string()¶
});¶

const invoiceText = "Invoice #12345, Date: 2024-01-15, Total: $500.00";¶
const response = await client.messages.parse({¶
model: "claude-opus-5-5",¶
max_tokens: 4096,¶
output_config: { format: zodOutputFormat(InvoiceSchema) },¶
messages: [{ role: "user", content: `Extract invoice data from: ${invoiceText}` }]¶
});¶
console.log(response.parsed_output);¶
```¶

```csharp C#¶
AnthropicClient client = new();¶

string invoiceText = "Invoice #12345, Date: 2024-01-15, Total: $500.00";¶

var parameters = new MessageCreateParams¶
{¶
Model = Model.ClaudeOpus5_5,¶
MaxTokens = 4096,¶
OutputConfig = new OutputConfig¶
{¶
Format = new JsonOutputFormat¶
{¶
Schema = new Dictionary<string, JsonElement>¶
{¶
["type"] = JsonSerializer.SerializeToElement("object"),¶
["properties"] = JsonSerializer.SerializeToElement(new¶
{¶
invoice_number = new { type = "string" },¶
date = new { type = "string" },¶
total_amount = new { type = "number" },¶
line_items = new¶
{¶
type = "array",¶
items = new¶
{¶
type = "object",¶
additionalProperties = false,¶
},¶
},¶
customer_name = new { type = "string" },¶
}),¶
["required"] = JsonSerializer.SerializeToElement(new[] { "invoice_number", "date", "total_amount", "line_items", "customer_name" }),¶
["additionalProperties"] = JsonSerializer.SerializeToElement(false),¶
},¶
},¶
},¶
Messages = [new() { Role = Role.User, Content = $"Extract invoice data from: {invoiceText}" }]¶
};¶

var message = await client.Messages.Create(parameters);¶
Console.WriteLine(message);¶
```¶

```go Go¶
client := anthropic.NewClient()¶

invoiceText := "Invoice #12345, Date: 2024-01-15, Total: $500.00"¶

schema := map[string]any{¶
"type": "object",¶
"additionalProperties": false,¶
"properties": map[string]any{¶
"invoice_number": map[string]any{"type": "string"},¶
"date": map[string]any{"type": "string"},¶
"total_amount": map[string]any{"type": "number"},¶
"line_items": map[string]any{¶
"type": "array",¶
"items": map[string]any{¶
"type": "object",¶
"additionalProperties": false,¶
"properties": map[string]any{¶
"description": map[string]any{"type": "string"},¶
"quantity": map[string]any{"type": "number"},¶
"unit_price": map[string]any{"type": "number"},¶
},¶
"required": []string{"description", "quantity", "unit_price"},¶
},¶
},¶
"customer_name": map[string]any{"type": "string"},¶
},¶
"required": []string{"invoice_number", "date", "total_amount", "line_items", "customer_name"},¶
}¶

response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{¶
Model: anthropic.ModelClaudeOpus5_5,¶
MaxTokens: 4096,¶
OutputConfig: anthropic.OutputConfigParam{¶
Format: anthropic.JSONOutputFormatParam{¶
Schema: schema,¶
},¶
},¶
Messages: []anthropic.MessageParam{¶
anthropic.NewUserMessage(anthropic.NewTextBlock(fmt.Sprintf("Extract invoice data from: %s", invoiceText))),¶
},¶
})¶
if err != nil {¶
log.Fatal(err)¶
}¶
for _, block := range response.Content {¶
switch variant := block.AsAny().(type) {¶
case anthropic.TextBlock:¶
fmt.Println(variant.Text)¶
}¶
}¶
```¶

```java Java¶
import com.fasterxml.jackson.annotation.JsonProperty;¶

static class LineItem {¶
@JsonProperty("description")¶
public String description;¶

@JsonProperty("quantity")¶
public int quantity;¶

@JsonProperty("unit_price")¶
public double unitPrice;¶
}¶

static class Invoice {¶
@JsonProperty("invoice_number")¶
public String invoiceNumber;¶

@JsonProperty("date")¶
public String date;¶

@JsonProperty("total_amount")¶
public double totalAmount;¶

@JsonProperty("line_items")¶
public List<LineItem> lineItems;¶

@JsonProperty("customer_name")¶
public String customerName;¶
}¶

void main() {¶
AnthropicClient client = AnthropicOkHttpClient.fromEnv();¶

String invoiceText = "Invoice #12345, Date: 2024-01-15, Total: $500.00";¶

StructuredMessageCreateParams<Invoice> params = MessageCreateParams.builder()¶
.model(Model.CLAUDE_OPUS_5_5)¶
.maxTokens(4096L)¶
.outputConfig(Invoice.class)¶
.addUserMessage("Extract invoice data from: " + invoiceText)¶
.build();¶

StructuredMessage<Invoice> response = client.messages().create(params);¶
Invoice invoice = response.content().stream()¶
.flatMap(block -> block.text().stream())¶
.findFirst().orElseThrow().text();¶
IO.println(invoice.invoiceNumber + ": $" + invoice.totalAmount);¶
}¶
```¶

```php PHP¶
use Anthropic\Lib\Concerns\StructuredOutputModelTrait;¶
use Anthropic\Lib\Contracts\StructuredOutputModel;¶

$client = new Client();¶

class Invoice implements StructuredOutputModel¶
{¶
use StructuredOutputModelTrait;¶

public string $invoice_number;¶
public string $date;¶
public float $total_amount;¶
public array $line_items;¶
public string $customer_name;¶
}¶

$invoiceText = "Invoice #12345, Date: 2024-01-15, Total: $500.00";¶

$message = $client->messages->create(¶
maxTokens: 4096,¶
messages: [¶
['role' => 'user', 'content' => "Extract invoice data from: $invoiceText"]¶
],¶
model: 'claude-opus-5-5',¶
outputConfig: ['format' => Invoice::class],¶
);¶

$invoice = $message->parsedOutput();¶
if ($invoice instanceof Invoice) {¶
echo "Invoice {$invoice->invoice_number}: \${$invoice->total_amount}\n";¶
}¶
```¶

```ruby Ruby¶
client = Anthropic::Client.new¶

class LineItem < Anthropic::BaseModel¶
required :description, String¶
required :amount, Float¶
end¶

class Invoice < Anthropic::BaseModel¶
required :invoice_number, String¶
required :date, String¶
required :total_amount, Float¶
required :line_items, Anthropic::ArrayOf[LineItem]¶
required :customer_name, String¶
end¶

invoice_text = "Invoice #12345, Date: 2024-01-15, Total: $500.00"¶

message = client.messages.create(¶
model: "claude-opus-5-5",¶
max_tokens: 4096,¶
output_config: {format: Invoice},¶
messages: [¶
{role: "user", content: "Extract invoice data from: #{invoice_text}"}¶
]¶
)¶

invoice = message.parsed_output¶
puts "Invoice #{invoice.invoice_number}: $#{invoice.total_amount}"¶
```¶
</CodeGroup>¶
</Accordion>¶

<Accordion title="Classification">¶
Classify content with structured categories:¶

<CodeGroup>¶
```bash cURL¶
curl 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-5",¶
"max_tokens": 1024,¶
"messages": [¶
{¶
"role": "user",¶
"content": "Classify this feedback: Great product, fast shipping!"¶
}¶
],¶
"output_config": {¶
"format": {¶
"type": "json_schema",¶
"schema": {¶
"type": "object",¶
"properties": {¶
"category": {"type": "string"},¶
"confidence": {"type": "number"},¶
"tags": {"type": "array", "items": {"type": "string"}},¶
"sentiment": {"type": "string"}¶
},¶
"required": ["category", "confidence", "tags", "sentiment"],¶
"additionalProperties": false¶
}¶
}¶
}¶
}'¶
```¶

```bash CLI¶
ant messages create \¶
--transform 'content.#(type=="text").text|@fromstr' \¶
--format jsonl <<'YAML'¶
model: claude-opus-5-5¶
max_tokens: 1024¶
messages:¶
- role: user¶
content: "Classify this feedback: Great product, fast shipping!"¶
output_config:¶
format:¶
type: json_schema¶
schema:¶
type: object¶
properties:¶
category:¶
type: string¶
confidence:¶
type: number¶
tags:¶
type: array¶
items:¶
type: string¶
sentiment:¶
type: string¶
required:¶
- category¶
- confidence¶
- tags¶
- sentiment¶
additionalProperties: false¶
YAML¶
```¶

```python Python¶
from pydantic import BaseModel¶

client = Anthropic()¶


class Classification(BaseModel):¶
category: str¶
confidence: float¶
tags: list[str]¶
sentiment: str¶


feedback_text = "Great product, but the delivery was slow."¶
response = client.messages.parse(¶
model="claude-opus-5-5",¶
max_tokens=1024,¶
output_format=Classification,¶
messages=[{"role": "user", "content": f"Classify this feedback: {feedback_text}"}],¶
)¶

print(response.parsed_output)¶
```¶

```typescript TypeScript¶
import { z } from "zod";¶
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";¶

const client = new Anthropic();¶

const ClassificationSchema = z.object({¶
category: z.string(),¶
confidence: z.number(),¶
tags: z.array(z.string()),¶
sentiment: z.string()¶
});¶

const feedbackText = "Great product, but the delivery was slow.";¶
const response = await client.messages.parse({¶
model: "claude-opus-5-5",¶
max_tokens: 1024,¶
output_config: { format: zodOutputFormat(ClassificationSchema) },¶
messages: [{ role: "user", content: `Classify this feedback: ${feedbackText}` }]¶
});¶

console.log(response.parsed_output);¶
```¶

```csharp C#¶
string feedbackText = "Great product, fast shipping!";¶

var parameters = new MessageCreateParams¶
{¶
Model = Model.ClaudeOpus5_5,¶
MaxTokens = 1024,¶
Messages = [new() { Role = Role.User, Content = $"Classify this feedback: {feedbackText}" }],¶
OutputConfig = new OutputConfig¶
{¶
Format = new JsonOutputFormat¶
{¶
Schema = new Dictionary<string, JsonElement>¶
{¶
["type"] = JsonSerializer.SerializeToElement("object"),¶
["properties"] = JsonSerializer.SerializeToElement(new¶
{¶
category = new { type = "string" },¶
confidence = new { type = "number" },¶
tags = new { type = "array", items = new { type = "string" } },¶
sentiment = new { type = "string" },¶
}),¶
["required"] = JsonSerializer.SerializeToElement(new[] { "category", "confidence", "tags", "sentiment" }),¶
["additionalProperties"] = JsonSerializer.SerializeToElement(false),¶
},¶
},¶
},¶
};¶

var message = await client.Messages.Create(parameters);¶
Console.WriteLine(message);¶
```¶

```go Go¶
feedbackText := "Great product, fast shipping!"¶

schema := map[string]any{¶
"type": "object",¶
"properties": map[string]any{¶
"category": map[string]any{"type": "string"},¶
"confidence": map[string]any{"type": "number"},¶
"tags": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},¶
"sentiment": map[string]any{"type": "string"},¶
},¶
"required": []string{"category", "confidence", "tags", "sentiment"},¶
"additionalProperties": false,¶
}¶

response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{¶
Model: anthropic.ModelClaudeOpus5_5,¶
MaxTokens: 1024,¶
OutputConfig: anthropic.OutputConfigParam{¶
Format: anthropic.JSONOutputFormatParam{¶
Schema: schema,¶
},¶
},¶
Messages: []anthropic.MessageParam{¶
anthropic.NewUserMessage(anthropic.NewTextBlock(fmt.Sprintf("Classify this feedback: %s", feedbackText))),¶
},¶
})¶
if err != nil {¶
log.Fatal(err)¶
}¶
for _, block := range response.Content {¶
switch variant := block.AsAny().(type) {¶
case anthropic.TextBlock:¶
var result map[string]any¶
json.Unmarshal([]byte(variant.Text), &result)¶
fmt.Println(result)¶
}¶
}¶
```¶

```java Java¶
import com.fasterxml.jackson.annotation.JsonProperty;¶

static class Classification {¶
@JsonProperty("category")¶
public String category;¶

@JsonProperty("confidence")¶
public double confidence;¶

@JsonProperty("tags")¶
public List<String> tags;¶

@JsonProperty("sentiment")¶
public String sentiment;¶
}¶

void main() {¶
AnthropicClient client = AnthropicOkHttpClient.fromEnv();¶

String feedbackText = "Great product, fast shipping!";¶

StructuredMessageCreateParams<Classification> params = MessageCreateParams.builder()¶
.model(Model.CLAUDE_OPUS_5_5)¶
.maxTokens(1024L)¶
.outputConfig(Classification.class)¶
.addUserMessage("Classify this feedback: " + feedbackText)¶
.build();¶

StructuredMessage<Classification> response = client.messages().create(params);¶
Classification result = response.content().stream()¶
.flatMap(block -> block.text().stream())¶
.findFirst().orElseThrow().text();¶
IO.println(result.category + " (" + result.confidence + ")");¶
}¶
```¶

```php PHP¶
use Anthropic\Lib\Concerns\StructuredOutputModelTrait;¶
use Anthropic\Lib\Contracts\StructuredOutputModel;¶

$client = new Client();¶

class Classification implements StructuredOutputModel¶
{¶
use StructuredOutputModelTrait;¶

public string $category;¶
public float $confidence;¶
public array $tags;¶
public string $sentiment;¶
}¶

$feedbackText = "Great product, fast shipping!";¶

$message = $client->messages->create(¶
maxTokens: 1024,¶
messages: [¶
['role' => 'user', 'content' => "Classify this feedback: {$feedbackText}"]¶
],¶
model: 'claude-opus-5-5',¶
outputConfig: ['format' => Classification::class],¶
);¶

$result = $message->parsedOutput();¶
if ($result instanceof Classification) {¶
echo "{$result->category} ({$result->confidence}): {$result->sentiment}\n";¶
}¶
```¶

```ruby Ruby¶
client = Anthropic::Client.new¶

class Classification < Anthropic::BaseModel¶
required :category, String¶
required :confidence, Float¶
required :tags, Anthropic::ArrayOf[String]¶
required :sentiment, String¶
end¶

feedback_text = "Great product, fast shipping!"¶

message = client.messages.create(¶
model: "claude-opus-5-5",¶
max_tokens: 1024,¶
output_config: {format: Classification},¶
messages: [¶
{role: "user", content: "Classify this feedback: #{feedback_text}"}¶
]¶
)¶
puts message.parsed_output¶
```¶
</CodeGroup>¶
</Accordion>¶

<Accordion title="API response formatting">¶
Generate API-ready responses:¶

<CodeGroup>¶
```bash cURL¶
curl 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-5",¶
"max_tokens": 1024,¶
"messages": [¶
{¶
"role": "user",¶
"content": "Process this request: ..."¶
}¶
],¶
"output_config": {¶
"format": {¶
"type": "json_schema",¶
"schema": {¶
"type": "object",¶
"properties": {¶
"status": {"type": "string"},¶
"data": {"type": "object", "additionalProperties": false},¶
"errors": {¶
"type": "array",¶
"items": {"type": "object", "additionalProperties": false}¶
},¶
"metadata": {"type": "object", "additionalProperties": false}¶
},¶
"required": ["status", "data", "metadata"],¶
"additionalProperties": false¶
}¶
}¶
}¶
}'¶
```¶

```bash CLI¶
ant messages create \¶
--transform 'content.#(type=="text").text' \¶
--raw-output <<'YAML'¶
model: claude-opus-5-5¶
max_tokens: 1024¶
output_config:¶
format:¶
type: json_schema¶
schema:¶
type: object¶
properties:¶
status:¶
type: string¶
data:¶
type: object¶
additionalProperties: false¶
errors:¶
type: array¶
items:¶
type: object¶
additionalProperties: false¶
metadata:¶
type: object¶
additionalProperties: false¶
required:¶
- status¶
- data¶
- metadata¶
additionalProperties: false¶
messages:¶
- role: user¶
content: "Process this request: ..."¶
YAML¶
```¶

```python Python¶
from pydantic import BaseModel¶

client = Anthropic()¶


class APIResponse(BaseModel):¶
status: str¶
data: dict¶
errors: list[dict] | None¶
metadata: dict¶


response = client.messages.parse(¶
model="claude-opus-5-5",¶
max_tokens=1024,¶
output_format=APIResponse,¶
messages=[{"role": "user", "content": "Process this request: ..."}],¶
)¶

print(response.parsed_output)¶
```¶

```typescript TypeScript¶
import { z } from "zod";¶
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";¶

const client = new Anthropic();¶

const APIResponseSchema = z.object({¶
status: z.string(),¶
data: z.record(z.string(), z.any()),¶
errors: z.array(z.record(z.string(), z.any())).optional(),¶
metadata: z.record(z.string(), z.any())¶
});¶

const response = await client.messages.parse({¶
model: "claude-opus-5-5",¶
max_tokens: 1024,¶
output_config: { format: zodOutputFormat(APIResponseSchema) },¶
messages: [{ role: "user", content: "Process this request..." }]¶
});¶

console.log(response.parsed_output);¶
```¶

```csharp C#¶
var parameters = new MessageCreateParams¶
{¶
Model = Model.ClaudeOpus5_5,¶
MaxTokens = 1024,¶
Messages = [new() { Role = Role.User, Content = "Process this request: ..." }],¶
OutputConfig = new OutputConfig¶
{¶
Format = new JsonOutputFormat¶
{¶
Schema = new Dictionary<string, JsonElement>¶
{¶
["type"] = JsonSerializer.SerializeToElement("object"),¶
["properties"] = JsonSerializer.SerializeToElement(new¶
{¶
status = new { type = "string" },¶
data = new { type = "object", additionalProperties = false },¶
errors = new¶
{¶
type = "array",¶
items = new { type = "object", additionalProperties = false },¶
},¶
metadata = new { type = "object", additionalProperties = false },¶
}),¶
["required"] = JsonSerializer.SerializeToElement(new[] { "status", "data", "metadata" }),¶
["additionalProperties"] = JsonSerializer.SerializeToElement(false),¶
},¶
},¶
},¶
};¶

var message = await client.Messages.Create(parameters);¶
Console.WriteLine(message);¶
```¶

```go Go¶
client := anthropic.NewClient()¶

response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{¶
Model: anthropic.ModelClaudeOpus5_5,¶
MaxTokens: 1024,¶
OutputConfig: anthropic.OutputConfigParam{¶
Format: anthropic.JSONOutputFormatParam{¶
Schema: map[string]any{¶
"type": "object",¶
"additionalProperties": false,¶
"properties": map[string]any{¶
"status": map[string]any{¶
"type": "string",¶
},¶
"data": map[string]any{¶
"type": "object",¶
"additionalProperties": false,¶
},¶
"errors": map[string]any{¶
"type": "array",¶
"items": map[string]any{¶
"type": "object",¶
"additionalProperties": false,¶
},¶
},¶
"metadata": map[string]any{¶
"type": "object",¶
"additionalProperties": false,¶
},¶
},¶
"required": []string{"status", "data", "metadata"},¶
},¶
},¶
},¶
Messages: []anthropic.MessageParam{¶
anthropic.NewUserMessage(anthropic.NewTextBlock("Process this request: ...")),¶
},¶
})¶
if err != nil {¶
log.Fatal(err)¶
}¶
for _, block := range response.Content {¶
switch variant := block.AsAny().(type) {¶
case anthropic.TextBlock:¶
fmt.Println(variant.Text)¶
}¶
}¶
```¶

```java Java¶
import com.fasterxml.jackson.annotation.JsonProperty;¶

static class APIData {¶
@JsonProperty("message")¶
public String message;¶

@JsonProperty("resource_id")¶
public String resourceId;¶
}¶

static class APIError {¶
@JsonProperty("code")¶
public String code;¶

@JsonProperty("message")¶
public String message;¶
}¶

static class APIMetadata {¶
@JsonProperty("request_id")¶
public String requestId;¶

@JsonProperty("timestamp")¶
public String timestamp;¶
}¶

static class APIResponse {¶
@JsonProperty("status")¶
public String status;¶

@JsonProperty("data")¶
public APIData data;¶

@JsonProperty("errors")¶
public List<APIError> errors;¶

@JsonProperty("metadata")¶
public APIMetadata metadata;¶
}¶

void main() {¶
AnthropicClient client = AnthropicOkHttpClient.fromEnv();¶

StructuredMessageCreateParams<APIResponse> params = MessageCreateParams.builder()¶
.model(Model.CLAUDE_OPUS_5_5)¶
.maxTokens(1024L)¶
.outputConfig(APIResponse.class)¶
.addUserMessage("Process this request: ...")¶
.build();¶

StructuredMessage<APIResponse> response = client.messages().create(params);¶
APIResponse result = response.content().stream()¶
.flatMap(block -> block.text().stream())¶
.findFirst().orElseThrow().text();¶
IO.println(result.status);¶
}¶
```¶

```php PHP¶
use Anthropic\Lib\Attributes\Constrained;¶
use Anthropic\Lib\Concerns\StructuredOutputModelTrait;¶
use Anthropic\Lib\Contracts\StructuredOutputModel;¶

$client = new Client();¶

class Payload implements StructuredOutputModel { use StructuredOutputModelTrait; public string $message; }¶

class APIError implements StructuredOutputModel { use StructuredOutputModelTrait; public string $code; public string $detail; }¶

class Metadata implements StructuredOutputModel { use StructuredOutputModelTrait; public string $request_id; }¶

class APIResponse implements StructuredOutputModel¶
{¶
use StructuredOutputModelTrait;¶

public string $status;¶
public Payload $data;¶
#[Constrained(itemClass: APIError::class)]¶
public ?array $errors;¶
public Metadata $metadata;¶
}¶

$message = $client->messages->create(¶
maxTokens: 1024,¶
messages: [¶
['role' => 'user', 'content' => 'Process this request: ...']¶
],¶
model: 'claude-opus-5-5',¶
outputConfig: ['format' => APIResponse::class],¶
);¶

$result = $message->parsedOutput();¶
if ($result instanceof APIResponse) {¶
echo "{$result->status}: {$result->data->message}\n";¶
}¶
```¶

```ruby Ruby¶
client = Anthropic::Client.new¶

class Payload < Anthropic::BaseModel¶
required :message, String¶
end¶

class APIError < Anthropic::BaseModel¶
required :code, String¶
required :detail, String¶
end¶

class Metadata < Anthropic::BaseModel¶
required :request_id, String¶
end¶

class APIResponse < Anthropic::BaseModel¶
required :status, String¶
required :data, Payload¶
optional :errors, Anthropic::ArrayOf[APIError]¶
required :metadata, Metadata¶
end¶

message = client.messages.create(¶
model: "claude-opus-5-5",¶
max_tokens: 1024,¶
output_config: {format: APIResponse},¶
messages: [¶
{role: "user", content: "Process this request: ..."}¶
]¶
)¶
puts message.parsed_output¶
```¶
</CodeGroup>¶
</Accordion>¶
</AccordionGroup>¶

## Strict tool use¶

To enforce JSON Schema compliance on tool inputs with grammar-constrained sampling, see [Strict tool use](https://platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use).¶

### Using both features together¶

JSON outputs and strict tool use solve different problems and work together:¶

* **JSON outputs** control Claude's response format (what Claude says)¶
* **Strict tool use** validates tool parameters (how Claude calls your functions)¶

When combined, Claude can call tools with guaranteed-valid parameters AND return structured JSON responses. This is useful for agentic workflows where you need both reliable tool calls and structured final outputs.¶

<CodeGroup>¶
```bash cURL¶
curl 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-5",¶
"max_tokens": 1024,¶
"messages": [¶
{¶
"role": "user",¶
"content": "Help me plan a trip to Paris departing May 15, 2026"¶
}¶
],¶
"output_config": {¶
"format": {¶
"type": "json_schema",¶
"schema": {¶
"type": "object",¶
"properties": {¶
"summary": {"type": "string"},¶
"next_steps": {"type": "array", "items": {"type": "string"}}¶
},¶
"required": ["summary", "next_steps"],¶
"additionalProperties": false¶
}¶
}¶
},¶
"tools": [¶
{¶
"name": "search_flights",¶
"strict": true,¶
"input_schema": {¶
"type": "object",¶
"properties": {¶
"destination": {"type": "string"},¶
"date": {"type": "string", "format": "date"}¶
},¶
"required": ["destination", "date"],¶
"additionalProperties": false¶
}¶
}¶
]¶
}'¶
```¶

```bash CLI¶
ant messages create <<'YAML'¶
model: claude-opus-5-5¶
max_tokens: 1024¶
messages:¶
- role: user¶
content: Help me plan a trip to Paris departing May 15, 2026¶
# JSON outputs: structured response format¶
output_config:¶
format:¶
type: json_schema¶
schema:¶
type: object¶
properties:¶
summary:¶
type: string¶
next_steps:¶
type: array¶
items:¶
type: string¶
required: [summary, next_steps]¶
additionalProperties: false¶
# Strict tool use: guaranteed tool parameters¶
tools:¶
- name: search_flights¶
strict: true¶
input_schema:¶
type: object¶
properties:¶
destination:¶
type: string¶
date:¶
type: string¶
format: date¶
required: [destination, date]¶
additionalProperties: false¶
YAML¶
```¶

```python Python¶
response = client.messages.create(¶
model="claude-opus-5-5",¶
max_tokens=1024,¶
messages=[¶
{¶
"role": "user",¶
"content": "Help me plan a trip to Paris departing May 15, 2026",¶
}¶
],¶
# JSON outputs: structured response format¶
output_config={¶
"format": {¶
"type": "json_schema",¶
"schema": {¶
"type": "object",¶
"properties": {¶
"summary": {"type": "string"},¶
"next_steps": {"type": "array", "items": {"type": "string"}},¶
},¶
"required": ["summary", "next_steps"],¶
"additionalProperties": False,¶
},¶
}¶
},¶
# Strict tool use: guaranteed tool parameters¶
tools=[¶
{¶
"name": "search_flights",¶
"strict": True,¶
"input_schema": {¶
"type": "object",¶
"properties": {¶
"destination": {"type": "string"},¶
"date": {"type": "string", "format": "date"},¶
},¶
"required": ["destination", "date"],¶
"additionalProperties": False,¶
},¶
}¶
],¶
)¶

print(response)¶
```¶

```typescript TypeScript¶
const response = await client.messages.create({¶
model: "claude-opus-5-5",¶
max_tokens: 1024,¶
messages: [{ role: "user", content: "Help me plan a trip to Paris departing May 15, 2026" }],¶
// JSON outputs: structured response format¶
output_config: {¶
format: {¶
type: "json_schema",¶
schema: {¶
type: "object",¶
properties: {¶
summary: { type: "string" },¶
next_steps: { type: "array", items: { type: "string" } }¶
},¶
required: ["summary", "next_steps"],¶
additionalProperties: false¶
}¶
}¶
},¶
// Strict tool use: guaranteed tool parameters¶
tools: [¶
{¶
name: "search_flights",¶
description: "Search for available flights to a destination on a specific date",¶
strict: true,¶
input_schema: {¶
type: "object",¶
properties: {¶
destination: { type: "string" },¶
date: { type: "string", format: "date" }¶
},¶
required: ["destination", "date"],¶
additionalProperties: false¶
}¶
}¶
]¶
});¶

// Claude may call the tool first (tool_use) or respond with JSON (text)¶
console.log("Stop reason:", response.stop_reason);¶
for (const block of response.content) {¶
switch (block.type) {¶
case "tool_use":¶
console.log(`Tool call: ${block.name}(${JSON.stringify(block.input)})`);¶
break;¶
case "text":¶
console.log("Response:", block.text);¶
break;¶
}¶
}¶
```¶

```csharp C#¶
var parameters = new MessageCreateParams¶
{¶
Model = Model.ClaudeOpus5_5,¶
MaxTokens = 1024,¶
Messages = [new() { Role = Role.User, Content = "Help me plan a trip to Paris departing May 15, 2026" }],¶
// JSON outputs: structured response format¶
OutputConfig = new OutputConfig¶
{¶
Format = new JsonOutputFormat¶
{¶
Schema = new Dictionary<string, JsonElement>¶
{¶
["type"] = JsonSerializer.SerializeToElement("object"),¶
["properties"] = JsonSerializer.SerializeToElement(new¶
{¶
summary = new { type = "string" },¶
next_steps = new { type = "array", items = new { type = "string" } },¶
}),¶
["required"] = JsonSerializer.SerializeToElement(new[] { "summary", "next_steps" }),¶
["additionalProperties"] = JsonSerializer.SerializeToElement(false),¶
},¶
},¶
},¶
// Strict tool use: guaranteed tool parameters¶
Tools =¶
[¶
new Tool¶
{¶
Name = "search_flights",¶
Strict = true,¶
InputSchema = new InputSchema(new Dictionary<string, JsonElement>¶
{¶
["properties"] = JsonSerializer.SerializeToElement(new Dictionary<string, object>¶
{¶
["destination"] = new { type = "string" },¶
["date"] = new { type = "string", format = "date" },¶
}),¶
["required"] = JsonSerializer.SerializeToElement(new[] { "destination", "date" }),¶
["additionalProperties"] = JsonSerializer.SerializeToElement(false),¶
}),¶
}¶
],¶
};¶

var message = await client.Messages.Create(parameters);¶
Console.WriteLine(message);¶
```¶

```go Go¶
client := anthropic.NewClient()¶

response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{¶
Model: anthropic.ModelClaudeOpus5_5,¶
MaxTokens: 1024,¶
Messages: []anthropic.MessageParam{¶
anthropic.NewUserMessage(anthropic.NewTextBlock("Help me plan a trip to Paris departing May 15, 2026")),¶
},¶
// JSON outputs: structured response format¶
OutputConfig: anthropic.OutputConfigParam{¶
Format: anthropic.JSONOutputFormatParam{¶
Schema: map[string]any{¶
"type": "object",¶
"additionalProperties": false,¶
"properties": map[string]any{¶
"summary": map[string]any{"type": "string"},¶
"next_steps": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},¶
},¶
"required": []string{"summary", "next_steps"},¶
},¶
},¶
},¶
// Strict tool use: guaranteed tool parameters¶
Tools: []anthropic.ToolUnionParam{¶
{OfTool: &anthropic.ToolParam{¶
Name: "search_flights",¶
Strict: anthropic.Bool(true),¶
InputSchema: anthropic.ToolInputSchemaParam{¶
Properties: map[string]any{¶
"destination": map[string]any{"type": "string"},¶
"date": map[string]any{"type": "string", "format": "date"},¶
},¶
Required: []string{"destination", "date"},¶
ExtraFields: map[string]any{¶
"additionalProperties": false,¶
},¶
}}},¶
},¶
})¶
if err != nil {¶
log.Fatal(err)¶
}¶
fmt.Println(response.Content)¶
```¶

```java Java¶
AnthropicClient client = AnthropicOkHttpClient.fromEnv();¶

// JSON outputs: structured response format¶
JsonOutputFormat.Schema outputSchema = JsonOutputFormat.Schema.builder()¶
.putAdditionalProperty("type", JsonValue.from("object"))¶
.putAdditionalProperty("properties", JsonValue.from(Map.of(¶
"summary", Map.of("type", "string"),¶
"next_steps", Map.of("type", "array", "items", Map.of("type", "string"))¶
)))¶
.putAdditionalProperty("required", JsonValue.from(List.of("summary", "next_steps")))¶
.putAdditionalProperty("additionalProperties", JsonValue.from(false))¶
.build();¶

// Strict tool use: guaranteed tool parameters¶
InputSchema toolSchema = InputSchema.builder()¶
.properties(JsonValue.from(Map.of(¶
"destination", Map.of("type", "string"),¶
"date", Map.of("type", "string", "format", "date")¶
)))¶
.putAdditionalProperty("required", JsonValue.from(List.of("destination", "date")))¶
.putAdditionalProperty("additionalProperties", JsonValue.from(false))¶
.build();¶

MessageCreateParams params = MessageCreateParams.builder()¶
.model(Model.CLAUDE_OPUS_5_5)¶
.maxTokens(1024L)¶
.addUserMessage("Help me plan a trip to Paris departing May 15, 2026")¶
.outputConfig(OutputConfig.builder()¶
.format(JsonOutputFormat.builder().schema(outputSchema).build())¶
.build())¶
.addTool(Tool.builder()¶
.name("search_flights")¶
.description("Search for available flights to a destination on a specific date")¶
.strict(true)¶
.inputSchema(toolSchema)¶
.build())¶
.build();¶

Message response = client.messages().create(params);¶
IO.println(response);¶
```¶

```php PHP¶
use Anthropic\Lib\Concerns\StructuredOutputModelTrait;¶
use Anthropic\Lib\Contracts\StructuredOutputModel;¶
use Anthropic\Messages\ToolUseBlock;¶

$client = new Client();¶

class TripPlan implements StructuredOutputModel¶
{¶
use StructuredOutputModelTrait;¶

public string $summary;¶
public array $next_steps;¶
}¶

$message = $client->messages->create(¶
maxTokens: 1024,¶
messages: [¶
['role' => 'user', 'content' => 'Help me plan a trip to Paris departing May 15, 2026']¶
],¶
model: 'claude-opus-5-5',¶
// JSON outputs: structured response format¶
outputConfig: ['format' => TripPlan::class],¶
// Strict tool use: guaranteed tool parameters¶
tools: [¶
[¶
'name' => 'search_flights',¶
'strict' => true,¶
'input_schema' => [¶
'type' => 'object',¶
'properties' => [¶
'destination' => ['type' => 'string'],¶
'date' => ['type' => 'string', 'format' => 'date']¶
],¶
'required' => ['destination', 'date'],¶
'additionalProperties' => false¶
]¶
]¶
],¶
);¶

// Claude may call the tool first (tool_use) or respond with JSON (text)¶
$plan = $message->parsedOutput();¶
if ($plan instanceof TripPlan) {¶
echo $plan->summary, "\n";¶
} elseif ($toolUse = array_find($message->content, fn($block) => $block instanceof ToolUseBlock)) {¶
echo "Tool call: {$toolUse->name}(", json_encode($toolUse->input), ")\n";¶
}¶
```¶

```ruby Ruby¶
client = Anthropic::Client.new¶

message = client.messages.create(¶
model: "claude-opus-5-5",¶
max_tokens: 1024,¶
messages: [¶
{role: "user", content: "Help me plan a trip to Paris departing May 15, 2026"}¶
],¶
# JSON outputs: structured response format¶
output_config: {¶
format: {¶
type: :json_schema,¶
schema: {¶
type: "object",¶
properties: {¶
summary: {type: "string"},¶
next_steps: {type: "array", items: {type: "string"}}¶
},¶
required: ["summary", "next_steps"],¶
additionalProperties: false¶
}¶
}¶
},¶
# Strict tool use: guaranteed tool parameters¶
tools: [¶
{¶
name: "search_flights",¶
strict: true,¶
input_schema: {¶
type: "object",¶
properties: {¶
destination: {type: "string"},¶
date: {type: "string", format: "date"}¶
},¶
required: ["destination", "date"],¶
additionalProperties: false¶
}¶
}¶
]¶
)¶
puts message¶
```¶
</CodeGroup>¶

## Important considerations¶

### Grammar compilation and caching¶

Structured outputs use constrained sampling with compiled grammar artifacts. This introduces some performance characteristics to be aware of:¶

* **First request latency:** The first time you use a specific schema, there is additional latency while the grammar compiles¶

* **Automatic caching:** Compiled grammars are cached for 24 hours from last use, making subsequent requests much faster¶

* **Cache invalidation:** The cache is invalidated if you change:¶

* The JSON schema structure¶
* The set of tools in your request (when using both structured outputs and tool use)¶
* Changing only `name` or `description` fields does not invalidate the cache¶

### Prompt modification and token costs¶

When using structured outputs, Claude automatically receives an additional system prompt explaining the expected output format. This means:¶

* Your input token count is slightly higher¶
* The injected prompt costs you tokens like any other system prompt¶
* Changing the `output_config.format` parameter will invalidate any [prompt cache](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) for that conversation thread¶

### JSON Schema limitations¶

Structured outputs support standard JSON Schema with some limitations. Both JSON outputs and strict tool use share these limitations.¶

<Accordion title="Supported features">¶
* All basic types: object, array, string, integer, number, boolean, null¶
* `enum` (strings, numbers, bools, or nulls only - no complex types). For a capitalization caveat, see [Invalid outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#invalid-outputs).¶
* `const`¶
* `anyOf` and `allOf` (with limitations - `allOf` with `$ref` not supported)¶
* `$ref`, `$def`, and `definitions` (external `$ref` not supported)¶
* `default` property for all supported types¶
* `required` and `additionalProperties` (must be set to `false` for objects)¶
* String formats: `date-time`, `time`, `date`, `duration`, `email`, `hostname`, `uri`, `ipv4`, `ipv6`, `uuid`¶
* Array `minItems` (only values 0 and 1 supported)¶
</Accordion>¶

<Accordion title="Not supported">¶
* Recursive schemas¶
* Complex types within enums¶
* External `$ref` (for example, `'$ref': 'http://...'`)¶
* Numerical constraints (such as `minimum`, `maximum`, `multipleOf`)¶
* String constraints (`minLength`, `maxLength`)¶
* Array constraints beyond `minItems` of 0 or 1¶
* `additionalProperties` set to anything other than `false`¶

If you use an unsupported feature, you'll receive a 400 error with details.¶
</Accordion>¶

<Accordion title="Pattern support (regex)">¶
**Supported regex features:**¶

* Full matching (`^...$`) and partial matching¶
* Quantifiers: `*`, `+`, `?`, simple `{n,m}` cases¶
* Character classes: `[]`, `.`, `\d`, `\w`, `\s`¶
* Groups: `(...)`¶

**NOT supported:**¶

* Backreferences to groups (for example, `\1`, `\2`)¶
* Lookahead/lookbehind assertions (for example, `(?=...)`, `(?!...)`)¶
* Word boundaries: `\b`, `\B`¶
* Complex `{n,m}` quantifiers with large ranges¶

Simple regex patterns work well. Complex patterns may result in 400 errors.¶
</Accordion>¶

<Tip>¶
SDK helpers can transform schemas with [constraints not supported by the API](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-schema-limitations). See [How SDK transformation works](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#how-sdk-transformation-works).¶
</Tip>¶

### Property ordering¶

When using structured outputs, properties in objects maintain their defined ordering from your schema, with one important caveat: **required properties appear first, followed by optional properties**.¶

For example, given this schema:¶

```json¶
{¶
"type": "object",¶
"properties": {¶
"notes": { "type": "string" },¶
"name": { "type": "string" },¶
"email": { "type": "string" },¶
"age": { "type": "integer" }¶
},¶
"required": ["name", "email"],¶
"additionalProperties": false¶
}¶
```¶

The output will order properties as:¶

1. `name` (required, in schema order)¶
2. `email` (required, in schema order)¶
3. `notes` (optional, in schema order)¶
4. `age` (optional, in schema order)¶

This means the output might look like:¶

```json¶
{¶
"name": "John Smith",¶
"email": "john@example.com",¶
"notes": "Interested in enterprise plan",¶
"age": 35¶
}¶
```¶

If property order in the output is important to your application, mark all properties as required, or account for this reordering in your parsing logic.¶

### Invalid outputs¶

While structured outputs guarantee schema compliance in most cases, there are scenarios where the output may not match your schema:¶

**Refusals** (`stop_reason: "refusal"`)¶

Claude maintains its safety and helpfulness properties even when using structured outputs. If Claude refuses a request for safety reasons:¶

* The response has `stop_reason: "refusal"`¶
* You'll receive a 200 status code¶
* You'll be billed for the tokens generated¶
* The output may not match your schema because the refusal message takes precedence over schema constraints¶

A property that asks for the model's thinking or step-by-step reasoning may lead to a `reasoning_extraction` refusal. Ask for a short explanation instead. See [Keep reasoning in thinking blocks](https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback#keep-reasoning-in-thinking-blocks).¶

**Token limit reached** (`stop_reason: "max_tokens"`)¶

If the response is cut off due to reaching the `max_tokens` limit:¶

* The response has `stop_reason: "max_tokens"`¶
* The output may be incomplete and not match your schema¶
* Retry with a higher `max_tokens` value to get the complete structured output¶

**Enum value casing**¶

Structured outputs don't guarantee the capitalization of string `enum` and `const` values: Claude may return a value that differs from your schema only in capitalization, typically in the first letter of a word following a space. For example, given this schema:¶

```json¶
{¶
"type": "string",¶
"enum": ["Conversation Topic 1", "Conversation Topic 2", "Conversation topic 3"]¶
}¶
```¶

The output may contain `"Conversation Topic 3"` (capital "T") even though that exact value isn't in the enum. The response completes normally, with no error and no special `stop_reason`. This applies to both JSON outputs and strict tool use. Compare enum values case-insensitively, and avoid enum values that differ only in capitalization.¶

### Schema complexity limits¶

Structured outputs work by compiling your JSON schemas into a grammar that constrains Claude's output. More complex schemas produce larger grammars that take longer to compile. To protect against excessive compilation times, the API enforces several complexity limits.¶

#### Explicit limits¶

The following limits apply to all requests with `output_config.format` or `strict: true`:¶

| Limit | Value | Description |¶
| --------------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |¶
| Strict tools per request | 20 | Maximum number of tools with `strict: true`. Non-strict tools don't count toward this limit. |¶
| Optional parameters | 24 | Total optional parameters across all strict tool schemas and JSON output schemas. Each parameter not listed in `required` counts toward this limit. |¶
| Parameters with union types | 16 | Total parameters that use `anyOf` or type arrays (for example, `"type": ["string", "null"]`) across all strict schemas. These are especially expensive because they create exponential compilation cost. |¶

<Note>¶
These limits apply to the combined total across all strict schemas in a single request. For example, if you have 4 strict tools with 6 optional parameters each, you'll reach the 24-parameter limit even though no single tool seems complex.¶
</Note>¶

#### Additional internal limits¶

Beyond the explicit limits in the preceding table, there are additional internal limits on the compiled grammar size. These limits exist because schema complexity doesn't reduce to a single dimension: features like optional parameters, union types, nested objects, and number of tools interact with each other in ways that can make the compiled grammar disproportionately large.¶

When these limits are exceeded, you'll receive a 400 error with the message "Schema is too complex for compilation." These errors mean the combined complexity of your schemas exceeds what can be efficiently compiled, even if each individual limit in the preceding table is satisfied. As a final stop-gap, the API also enforces a **compilation timeout of 180 seconds**. Schemas that pass all explicit checks but produce very large compiled grammars may hit this timeout.¶

#### Tips for reducing schema complexity¶

If you're hitting complexity limits, try these strategies in order:¶

1. **Mark only critical tools as strict.** If you have many tools, reserve it for tools where schema violations cause real problems, and rely on Claude's natural adherence for simpler tools.¶

2. **Reduce optional parameters.** Make parameters `required` where possible. Each optional parameter roughly doubles a portion of the grammar's state space. If a parameter always has a reasonable default, consider making it required and having Claude provide that default explicitly.¶

3. **Simplify nested structures.** Deeply nested objects with optional fields compound the complexity. Flatten structures where possible.¶

4. **Split into multiple requests.** If you have many strict tools, consider splitting them across separate requests or sub-agents.¶

For persistent issues with valid schemas, [contact support](https://support.claude.com/en/articles/9015913-how-to-get-support) with your schema definition.¶

## Migrating from the beta¶

The `output_format` parameter has moved to `output_config.format`, and beta headers are no longer required. The `output_format` parameter is deprecated and will be removed in the future. To use it anyway, add the `structured-outputs-2025-11-13` beta header. Without it, the API returns a 400 error.¶

The Python SDK (v1.0 and later) does not accept `output_format={...}` on `client.beta.messages.create()` or `count_tokens()` and raises a `TypeError`. Use `output_config` instead. See [Using a raw JSON schema](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#using-a-raw-json-schema) for the updated API shape.¶

## Data retention¶

Prompts and responses are processed with ZDR when using structured outputs. However, the JSON schema itself is temporarily cached for up to 24 hours since last use for optimization purposes. No prompt or response data is retained beyond the API response.¶

Structured outputs are HIPAA eligible, but **PHI must not be included in JSON schema definitions**. The API compiles JSON schemas into grammars that are cached separately from message content, and these cached schemas do not receive the same PHI protections as prompts and responses. Do not include PHI in schema property names, `enum` values, `const` values, or `pattern` regular expressions. PHI should only appear in message content (prompts and responses), where it is protected under HIPAA safeguards.¶

For ZDR and HIPAA eligibility across all features, see [API and data retention](https://platform.claude.com/docs/en/manage-claude/api-and-data-retention).¶

## Feature compatibility¶

**Works with:**¶

* **[Batch processing](https://platform.claude.com/docs/en/build-with-claude/batch-processing):** Process structured outputs at scale with 50% discount¶
* **[Token counting](https://platform.claude.com/docs/en/build-with-claude/token-counting):** Count tokens without compilation¶
* **[Streaming](https://platform.claude.com/docs/en/build-with-claude/streaming):** Stream structured outputs like normal responses¶
* **[Combined usage](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#using-both-features-together):** Use JSON outputs (`output_config.format`) and strict tool use (`strict: true`) together in the same request¶

**Incompatible with:**¶

* **[Citations](https://platform.claude.com/docs/en/build-with-claude/citations):** Citations require interleaving citation blocks with text, which conflicts with strict JSON schema constraints. Returns 400 error if citations enabled with `output_config.format`.¶
* **Message Prefilling:** Incompatible with JSON outputs¶

<Tip>¶
**Grammar scope:** Grammars apply only to Claude's direct output, not to tool use calls, tool results, or thinking tags (when using [thinking](https://platform.claude.com/docs/en/build-with-claude/thinking)). Grammar state resets between sections, allowing Claude to think freely while still producing structured output in the final response.¶
</Tip>¶

## Next steps¶

<CardGroup cols={2}>¶
<Card title="Citations" icon="book-bookmark" href="https://platform.claude.com/docs/en/build-with-claude/citations">¶
Have Claude cite its sources when answering questions about provided documents.¶
</Card>¶

<Card title="Strict tool use" icon="check" href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use">¶
Enforce JSON Schema compliance on Claude's tool inputs with grammar-constrained sampling.¶
</Card>¶

<Card title="Tool use with Claude" icon="wrench" href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview">¶
Connect Claude to external tools and APIs. Learn where tools execute and how the agentic loop works.¶
</Card>¶

<Card title="Pricing" icon="calculator" href="https://platform.claude.com/docs/en/about-claude/pricing">¶
Learn about Anthropic's pricing structure for models and features.¶
</Card>¶
</CardGroup>¶

Unified Diff

--- a/build-with-claude/structured-outputs.md
+++ b/build-with-claude/structured-outputs.md
@@ -2715,6 +2715,8 @@
 * You'll be billed for the tokens generated
 * The output may not match your schema because the refusal message takes precedence over schema constraints
 
+A property that asks for the model's thinking or step-by-step reasoning may lead to a `reasoning_extraction` refusal. Ask for a short explanation instead. See [Keep reasoning in thinking blocks](https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback#keep-reasoning-in-thinking-blocks).
+
 **Token limit reached** (`stop_reason: "max_tokens"`)
 
 If the response is cut off due to reaching the `max_tokens` limit: