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
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",
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",
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
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: >-
705 interested in the Pro plan
134 Extract the key information from this email: John Smith
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: "
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: " +
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',
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: "
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: " +
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: "
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: '
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"]=>
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: " \
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",
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
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: "
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
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: "
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: " +
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(
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(
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
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: '
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"]=>
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: " \
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
No line in this hunk matches that.