Follow Discord
Sweep 03 Oct 2026 · 20:28Z Build v2.1.289 510 read Stable v2.1.285 Latest v2.1.289 Next v2.1.289 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One capture · api

One read of Claude Developer Platformapi-20260929T200717Z

12 pages moved out of 642 read.

Pages moved 12 significant first
Pages read 642 in this capture
Captured 20:07 UTC
Corpus hash a0035ccb6439 corpus-hash

What this read moved

1-12 of 12

agents-and-tools/tool-use/tool-runner Changed · +6 / -6 lines

from line 23
2323 
2424Depending on the SDK's tool signature, a tool returns its result as a string or as content blocks (text, image, or document blocks), so a tool can return multimodal results. A returned string becomes a single text content block. To return structured data, such as a JSON object or a number, encode it as a string first.
2525 
26<Tabs>
26<Tabs exclude="shell">
2727 <Tab title="Python">
2828 Use the `@beta_tool` decorator to define tools with type hints and docstrings.
2929 
from line 593
593593 
594594If you don't need intermediate messages, you can get the final message directly:
595595 
596<Tabs>
596<Tabs exclude="shell">
597597 <Tab title="Python">
598598 Use `runner.until_done()` to get the final message.
599599 
from line 823
823823 
824824When you take over for an iteration, the runner does not append the assistant message or tool results from that turn. You become responsible for keeping the conversation valid: append the assistant message and a tool result yourself (if you want the turn to count), modify state conditionally so the loop can still exit when there are no tool calls, and pass `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`) to bound the loop. All seven SDKs support `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`).
825825 
826<Tabs>
826<Tabs exclude="shell">
827827 <Tab title="Python">
828828 Use `generate_tool_call_response()` to inspect or compute the tool result. Calling `append_messages()` inside the loop tells the runner you're managing history yourself, so include the assistant message and tool result in what you append.
829829 
from line 1130
11301130 
11311131In the Python and TypeScript SDKs, use the tool response method (`generate_tool_call_response()` in Python, `generateToolResponse()` in TypeScript) to intercept tool results and check for errors before they're sent to Claude. The other SDKs don't expose that hook. Their tabs describe the closest alternative:
11321132 
1133<Tabs>
1133<Tabs exclude="shell">
11341134 <Tab title="Python">
11351135 ```python
11361136 client = anthropic.Anthropic()
from line 1307
13071307 
13081308In the Python and TypeScript SDKs, use the tool response method to get the tool result, then modify it before the runner proceeds. Whether you explicitly append the modified result or mutate it in place depends on the SDK. See the code comments in each tab.
13091309 
1310<Tabs>
1310<Tabs exclude="shell">
13111311 <Tab title="Python">
13121312 ```python
13131313 client = anthropic.Anthropic()
from line 1535
15351535 
15361536Enable streaming to process each turn's response incrementally. Each iteration yields a stream object that you can iterate for events.
15371537 
1538<Tabs>
1538<Tabs exclude="shell">
15391539 <Tab title="Python">
15401540 Set `stream=True` and use `get_final_message()` to get the accumulated message.
15411541 

build-with-claude/preserved-thinking Changed · +9 / -3 lines

