Follow Discord
Sweep 02 Oct 2026 · 18:55Z Build v2.1.288 509 read Stable v2.1.285 Latest v2.1.288 Next v2.1.288 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · api

structured-outputs changedbuild-with-claude/structured-outputs

Nearest release: v2.1.285, published 2 hours before this site recorded the change. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.

Recorded here
Lines+934added
Lines−1,154removed
From line 36 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits14to this page, all time

## How it works ## Usage ## How SDK transformation works ## Using a raw JSON schema ## Common use cases ### Using both features together ## Migrating from the beta ## JSON outputs ### Quick start ### How it works ### Working with JSON outputs in SDKs #### Using native schema definitions #### SDK-specific methods #### How SDK transformation works ### Common use cases ## Using both features together

The whole hunk

from line 36, old and new numbered
/
lines

The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.

from line 36
3636 
3737Structured outputs constrain Claude's responses to follow a specific schema, ensuring valid, parseable output for downstream processing. Structured outputs provide two complementary features:
3838 
39* **JSON outputs** (`output_config.format`): Get Claude's response in a specific JSON format
40* **Strict tool use** (`strict: true`): Guarantee schema validation on tool names and inputs
41 
42You can use these features independently or together in the same request.
43 
44<Tip>
45 **Migrating from 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 the following code examples for the updated API shape.
46</Tip>
39* **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.
40* **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).
41 
42You 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).
4743 
4844## Why use structured outputs
4945 
from line 56
6056* **Type safe:** Guaranteed field types and required fields
6157* **Reliable:** No retries needed for schema violations
6258 
63## JSON outputs
64 
65JSON outputs control Claude's response format, ensuring Claude returns valid JSON matching your schema. Use JSON outputs when you need to:
66 
67* Control Claude's response format
68* Extract data from images or text
69* Generate structured reports
70* Format API responses
71 
72### Quick start
73 
74<CodeGroup>
75 ```bash cURL
76 curl https://api.anthropic.com/v1/messages \
77 -H "content-type: application/json" \
78 -H "x-api-key: $ANTHROPIC_API_KEY" \
79 -H "anthropic-version: 2023-06-01" \
80 -d '{
81 "model": "claude-opus-5-5",
82 "max_tokens": 1024,
83 "messages": [
84 {
85 "role": "user",
86 "content": "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
87 }
88 ],
89 "output_config": {
90 "format": {
91 "type": "json_schema",
92 "schema": {
93 "type": "object",
94 "properties": {
95 "name": {"type": "string"},
96 "email": {"type": "string"},
97 "plan_interest": {"type": "string"},
98 "demo_requested": {"type": "boolean"}
99 },
100 "required": ["name", "email", "plan_interest", "demo_requested"],
101 "additionalProperties": false
59## How it works
60 
61<Steps>
62 <Step title="Define your schema">
63 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).
64 </Step>
65 
66 <Step title="Send it in output_config.format">
67 The request carries the schema in `output_config.format` with `type: "json_schema"`. SDK helpers set this for you.
68 </Step>
69 
70 <Step title="Read the response">
71 Claude returns valid JSON that matches your schema in the response's text content block. SDK helpers parse it into your type.
72 </Step>
73</Steps>
74 
75## Usage
76 
77<Tabs>
78 <Tab title="cURL">
79 ```bash
80 curl https://api.anthropic.com/v1/messages \
81 -H "content-type: application/json" \
82 -H "x-api-key: $ANTHROPIC_API_KEY" \
83 -H "anthropic-version: 2023-06-01" \
84 -d '{
85 "model": "claude-opus-5-5",
86 "max_tokens": 1024,
87 "messages": [
88 {
89 "role": "user",
90 "content": "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
91 }
92 ],
93 "output_config": {
94 "format": {
95 "type": "json_schema",
96 "schema": {
97 "type": "object",
98 "properties": {
99 "name": {"type": "string"},
100 "email": {"type": "string"},
101 "plan_interest": {"type": "string"},
102 "demo_requested": {"type": "boolean"}
103 },
104 "required": ["name", "email", "plan_interest", "demo_requested"],
105 "additionalProperties": false
106 }
102107 }
103108 }
104 }
105 }'
106 ```
107 
108 ```bash CLI
109 ant messages create \
110 --transform 'content.#(type=="text").text|@fromstr' \
111 --format jsonl <<'YAML'
112 model: claude-opus-5-5
113 max_tokens: 1024
114 messages:
115 - role: user
116 content: >-
117 Extract the key information from this email: John Smith
118 ([email protected]) is interested in our Enterprise plan and wants
119 to schedule a demo for next Tuesday at 2pm.
120 output_config:
121 format:
122 type: json_schema
123 schema:
124 type: object
125 properties:
126 name: {type: string}
127 email: {type: string}
128 plan_interest: {type: string}
129 demo_requested: {type: boolean}
130 required: [name, email, plan_interest, demo_requested]
131 additionalProperties: false
132 YAML
133 ```
134 
135 ```python Python
136 client = anthropic.Anthropic()
137 
138 response = client.messages.create(
139 model="claude-opus-5-5",
140 max_tokens=1024,
141 messages=[
142 {
143 "role": "user",
144 "content": "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.",
145 }
146 ],
147 output_config={
148 "format": {
149 "type": "json_schema",
150 "schema": {
151 "type": "object",
152 "properties": {
153 "name": {"type": "string"},
154 "email": {"type": "string"},
155 "plan_interest": {"type": "string"},
156 "demo_requested": {"type": "boolean"},
157 },
158 "required": ["name", "email", "plan_interest", "demo_requested"],
159 "additionalProperties": False,
160 },
161 }
162 },
163 )
164 print(next(block.text for block in response.content if block.type == "text"))
165 ```
166 
167 ```typescript TypeScript
168 const client = new Anthropic();
169 
170 const response = await client.messages.create({
171 model: "claude-opus-5-5",
172 max_tokens: 1024,
173 messages: [
174 {
175 role: "user",
176 content:
177 "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
178 }
179 ],
180 output_config: {
181 format: {
182 type: "json_schema",
183 schema: {
184 type: "object",
185 properties: {
186 name: { type: "string" },
187 email: { type: "string" },
188 plan_interest: { type: "string" },
189 demo_requested: { type: "boolean" }
190 },
191 required: ["name", "email", "plan_interest", "demo_requested"],
192 additionalProperties: false
193 }
194 }
109 }'
110 ```
111 
112 Claude returns JSON like this in the response's text content block:
113 
114 ```json Output
115 {
116 "name": "John Smith",
117 "email": "[email protected]",
118 "plan_interest": "Enterprise",
119 "demo_requested": true
195120 }
196 });
197 
198 for (const block of response.content) {
199 if (block.type === "text") {
200 console.log(block.text);
201 }
202 }
203 ```
204 
205 ```csharp C#
206 using System.Text.Json;
207 using Anthropic;
208 using Anthropic.Models.Messages;
209 
210 AnthropicClient client = new();
211 
212 var parameters = new MessageCreateParams
213 {
214 Model = Model.ClaudeOpus5_5,
215 MaxTokens = 1024,
216 Messages = [new() { Role = Role.User, Content = "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan." }],
217 OutputConfig = new OutputConfig
218 {
219 Format = new JsonOutputFormat
220 {
221 Schema = new Dictionary<string, JsonElement>
222 {
223 ["type"] = JsonSerializer.SerializeToElement("object"),
224 ["properties"] = JsonSerializer.SerializeToElement(new
225 {
226 name = new { type = "string" },
227 email = new { type = "string" },
228 plan_interest = new { type = "string" },
229 demo_requested = new { type = "boolean" },
230 }),
231 ["required"] = JsonSerializer.SerializeToElement(new[] { "name", "email", "plan_interest", "demo_requested" }),
232 ["additionalProperties"] = JsonSerializer.SerializeToElement(false),
233 },
234 },
235 },
236 };
237 
238 var message = await client.Messages.Create(parameters);
239 Console.WriteLine(message);
240 ```
241 
242 ```go Go
243 client := anthropic.NewClient()
244 
245 response, _ := client.Messages.New(context.Background(),
246 anthropic.MessageNewParams{
247 Model: anthropic.ModelClaudeOpus5_5,
248 MaxTokens: 1024,
249 Messages: []anthropic.MessageParam{
250 anthropic.NewUserMessage(
251 anthropic.NewTextBlock("Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan."),
252 ),
253 },
254 OutputConfig: anthropic.OutputConfigParam{
255 Format: anthropic.JSONOutputFormatParam{
256 Schema: map[string]any{
257 "type": "object",
258 "properties": map[string]any{
259 "name": map[string]string{"type": "string"},
260 "email": map[string]string{"type": "string"},
261 "plan_interest": map[string]string{"type": "string"},
262 "demo_requested": map[string]string{"type": "boolean"},
263 },
264 "required": []string{"name", "email", "plan_interest", "demo_requested"},
265 "additionalProperties": false,
266 },
267 },
268 },
269 })
270 
271 for _, block := range response.Content {
272 if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
273 fmt.Println(textBlock.Text)
274 break
275 }
276 }
277 ```
278 
279 ```java Java
280 static class ContactInfo {
281 public String name;
282 public String email;
283 public String plan_interest;
284 public boolean demo_requested;
285 }
286 
287 void main() {
288 AnthropicClient client = AnthropicOkHttpClient.fromEnv();
289 
290 StructuredMessageCreateParams<ContactInfo> params = MessageCreateParams.builder()
291 .model(Model.CLAUDE_OPUS_5_5)
292 .maxTokens(1024)
293 .addUserMessage("Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan.")
294 .outputConfig(ContactInfo.class)
295 .build();
296 
297 StructuredMessage<ContactInfo> response = client.messages().create(params);
298 ContactInfo contact = response.content().stream()
299 .flatMap(block -> block.text().stream())
300 .findFirst().orElseThrow().text();
301 IO.println(contact.name + " (" + contact.email + ")");
302 }
303 ```
304 
305 ```php PHP
306 $client = new Client();
307 
308 $response = $client->messages->create(
309 maxTokens: 1024,
310 messages: [
311 [
312 'role' => 'user',
313 'content' => 'Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan.'
314 ]
315 ],
316 model: 'claude-opus-5-5',
317 outputConfig: [
318 'format' => [
319 'type' => 'json_schema',
320 'schema' => [
321 'type' => 'object',
322 'properties' => [
323 'name' => ['type' => 'string'],
324 'email' => ['type' => 'string'],
325 'plan_interest' => ['type' => 'string'],
326 'demo_requested' => ['type' => 'boolean']
327 ],
328 'required' => ['name', 'email', 'plan_interest', 'demo_requested'],
329 'additionalProperties' => false
330 ]
331 ]
332 ],
333 );
334 
335 $textBlock = array_find($response->content, static fn ($block): bool => $block->type === 'text');
336 echo $textBlock->text;
337 ```
338 
339 ```ruby Ruby
340 client = Anthropic::Client.new
341 
342 response = client.messages.create(
343 model: "claude-opus-5-5",
344 max_tokens: 1024,
345 messages: [
346 {
347 role: "user",
348 content: "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan."
349 }
350 ],
351 output_config: {
352 format: {
353 type: "json_schema",
354 schema: {
355 type: "object",
356 properties: {
357 name: { type: "string" },
358 email: { type: "string" },
359 plan_interest: { type: "string" },
360 demo_requested: { type: "boolean" }
361 },
362 required: ["name", "email", "plan_interest", "demo_requested"],
363 additionalProperties: false
364 }
365 }
366 }
367 )
368 
369 puts response.content.find { it.type == :text }.text
370 ```
371</CodeGroup>
372 
373**Response format:** Valid JSON matching your schema in the response's text content block
374 
375```json Output
376{
377 "name": "John Smith",
378 "email": "[email protected]",
379 "plan_interest": "Enterprise",
380 "demo_requested": true
381}
382```
383 
384### How it works
385 
386<Steps>
387 <Step title="Define your JSON schema">
388 Create a JSON schema that describes the structure you want Claude to follow. The schema uses standard JSON Schema format with some limitations (see [JSON Schema limitations](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-schema-limitations)).
389 </Step>
390 
391 <Step title="Add the output_config.format parameter">
392 Include the `output_config.format` parameter in your API request with `type: "json_schema"` and your schema definition.
393 </Step>
394 
395 <Step title="Parse the response">
396 Claude's response is valid JSON matching your schema, returned in the response's text content block.
397 </Step>
398</Steps>
399 
400### Working with JSON outputs in SDKs
401 
402The SDKs provide helpers that make it easier to work with JSON outputs, including schema transformation, automatic validation, and integration with popular schema libraries.
403 
404<Note>
405 The Python SDK's `client.messages.parse()` still accepts `output_format` as a convenience parameter and translates it to `output_config.format` internally. Other SDKs require `output_config` directly. The following examples show the SDK helper syntax.
406</Note>
407 
408#### Using native schema definitions
409 
410Instead of writing raw JSON schemas, you can use familiar schema definition tools in your language:
411 
412* **Python:** [Pydantic](https://docs.pydantic.dev/) models with `client.messages.parse()`
413* **TypeScript:** [Zod](https://zod.dev/) schemas with `zodOutputFormat()` or typed JSON Schema literals with `jsonSchemaOutputFormat()`
414* **Java:** Plain Java classes with automatic schema derivation through `outputConfig(Class<T>)`
415* **Ruby:** `Anthropic::BaseModel` classes with `output_config: {format: Model}`
416* **PHP:** Classes implementing `StructuredOutputModel` with `outputConfig: ['format' => MyClass::class]`
417* **C#:** Plain C# classes with the generic `Create<T>()` overload, which derives the schema automatically
418* **Go:** Go structs reflected into JSON schemas automatically on the beta API, or raw JSON schemas through `output_config`
419* **CLI:** Raw JSON schemas passed through `output_config`
420 
421<CodeGroup exclude="shell:cURL">
422 ```bash CLI
423 ant messages create \
424 --transform 'content.#(type=="text").text|@fromstr|{name,email}' \
425 --format yaml <<'YAML'
426 model: claude-opus-5-5
427 max_tokens: 1024
428 messages:
429 - role: user
430 content: >-
431 Extract the key information from this email: John Smith
432 ([email protected]) is interested in our Enterprise plan and wants
433 to schedule a demo for next Tuesday at 2pm.
434 output_config:
435 format:
436 type: json_schema
437 schema:
438 type: object
439 properties:
440 name: {type: string}
441 email: {type: string}
442 plan_interest: {type: string}
443 demo_requested: {type: boolean}
444 required: [name, email, plan_interest, demo_requested]
445 additionalProperties: false
446 YAML
447 ```
448 
449 ```python Python
450 from pydantic import BaseModel
451 from anthropic import Anthropic
452 
453 
454 class ContactInfo(BaseModel):
455 name: str
456 email: str
457 plan_interest: str
458 demo_requested: bool
459 
460 
461 client = Anthropic()
462 
463 response = client.messages.parse(
464 model="claude-opus-5-5",
465 max_tokens=1024,
466 messages=[
467 {
468 "role": "user",
469 "content": "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.",
470 }
471 ],
472 output_format=ContactInfo,
473 )
474 
475 print(response.parsed_output)
476 ```
477 
478 ```typescript TypeScript
479 import { z } from "zod";
480 import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
481 
482 const ContactInfoSchema = z.object({
483 name: z.string(),
484 email: z.string(),
485 plan_interest: z.string(),
486 demo_requested: z.boolean()
487 });
488 
489 const client = new Anthropic();
490 
491 const response = await client.messages.parse({
492 model: "claude-opus-5-5",
493 max_tokens: 1024,
494 messages: [
495 {
496 role: "user",
497 content:
498 "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
499 }
500 ],
501 output_config: { format: zodOutputFormat(ContactInfoSchema) }
502 });
503 
504 // Automatically parsed and validated
505 console.log(response.parsed_output);
506 ```
507 
508 ```csharp C#
509 using System.Text.Json;
510 using Anthropic;
511 using Anthropic.Models.Messages;
512 
513 var client = new AnthropicClient();
514 
515 var response = await client.Messages.Create(new MessageCreateParams
516 {
517 Model = Model.ClaudeOpus5_5,
518 MaxTokens = 1024,
519 Messages = [new() {
520 Role = Role.User,
521 Content = "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
522 }],
523 OutputConfig = new OutputConfig
524 {
525 Format = new JsonOutputFormat
526 {
527 Schema = new Dictionary<string, JsonElement>
528 {
529 ["type"] = JsonSerializer.SerializeToElement("object"),
530 ["properties"] = JsonSerializer.SerializeToElement(new
531 {
532 name = new { type = "string" },
533 email = new { type = "string" },
534 plan_interest = new { type = "string" },
535 demo_requested = new { type = "boolean" },
536 }),
537 ["required"] = JsonSerializer.SerializeToElement(
538 new[] { "name", "email", "plan_interest", "demo_requested" }),
539 ["additionalProperties"] = JsonSerializer.SerializeToElement(false),
540 },
541 },
542 },
543 });
544 
545 if (response.Content.Select(b => b.Value).OfType<TextBlock>().FirstOrDefault() is { } textBlock)
546 {
547 // JSON is guaranteed to match the schema
548 var contact = JsonSerializer.Deserialize<Dictionary<string, object>>(textBlock.Text)!;
549 Console.WriteLine($"{contact["name"]} ({contact["email"]})");
550 }
551 ```
552 
553 ```go Go
554 import (
555 // ...
556 "github.com/anthropics/anthropic-sdk-go"
557 "github.com/invopop/jsonschema"
558 )
559 
560 type ContactInfo struct {
561 Name string `json:"name" jsonschema:"description=Full name"`
562 Email string `json:"email" jsonschema:"description=Email address"`
563 PlanInterest string `json:"plan_interest" jsonschema:"description=Plan type"`
564 DemoRequested bool `json:"demo_requested" jsonschema:"description=Whether a demo was requested"`
565 }
566 
567 func generateSchema(v any) map[string]any {
568 r := jsonschema.Reflector{AllowAdditionalProperties: false, DoNotReference: true}
569 s := r.Reflect(v)
570 b, _ := json.Marshal(s)
571 var m map[string]any
572 json.Unmarshal(b, &m)
573 return m
574 }
575 // ...
576 schema := generateSchema(&ContactInfo{})
577 
578 message, _ := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
579 Model: anthropic.ModelClaudeOpus5_5,
580 MaxTokens: 1024,
581 Messages: []anthropic.MessageParam{
582 anthropic.NewUserMessage(anthropic.NewTextBlock(
583 "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.",
584 )),
585 },
586 OutputConfig: anthropic.OutputConfigParam{
587 Format: anthropic.JSONOutputFormatParam{
588 Schema: schema,
589 },
590 },
591 })
592 
593 for _, block := range message.Content {
594 switch variant := block.AsAny().(type) {
595 case anthropic.TextBlock:
596 var contact ContactInfo
597 json.Unmarshal([]byte(variant.Text), &contact)
598 fmt.Printf("%s (%s)\n", contact.Name, contact.Email)
599 }
600 }
601 ```
602 
603 ```java Java
604 static class ContactInfo {
605 public String name;
606 public String email;
607 public String planInterest;
608 public boolean demoRequested;
609 }
610 
611 void main() {
612 AnthropicClient client = AnthropicOkHttpClient.fromEnv();
613 
614 StructuredMessageCreateParams<ContactInfo> createParams = MessageCreateParams.builder()
615 .model(Model.CLAUDE_OPUS_5_5)
616 .maxTokens(1024)
617 .outputConfig(ContactInfo.class)
618 .addUserMessage("Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.")
619 .build();
620 
621 StructuredMessage<ContactInfo> response = client.messages().create(createParams);
622 ContactInfo contact = response.content().stream()
623 .flatMap(block -> block.text().stream())
624 .findFirst().orElseThrow().text();
625 IO.println(contact.name + " (" + contact.email + ")");
626 }
627 ```
628 
629 ```php PHP
630 use Anthropic\Lib\Concerns\StructuredOutputModelTrait;
631 use Anthropic\Lib\Contracts\StructuredOutputModel;
632 
633 $client = new Client();
634 
635 class ContactInfo implements StructuredOutputModel
636 {
637 use StructuredOutputModelTrait;
638 
639 public string $name;
640 public string $email;
641 public string $plan_interest;
642 public bool $demo_requested;
643 }
644 
645 $message = $client->messages->create(
646 maxTokens: 1024,
647 messages: [
648 ['role' => 'user', 'content' => 'Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.'],
649 ],
650 model: 'claude-opus-5-5',
651 outputConfig: ['format' => ContactInfo::class],
652 );
653 
654 $contact = $message->parsedOutput();
655 if ($contact instanceof ContactInfo) {
656 echo "{$contact->name} ({$contact->email})\n";
657 }
658 ```
659 
660 ```ruby Ruby
661 client = Anthropic::Client.new
662 
663 class ContactInfo < Anthropic::BaseModel
664 required :name, String
665 required :email, String
666 required :plan_interest, String
667 required :demo_requested, Anthropic::Boolean
668 end
669 
670 message = client.messages.create(
671 model: "claude-opus-5-5",
672 max_tokens: 1024,
673 messages: [{
674 role: "user",
675 content: "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
676 }],
677 output_config: {format: ContactInfo}
678 )
679 
680 contact = message.parsed_output
681 puts "#{contact.name} (#{contact.email})"
682 ```
683</CodeGroup>
684 
685#### SDK-specific methods
686 
687Each SDK provides helpers that make working with structured outputs easier. See individual SDK pages for full details.
688 
689<Tabs>
121 ```
122 </Tab>
123 
690124 <Tab title="CLI">
691 **Raw JSON schemas through heredoc body**
692 
693 The CLI passes raw JSON schemas as a YAML heredoc body. Use the GJSON `@fromstr` modifier with `--transform` to parse the JSON string returned in the text content block and project specific fields.
694 
695125 ```bash
696126 ant messages create \
697 --transform 'content.#(type=="text").text|@fromstr|{name,email}' \
698 --format yaml <<'YAML'
127 --transform 'content.#(type=="text").text|@fromstr' \
128 --format jsonl <<'YAML'
699129 model: claude-opus-5-5
700130 max_tokens: 1024
701131 messages:
702132 - role: user
703133 content: >-
704 Extract contact info: John Smith, [email protected],
705 interested in the Pro plan
134 Extract the key information from this email: John Smith
135 ([email protected]) is interested in our Enterprise plan and wants
136 to schedule a demo for next Tuesday at 2pm.
706137 output_config:
707138 format:
708139 type: json_schema
from line 143
712143 name: {type: string}
713144 email: {type: string}
714145 plan_interest: {type: string}
715 required: [name, email, plan_interest]
146 demo_requested: {type: boolean}
147 required: [name, email, plan_interest, demo_requested]
716148 additionalProperties: false
717149 YAML
718150 ```
719151 
720 ```yaml Output
721 name: John Smith
152 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`.
153 
154 The example above outputs:
155 
156 ```text Output wrap
157 {"name":"John Smith","email":"[email protected]","plan_interest":"Enterprise","demo_requested":true}
723158 ```
724159 </Tab>
725160 
726161 <Tab title="Python">
727 **`client.messages.parse()` (Recommended)**
728 
729 The `parse()` method automatically transforms your Pydantic model, validates the response, and returns a `parsed_output` attribute.
730 
731162 ```python
732163 from pydantic import BaseModel
733164 # ...
165 
166 
734167 class ContactInfo(BaseModel):
735168 name: str
736169 email: str
737170 plan_interest: str
738 # ...
171 demo_requested: bool
172 
173 
174 client = Anthropic()
175 
739176 response = client.messages.parse(
740177 model="claude-opus-5-5",
741178 max_tokens=1024,
742179 messages=[
743180 {
744181 "role": "user",
745 "content": "Extract contact info: John Smith, [email protected], interested in the Pro plan",
182 "content": (
183 "Extract the key information from this email: "
184 "John Smith ([email protected]) is interested in our Enterprise plan "
185 "and wants to schedule a demo for next Tuesday at 2pm."
186 ),
746187 }
747188 ],
748189 output_format=ContactInfo,
749190 )
750191 
751 # Access the parsed output directly
752 contact = response.parsed_output
753 print(contact.name, contact.email)
754 ```
755 
756 **`transform_schema()` helper**
757 
758 For when you need to manually transform schemas before sending, or when you want to modify a Pydantic-generated schema. Unlike `client.messages.parse()`, which transforms provided schemas automatically, this gives you the transformed schema so you can further customize it.
759 
760 ```python
761 from anthropic import transform_schema
762 from pydantic import TypeAdapter
763 # ...
764 
765 # First convert Pydantic model to JSON schema, then transform
766 schema = TypeAdapter(ContactInfo).json_schema()
767 schema = transform_schema(schema)
768 # Modify schema if needed
769 schema["properties"]["custom_field"] = {"type": "string"}
770 
771 response = client.messages.create(
772 model="claude-opus-5-5",
773 max_tokens=1024,
774 messages=[{"role": "user", "content": "..."}],
775 output_config={
776 "format": {"type": "json_schema", "schema": schema},
777 },
778 )
192 print(response.parsed_output)
193 ```
194 
195 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`.
196 
197 The example above outputs:
198 
199 ```text Output wrap
200 name='John Smith' email='[email protected]' plan_interest='Enterprise' demo_requested=True
779201 ```
780202 </Tab>
781203 
782204 <Tab title="TypeScript">
783 **`client.messages.parse()` with `zodOutputFormat()`**
784 
785 The `parse()` method accepts a Zod schema, validates the response, and returns a `parsed_output` attribute with the inferred TypeScript type matching the schema.
786 
787205 ```typescript
788206 import { z } from "zod";
789207 import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
790208 
791 const ContactInfo = z.object({
209 const ContactInfoSchema = z.object({
792210 name: z.string(),
793211 email: z.string(),
794 planInterest: z.string()
212 plan_interest: z.string(),
213 demo_requested: z.boolean()
795214 });
796215 
797216 const client = new Anthropic();
from line 221
802221 messages: [
803222 {
804223 role: "user",
805 content: "Extract contact info: John Smith, [email protected], interested in the Pro plan"
224 content:
225 "Extract the key information from this email: " +
226 "John Smith ([email protected]) is interested in our Enterprise plan " +
227 "and wants to schedule a demo for next Tuesday at 2pm."
806228 }
807229 ],
808 output_config: { format: zodOutputFormat(ContactInfo) }
230 output_config: { format: zodOutputFormat(ContactInfoSchema) }
809231 });
810232 
811 // Guaranteed type-safe
812 console.log(response.parsed_output!.email);
813 ```
814 
815 **`client.messages.parse()` with `jsonSchemaOutputFormat()`**
816 
817 The `jsonSchemaOutputFormat()` helper accepts a JSON Schema object and integrates it with `parse()` without requiring Zod. Zod is an optional peer dependency you install separately; `jsonSchemaOutputFormat()` works out of the box because the SDK bundles `json-schema-to-ts` directly.
818 
819 For **inline schema literals** (declared with `as const` in your source), you also get compile-time type inference: `parsed_output` is typed to match the schema structure. For **imported or generated schemas** (from a JSON file or OpenAPI codegen), the helper still sends the schema and parses the response, but the inferred type is `unknown` because `as const` can only apply to literal expressions.
820 
233 // Automatically parsed and validated
234 console.log(response.parsed_output);
235 ```
236 
237 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.
238 
239 The example above outputs:
240 
241 ```javascript Output
242 {
243 name: 'John Smith',
244 email: '[email protected]',
245 plan_interest: 'Enterprise',
246 demo_requested: true
247 }
248 ```
249 </Tab>
250 
251 <Tab title="C#">
252 ```csharp
253 using Anthropic;
254 using Anthropic.Models.Messages;
255 using Anthropic.Services;
256 
257 var client = new AnthropicClient();
258 
259 var response = await client.Messages.Create<ContactInfo>(new MessageCreateParams
260 {
261 Model = Model.ClaudeOpus5_5,
262 MaxTokens = 1024,
263 Messages = [new() {
264 Role = Role.User,
265 Content = "Extract the key information from this email: "
266 + "John Smith ([email protected]) is interested in our Enterprise plan "
267 + "and wants to schedule a demo for next Tuesday at 2pm."
268 }],
269 });
270 
271 if (response.Content.Select(block => block.Parsed()).OfType<ContactInfo>().FirstOrDefault() is { } contact)
272 {
273 Console.WriteLine(contact);
274 }
275 
276 public record ContactInfo
277 {
278 public string Name { get; set; } = "";
279 public string Email { get; set; } = "";
280 public string PlanInterest { get; set; } = "";
281 public bool DemoRequested { get; set; }
282 }
283 ```
284 
285 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.
286 
287 The example above outputs:
288 
289 ```text Output wrap
290 ContactInfo { Name = John Smith, Email = [email protected], PlanInterest = Enterprise, DemoRequested = True }
291 ```
292 </Tab>
293 
294 <Tab title="Go">
295 ```go
296 type ContactInfo struct {
297 Name string `json:"name"`
298 Email string `json:"email"`
299 PlanInterest string `json:"plan_interest"`
300 DemoRequested bool `json:"demo_requested"`
301 }
302 
303 func main() {
304 client := anthropic.NewClient()
305 
306 var contact ContactInfo
307 _, err := client.Beta.Messages.New(context.Background(), anthropic.BetaMessageNewParams{
308 Model: anthropic.ModelClaudeOpus5_5,
309 MaxTokens: 1024,
310 Messages: []anthropic.BetaMessageParam{
311 anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(
312 "Extract the key information from this email: " +
313 "John Smith ([email protected]) is interested in our Enterprise plan " +
314 "and wants to schedule a demo for next Tuesday at 2pm.",
315 )),
316 },
317 OutputConfig: anthropic.BetaOutputConfigParam{
318 Format: anthropic.BetaJSONOutputFormatParam{Schema: &contact},
319 },
320 })
321 if err != nil {
322 panic(err)
323 }
324 
325 fmt.Printf("%#v\n", contact)
326 }
327 ```
328 
329 <Note>
330 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).
331 </Note>
332 
333 The SDK generates and transforms the JSON schema from the struct and unmarshals the response into it.
334 
335 The example above outputs:
336 
337 ```text Output wrap
338 main.ContactInfo{Name:"John Smith", Email:"[email protected]", PlanInterest:"Enterprise", DemoRequested:true}
339 ```
340 </Tab>
341 
342 <Tab title="Java">
343 ```java
344 static class ContactInfo {
345 public String name;
346 public String email;
347 public String plan_interest;
348 public boolean demo_requested;
349 }
350 
351 void main() throws Exception {
352 AnthropicClient client = AnthropicOkHttpClient.fromEnv();
353 
354 StructuredMessageCreateParams<ContactInfo> params = MessageCreateParams.builder()
355 .model(Model.CLAUDE_OPUS_5_5)
356 .maxTokens(1024)
357 .addUserMessage("Extract the key information from this email: "
358 + "John Smith ([email protected]) is interested in our Enterprise plan "
359 + "and wants to schedule a demo for next Tuesday at 2pm.")
360 .outputConfig(ContactInfo.class)
361 .build();
362 
363 StructuredMessage<ContactInfo> response = client.messages().create(params);
364 ContactInfo contact = response.content().stream()
365 .flatMap(block -> block.text().stream())
366 .findFirst().orElseThrow().text();
367 IO.println(new ObjectMapper().writeValueAsString(contact));
368 }
369 ```
370 
371 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()`.
372 
373 The example above outputs:
374 
375 ```text Output wrap
376 {"name":"John Smith","email":"[email protected]","plan_interest":"Enterprise","demo_requested":true}
377 ```
378 
379 <Note>
380 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.
381 </Note>
382 
383 <Accordion title="Generic type erasure">
384 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.
385 
386 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.
387 </Accordion>
388 
389 <Accordion title="Local schema validation">
390 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.
391 
392 Key points:
393 
394 * **Local validation** occurs without sending requests to the remote AI model.
395 * **Remote validation** is also performed by the AI model upon receiving the JSON schema.
396 * **Version compatibility:** Local validation may fail while remote validation succeeds if the SDK version is outdated.
397 * **Disabling local validation:** Pass `JsonSchemaLocalValidation.NO` if you encounter compatibility issues:
398 
399 ```java
400 import com.anthropic.core.JsonSchemaLocalValidation;
401 // ...
402 
403 static class BookList {
404 public List<String> books;
405 }
406 
407 void main() {
408 StructuredMessageCreateParams<BookList> createParams = MessageCreateParams.builder()
409 .model(Model.CLAUDE_OPUS_5_5)
410 .maxTokens(2048)
411 .outputConfig(BookList.class, JsonSchemaLocalValidation.NO)
412 .addUserMessage("List some famous late twentieth century novels.")
413 .build();
414 }
415 ```
416 </Accordion>
417 
418 <Accordion title="Streaming">
419 Structured outputs also work with streaming. As responses arrive in stream events, you need to accumulate the full response before deserializing the JSON.
420 
421 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.
422 </Accordion>
423 
424 <Accordion title="JSON schema properties">
425 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.
426 
427 You can control visibility with annotations:
428 
429 * `@JsonIgnore` excludes a `public` field or getter method
430 * `@JsonProperty` includes a non-`public` field or getter method
431 
432 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`.
433 
434 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:
435 
436 * There are no fields or getter methods in the class
437 * All `public` members are annotated with `@JsonIgnore`
438 * All non-`public` members lack `@JsonProperty` annotations
439 * A field uses a `Map` type, which produces an empty `"properties"` field
440 </Accordion>
441 
442 <Accordion title="Composition and inheritance">
443 Your Java classes can use composition and inheritance to share structure when defining JSON schemas. Each pattern affects the output structure differently.
444 
445 **Composition** produces nested JSON output. Deriving a schema from class `Composed` that composes `A` and `B`:
446 
447 ```java
448 static class A {
449 public String a;
450 }
451 
452 static class B {
453 public String b;
454 }
455 
456 static class Composed {
457 public A composedA;
458 public B composedB;
459 }
460 ```
461 
462 The JSON output has this nested structure:
463 
464 ```json
465 {
466 "composedA": { "a": "hello" },
467 "composedB": { "b": "world" }
468 }
469 ```
470 
471 **Inheritance** produces flat JSON output. Deriving a schema from class `Derived` that extends `Base`:
472 
473 ```java
474 static class Base {
475 public String a;
476 }
477 
478 static class Derived extends Base {
479 public String b;
480 }
481 ```
482 
483 The JSON output has this flat structure:
484 
485 ```json
486 {
487 "a": "hello",
488 "b": "world"
489 }
490 ```
491 </Accordion>
492 
493 <Accordion title="Annotations (Jackson and Swagger)">
494 You can use Jackson Databind annotations to enrich the JSON schema derived from your Java classes:
495 
496 ```java
497 import com.fasterxml.jackson.annotation.JsonClassDescription;
498 import com.fasterxml.jackson.annotation.JsonIgnore;
499 import com.fasterxml.jackson.annotation.JsonPropertyDescription;
500 
501 static class Person {
502 
503 @JsonPropertyDescription("The first name and surname of the person")
504 public String name;
505 
506 public int birthYear;
507 
508 @JsonPropertyDescription("The year the person died, or 'present' if the person is living.")
509 public String deathYear;
510 }
511 
512 @JsonClassDescription("The details of one published book")
513 static class Book {
514 
515 public String title;
516 public Person author;
517 
518 @JsonPropertyDescription("The year in which the book was first published.")
519 public int publicationYear;
520 
521 @JsonIgnore
522 public String genre;
523 }
524 
525 static class BookList {
526 public List<Book> books;
527 }
528 ```
529 
530 Annotation summary:
531 
532 * `@JsonClassDescription`: Add a description to a class
533 * `@JsonPropertyDescription`: Add a description to a field or getter method
534 * `@JsonIgnore`: Exclude a `public` field or getter from the schema
535 * `@JsonProperty`: Include a non-`public` field or getter in the schema
536 
537 If you use `@JsonProperty(required = false)`, the SDK ignores the `false` value. Class-derived schemas always mark all properties as required.
538 
539 You can also use Swagger Core (OpenAPI 3) `@Schema` and `@ArraySchema` annotations for type-specific constraints:
540 
541 ```java
542 import io.swagger.v3.oas.annotations.media.ArraySchema;
543 import io.swagger.v3.oas.annotations.media.Schema;
544 
545 static class Article {
546 
547 @ArraySchema(minItems = 1)
548 public List<String> authors;
549 
550 public String title;
551 
552 @Schema(format = "date")
553 public String publicationDate;
554 
555 public int pageCount;
556 }
557 ```
558 
559 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.
560 
561 If you use both Jackson and Swagger annotations to set the same schema field, the Jackson annotation takes precedence.
562 </Accordion>
563 </Tab>
564 
565 <Tab title="PHP">
566 ```php
567 use Anthropic\Lib\Concerns\StructuredOutputModelTrait;
568 use Anthropic\Lib\Contracts\StructuredOutputModel;
569 
570 $client = new Client();
571 
572 class ContactInfo implements StructuredOutputModel
573 {
574 use StructuredOutputModelTrait;
575 
576 public string $name;
577 public string $email;
578 public string $plan_interest;
579 public bool $demo_requested;
580 }
581 
582 $message = $client->messages->create(
583 maxTokens: 1024,
584 messages: [
585 [
586 'role' => 'user',
587 'content' => 'Extract the key information from this email: '
588 . 'John Smith ([email protected]) is interested in our Enterprise plan '
589 . 'and wants to schedule a demo for next Tuesday at 2pm.',
590 ],
591 ],
592 model: 'claude-opus-5-5',
593 outputConfig: ['format' => ContactInfo::class],
594 );
595 
596 $contact = $message->parsedOutput();
597 if ($contact instanceof ContactInfo) {
598 var_dump($contact);
599 }
600 ```
601 
602 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()`.
603 
604 `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.
605 
606 The example above outputs:
607 
608 ```text Output wrap
609 object(ContactInfo)#42 (4) {
610 ["name"]=>
611 string(10) "John Smith"
612 ["email"]=>
613 string(16) "[email protected]"
614 ["plan_interest"]=>
615 string(10) "Enterprise"
616 ["demo_requested"]=>
617 bool(true)
618 }
619 ```
620 
621 <Accordion title="Type inference">
622 The SDK maps native PHP 8 property types to JSON Schema:
623 
624 | PHP type | JSON Schema |
625 | ------------------------------------------ | ---------------------------------- |
626 | `string` | `"string"` |
627 | `int` | `"integer"` |
628 | `float` | `"number"` |
629 | `bool` | `"boolean"` |
630 | `array` | `"array"` (see the following note) |
631 | `?type` (nullable) | Optional field |
632 | Class implementing `StructuredOutputModel` | Nested object |
633 
634 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"}`.
635 
636 All non-nullable properties become required fields.
637 </Accordion>
638 
639 <Accordion title="Constraints with the #[Constrained] attribute">
640 Add constraints with the `#[Constrained]` attribute:
641 
642 ```php
643 use Anthropic\Lib\Attributes\Constrained;
644 use Anthropic\Lib\Concerns\StructuredOutputModelTrait;
645 use Anthropic\Lib\Contracts\StructuredOutputModel;
646 
647 class Address implements StructuredOutputModel { use StructuredOutputModelTrait; public string $street; }
648 
649 class Profile implements StructuredOutputModel
650 {
651 use StructuredOutputModelTrait;
652 
653 #[Constrained(description: 'Age in years', minimum: 0, maximum: 150)]
654 public int $age;
655 
656 #[Constrained(format: 'email')]
657 public string $email;
658 
659 #[Constrained(itemClass: Address::class, minItems: 1)]
660 public array $addresses;
661 }
662 ```
663 
664 **API-enforced constraints** (sent in the schema): `description`, `format`, `const`, `itemClass`, `minItems` (0 or 1 only).
665 
666 **SDK-validated constraints** (stripped from the wire schema, appended to the description, and validated against the response): `minimum`, `maximum`, `multipleOf`, `minLength`, `maxLength`.
667 </Accordion>
668 </Tab>
669 
670 <Tab title="Ruby">
671 ```ruby
672 client = Anthropic::Client.new
673 
674 class ContactInfo < Anthropic::BaseModel
675 required :name, String
676 required :email, String
677 required :plan_interest, String
678 required :demo_requested, Anthropic::Boolean
679 end
680 
681 message = client.messages.create(
682 model: "claude-opus-5-5",
683 max_tokens: 1024,
684 messages: [{
685 role: "user",
686 content: "Extract the key information from this email: " \
687 "John Smith ([email protected]) is interested in our Enterprise plan " \
688 "and wants to schedule a demo for next Tuesday at 2pm."
689 }],
690 output_config: {format: ContactInfo}
691 )
692 
693 contact = message.parsed_output
694 puts contact
695 ```
696 
697 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.
698 
699 The example above outputs:
700 
701 ```text Output wrap
702 {name: "John Smith", email: "[email protected]", plan_interest: "Enterprise", demo_requested: true}
703 ```
704 
705 <Accordion title="Advanced model features">
706 The Ruby SDK supports additional model definition features for richer schemas:
707 
708 * **`doc:` keyword:** Add descriptions to fields for more informative schema output
709 * **`Anthropic::ArrayOf[T]`:** Typed arrays. Pass array-level constraints (`min_items:`, `max_items:`) as keywords on `required`/`optional`, not on `ArrayOf` itself
710 * **`Anthropic::EnumOf[:a, :b]`:** Enum fields with constrained values
711 * **`Anthropic::UnionOf[T1, T2]`:** Union types mapped to `anyOf`
712 
713 ```ruby
714 class FamousNumber < Anthropic::BaseModel
715 required :value, Float
716 optional :reason, String, doc: "why is this number mathematically significant?"
717 end
718 
719 class Output < Anthropic::BaseModel
720 required :numbers, Anthropic::ArrayOf[FamousNumber], min_items: 3, max_items: 5
721 end
722 
723 message = client.messages.create(
724 model: "claude-opus-5-5",
725 max_tokens: 1024,
726 messages: [{role: "user", content: "give me some famous numbers"}],
727 output_config: {format: Output}
728 )
729 
730 message.parsed_output
731 # => #<Output numbers=[#<FamousNumber value=3.14159... reason="Pi is...">...]>
732 ```
733 </Accordion>
734 </Tab>
735</Tabs>
736 
737## How SDK transformation works
738 
739Most SDK helpers transform schemas that use unsupported features. The transformation steps:
740 
7411. **Remove unsupported constraints** (for example, `minimum`, `maximum`, `minLength`, `maxLength`)
7422. **Update descriptions** by adding each unsupported constraint to the field's description (for example, `{minimum: 100}`)
7433. **Add `additionalProperties: false`** to all objects
7444. **Filter string formats** to supported list only
7455. **Validate responses** against your original schema and all its constraints, if the helper validates responses
746 
747Claude receives a simplified schema, but a helper that validates responses still enforces every constraint in your code.
748 
749**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`.
750 
751## Using a raw JSON schema
752 
753To use a JSON schema from a file, an OpenAPI spec, or code that builds it at runtime, pass it in `output_config.format`.
754 
755<Tabs>
756 <Tab title="cURL">
757 ```bash
758 curl https://api.anthropic.com/v1/messages \
759 -H "content-type: application/json" \
760 -H "x-api-key: $ANTHROPIC_API_KEY" \
761 -H "anthropic-version: 2023-06-01" \
762 -d '{
763 "model": "claude-opus-5-5",
764 "max_tokens": 1024,
765 "messages": [
766 {
767 "role": "user",
768 "content": "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
769 }
770 ],
771 "output_config": {
772 "format": {
773 "type": "json_schema",
774 "schema": {
775 "type": "object",
776 "properties": {
777 "name": {"type": "string"},
778 "email": {"type": "string"},
779 "plan_interest": {"type": "string"},
780 "demo_requested": {"type": "boolean"}
781 },
782 "required": ["name", "email", "plan_interest", "demo_requested"],
783 "additionalProperties": false
784 }
785 }
786 }
787 }'
788 ```
789 
790 Claude returns JSON like this in the response's text content block:
791 
792 ```json Output
793 {
794 "name": "John Smith",
795 "email": "[email protected]",
796 "plan_interest": "Enterprise",
797 "demo_requested": true
798 }
799 ```
800 </Tab>
801 
802 <Tab title="CLI">
803 ```bash
804 ant messages create \
805 --transform 'content.#(type=="text").text|@fromstr' \
806 --format jsonl <<'YAML'
807 model: claude-opus-5-5
808 max_tokens: 1024
809 messages:
810 - role: user
811 content: >-
812 Extract the key information from this email: John Smith
813 ([email protected]) is interested in our Enterprise plan and wants
814 to schedule a demo for next Tuesday at 2pm.
815 output_config:
816 format:
817 type: json_schema
818 schema:
819 type: object
820 properties:
821 name: {type: string}
822 email: {type: string}
823 plan_interest: {type: string}
824 demo_requested: {type: boolean}
825 required: [name, email, plan_interest, demo_requested]
826 additionalProperties: false
827 YAML
828 ```
829 
830 The example above outputs:
831 
832 ```text Output wrap
833 {"name":"John Smith","email":"[email protected]","plan_interest":"Enterprise","demo_requested":true}
834 ```
835 </Tab>
836 
837 <Tab title="Python">
838 ```python
839 client = anthropic.Anthropic()
840 
841 response = client.messages.create(
842 model="claude-opus-5-5",
843 max_tokens=1024,
844 messages=[
845 {
846 "role": "user",
847 "content": (
848 "Extract the key information from this email: "
849 "John Smith ([email protected]) is interested in our Enterprise plan "
850 "and wants to schedule a demo for next Tuesday at 2pm."
851 ),
852 }
853 ],
854 output_config={
855 "format": {
856 "type": "json_schema",
857 "schema": {
858 "type": "object",
859 "properties": {
860 "name": {"type": "string"},
861 "email": {"type": "string"},
862 "plan_interest": {"type": "string"},
863 "demo_requested": {"type": "boolean"},
864 },
865 "required": ["name", "email", "plan_interest", "demo_requested"],
866 "additionalProperties": False,
867 },
868 }
869 },
870 )
871 text = next(block.text for block in response.content if block.type == "text")
872 contact = json.loads(text)
873 print(contact)
874 ```
875 
876 The example above outputs:
877 
878 ```text Output wrap
879 {'name': 'John Smith', 'email': '[email protected]', 'plan_interest': 'Enterprise', 'demo_requested': True}
880 ```
881 
882 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.
883 </Tab>
884 
885 <Tab title="TypeScript">
821886 ```typescript
822887 import { jsonSchemaOutputFormat } from "@anthropic-ai/sdk/helpers/json-schema";
823888 
from line 912
847912 });
848913 
849914 // response.parsed_output is typed as { name: string; email: string; planInterest: string } | null
850 console.log(response.parsed_output!.email);
851 ```
852 
853 **Type inference requires `as const`.** Use a literal object expression with a `const` assertion so TypeScript can narrow the property types. Without `as const`, the inferred type collapses to `unknown`.
854 
855 **Schema transformation.** By default, the helper transforms the schema the same way `zodOutputFormat()` does: removing unsupported constraints, adding `additionalProperties: false` to objects, and filtering string formats. Pass `jsonSchemaOutputFormat(schema, { transform: false })` to send your schema to the API unchanged. See [How SDK transformation works](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#how-sdk-transformation-works).
915 console.log(response.parsed_output);
916 ```
917 
918 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`.
919 
920 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`.
921 
922 The example above outputs:
923 
924 ```javascript Output
925 { name: 'John Smith', email: '[email protected]', planInterest: 'Pro' }
926 ```
856927 </Tab>
857928 
858929 <Tab title="C#">
859 **JSON schemas through `OutputConfig`**
860 
861 The C# SDK accepts raw JSON schemas built programmatically with `JsonSerializer.SerializeToElement`, as shown here, or derives the schema from a plain C# class with the generic `Create<T>()` overload. Deserialize the response JSON with `JsonSerializer.Deserialize`.
862 
863930 ```csharp
864931 using System.Text.Json;
865932 using Anthropic;
from line 940
873940 MaxTokens = 1024,
874941 Messages = [new() {
875942 Role = Role.User,
876 Content = "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan."
943 Content = "Extract the key information from this email: "
944 + "John Smith ([email protected]) is interested in our Enterprise plan "
945 + "and wants to schedule a demo for next Tuesday at 2pm."
877946 }],
878947 OutputConfig = new OutputConfig
879948 {
from line 956
887956 name = new { type = "string" },
888957 email = new { type = "string" },
889958 plan_interest = new { type = "string" },
959 demo_requested = new { type = "boolean" },
890960 }),
891961 ["required"] = JsonSerializer.SerializeToElement(
892 new[] { "name", "email", "plan_interest" }),
962 new[] { "name", "email", "plan_interest", "demo_requested" }),
893963 ["additionalProperties"] = JsonSerializer.SerializeToElement(false),
894964 },
895965 },
from line 969
899969 if (response.Content.Select(b => b.Value).OfType<TextBlock>().FirstOrDefault() is { } textBlock)
900970 {
901971 // JSON is guaranteed to match the schema
902 var contact = JsonSerializer.Deserialize<Dictionary<string, object>>(textBlock.Text)!;
903 Console.WriteLine($"{contact["name"]} ({contact["email"]})");
972 var contact = JsonSerializer.Deserialize<JsonElement>(textBlock.Text);
973 Console.WriteLine(contact);
904974 }
905975 ```
976 
977 The example above outputs:
978 
979 ```text Output wrap
980 {"name":"John Smith","email":"[email protected]","plan_interest":"Enterprise","demo_requested":true}
981 ```
906982 </Tab>
907983 
908984 <Tab title="Go">
909 **Raw JSON schemas through `OutputConfigParam`**
910 
911 The Go SDK works with raw JSON schemas. Define a Go struct with json tags, generate the JSON schema (for example, using `invopop/jsonschema`), and unmarshal the response text into your struct. On the beta API, passing a struct as the output format schema reflects it into a JSON schema automatically.
912 
913985 ```go
914 import (
915 // ...
916 "github.com/anthropics/anthropic-sdk-go"
917 "github.com/invopop/jsonschema"
918 )
919 
920986 type ContactInfo struct {
921 Name string `json:"name" jsonschema:"description=Full name"`
922 Email string `json:"email" jsonschema:"description=Email address"`
923 PlanInterest string `json:"plan_interest" jsonschema:"description=Plan type"`
987 Name string `json:"name"`
988 Email string `json:"email"`
989 PlanInterest string `json:"plan_interest"`
990 DemoRequested bool `json:"demo_requested"`
924991 }
925992 
926 func generateSchema(v any) map[string]any {
927 r := jsonschema.Reflector{AllowAdditionalProperties: false, DoNotReference: true}
928 s := r.Reflect(v)
929 b, _ := json.Marshal(s)
930 var m map[string]any
931 json.Unmarshal(b, &m)
932 return m
933 }
934 // ...
935 schema := generateSchema(&ContactInfo{})
993 func main() {
994 client := anthropic.NewClient()
936995 
937996 message, _ := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
938997 Model: anthropic.ModelClaudeOpus5_5,
939998 MaxTokens: 1024,
940999 Messages: []anthropic.MessageParam{
9411000 anthropic.NewUserMessage(anthropic.NewTextBlock(
942 "Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan.",
1001 "Extract the key information from this email: " +
1002 "John Smith ([email protected]) is interested in our Enterprise plan " +
1003 "and wants to schedule a demo for next Tuesday at 2pm.",
9431004 )),
9441005 },
9451006 OutputConfig: anthropic.OutputConfigParam{
9461007 Format: anthropic.JSONOutputFormatParam{
947 Schema: schema,
1008 Schema: map[string]any{
1009 "type": "object",
1010 "properties": map[string]any{
1011 "name": map[string]string{"type": "string"},
1012 "email": map[string]string{"type": "string"},
1013 "plan_interest": map[string]string{"type": "string"},
1014 "demo_requested": map[string]string{"type": "boolean"},
1015 },
1016 "required": []string{"name", "email", "plan_interest", "demo_requested"},
1017 "additionalProperties": false,
1018 },
9481019 },
9491020 },
9501021 })
from line 1025
9541025 case anthropic.TextBlock:
9551026 var contact ContactInfo
9561027 json.Unmarshal([]byte(variant.Text), &contact)
957 fmt.Printf("%s (%s)\n", contact.Name, contact.Email)
1028 fmt.Printf("%#v\n", contact)
9581029 }
9591030 }
960 ```
1031 }
1032 ```
1033 
1034 The example above outputs:
1035 
1036 ```text Output wrap
1037 main.ContactInfo{Name:"John Smith", Email:"[email protected]", PlanInterest:"Enterprise", DemoRequested:true}
1038 ```
1039 
1040 To have the SDK transform a map schema, pass it through `BetaJSONSchemaOutputFormat()` on the beta API.
9611041 </Tab>
9621042 
9631043 <Tab title="Java">
964 Java examples on this page use [JDK 25 compact source file](https://openjdk.org/jeps/512) syntax; see the [Java SDK requirements](https://platform.claude.com/docs/en/cli-sdks-libraries/sdks/java#requirements) for the substitution on earlier JDKs.
965 
966 **`outputConfig(Class<T>)` method**
967 
968 Pass a Java class to `outputConfig()` and the SDK automatically derives a JSON schema, validates it, and returns a `StructuredMessageCreateParams<T>`. Access the parsed result through `response.content().stream().flatMap(block -> block.text().stream()).findFirst().orElseThrow().text()`.
969 
970 <Note>
971 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.
972 </Note>
973 
9741044 ```java
975 static class ContactInfo {
976 public String name;
977 public String email;
978 public String planInterest;
979 }
980 
981 void main() {
1045 import com.anthropic.core.JsonValue;
1046 import com.anthropic.models.messages.JsonOutputFormat;
1047 // ...
1048 import com.anthropic.models.messages.OutputConfig;
1049 import com.fasterxml.jackson.databind.JsonNode;
1050 import com.fasterxml.jackson.databind.ObjectMapper;
1051 
1052 void main() throws Exception {
9821053 AnthropicClient client = AnthropicOkHttpClient.fromEnv();
9831054 
984 StructuredMessageCreateParams<ContactInfo> createParams = MessageCreateParams.builder()
1055 JsonOutputFormat.Schema schema = JsonOutputFormat.Schema.builder()
1056 .putAdditionalProperty("type", JsonValue.from("object"))
1057 .putAdditionalProperty("properties", JsonValue.from(Map.of(
1058 "name", Map.of("type", "string"),
1059 "email", Map.of("type", "string"),
1060 "plan_interest", Map.of("type", "string"))))
1061 .putAdditionalProperty("required", JsonValue.from(
1062 List.of("name", "email", "plan_interest")))
1063 .putAdditionalProperty("additionalProperties", JsonValue.from(false))
1064 .build();
1065 
1066 OutputConfig outputConfig = OutputConfig.builder()
1067 .format(JsonOutputFormat.builder().schema(schema).build())
1068 .build();
1069 
1070 MessageCreateParams createParams = MessageCreateParams.builder()
9851071 .model(Model.CLAUDE_OPUS_5_5)
9861072 .maxTokens(1024)
987 .outputConfig(ContactInfo.class)
988 .addUserMessage("Extract contact info: John Smith, [email protected], interested in the Pro plan")
1073 .outputConfig(outputConfig)
1074 .addUserMessage(
1075 "John Smith ([email protected]) is interested in our Enterprise plan.")
9891076 .build();
9901077 
991 StructuredMessage<ContactInfo> response = client.messages().create(createParams);
992 ContactInfo contact = response.content().stream()
993 .flatMap(block -> block.text().stream())
994 .findFirst().orElseThrow().text();
995 IO.println(contact.name + " (" + contact.email + ")");
1078 String json = client.messages().create(createParams).content().stream()
1079 .flatMap(contentBlock -> contentBlock.text().stream())
1080 .findFirst()
1081 .orElseThrow()
1082 .text();
1083 
1084 JsonNode contact = new ObjectMapper().readTree(json);
1085 IO.println(contact);
9961086 }
9971087 ```
9981088 
999 <Accordion title="Generic type erasure">
1000 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.
1001 
1002 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.
1003 </Accordion>
1004 
1005 <Accordion title="Local schema validation">
1006 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.
1007 
1008 Key points:
1009 
1010 * **Local validation** occurs without sending requests to the remote AI model.
1011 * **Remote validation** is also performed by the AI model upon receiving the JSON schema.
1012 * **Version compatibility:** Local validation may fail while remote validation succeeds if the SDK version is outdated.
1013 * **Disabling local validation:** Pass `JsonSchemaLocalValidation.NO` if you encounter compatibility issues:
1014 
1015 ```java
1016 import com.anthropic.core.JsonSchemaLocalValidation;
1017 // ...
1018 
1019 static class BookList {
1020 public List<String> books;
1021 }
1022 
1023 void main() {
1024 StructuredMessageCreateParams<BookList> createParams = MessageCreateParams.builder()
1025 .model(Model.CLAUDE_OPUS_5_5)
1026 .maxTokens(2048)
1027 .outputConfig(BookList.class, JsonSchemaLocalValidation.NO)
1028 .addUserMessage("List some famous late twentieth century novels.")
1029 .build();
1030 }
1031 ```
1032 </Accordion>
1033 
1034 <Accordion title="Streaming">
1035 Structured outputs also work with streaming. As responses arrive in stream events, you need to accumulate the full response before deserializing the JSON.
1036 
1037 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.
1038 </Accordion>
1039 
1040 <Accordion title="JSON schema properties">
1041 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.
1042 
1043 You can control visibility with annotations:
1044 
1045 * `@JsonIgnore` excludes a `public` field or getter method
1046 * `@JsonProperty` includes a non-`public` field or getter method
1047 
1048 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`.
1049 
1050 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:
1051 
1052 * There are no fields or getter methods in the class
1053 * All `public` members are annotated with `@JsonIgnore`
1054 * All non-`public` members lack `@JsonProperty` annotations
1055 * A field uses a `Map` type, which produces an empty `"properties"` field
1056 </Accordion>
1057 
1058 <Accordion title="Composition and inheritance">
1059 Your Java classes can use composition and inheritance to share structure when defining JSON schemas. Each pattern affects the output structure differently.
1060 
1061 **Composition** produces nested JSON output. Deriving a schema from class `Composed` that composes `A` and `B`:
1062 
1063 ```java
1064 static class A {
1065 public String a;
1066 }
1067 
1068 static class B {
1069 public String b;
1070 }
1071 
1072 static class Composed {
1073 public A composedA;
1074 public B composedB;
1075 }
1076 ```
1077 
1078 The JSON output has this nested structure:
1079 
1080 ```json
1081 {
1082 "composedA": { "a": "hello" },
1083 "composedB": { "b": "world" }
1084 }
1085 ```
1086 
1087 **Inheritance** produces flat JSON output. Deriving a schema from class `Derived` that extends `Base`:
1088 
1089 ```java
1090 static class Base {
1091 public String a;
1092 }
1093 
1094 static class Derived extends Base {
1095 public String b;
1096 }
1097 ```
1098 
1099 The JSON output has this flat structure:
1100 
1101 ```json
1102 {
1103 "a": "hello",
1104 "b": "world"
1105 }
1106 ```
1107 </Accordion>
1108 
1109 <Accordion title="Annotations (Jackson and Swagger)">
1110 You can use Jackson Databind annotations to enrich the JSON schema derived from your Java classes:
1111 
1112 ```java
1113 import com.fasterxml.jackson.annotation.JsonClassDescription;
1114 import com.fasterxml.jackson.annotation.JsonIgnore;
1115 import com.fasterxml.jackson.annotation.JsonPropertyDescription;
1116 
1117 static class Person {
1118 
1119 @JsonPropertyDescription("The first name and surname of the person")
1120 public String name;
1121 
1122 public int birthYear;
1123 
1124 @JsonPropertyDescription("The year the person died, or 'present' if the person is living.")
1125 public String deathYear;
1126 }
1127 
1128 @JsonClassDescription("The details of one published book")
1129 static class Book {
1130 
1131 public String title;
1132 public Person author;
1133 
1134 @JsonPropertyDescription("The year in which the book was first published.")
1135 public int publicationYear;
1136 
1137 @JsonIgnore
1138 public String genre;
1139 }
1140 
1141 static class BookList {
1142 public List<Book> books;
1143 }
1144 ```
1145 
1146 Annotation summary:
1147 
1148 * `@JsonClassDescription`: Add a description to a class
1149 * `@JsonPropertyDescription`: Add a description to a field or getter method
1150 * `@JsonIgnore`: Exclude a `public` field or getter from the schema
1151 * `@JsonProperty`: Include a non-`public` field or getter in the schema
1152 
1153 If you use `@JsonProperty(required = false)`, the SDK ignores the `false` value. Class-derived schemas always mark all properties as required.
1154 
1155 You can also use Swagger Core (OpenAPI 3) `@Schema` and `@ArraySchema` annotations for type-specific constraints:
1156 
1157 ```java
1158 import io.swagger.v3.oas.annotations.media.ArraySchema;
1159 import io.swagger.v3.oas.annotations.media.Schema;
1160 
1161 static class Article {
1162 
1163 @ArraySchema(minItems = 1)
1164 public List<String> authors;
1165 
1166 public String title;
1167 
1168 @Schema(format = "date")
1169 public String publicationDate;
1170 
1171 public int pageCount;
1172 }
1173 ```
1174 
1175 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.
1176 
1177 If you use both Jackson and Swagger annotations to set the same schema field, the Jackson annotation takes precedence.
1178 </Accordion>
1179 
1180 <Accordion title="Defining schemas without a Java class">
1181 Class-based schema derivation is the most convenient path, but for direct control over the schema structure you can build a `JsonOutputFormat.Schema` manually and wrap it in an `OutputConfig`.
1182 
1183 ```java
1184 import com.anthropic.core.JsonValue;
1185 import com.anthropic.models.messages.JsonOutputFormat;
1186 // ...
1187 import com.anthropic.models.messages.OutputConfig;
1188 
1189 void main() {
1190 AnthropicClient client = AnthropicOkHttpClient.fromEnv();
1191 
1192 JsonOutputFormat.Schema schema = JsonOutputFormat.Schema.builder()
1193 .putAdditionalProperty("type", JsonValue.from("object"))
1194 .putAdditionalProperty("properties", JsonValue.from(Map.of(
1195 "name", Map.of("type", "string"),
1196 "email", Map.of("type", "string"),
1197 "plan_interest", Map.of("type", "string"))))
1198 .putAdditionalProperty("required", JsonValue.from(
1199 List.of("name", "email", "plan_interest")))
1200 .putAdditionalProperty("additionalProperties", JsonValue.from(false))
1201 .build();
1202 
1203 OutputConfig outputConfig = OutputConfig.builder()
1204 .format(JsonOutputFormat.builder().schema(schema).build())
1205 .build();
1206 
1207 MessageCreateParams createParams = MessageCreateParams.builder()
1208 .model(Model.CLAUDE_OPUS_5_5)
1209 .maxTokens(1024)
1210 .outputConfig(outputConfig)
1211 .addUserMessage(
1212 "John Smith ([email protected]) is interested in our Enterprise plan.")
1213 .build();
1214 
1215 client.messages().create(createParams).content().stream()
1216 .flatMap(contentBlock -> contentBlock.text().stream())
1217 .forEach(textBlock -> IO.println(textBlock.text()));
1218 }
1219 ```
1220 
1221 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.
1222 </Accordion>
1089 The example above outputs:
1090 
1091 ```text Output wrap
1092 {"name":"John Smith","email":"[email protected]","plan_interest":"Enterprise"}
1093 ```
1094 
1095 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.
12231096 </Tab>
12241097 
12251098 <Tab title="PHP">
1226 **Classes through the `StructuredOutputModel` interface**
1227 
1228 Define a PHP class implementing `StructuredOutputModel` (using `StructuredOutputModelTrait`) and pass the class name to `outputConfig: ['format' => MyClass::class]`. The SDK derives a JSON schema from your native PHP 8 property types and returns a typed instance through `$message->parsedOutput()`.
1229 
1230 `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.
1231 
12321099 ```php
1233 use Anthropic\Lib\Concerns\StructuredOutputModelTrait;
1234 use Anthropic\Lib\Contracts\StructuredOutputModel;
1100 use Anthropic\Messages\OutputConfig;
1101 use Anthropic\Messages\JSONOutputFormat;
12351102 
12361103 $client = new Client();
1237 
1238 class ContactInfo implements StructuredOutputModel
1239 {
1240 use StructuredOutputModelTrait;
1241 
1242 public string $name;
1243 public string $email;
1244 public string $plan_interest;
1245 }
12461104 
12471105 $message = $client->messages->create(
12481106 maxTokens: 1024,
12491107 messages: [
1250 ['role' => 'user', 'content' => 'Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan.'],
1108 [
1109 'role' => 'user',
1110 'content' => 'Extract the key information from this email: '
1111 . 'John Smith ([email protected]) is interested in our Enterprise plan.',
1112 ],
12511113 ],
12521114 model: 'claude-opus-5-5',
1253 outputConfig: ['format' => ContactInfo::class],
1115 outputConfig: OutputConfig::with(format: JSONOutputFormat::with(schema: [
1116 'type' => 'object',
1117 'properties' => [
1118 'name' => ['type' => 'string'],
1119 'email' => ['type' => 'string'],
1120 'plan_interest' => ['type' => 'string'],
1121 ],
1122 'required' => ['name', 'email', 'plan_interest'],
1123 'additionalProperties' => false,
1124 ])),
12541125 );
12551126 
1256 $contact = $message->parsedOutput();
1257 if ($contact instanceof ContactInfo) {
1258 echo "{$contact->name} ({$contact->email})\n";
1127 $textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
1128 $contact = json_decode($textBlock->text, associative: true);
1129 var_dump($contact);
1130 ```
1131 
1132 Pass the schema as an associative array to `OutputConfig::with()`, and decode the response with `json_decode()`.
1133 
1134 The example above outputs:
1135 
1136 ```text Output wrap
1137 array(3) {
1138 ["name"]=>
1139 string(10) "John Smith"
1140 ["email"]=>
1141 string(16) "[email protected]"
1142 ["plan_interest"]=>
1143 string(10) "Enterprise"
12591144 }
12601145 ```
1261 
1262 <Accordion title="Type inference">
1263 The SDK maps native PHP 8 property types to JSON Schema:
1264 
1265 | PHP type | JSON Schema |
1266 | ------------------------------------------ | ---------------------------------- |
1267 | `string` | `"string"` |
1268 | `int` | `"integer"` |
1269 | `float` | `"number"` |
1270 | `bool` | `"boolean"` |
1271 | `array` | `"array"` (see the following note) |
1272 | `?type` (nullable) | Optional field |
1273 | Class implementing `StructuredOutputModel` | Nested object |
1274 
1275 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"}`.
1276 
1277 All non-nullable properties become required fields.
1278 </Accordion>
1279 
1280 <Accordion title="Constraints with the #[Constrained] attribute">
1281 Add constraints with the `#[Constrained]` attribute:
1282 
1283 ```php
1284 use Anthropic\Lib\Attributes\Constrained;
1285 use Anthropic\Lib\Concerns\StructuredOutputModelTrait;
1286 use Anthropic\Lib\Contracts\StructuredOutputModel;
1287 
1288 class Address implements StructuredOutputModel { use StructuredOutputModelTrait; public string $street; }
1289 
1290 class Profile implements StructuredOutputModel
1291 {
1292 use StructuredOutputModelTrait;
1293 
1294 #[Constrained(description: 'Age in years', minimum: 0, maximum: 150)]
1295 public int $age;
1296 
1297 #[Constrained(format: 'email')]
1298 public string $email;
1299 
1300 #[Constrained(itemClass: Address::class, minItems: 1)]
1301 public array $addresses;
1302 }
1303 ```
1304 
1305 **API-enforced constraints** (sent in the schema): `description`, `format`, `const`, `itemClass`, `minItems` (0 or 1 only).
1306 
1307 **SDK-validated constraints** (stripped from the wire schema, appended to the description, and validated against the response): `minimum`, `maximum`, `multipleOf`, `minLength`, `maxLength`.
1308 </Accordion>
1309 
1310 <Accordion title="Raw JSON schema fallback">
1311 For schemas that PHP type hints can't express, pass a raw associative array through `OutputConfig::with()`. This path skips the `parsedOutput()` helper; decode the response with `json_decode()`:
1312 
1313 ```php
1314 use Anthropic\Messages\OutputConfig;
1315 use Anthropic\Messages\JSONOutputFormat;
1316 
1317 $client = new Client();
1318 
1319 $message = $client->messages->create(
1320 maxTokens: 1024,
1321 messages: [
1322 ['role' => 'user', 'content' => 'Extract the key information from this email: John Smith ([email protected]) is interested in our Enterprise plan.'],
1323 ],
1324 model: 'claude-opus-5-5',
1325 outputConfig: OutputConfig::with(format: JSONOutputFormat::with(schema: [
1326 'type' => 'object',
1327 'properties' => [
1328 'name' => ['type' => 'string'],
1329 'email' => ['type' => 'string'],
1330 'plan_interest' => ['type' => 'string'],
1331 ],
1332 'required' => ['name', 'email', 'plan_interest'],
1333 'additionalProperties' => false,
1334 ])),
1335 );
1336 
1337 $textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
1338 $contact = json_decode($textBlock->text, associative: true);
1339 echo "{$contact['name']} ({$contact['email']})\n";
1340 ```
1341 </Accordion>
13421146 </Tab>
13431147 
13441148 <Tab title="Ruby">
1345 **`output_config: {format: Model}` with `parsed_output`**
1346 
1347 Define a model class extending `Anthropic::BaseModel` and pass it as the format to `messages.create()`. The response includes a `parsed_output` attribute with a typed Ruby object.
1348 
13491149 ```ruby
1350 class ContactInfo < Anthropic::BaseModel
1351 required :name, String
1352 required :email, String
1353 required :plan_interest, String
1354 end
1355 
13561150 client = Anthropic::Client.new
13571151 
1358 message = client.messages.create(
1152 response = client.messages.create(
13591153 model: "claude-opus-5-5",
13601154 max_tokens: 1024,
13611155 messages: [
13621156 {
13631157 role: "user",
1364 content: "Extract contact info: John Smith, [email protected], interested in the Pro plan"
1158 content: "Extract the key information from this email: " \
1159 "John Smith ([email protected]) is interested in our Enterprise plan " \
1160 "and wants to schedule a demo for next Tuesday at 2pm."
13651161 }
13661162 ],
1367 output_config: {format: ContactInfo}
1163 output_config: {
1164 format: {
1165 type: "json_schema",
1166 schema: {
1167 type: "object",
1168 properties: {
1169 name: { type: "string" },
1170 email: { type: "string" },
1171 plan_interest: { type: "string" },
1172 demo_requested: { type: "boolean" }
1173 },
1174 required: ["name", "email", "plan_interest", "demo_requested"],
1175 additionalProperties: false
1176 }
1177 }
1178 }
13681179 )
13691180 
1370 contact = message.parsed_output
1371 puts "#{contact.name} (#{contact.email})"
1372 ```
1373 
1374 <Accordion title="Advanced model features">
1375 The Ruby SDK supports additional model definition features for richer schemas:
1376 
1377 * **`doc:` keyword:** Add descriptions to fields for more informative schema output
1378 * **`Anthropic::ArrayOf[T]`:** Typed arrays. Pass array-level constraints (`min_items:`, `max_items:`) as keywords on `required`/`optional`, not on `ArrayOf` itself
1379 * **`Anthropic::EnumOf[:a, :b]`:** Enum fields with constrained values
1380 * **`Anthropic::UnionOf[T1, T2]`:** Union types mapped to `anyOf`
1381 
1382 ```ruby
1383 class FamousNumber < Anthropic::BaseModel
1384 required :value, Float
1385 optional :reason, String, doc: "why is this number mathematically significant?"
1386 end
1387 
1388 class Output < Anthropic::BaseModel
1389 required :numbers, Anthropic::ArrayOf[FamousNumber], min_items: 3, max_items: 5
1390 end
1391 
1392 message = client.messages.create(
1393 model: "claude-opus-5-5",
1394 max_tokens: 1024,
1395 messages: [{role: "user", content: "give me some famous numbers"}],
1396 output_config: {format: Output}
1397 )
1398 
1399 message.parsed_output
1400 # => #<Output numbers=[#<FamousNumber value=3.14159... reason="Pi is...">...]>
1401 ```
1402 </Accordion>
1181 text = response.content.find { it.type == :text }.text
1182 contact = JSON.parse(text)
1183 p contact
1184 ```
1185 
1186 The example above outputs:
1187 
1188 ```text Output wrap
1189 {"name" => "John Smith", "email" => "[email protected]", "plan_interest" => "Enterprise", "demo_requested" => true}
1190 ```
14031191 </Tab>
14041192</Tabs>
14051193 
1406#### How SDK transformation works
1407 
1408The Python, TypeScript, Ruby, and PHP SDKs automatically transform schemas with unsupported features. The C# and Go SDKs apply the same transformations when the schema is derived from a native type (`Create<T>()` in C#; struct reflection or `BetaJSONSchemaOutputFormat()` on the Go beta API). The transformation steps:
1409 
14101. **Remove unsupported constraints** (for example, `minimum`, `maximum`, `minLength`, `maxLength`)
14112. **Update descriptions** with constraint info (for example, "Must be at least 100"), when the constraint is not directly supported with structured outputs
14123. **Add `additionalProperties: false`** to all objects
14134. **Filter string formats** to supported list only
14145. **Validate responses** against your original schema (with all constraints)
1415 
1416This means Claude receives a simplified schema, but your code still enforces all constraints through validation.
1417 
1418**Example:** A Pydantic field with `minimum: 100` becomes a plain integer in the sent schema, but the SDK updates the description to "Must be at least 100" and validates the response against the original constraint.
1419 
1420### Common use cases
1194## Common use cases
14211195 
14221196<AccordionGroup>
14231197 <Accordion title="Data extraction">
from line 2161
23872161 
23882162To 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).
23892163 
2390## Using both features together
2164### Using both features together
23912165 
23922166JSON outputs and strict tool use solve different problems and work together:
23932167 
from line 2618
28442618 
28452619<Accordion title="Supported features">
28462620 * All basic types: object, array, string, integer, number, boolean, null
2847 * `enum` (strings, numbers, bools, or nulls only - no complex types; see [Invalid outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#invalid-outputs) for a capitalization caveat)
2621 * `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).
28482622 * `const`
28492623 * `anyOf` and `allOf` (with limitations - `allOf` with `$ref` not supported)
28502624 * `$ref`, `$def`, and `definitions` (external `$ref` not supported)
from line 2659
28852659</Accordion>
28862660 
28872661<Tip>
2888 The Python, TypeScript, Ruby, and PHP SDKs can automatically transform schemas with unsupported features by removing them and adding constraints to field descriptions. The C# and Go SDKs do the same when the schema is derived from a native type. See [SDK-specific methods](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#sdk-specific-methods) for details.
2662 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).
28892663</Tip>
28902664 
28912665### Property ordering
from line 2774
30002774 
30012775For persistent issues with valid schemas, [contact support](https://support.claude.com/en/articles/9015913-how-to-get-support) with your schema definition.
30022776 
2777## Migrating from the beta
2778 
2779The `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.
2780 
2781The 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.
2782 
30032783## Data retention
30042784 
30052785Prompts 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.
from line 2795
30152795* **[Batch processing](https://platform.claude.com/docs/en/build-with-claude/batch-processing):** Process structured outputs at scale with 50% discount
30162796* **[Token counting](https://platform.claude.com/docs/en/build-with-claude/token-counting):** Count tokens without compilation
30172797* **[Streaming](https://platform.claude.com/docs/en/build-with-claude/streaming):** Stream structured outputs like normal responses
3018* **Combined usage:** Use JSON outputs (`output_config.format`) and strict tool use (`strict: true`) together in the same request
2798* **[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
30192799 
30202800**Incompatible with:**
30212801 
30222802 
Feedback