from line 45
4545 
4646Keep sending the full history on every request, thinking blocks included, and let the API drop what the current model can't read. The API never edits your `messages` array, so the dropped blocks stay in your history. When the same history goes back to Claude Fable 5.1, its blocks are readable again, along with the earlier model's thinking. The reasoning is lost for good only if your client removes the blocks itself, for example a harness that strips thinking on a model switch or rebuilds the history from what each model used.
4747 
48![Animation: switching to Claude Opus skips Claude Fable 5.1's thinking for that turn; switching back, everything is read again](https://platform.claude.com/docs/images/preserved-thinking-model-switch.svg)
48<Frame>
49 ![Animation: switching to Claude Opus skips Claude Fable 5.1's thinking for that turn; switching back, everything is read again](https://platform.claude.com/docs/images/preserved-thinking-model-switch.svg)
50</Frame>
4951 
5052With the `thinking-binding-controls-2026-08-01` [beta header](https://platform.claude.com/docs/en/api/beta-headers), the response lists each dropped block in a top-level `input_transformations` array with `reason: "model_binding_mismatch"`:
5153 
from line 1429
14271429 
14281430When the conversation grows too long, summarize the whole session into one user message and send only that message plus the next instruction. Nothing earlier is replayed, so there's no thinking left to fail the check, and the model reasons afresh from the summary.
14291431 
1430![Simple compaction: request 4 sends the full history with thinking on each assistant turn; request 5 sends one user message holding a summary of turns 1 to 4 plus the next instruction, so no earlier thinking is sent and nothing is checked](https://platform.claude.com/docs/images/preserved-thinking-simple-compaction.svg)
1432<Frame>
1433 ![Simple compaction: request 4 sends the full history with thinking on each assistant turn; request 5 sends one user message holding a summary of turns 1 to 4 plus the next instruction, so no earlier thinking is sent and nothing is checked](https://platform.claude.com/docs/images/preserved-thinking-simple-compaction.svg)
1434</Frame>
14311435 
14321436```json
14331437[
from line 1452
14481452 
14491453The rest of this section covers a summary you write yourself.
14501454 
1451![Keep-tail compaction: the history is replaced by a summary of turns 1 and 2 followed by turns 3 to 5 verbatim; the thinking on assistant turns 3 and 4 was produced after the original turns, not the summary, so it fails; the same request sent with prefix\_mismatch\_behavior drop\_block succeeds, the API drops those two blocks and lists them in input\_transformations](https://platform.claude.com/docs/images/preserved-thinking-keep-tail-compaction.svg)
1455<Frame>
1456 ![Keep-tail compaction: the history is replaced by a summary of turns 1 and 2 followed by turns 3 to 5 verbatim; the thinking on assistant turns 3 and 4 was produced after the original turns, not the summary, so it fails; the same request sent with prefix\_mismatch\_behavior drop\_block succeeds, the API drops those two blocks and lists them in input\_transformations](https://platform.claude.com/docs/images/preserved-thinking-keep-tail-compaction.svg)
1457</Frame>
14521458 
14531459Fix: keep the turns exactly as they are and send `prefix_mismatch_behavior: "drop_block"`. The API drops the stale thinking blocks, the model reads the kept turns' `text` and `tool_use` blocks, and the request succeeds.
14541460 

build-with-claude/structured-outputs Changed · +934 / -1154 lines

## 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 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 

build-with-claude/thinking Changed · +6 / -2 lines

from line 16
1616 
1717## How thinking works
1818 
19![Diagram of how thinking works: Claude evaluates the request and decides whether to think up front; with tool use, thinking can recur between tool calls; one response returns thinking blocks, then text blocks](https://platform.claude.com/docs/images/how-thinking-works.svg)
19<Frame>
20 ![Diagram of how thinking works: Claude evaluates the request and decides whether to think up front; with tool use, thinking can recur between tool calls; one response returns thinking blocks, then text blocks](https://platform.claude.com/docs/images/how-thinking-works.svg)
21</Frame>
2022 
2123Whether Claude thinks on a given request, and how deeply, depends on your thinking configuration and the complexity of the request.
2224 
from line 535
533535 
534536Thinking works with [streaming](https://platform.claude.com/docs/en/build-with-claude/streaming). Thinking blocks stream as `thinking_delta` events inside `content_block_delta` events, followed by a single `signature_delta` event just before the block's `content_block_stop`. Text blocks stream afterward as usual.
535537 
536![Diagram of the streaming event sequence with thinking: the thinking block opens, thinking deltas carry text only when the display setting returns text (summarized, or updates for progress-update blocks), a single signature delta closes the block, then text deltas stream](https://platform.claude.com/docs/images/how-thinking-streams.svg)
538<Frame>
539 ![Diagram of the streaming event sequence with thinking: the thinking block opens, thinking deltas carry text only when the display setting returns text (summarized, or updates for progress-update blocks), a single signature delta closes the block, then text deltas stream](https://platform.claude.com/docs/images/how-thinking-streams.svg)
540</Frame>
537541 
538542The following examples stream a response with adaptive thinking, printing thinking and text deltas as they arrive:
539543 

cli-sdks-libraries/cli/quickstart Changed · +4 / -1 lines

from line 7
77The `ant` CLI provides access to the Claude API from your terminal. Every API resource is exposed as a subcommand, with output formatting, response filtering, and YAML or JSON file input.
88 
99<Frame caption="The ant CLI in action.">
10 [](https://platform.claude.com/docs/videos/ant-cli-demo.webm)
10 <video aria-label="Screen recording of the ant CLI running in a terminal.">
11 <source src="https://platform.claude.com/docs/videos/ant-cli-demo.webm" type="video/webm" />
12 <source src="https://platform.claude.com/docs/videos/ant-cli-demo.mp4" type="video/mp4" />
13 </video>
1114</Frame>
1215 
1316Compared to `curl`, `ant` builds request bodies from typed flags or piped YAML instead of hand-written JSON, and inlines file contents into string fields with an `@path` reference. It extracts response fields with a built-in `--transform` query, so you don't need a separate tool such as `jq`, and it paginates list endpoints automatically.

managed-agents/self-hosted-sandboxes Changed · +81 / -77 lines

from line 257
257257 
258258 If you need stronger isolation (a fresh filesystem, resource limits, or per-session network controls), run each session in its own sandbox. Build an image with `ant` installed and `ant beta:worker run` as the entrypoint. The base image must provide `/bin/bash`; `curl` is only used at build time. When a sandbox starts, it reads session details from environment variables, handles that session, and exits:
259259 
260 ```text
260 ```dockerfile
261261 FROM your-base-image
262262 ARG ANT_VERSION=1.36.0
263263 ARG TARGETARCH
from line 416
416416 </Step>
417417 
418418 <Step title="Export the webhook signing key">
419 In addition to the environment ID and key from [Before you begin](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes#before-you-begin), export the webhook signing key on your handler host so the handler can verify incoming payloads. Signature verification in the Python handler needs the webhooks extra: `pip install "anthropic[webhooks]"`.
419 In addition to the environment ID and key from [Before you begin](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes#before-you-begin), export the webhook signing key on your handler host so the handler can verify incoming payloads.
420420 
421421 ```bash
422422 export ANTHROPIC_WEBHOOK_SIGNING_KEY="whsec_..."
from line 429
429429 When you hand a claimed work item to `handle_item()` (typescript: `handleItem()`; go: `HandleItem()`) yourself, as this handler does, pass the work item's `secret` along as `work_secret` (typescript: `workSecret`; go: `WorkSecret`) so the session can mount any [memory stores](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes#use-memory-stores) attached to it. A handler like this one runs every claimed item in one process on one host, so two sessions that attach the same memory store cannot run through it at the same time (see [Prepare the host](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes#prepare-the-host)); if your sessions share stores, launch [one sandbox per session](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes#run-one-sandbox-per-session) instead.
430430 
431431 <CodeGroup exclude="shell">
432 ```python Python
433 import asyncio
434 import os
435 import anthropic
436 import standardwebhooks # installed by the anthropic[webhooks] extra
432 <CodeGroupItem>
433 To verify webhook signatures, install the webhooks extra: `pip install "anthropic[webhooks]"`.
437434 
438 environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
439 environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
440 client = anthropic.AsyncAnthropic(
441 auth_token=environment_key,
442 )
443 # Cancelled by shutdown() so an in-flight work item can upload changed memory files and
444 # remove its store directories before the process exits.
445 inflight: set[asyncio.Task[None]] = set()
435 ```python Python
436 import asyncio
437 import os
438 import anthropic
439 import standardwebhooks # installed by the anthropic[webhooks] extra
446440 
441 environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
442 environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
443 client = anthropic.AsyncAnthropic(
444 auth_token=environment_key,
445 )
446 # Cancelled by shutdown() so an in-flight work item can upload changed memory files and
447 # remove its store directories before the process exits.
448 inflight: set[asyncio.Task[None]] = set()
447449 
448 # Await this from the host's shutdown hook, such as an ASGI lifespan shutdown (the code after
449 # `yield` in a FastAPI lifespan), which uvicorn runs on SIGTERM. uvicorn lets open requests
450 # finish before that hook runs, so set --timeout-graceful-shutdown to bound the wait.
451 async def shutdown() -> None:
452 for task in inflight:
453 task.cancel()
454 await asyncio.gather(*inflight, return_exceptions=True)
455450 
451 # Await this from the host's shutdown hook, such as an ASGI lifespan shutdown (the code after
452 # `yield` in a FastAPI lifespan), which uvicorn runs on SIGTERM. uvicorn lets open requests
453 # finish before that hook runs, so set --timeout-graceful-shutdown to bound the wait.
454 async def shutdown() -> None:
455 for task in inflight:
456 task.cancel()
457 await asyncio.gather(*inflight, return_exceptions=True)
456458 
457 async def handle(raw: bytes, headers: dict[str, str]) -> tuple[dict[str, str], int]:
458 try:
459 event = client.beta.webhooks.unwrap(raw.decode(), headers=headers)
460 except standardwebhooks.WebhookVerificationError:
461 return {"error": "signature verification failed"}, 401
462 if event.data.type != "session.status_run_started":
463 return {"status": "ignored"}, 200
464 task = asyncio.create_task(run_queued_work())
465 inflight.add(task)
466 task.add_done_callback(inflight.discard)
467 try:
468 # Shielded: a dropped or timed-out delivery must not cancel the item; shutdown() does.
469 await asyncio.shield(task)
470 except asyncio.CancelledError:
471 return {"status": "shutting down"}, 503
472 return {"status": "ok"}, 200
473459 
460 async def handle(raw: bytes, headers: dict[str, str]) -> tuple[dict[str, str], int]:
461 try:
462 event = client.beta.webhooks.unwrap(raw.decode(), headers=headers)
463 except standardwebhooks.WebhookVerificationError:
464 return {"error": "signature verification failed"}, 401
465 if event.data.type != "session.status_run_started":
466 return {"status": "ignored"}, 200
467 task = asyncio.create_task(run_queued_work())
468 inflight.add(task)
469 task.add_done_callback(inflight.discard)
470 try:
471 # Shielded: a dropped or timed-out delivery must not cancel the item; shutdown() does.
472 await asyncio.shield(task)
473 except asyncio.CancelledError:
474 return {"status": "shutting down"}, 503
475 return {"status": "ok"}, 200
474476 
475 async def run_queued_work() -> None:
476 async for work in client.beta.environments.work.poller(
477 environment_id=environment_id,
478 environment_key=environment_key,
479 block_ms=None,
480 reclaim_older_than_ms=2000,
481 drain=True,
482 auto_stop=False,
483 ):
484 await client.beta.environments.work.worker(workdir="/workspace").handle_item(
485 work_id=work.id,
486 environment_id=environment_id,
487 session_id=work.data.id,
488 environment_key=environment_key,
489 # The per-session secret is what lets the worker mount the session's memory stores.
490 work_secret=work.secret,
491 )
492 ```
493477 
478 async def run_queued_work() -> None:
479 async for work in client.beta.environments.work.poller(
480 environment_id=environment_id,
481 environment_key=environment_key,
482 block_ms=None,
483 reclaim_older_than_ms=2000,
484 drain=True,
485 auto_stop=False,
486 ):
487 await client.beta.environments.work.worker(workdir="/workspace").handle_item(
488 work_id=work.id,
489 environment_id=environment_id,
490 session_id=work.data.id,
491 environment_key=environment_key,
492 # The per-session secret is what lets the worker mount the session's memory stores.
493 work_secret=work.secret,
494 )
495 ```
496 </CodeGroupItem>
497 
494498 ```typescript TypeScript
495499 import Anthropic from "@anthropic-ai/sdk";
496500 
from line 702
698702 * `drain` (go: `Drain`): whether to stop polling once the queue is empty rather than waiting for new work.
699703 * `block_ms` (python; typescript: `blockMs`; go: `BlockMs`): how long to wait for work to arrive before returning, in milliseconds. Must be between 1 and 999 (per-poll wait; the helper re-polls automatically). Pass `null` (typescript; python: `None`; go: `param.Null[int64]()`) for a non-blocking check; omitting the parameter uses the default 999 ms long-poll.
700704 * `reclaim_older_than_ms` (typescript: `reclaimOlderThanMs`; go: `ReclaimOlderThanMs`): re-claim work items that were claimed but never acknowledged within this many milliseconds.
701 * `auto_stop` (typescript: `autoStop`; go: `AutoStop`): whether to post a stop signal for each work item once your loop body finishes with it. Turn it off whenever whatever runs the work item posts the stop itself: `handle_item()` (typescript: `handleItem()`; go: `HandleItem()`) does, so set it to false when you hand claimed items to `handle_item()` (typescript: `handleItem()`; go: `HandleItem()`) as the webhook handlers on this page do, and so does a sandbox you launch that owns the stop call.
705 * `auto_stop` (typescript: `autoStop`; go: `AutoStop`): whether to post a stop signal for each work item once your loop body finishes with it. Turn it off whenever whatever runs the work item posts the stop itself: `handle_item()` (typescript: `handleItem()`; go: `HandleItem()`) does, so set it to `false` (python: `False`; go: `param.NewOpt(false)`) when you hand claimed items to `handle_item()` (typescript: `handleItem()`; go: `HandleItem()`) as the webhook handlers on this page do, and so does a sandbox you launch that owns the stop call.
702706 
703* **`client.beta.sessions.events.tool_runner()`:** runs tool calls for a single session, given the session ID and a tool list. Use when you've already claimed the work and only need the execution layer.
707* **`client.beta.sessions.events.tool_runner()` (typescript: `client.beta.sessions.events.toolRunner()`; go: `client.Beta.Sessions.Events.NewToolRunner()`):** runs tool calls for a single session, given the session ID and a tool list. Use when you've already claimed the work and only need the execution layer.
704708 
705709Use `work.poller()` (typescript: `new WorkPoller()`; go: `environments.NewWorkPoller()`) directly when you want to launch your own per-session process, for example spinning up a sandbox for each claimed session:
706710 
from line 964
960964 ```
961965</CodeGroup>
962966 
963**With `work.poller()` (typescript; go: `environments.NewWorkPoller()`) and `tool_runner()`:** pass a tool list as `tools` to `client.beta.sessions.events.tool_runner()`. To build that list, set up `AgentToolContext` yourself and call `beta_agent_toolset_20260401(env)` (typescript: `betaAgentToolset20260401(ctx)`; go: `agenttoolset.BetaAgentToolset20260401(env)`):
967**With `work.poller()` (typescript; go: `environments.NewWorkPoller()`) and `client.beta.sessions.events.tool_runner()` (typescript: `client.beta.sessions.events.toolRunner()`; go: `client.Beta.Sessions.Events.NewToolRunner()`):** pass it a tool list as `tools` (go: `Tools`). To build that list, set up `AgentToolContext` yourself and call `beta_agent_toolset_20260401(env)` (typescript: `betaAgentToolset20260401(ctx)`; go: `agenttoolset.BetaAgentToolset20260401(env)`):
964968 
965969<CodeGroup exclude="shell">
966970 ```python Python
from line 1146
11421146When the worker claims a work item whose session has memory stores attached, it:
11431147 
114411481. Downloads each attached store to its `mount_path` on the worker host, authenticating with the work item's per-session `secret`. The `mount_path` is the same directory under `/mnt/memory/` that cloud sessions use (for example, `/mnt/memory/user-preferences/` for a store named "User Preferences"), and the session's system prompt describes it to the agent.
11452. Adds those directories to the file tools' allowed roots, and the directories of stores attached with `access: "read_only"` to their read-only roots, so the agent works on memories with the same `read`, `write`, `edit`, `glob`, and `grep` tools it uses in the working directory.
11463. Reconciles local and remote changes after tool calls, at most once per sync interval (15 seconds by default): memories that changed in the store are written to disk, and files the agent changed are uploaded to the store.
11492. Adds those directories to `allowed_roots` (typescript: `allowedRoots`; go: `AllowedRoots`), and the directories of stores attached with `access: "read_only"` to `read_only_roots` (typescript: `readOnlyRoots`; go: `ReadOnlyRoots`), so the agent works on memories with the same `read`, `write`, `edit`, `glob`, and `grep` tools it uses in the working directory.
11503. Reconciles local and remote changes after tool calls, at most once per `memory_sync_interval` (typescript: `memorySyncIntervalMs`; go: `MemorySyncInterval`) (15 seconds by default): memories that changed in the store are written to disk, and files the agent changed are uploaded to the store.
114711514. Runs a final sync when the session ends, flushes any uploads still pending for up to 30 seconds, and then removes the directories it created. A worker that is cancelled while a session runs skips the final sync but still uploads changed files and removes the directories before it exits.
11481152 
11491153The memory store on Anthropic's side remains the source of truth. [Memory versions](https://platform.claude.com/docs/en/managed-agents/memory#audit-memory-changes), redaction, and viewing or editing memories in the Console work as they do for cloud sessions, and the agent's memory reads and writes appear in the [event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming) as ordinary tool events. Because each worker syncs on an interval, a change written in one session becomes visible to another running session only after both have synced, typically well under a minute at the default interval; sessions on cloud sandboxes see each other's changes almost immediately.
from line 1167
11631167Do not create the per-store directories yourself. The worker creates each store's `mount_path` directory (for example, `/mnt/memory/user-preferences`) when a session starts, refuses to start the session's work if something already exists at that path, and removes the directory when the session ends. Two operating rules follow:
11641168 
11651169* **Run one session per filesystem when sessions attach the same store.** Two sessions cannot mount the same store on one host at the same time, because both need the same path. Giving each session its own sandbox, as described in [Run one sandbox per session](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes#run-one-sandbox-per-session), satisfies this rule.
1166* **Stop workers gracefully.** When you stop a worker while a session runs, `EnvironmentWorker` uploads the session's changed memory files and removes its store directories only if it is cancelled rather than killed: a killed process runs no teardown, and the worker does not install signal handlers itself. Wire SIGTERM and SIGINT to cancellation in the process that runs it: abort the `signal` you pass to the worker in TypeScript, cancel the context in Go, and in Python cancel the task that runs `run()` or `handle_item()`. Do that from a signal handler when your worker is the process, as the standalone workers on this page do, or from your server's own shutdown hook when the worker runs inside a webhook handler, which must not take over the server's signals. Then stop workers with SIGTERM and give them at least 30 seconds to exit before any hard kill, because the final upload can take that long. If a worker is killed before its teardown runs, remove the leftover store directory under `/mnt/memory/` before the next session that attaches that store; any edits in it that had not synced are lost.
1170* **Stop workers gracefully.** When you stop a worker while a session runs, `EnvironmentWorker` uploads the session's changed memory files and removes its store directories only if it is cancelled rather than killed: a killed process runs no teardown, and the worker does not install signal handlers itself. Wire SIGTERM and SIGINT to cancel the worker. Do that from a signal handler when your worker is the process, as the standalone workers on this page do, or from your server's own shutdown hook when the worker runs inside a webhook handler, which must not take over the server's signals. Then stop workers with SIGTERM and give them at least 30 seconds to exit before any hard kill, because the final upload can take that long. If a worker is killed before its teardown runs, remove the leftover store directory under `/mnt/memory/` before the next session that attaches that store; any edits in it that had not synced are lost.
11671171 
11681172### Run one sandbox per session
11691173 
from line 1300
12961300 
12971301Two `EnvironmentWorker` options control memory behavior:
12981302 
1299* **`memory_sync_interval` (typescript: `memorySyncIntervalMs`; go: `MemorySyncInterval`)** (in seconds in Python, in milliseconds in TypeScript, a duration in Go): how often attached stores reconcile with the server while the session runs. Defaults to 15 seconds; the minimum is 5 seconds. A shorter interval narrows the window in which another session sees stale memories, at the cost of more memory store requests. `None` in Python, `null` in TypeScript, or a negative duration in Go disables memory support entirely: the worker neither downloads nor syncs stores, and a session with memory stores attached runs without them even though its system prompt still describes them, so disable memory support only on workers whose sessions attach no memory stores. While memory support is enabled, a work item that arrives without a per-session `secret` for a session with attached stores fails rather than running without memory (see [Troubleshoot memory mounts](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes#troubleshoot-memory-mounts)).
1300* **`memory_sync_deletions` (typescript: `memorySyncDeletions`; go: `MemorySyncDeletions`)**: whether a file the agent deletes locally is also deleted from the store. The value is one of `"enabled"` (the default), `"log_only"`, or `"disabled"` in Python and TypeScript, and one of the constants `environments.MemorySyncDeletionsEnabled` (the zero value), `environments.MemorySyncDeletionsLogOnly`, or `environments.MemorySyncDeletionsDisabled` in Go. When enabled, the worker deletes the memory from the store once a later sync confirms the file is still gone; in log-only mode it runs the same checks but only logs what it would have deleted, which lets you watch what your workers would delete before you trust the enabled mode; when disabled, it never deletes from the store. Uploads and downloads are unaffected by this setting.
1303* **`memory_sync_interval` (typescript: `memorySyncIntervalMs`; go: `MemorySyncInterval`)** (for example, `10` (python; typescript: `10_000`; go: `10 * time.Second`) for 10 seconds): how often attached stores reconcile with the server while the session runs. Defaults to 15 seconds; the minimum is 5 seconds. A shorter interval narrows the window in which another session sees stale memories, at the cost of more memory store requests. Setting it to `None` (python; typescript: `null`; go: `-1`) disables memory support entirely: the worker neither downloads nor syncs stores, and a session with memory stores attached runs without them even though its system prompt still describes them, so disable memory support only on workers whose sessions attach no memory stores. While memory support is enabled, a work item that arrives without a per-session `secret` for a session with attached stores fails rather than running without memory (see [Troubleshoot memory mounts](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes#troubleshoot-memory-mounts)).
1304* **`memory_sync_deletions` (typescript: `memorySyncDeletions`; go: `MemorySyncDeletions`)**: whether a file the agent deletes locally is also deleted from the store. The value is one of `"enabled"` (go: `environments.MemorySyncDeletionsEnabled`) (the default), `"log_only"` (go: `environments.MemorySyncDeletionsLogOnly`), or `"disabled"` (go: `environments.MemorySyncDeletionsDisabled`). When enabled, the worker deletes the memory from the store once a later sync confirms the file is still gone; in log-only mode it runs the same checks but only logs what it would have deleted, which lets you watch what your workers would delete before you trust the enabled mode; when disabled, it never deletes from the store. Uploads and downloads are unaffected by this setting.
13011305 
1302Set these options where you construct the worker, whether through the `EnvironmentWorker` constructor or, in Python and TypeScript, the `client.beta.environments.work.worker()` factory that the webhook handler uses.
1306Set these options wherever you construct the worker, including in the webhook handler.
13031307 
13041308For example, to sync every 10 seconds and only log the deletes the worker would have made:
13051309 
from line 1566
156215662. The worker, inside your sandbox, forwards the call over its open MCP session to the server on your network.
156315673. The worker posts the server's response as the `user.custom_tool_result`.
15641568 
1565The SDKs' [Client-side MCP helpers](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector#client-side-mcp-helpers) convert the server's tools into the runnable tools the worker accepts; install an MCP SDK alongside the Anthropic SDK (`pip install "anthropic[mcp]" "mcp>=1.24"`, `npm install @modelcontextprotocol/sdk`, `go get github.com/modelcontextprotocol/go-sdk`). The examples connect without authentication; to send credentials, configure the HTTP client or request options you hand to the MCP transport (`http_client` (typescript: `requestInit`; go: `HTTPClient`)).
1569The SDK's [Client-side MCP helpers](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector#client-side-mcp-helpers) convert the server's tools into the runnable tools the worker accepts. Install an MCP SDK alongside the Anthropic SDK: `pip install "anthropic[mcp]" "mcp>=1.24"` (python; typescript: `npm install @modelcontextprotocol/sdk`; go: `go get github.com/modelcontextprotocol/go-sdk`). The examples connect without authentication. To send credentials, configure the `http_client` (typescript: `requestInit`; go: `HTTPClient`) you hand to the MCP transport.
15661570 
15671571<Steps>
15681572 <Step title="Declare the server's tools on the agent">
from line 1971
19671971 
19681972### Read queue depth
19691973 
1970`work.stats` returns the queue state for an environment:
1974`GET /v1/environments/{environment_id}/work/stats` (curl; python, typescript, ruby: `client.beta.environments.work.stats()`; go, csharp: `client.Beta.Environments.Work.Stats()`; java: `client.beta().environments().work().stats()`; php: `$client->beta->environments->work->stats()`; cli: `ant beta:environments:work stats`) returns the queue state for an environment:
19711975 
19721976* `depth` is the number of items waiting to be claimed. Scale your worker fleet or alert on backlog based on this value.
19731977* `pending` is the number of items claimed by a worker but not yet acknowledged. The worker helpers acknowledge each item before processing it, so this value stays near zero in normal operation; a sustained non-zero value means a worker stalled between claiming and acknowledging.
from line 2103
20992103 
21002104### Stop a session gracefully
21012105 
2102Use `work.stop` to ask the worker handling a specific session to shut it down. By default the work item moves to `stopping`: the worker notices on its next lease heartbeat, cancels the session's in-flight tool call, and confirms the shutdown, at which point the work item becomes `stopped`. Pass `force: true` in the request body (with the CLI, pass `--force`) to mark the work item `stopped` immediately instead of waiting for the worker's confirmation.
2106Use `POST /v1/environments/{environment_id}/work/{work_id}/stop` (curl; python, typescript, ruby: `client.beta.environments.work.stop()`; go, csharp: `client.Beta.Environments.Work.Stop()`; java: `client.beta().environments().work().stop()`; php: `$client->beta->environments->work->stop()`; cli: `ant beta:environments:work stop`) to ask the worker handling a specific session to shut it down. By default the work item moves to `stopping`: the worker notices on its next lease heartbeat, cancels the session's in-flight tool call, and confirms the shutdown, at which point the work item becomes `stopped`. Pass `force: true` (python: `force=True`; cli: `--force`) to mark the work item `stopped` immediately instead of waiting for the worker's confirmation.
21032107 
21042108Because these calls run from your operations tooling rather than the worker host, `ANTHROPIC_WORK_ID` isn't set automatically. Set it to the target work item's ID before running the following examples. To find a work item's ID, list the environment's work items through the [Environments Work endpoints](https://platform.claude.com/docs/en/api/beta/environments/work).
21052109 

api/errors Changed · +1 / -1 lines

from line 441
441441}
442442```
443443 
444Use [structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs) on models that support it, system prompt instructions, or [`output_config.format`](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-outputs) instead.
444Use [structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs) on models that support it, system prompt instructions, or [`output_config.format`](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#usage) instead.
445445 
446446### Thinking blocks cannot be modified
447447 

build-with-claude/compaction-background Changed · +3 / -1 lines

from line 40
4040 
4141For example, if the compaction request held messages 1 to 5 and the conversation gained messages 6 to 8 while it ran, after the swap your history is the block followed by messages 6 to 8.
4242 
43![Background compaction timeline: the compaction request is sent with messages 1 to 5 while the conversation continues on its full history and gains messages 6 to 8; when the block arrives, it replaces messages 1 to 5 at the front of the history, and the history becomes the block followed by messages 6 to 8](https://platform.claude.com/docs/images/compaction-background-timeline.svg)
43<Frame>
44 ![Background compaction timeline: the compaction request is sent with messages 1 to 5 while the conversation continues on its full history and gains messages 6 to 8; when the block arrives, it replaces messages 1 to 5 at the front of the history, and the history becomes the block followed by messages 6 to 8](https://platform.claude.com/docs/images/compaction-background-timeline.svg)
45</Frame>
4446 
4547If the response has any other `stop_reason`, no summary was produced, which counts as a failure in step 2. Keep the full history; [Handle a missing summary or an error](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#when-no-summary-comes-back) lists the causes and what to do for each.
4648 

build-with-claude/compaction-on-demand Changed · +3 / -1 lines

from line 35
3535 
3636From then on the block takes the place of the messages it summarizes. It goes first in `messages`, the summarized messages are removed, and your next turn follows it. Claude sees the summary where those messages were.
3737 
38![On-demand compaction: a request that carries four messages and the compaction parameter returns one compaction block and no reply; on the next request the block comes first in messages in place of those four messages, followed by the next user turn](https://platform.claude.com/docs/images/compaction-on-demand-swap.svg)
38<Frame>
39 ![On-demand compaction: a request that carries four messages and the compaction parameter returns one compaction block and no reply; on the next request the block comes first in messages in place of those four messages, followed by the next user turn](https://platform.claude.com/docs/images/compaction-on-demand-swap.svg)
40</Frame>
3941 
4042## Request a summary
4143 

build-with-claude/compaction-threshold Changed · +3 / -1 lines

from line 54
5454 
5555On subsequent requests, append the response to your messages. The API automatically drops all content blocks prior to the `compaction` block, continuing the conversation from the summary.
5656 
57![Compaction flow: when input tokens reach the trigger, Claude writes a summary into a compaction block and continues](https://platform.claude.com/docs/images/compaction-flow.svg)
57<Frame>
58 ![Compaction flow: when input tokens reach the trigger, Claude writes a summary into a compaction block and continues](https://platform.claude.com/docs/images/compaction-flow.svg)
59</Frame>
5860 
5961## Basic usage
6062 

build-with-claude/prompt-engineering/prompting-claude-sonnet-5-5 Changed · +1 / -1 lines

from line 79
7979 
8080## Reasoning tasks with JSON output
8181 
82This section applies when you ask Claude Sonnet 5.5 for a JSON answer to a task that needs a few steps of working out. Examples include totaling figures from a document, applying a rule, or ranking items. On tasks like these, the model often answers without thinking first, particularly at `low` and `medium` effort. What helps depends on how you request JSON. Use [structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-outputs) where they're available. The response text is then JSON that matches your schema, so there's nothing to parse.
82This section applies when you ask Claude Sonnet 5.5 for a JSON answer to a task that needs a few steps of working out. Examples include totaling figures from a document, applying a rule, or ranking items. On tasks like these, the model often answers without thinking first, particularly at `low` and `medium` effort. What helps depends on how you request JSON. Use [structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs) where they're available. The response text is then JSON that matches your schema, so there's nothing to parse.
8383 
8484With structured outputs, the response text holds only the JSON, so the model can work the problem out only in its thinking. When it skips thinking, it can be less accurate on these tasks. These changes help keep accuracy high.
8585 

models/fable-5-1/migration-guide Changed · +1 / -1 lines

from line 572
572572 ```
573573 </CodeGroup>
574574 
575 See [Strict tool use](https://platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use) and [Forcing tool use](https://platform.claude.com/docs/en/agents-and-tools/tool-use/define-tools#forcing-tool-use). If you forced a tool only to get schema-conformant JSON, use [JSON outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-outputs) (`output_config.format`) instead.
575 See [Strict tool use](https://platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use) and [Forcing tool use](https://platform.claude.com/docs/en/agents-and-tools/tool-use/define-tools#forcing-tool-use). If you forced a tool only to get schema-conformant JSON, use [JSON outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#usage) (`output_config.format`) instead.
576576 
577577 If your application, rather than the user, requires a specific tool call on the current turn of a multi-turn conversation, append a [mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) after the latest `user` turn. Name the tool, say the call is required for this turn, and tell Claude to open its response with it. Because the message is appended rather than written into the top-level `system` prompt, earlier turns stay byte-identical and keep their [prompt cache](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) hits:
578578 
Feedback