
Claude Api
- 1 installs
- 6 repo stars
- Updated April 12, 2026
- yizhiyanhua-ai/agent-skills
Guides building apps with the Claude API and Anthropic SDKs across languages, covering surface choice, tool use, streaming, batches, files, thinking, and the Agent SDK.
About
A reference for integrating the Claude API/Anthropic SDK: detects the project language, picks the right tier (single call, workflow, or agent), and links model IDs, tool use, and streaming guidance. A developer uses it when writing or debugging Claude-powered application code.
- Language detection and surface decision tree for API vs Agent SDK
- Cached model table, adaptive thinking, effort, and compaction quick references
Claude Api by the numbers
- 1 all-time installs (skills.sh)
- Ranked #14,102 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yizhiyanhua-ai/agent-skills --skill claude-apiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 6 |
| Last updated | April 12, 2026 |
| Repository | yizhiyanhua-ai/agent-skills ↗ |
What it does
Guides building apps with the Claude API and Anthropic SDKs across languages, covering surface choice, tool use, streaming, batches, files, thinking, and the Agent SDK.
Files
Building LLM-Powered Applications with Claude
This skill helps you build LLM-powered applications with Claude. Choose the right surface based on your needs, detect the project language, then read the relevant language-specific documentation.
Defaults
Unless the user requests otherwise:
For the Claude model version, please use Claude Opus 4.6, which you can access via the exact model string claude-opus-4-6. Please default to using adaptive thinking (thinking: {type: "adaptive"}) for anything remotely complicated. And finally, please default to streaming for any request that may involve long input, long output, or high max_tokens — it prevents hitting request timeouts. Use the SDK's .get_final_message() / .finalMessage() helper to get the complete response if you don't need to handle individual stream events
---
Language Detection
Before reading code examples, determine which language the user is working in:
1. Look at project files to infer the language:
*.py,requirements.txt,pyproject.toml,setup.py,Pipfile→ Python — read frompython/*.ts,*.tsx,package.json,tsconfig.json→ TypeScript — read fromtypescript/*.js,*.jsx(no.tsfiles present) → TypeScript — JS uses the same SDK, read fromtypescript/*.java,pom.xml,build.gradle→ Java — read fromjava/*.kt,*.kts,build.gradle.kts→ Java — Kotlin uses the Java SDK, read fromjava/*.scala,build.sbt→ Java — Scala uses the Java SDK, read fromjava/*.go,go.mod→ Go — read fromgo/*.rb,Gemfile→ Ruby — read fromruby/*.cs,*.csproj→ C# — read fromcsharp/*.php,composer.json→ PHP — read fromphp/
2. If multiple languages detected (e.g., both Python and TypeScript files):
- Check which language the user's current file or question relates to
- If still ambiguous, ask: "I detected both Python and TypeScript files. Which language are you using for the Claude API integration?"
3. If language can't be inferred (empty project, no source files, or unsupported language):
- Use AskUserQuestion with options: Python, TypeScript, Java, Go, Ruby, cURL/raw HTTP, C#, PHP
- If AskUserQuestion is unavailable, default to Python examples and note: "Showing Python examples. Let me know if you need a different language."
4. If unsupported language detected (Rust, Swift, C++, Elixir, etc.):
- Suggest cURL/raw HTTP examples from
curl/and note that community SDKs may exist - Offer to show Python or TypeScript examples as reference implementations
5. If user needs cURL/raw HTTP examples, read from curl/.
Language-Specific Feature Support
| Language | Tool Runner | Agent SDK | Notes |
|---|---|---|---|
| Python | Yes (beta) | Yes | Full support — @beta_tool decorator |
| TypeScript | Yes (beta) | Yes | Full support — betaZodTool + Zod |
| Java | Yes (beta) | No | Beta tool use with annotated classes |
| Go | Yes (beta) | No | BetaToolRunner in toolrunner pkg |
| Ruby | Yes (beta) | No | BaseTool + tool_runner in beta |
| cURL | N/A | N/A | Raw HTTP, no SDK features |
| C# | No | No | Official SDK |
| PHP | No | No | Official SDK |
---
Which Surface Should I Use?
Start simple. Default to the simplest tier that meets your needs. Single API calls and workflows handle most use cases — only reach for agents when the task genuinely requires open-ended, model-driven exploration.
| Use Case | Tier | Recommended Surface | Why |
|---|---|---|---|
| Classification, summarization, extraction, Q&A | Single LLM call | Claude API | One request, one response |
| Batch processing or embeddings | Single LLM call | Claude API | Specialized endpoints |
| Multi-step pipelines with code-controlled logic | Workflow | Claude API + tool use | You orchestrate the loop |
| Custom agent with your own tools | Agent | Claude API + tool use | Maximum flexibility |
| AI agent with file/web/terminal access | Agent | Agent SDK | Built-in tools, safety, and MCP support |
| Agentic coding assistant | Agent | Agent SDK | Designed for this use case |
| Want built-in permissions and guardrails | Agent | Agent SDK | Safety features included |
Note: The Agent SDK is for when you want built-in file/web/terminal tools, permissions, and MCP out of the box. If you want to build an agent with your own tools, Claude API is the right choice — use the tool runner for automatic loop handling, or the manual loop for fine-grained control (approval gates, custom logging, conditional execution).
Decision Tree
What does your application need?
1. Single LLM call (classification, summarization, extraction, Q&A)
└── Claude API — one request, one response
2. Does Claude need to read/write files, browse the web, or run shell commands
as part of its work? (Not: does your app read a file and hand it to Claude —
does Claude itself need to discover and access files/web/shell?)
└── Yes → Agent SDK — built-in tools, don't reimplement them
Examples: "scan a codebase for bugs", "summarize every file in a directory",
"find bugs using subagents", "research a topic via web search"
3. Workflow (multi-step, code-orchestrated, with your own tools)
└── Claude API with tool use — you control the loop
4. Open-ended agent (model decides its own trajectory, your own tools)
└── Claude API agentic loop (maximum flexibility)Should I Build an Agent?
Before choosing the agent tier, check all four criteria:
- Complexity — Is the task multi-step and hard to fully specify in advance? (e.g., "turn this design doc into a PR" vs. "extract the title from this PDF")
- Value — Does the outcome justify higher cost and latency?
- Viability — Is Claude capable at this task type?
- Cost of error — Can errors be caught and recovered from? (tests, review, rollback)
If the answer is "no" to any of these, stay at a simpler tier (single call or workflow).
---
Architecture
Everything goes through POST /v1/messages. Tools and output constraints are features of this single endpoint — not separate APIs.
User-defined tools — You define tools (via decorators, Zod schemas, or raw JSON), and the SDK's tool runner handles calling the API, executing your functions, and looping until Claude is done. For full control, you can write the loop manually.
Server-side tools — Anthropic-hosted tools that run on Anthropic's infrastructure. Code execution is fully server-side (declare it in tools, Claude runs code automatically). Computer use can be server-hosted or self-hosted.
Structured outputs — Constrains the Messages API response format (output_config.format) and/or tool parameter validation (strict: true). The recommended approach is client.messages.parse() which validates responses against your schema automatically. Note: the old output_format parameter is deprecated; use output_config: {format: {...}} on messages.create().
Supporting endpoints — Batches (POST /v1/messages/batches), Files (POST /v1/files), Token Counting, and Models (GET /v1/models, GET /v1/models/{id} — live capability/context-window discovery) feed into or support Messages API requests.
---
Current Models (cached: 2026-02-17)
| Model | Model ID | Context | Input $/1M | Output $/1M |
|---|---|---|---|---|
| Claude Opus 4.6 | claude-opus-4-6 | 200K (1M beta) | $5.00 | $25.00 |
| Claude Sonnet 4.6 | claude-sonnet-4-6 | 200K (1M beta) | $3.00 | $15.00 |
| Claude Haiku 4.5 | claude-haiku-4-5 | 200K | $1.00 | $5.00 |
ALWAYS use `claude-opus-4-6` unless the user explicitly names a different model. This is non-negotiable. Do not use claude-sonnet-4-6, claude-sonnet-4-5, or any other model unless the user literally says "use sonnet" or "use haiku". Never downgrade for cost — that's the user's decision, not yours.
CRITICAL: Use only the exact model ID strings from the table above — they are complete as-is. Do not append date suffixes. For example, use claude-sonnet-4-5, never claude-sonnet-4-5-20250514 or any other date-suffixed variant you might recall from training data. If the user requests an older model not in the table (e.g., "opus 4.5", "sonnet 3.7"), read shared/models.md for the exact ID — do not construct one yourself.
A note: if any of the model strings above look unfamiliar to you, that's to be expected — that just means they were released after your training data cutoff. Rest assured they are real models; we wouldn't mess with you like that.
Live capability lookup: The table above is cached. When the user asks "what's the context window for X", "does X support vision/thinking/effort", or "which models support Y", query the Models API (client.models.retrieve(id) / client.models.list()) — see shared/models.md for the field reference and capability-filter examples.
---
Thinking & Effort (Quick Reference)
Opus 4.6 — Adaptive thinking (recommended): Use thinking: {type: "adaptive"}. Claude dynamically decides when and how much to think. No budget_tokens needed — budget_tokens is deprecated on Opus 4.6 and Sonnet 4.6 and must not be used. Adaptive thinking also automatically enables interleaved thinking (no beta header needed). When the user asks for "extended thinking", a "thinking budget", or `budget_tokens`: always use Opus 4.6 with `thinking: {type: "adaptive"}`. The concept of a fixed token budget for thinking is deprecated — adaptive thinking replaces it. Do NOT use `budget_tokens` and do NOT switch to an older model.
Effort parameter (GA, no beta header): Controls thinking depth and overall token spend via output_config: {effort: "low"|"medium"|"high"|"max"} (inside output_config, not top-level). Default is high (equivalent to omitting it). max is Opus 4.6 only. Works on Opus 4.5, Opus 4.6, and Sonnet 4.6. Will error on Sonnet 4.5 / Haiku 4.5. Combine with adaptive thinking for the best cost-quality tradeoffs. Use low for subagents or simple tasks; max for the deepest reasoning.
Sonnet 4.6: Supports adaptive thinking (thinking: {type: "adaptive"}). budget_tokens is deprecated on Sonnet 4.6 — use adaptive thinking instead.
Older models (only if explicitly requested): If the user specifically asks for Sonnet 4.5 or another older model, use thinking: {type: "enabled", budget_tokens: N}. budget_tokens must be less than max_tokens (minimum 1024). Never choose an older model just because the user mentions budget_tokens — use Opus 4.6 with adaptive thinking instead.
---
Compaction (Quick Reference)
Beta, Opus 4.6 and Sonnet 4.6. For long-running conversations that may exceed the 200K context window, enable server-side compaction. The API automatically summarizes earlier context when it approaches the trigger threshold (default: 150K tokens). Requires beta header compact-2026-01-12.
Critical: Append response.content (not just the text) back to your messages on every turn. Compaction blocks in the response must be preserved — the API uses them to replace the compacted history on the next request. Extracting only the text string and appending that will silently lose the compaction state.
See {lang}/claude-api/README.md (Compaction section) for code examples. Full docs via WebFetch in shared/live-sources.md.
---
Reading Guide
After detecting the language, read the relevant files based on what the user needs:
Quick Task Reference
Single text classification/summarization/extraction/Q&A: → Read only {lang}/claude-api/README.md
Chat UI or real-time response display: → Read {lang}/claude-api/README.md + {lang}/claude-api/streaming.md
Long-running conversations (may exceed context window): → Read {lang}/claude-api/README.md — see Compaction section
Function calling / tool use / agents: → Read {lang}/claude-api/README.md + shared/tool-use-concepts.md + {lang}/claude-api/tool-use.md
Batch processing (non-latency-sensitive): → Read {lang}/claude-api/README.md + {lang}/claude-api/batches.md
File uploads across multiple requests: → Read {lang}/claude-api/README.md + {lang}/claude-api/files-api.md
Agent with built-in tools (file/web/terminal): → Read {lang}/agent-sdk/README.md + {lang}/agent-sdk/patterns.md
Claude API (Full File Reference)
Read the language-specific Claude API folder ({language}/claude-api/):
1. `{language}/claude-api/README.md` — Read this first. Installation, quick start, common patterns, error handling. 2. `shared/tool-use-concepts.md` — Read when the user needs function calling, code execution, memory, or structured outputs. Covers conceptual foundations. 3. `{language}/claude-api/tool-use.md` — Read for language-specific tool use code examples (tool runner, manual loop, code execution, memory, structured outputs). 4. `{language}/claude-api/streaming.md` — Read when building chat UIs or interfaces that display responses incrementally. 5. `{language}/claude-api/batches.md` — Read when processing many requests offline (not latency-sensitive). Runs asynchronously at 50% cost. 6. `{language}/claude-api/files-api.md` — Read when sending the same file across multiple requests without re-uploading. 7. `shared/error-codes.md` — Read when debugging HTTP errors or implementing error handling. 8. `shared/live-sources.md` — WebFetch URLs for fetching the latest official documentation.
Note: For Java, Go, Ruby, C#, PHP, and cURL — these have a single file each covering all basics. Read that file plusshared/tool-use-concepts.mdandshared/error-codes.mdas needed.
Agent SDK
Read the language-specific Agent SDK folder ({language}/agent-sdk/). Agent SDK is available for Python and TypeScript only.
1. `{language}/agent-sdk/README.md` — Installation, quick start, built-in tools, permissions, MCP, hooks. 2. `{language}/agent-sdk/patterns.md` — Custom tools, hooks, subagents, MCP integration, session resumption. 3. `shared/live-sources.md` — WebFetch URLs for current Agent SDK docs.
---
When to Use WebFetch
Use WebFetch to get the latest documentation when:
- User asks for "latest" or "current" information
- Cached data seems incorrect
- User asks about features not covered here
Live documentation URLs are in shared/live-sources.md.
Common Pitfalls
- Don't truncate inputs when passing files or content to the API. If the content is too long to fit in the context window, notify the user and discuss options (chunking, summarization, etc.) rather than silently truncating.
- Opus 4.6 / Sonnet 4.6 thinking: Use
thinking: {type: "adaptive"}— do NOT usebudget_tokens(deprecated on both Opus 4.6 and Sonnet 4.6). For older models,budget_tokensmust be less thanmax_tokens(minimum 1024). This will throw an error if you get it wrong. - Opus 4.6 prefill removed: Assistant message prefills (last-assistant-turn prefills) return a 400 error on Opus 4.6. Use structured outputs (
output_config.format) or system prompt instructions to control response format instead. - `max_tokens` defaults: Don't lowball
max_tokens— hitting the cap truncates output mid-thought and requires a retry. For non-streaming requests, default to~16000(keeps responses under SDK HTTP timeouts). For streaming requests, default to~64000(timeouts aren't a concern, so give the model room). Only go lower when you have a hard reason: classification (~256), cost caps, or deliberately short outputs. - 128K output tokens: Opus 4.6 supports up to 128K
max_tokens, but the SDKs require streaming for values that large to avoid HTTP timeouts. Use.stream()with.get_final_message()/.finalMessage(). - Tool call JSON parsing (Opus 4.6): Opus 4.6 may produce different JSON string escaping in tool call
inputfields (e.g., Unicode or forward-slash escaping). Always parse tool inputs withjson.loads()/JSON.parse()— never do raw string matching on the serialized input. - Structured outputs (all models): Use
output_config: {format: {...}}instead of the deprecatedoutput_formatparameter onmessages.create(). This is a general API change, not 4.6-specific. - Don't reimplement SDK functionality: The SDK provides high-level helpers — use them instead of building from scratch. Specifically: use
stream.finalMessage()instead of wrapping.on()events innew Promise(); use typed exception classes (Anthropic.RateLimitError, etc.) instead of string-matching error messages; use SDK types (Anthropic.MessageParam,Anthropic.Tool,Anthropic.Message, etc.) instead of redefining equivalent interfaces. - Don't define custom types for SDK data structures: The SDK exports types for all API objects. Use
Anthropic.MessageParamfor messages,Anthropic.Toolfor tool definitions,Anthropic.ToolUseBlock/Anthropic.ToolResultBlockParamfor tool results,Anthropic.Messagefor responses. Defining your owninterface ChatMessage { role: string; content: unknown }duplicates what the SDK already provides and loses type safety. - Report and document output: For tasks that produce reports, documents, or visualizations, the code execution sandbox has
python-docx,python-pptx,matplotlib,pillow, andpypdfpre-installed. Claude can generate formatted files (DOCX, PDF, charts) and return them via the Files API — consider this for "report" or "document" type requests instead of plain stdout text.
Claude API — C#
Note: The C# SDK is the official Anthropic SDK for C#. Tool use is supported via the Messages API. A class-annotation-based tool runner is not available; use raw tool definitions with JSON schema. The SDK also supports Microsoft.Extensions.AI IChatClient integration with function invocation.
Installation
dotnet add package AnthropicClient Initialization
using Anthropic;
// Default (uses ANTHROPIC_API_KEY env var)
AnthropicClient client = new();
// Explicit API key (use environment variables — never hardcode keys)
AnthropicClient client = new() {
ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")
};---
Basic Message Request
using Anthropic.Models.Messages;
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_6,
MaxTokens = 16000,
Messages = [new() { Role = Role.User, Content = "What is the capital of France?" }]
};
var response = await client.Messages.Create(parameters);
// ContentBlock is a union wrapper. .Value unwraps to the variant object,
// then OfType<T> filters to the type you want. Or use the TryPick* idiom
// shown in the Thinking section below.
foreach (var text in response.Content.Select(b => b.Value).OfType<TextBlock>())
{
Console.WriteLine(text.Text);
}---
Streaming
using Anthropic.Models.Messages;
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_6,
MaxTokens = 64000,
Messages = [new() { Role = Role.User, Content = "Write a haiku" }]
};
await foreach (RawMessageStreamEvent streamEvent in client.Messages.CreateStreaming(parameters))
{
if (streamEvent.TryPickContentBlockDelta(out var delta) &&
delta.Delta.TryPickText(out var text))
{
Console.Write(text.Text);
}
}`RawMessageStreamEvent` TryPick methods (naming drops the Message/Raw prefix): TryPickStart, TryPickDelta, TryPickStop, TryPickContentBlockStart, TryPickContentBlockDelta, TryPickContentBlockStop. There is no TryPickMessageStop — use TryPickStop.
---
Thinking
Adaptive thinking is the recommended mode for Claude 4.6+ models. Claude decides dynamically when and how much to think.
using Anthropic.Models.Messages;
var response = await client.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus4_6,
MaxTokens = 16000,
// ThinkingConfigParam? implicitly converts from the concrete variant classes —
// no wrapper needed.
Thinking = new ThinkingConfigAdaptive(),
Messages =
[
new() { Role = Role.User, Content = "Solve: 27 * 453" },
],
});
// ThinkingBlock(s) precede TextBlock in Content. TryPick* narrows the union.
foreach (var block in response.Content)
{
if (block.TryPickThinking(out ThinkingBlock? t))
{
Console.WriteLine($"[thinking] {t.Thinking}");
}
else if (block.TryPickText(out TextBlock? text))
{
Console.WriteLine(text.Text);
}
}Deprecated: new ThinkingConfigEnabled { BudgetTokens = N } (fixed-budget extended thinking) still works on Claude 4.6 but is deprecated. Use adaptive thinking above.Alternative to TryPick*: .Select(b => b.Value).OfType<ThinkingBlock>() (same LINQ pattern as the Basic Message example).
---
Tool Use
Defining a tool
Tool (NOT ToolParam) with an InputSchema record. InputSchema.Type is auto-set to "object" by the constructor — don't set it. ToolUnion has an implicit conversion from Tool, triggered by the collection expression [...].
using System.Text.Json;
using Anthropic.Models.Messages;
var parameters = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 16000,
Tools = [
new Tool {
Name = "get_weather",
Description = "Get the current weather in a given location",
InputSchema = new() {
Properties = new Dictionary<string, JsonElement> {
["location"] = JsonSerializer.SerializeToElement(
new { type = "string", description = "City name" }),
},
Required = ["location"],
},
},
],
Messages = [new() { Role = Role.User, Content = "Weather in Paris?" }],
};Derived from anthropic-sdk-csharp/src/Anthropic/Models/Messages/Tool.cs and ToolUnion.cs:799 (implicit conversion).
See shared tool use concepts for the loop pattern.
Converting response content to the follow-up assistant message
When echoing Claude's response back in the assistant turn, there is no `.ToParam()` helper — manually reconstruct each ContentBlock variant as its *Param counterpart. Do NOT use new ContentBlockParam(block.Json): it compiles and serializes, but .Value stays null so TryPick*/Validate() fail (degraded JSON pass-through, not the typed path).
using Anthropic.Models.Messages;
Message response = await client.Messages.Create(parameters);
// No .ToParam() — reconstruct per variant. Implicit conversions from each
// *Param type to ContentBlockParam mean no explicit wrapper.
List<ContentBlockParam> assistantContent = [];
List<ContentBlockParam> toolResults = [];
foreach (ContentBlock block in response.Content)
{
if (block.TryPickText(out TextBlock? text))
{
assistantContent.Add(new TextBlockParam { Text = text.Text });
}
else if (block.TryPickThinking(out ThinkingBlock? thinking))
{
// Signature MUST be preserved — the API rejects tampering
assistantContent.Add(new ThinkingBlockParam
{
Thinking = thinking.Thinking,
Signature = thinking.Signature,
});
}
else if (block.TryPickRedactedThinking(out RedactedThinkingBlock? redacted))
{
assistantContent.Add(new RedactedThinkingBlockParam { Data = redacted.Data });
}
else if (block.TryPickToolUse(out ToolUseBlock? toolUse))
{
// ToolUseBlock has required Caller; ToolUseBlockParam.Caller is optional — don't copy it
assistantContent.Add(new ToolUseBlockParam
{
ID = toolUse.ID,
Name = toolUse.Name,
Input = toolUse.Input,
});
// Execute the tool; collect ONE result per tool_use block — the API
// rejects the follow-up if any tool_use ID lacks a matching tool_result.
string result = ExecuteYourTool(toolUse.Name, toolUse.Input);
toolResults.Add(new ToolResultBlockParam
{
ToolUseID = toolUse.ID,
Content = result,
});
}
}
// Follow-up: prior messages + assistant echo + user tool_result(s)
List<MessageParam> followUpMessages =
[
.. parameters.Messages,
new() { Role = Role.Assistant, Content = assistantContent },
new() { Role = Role.User, Content = toolResults },
];ToolResultBlockParam has no tuple constructor — use the object initializer. Content is a string-or-list union; a plain string implicitly converts.
---
Context Editing / Compaction (Beta)
Beta-namespace prefix is inconsistent (source-verified against src/Anthropic/Models/Beta/Messages/*.cs @ 12.8.0). No prefix: MessageCreateParams, MessageCountTokensParams, Role. Everything else has the `Beta` prefix: BetaMessageParam, BetaMessage, BetaContentBlock, BetaToolUseBlock, all block param types. The unprefixed Role WILL collide with Anthropic.Models.Messages.Role if you import both namespaces (CS0104). Safest: import only Beta; if mixing, alias the beta Role:
using Anthropic.Models.Beta.Messages;
using NonBeta = Anthropic.Models.Messages; // only if you also need non-beta types
// Now: MessageCreateParams, BetaMessageParam, Role (beta's), NonBeta.Role (if needed)BetaMessage.Content is IReadOnlyList<BetaContentBlock> — a 15-variant discriminated union. Narrow with TryPick*. Response `BetaContentBlock` is NOT assignable to param `BetaContentBlockParam` — there's no .ToParam() in C#. Round-trip by converting each block:
using Anthropic.Models.Beta.Messages;
var betaParams = new MessageCreateParams // no Beta prefix — one of only 2 unprefixed
{
Model = Model.ClaudeOpus4_6,
MaxTokens = 16000,
Betas = ["compact-2026-01-12"],
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit()],
},
Messages = messages,
};
BetaMessage resp = await client.Beta.Messages.Create(betaParams);
foreach (BetaContentBlock block in resp.Content)
{
if (block.TryPickCompaction(out BetaCompactionBlock? compaction))
{
// Content is nullable — compaction can fail server-side
Console.WriteLine($"compaction summary: {compaction.Content}");
}
}
// Context-edit metadata lives on a separate nullable field
if (resp.ContextManagement is { } ctx)
{
foreach (var edit in ctx.AppliedEdits)
Console.WriteLine($"cleared {edit.ClearedInputTokens} tokens");
}
// ROUND-TRIP: BetaMessageParam.Content is BetaMessageParamContent (a string|list
// union). It implicit-converts from List<BetaContentBlockParam>, NOT from the
// response's IReadOnlyList<BetaContentBlock>. Convert each block:
List<BetaContentBlockParam> paramBlocks = [];
foreach (var b in resp.Content)
{
if (b.TryPickText(out var t)) paramBlocks.Add(new BetaTextBlockParam { Text = t.Text });
else if (b.TryPickCompaction(out var c)) paramBlocks.Add(new BetaCompactionBlockParam { Content = c.Content });
// ... other variants as needed
}
messages.Add(new BetaMessageParam { Role = Role.Assistant, Content = paramBlocks });All 15 BetaContentBlock.TryPick* variants: Text, Thinking, RedactedThinking, ToolUse, ServerToolUse, WebSearchToolResult, WebFetchToolResult, CodeExecutionToolResult, BashCodeExecutionToolResult, TextEditorCodeExecutionToolResult, ToolSearchToolResult, McpToolUse, McpToolResult, ContainerUpload, Compaction.
`BetaToolUseBlock.Input` is `IReadOnlyDictionary<string, JsonElement>` — index by key then call the JsonElement extractor:
if (block.TryPickToolUse(out BetaToolUseBlock? tu))
{
int a = tu.Input["a"].GetInt32();
string s = tu.Input["name"].GetString()!;
}---
Effort Parameter
Effort is nested under OutputConfig, NOT a top-level property. ApiEnum<string, Effort> has an implicit conversion from the enum, so assign Effort.High directly.
OutputConfig = new OutputConfig { Effort = Effort.High },Values: Effort.Low, Effort.Medium, Effort.High, Effort.Max. Combine with Thinking = new ThinkingConfigAdaptive() for cost-quality control.
---
Prompt Caching
System takes MessageCreateParamsSystem? — a union of string or List<TextBlockParam>. There is no SystemTextBlockParam; use plain TextBlockParam. The implicit conversion needs the concrete List<TextBlockParam> type (array literals won't convert).
System = new List<TextBlockParam> {
new() {
Text = longSystemPrompt,
CacheControl = new CacheControlEphemeral(), // auto-sets Type = "ephemeral"
},
},Optional Ttl on CacheControlEphemeral: new() { Ttl = Ttl.Ttl1h } or Ttl.Ttl5m. CacheControl also exists on Tool.CacheControl and top-level MessageCreateParams.CacheControl.
---
Token Counting
MessageTokensCount result = await client.Messages.CountTokens(new MessageCountTokensParams {
Model = Model.ClaudeOpus4_6,
Messages = [new() { Role = Role.User, Content = "Hello" }],
});
long tokens = result.InputTokens;MessageCountTokensParams.Tools uses a different union type (MessageCountTokensTool) than MessageCreateParams.Tools (ToolUnion) — if you're passing tools, the compiler will tell you when it matters.
---
Structured Output
OutputConfig = new OutputConfig {
Format = new JsonOutputFormat {
Schema = new Dictionary<string, JsonElement> {
["type"] = JsonSerializer.SerializeToElement("object"),
["properties"] = JsonSerializer.SerializeToElement(
new { name = new { type = "string" } }),
["required"] = JsonSerializer.SerializeToElement(new[] { "name" }),
},
},
},JsonOutputFormat.Type is auto-set to "json_schema" by the constructor. Schema is required.
---
PDF / Document Input
DocumentBlockParam takes a DocumentBlockParamSource union: Base64PdfSource / UrlPdfSource / PlainTextSource / ContentBlockSource. Base64PdfSource auto-sets MediaType = "application/pdf" and Type = "base64".
new MessageParam {
Role = Role.User,
Content = new List<ContentBlockParam> {
new DocumentBlockParam { Source = new Base64PdfSource { Data = base64String } },
new TextBlockParam { Text = "Summarize this PDF" },
},
}---
Server-Side Tools
Web search, bash, text editor, and code execution are built-in server tools. Type names are version-suffixed; constructors auto-set name/type. All implicit-convert to ToolUnion.
Tools = [
new WebSearchTool20260209(),
new ToolBash20250124(),
new ToolTextEditor20250728(),
new CodeExecutionTool20260120(),
],Also available: WebFetchTool20260209, MemoryTool20250818. WebSearchTool20260209 optionals: AllowedDomains, BlockedDomains, MaxUses, UserLocation.
---
Files API (Beta)
Files live under client.Beta.Files (namespace Anthropic.Models.Beta.Files). BinaryContent implicit-converts from Stream and byte[].
using Anthropic.Models.Beta.Files;
using Anthropic.Models.Beta.Messages;
FileMetadata meta = await client.Beta.Files.Upload(
new FileUploadParams { File = File.OpenRead("doc.pdf") });
// Referencing the uploaded file requires Beta message types:
new BetaRequestDocumentBlock {
Source = new BetaFileDocumentSource { FileID = meta.ID },
}The non-beta DocumentBlockParamSource union has no file-ID variant — file references need client.Beta.Messages.Create().
Claude API — cURL / Raw HTTP
Use these examples when the user needs raw HTTP requests or is working in a language without an official SDK.
Setup
export ANTHROPIC_API_KEY="your-api-key"---
Basic Message Request
curl https://api.anthropic.com/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-6",
"max_tokens": 16000,
"messages": [
{"role": "user", "content": "What is the capital of France?"}
]
}'Parsing the response
Use jq to extract fields from the JSON response. Do not use grep/sed — JSON strings can contain any character and regex parsing will break on quotes, escapes, or multi-line content.
# Capture the response, then extract fields
response=$(curl -s https://api.anthropic.com/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"claude-opus-4-6","max_tokens":16000,"messages":[{"role":"user","content":"Hello"}]}')
# Print the first text block (-r strips the JSON quotes)
echo "$response" | jq -r '.content[0].text'
# Read usage fields
input_tokens=$(echo "$response" | jq -r '.usage.input_tokens')
output_tokens=$(echo "$response" | jq -r '.usage.output_tokens')
# Read stop reason (for tool-use loops)
stop_reason=$(echo "$response" | jq -r '.stop_reason')
# Extract all text blocks (content is an array; filter to type=="text")
echo "$response" | jq -r '.content[] | select(.type == "text") | .text'---
Streaming (SSE)
curl https://api.anthropic.com/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-6",
"max_tokens": 64000,
"stream": true,
"messages": [{"role": "user", "content": "Write a haiku"}]
}'The response is a stream of Server-Sent Events:
event: message_start
data: {"type":"message_start","message":{"id":"msg_...","type":"message",...}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":12}}
event: message_stop
data: {"type":"message_stop"}---
Tool Use
curl https://api.anthropic.com/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-6",
"max_tokens": 16000,
"tools": [{
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"}
},
"required": ["location"]
}
}],
"messages": [{"role": "user", "content": "What is the weather in Paris?"}]
}'When Claude responds with a tool_use block, send the result back:
curl https://api.anthropic.com/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-6",
"max_tokens": 16000,
"tools": [{
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"}
},
"required": ["location"]
}
}],
"messages": [
{"role": "user", "content": "What is the weather in Paris?"},
{"role": "assistant", "content": [
{"type": "text", "text": "Let me check the weather."},
{"type": "tool_use", "id": "toolu_abc123", "name": "get_weather", "input": {"location": "Paris"}}
]},
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "toolu_abc123", "content": "72°F and sunny"}
]}
]
}'---
Extended Thinking
Opus 4.6 and Sonnet 4.6: Use adaptive thinking. budget_tokens is deprecated on both Opus 4.6 and Sonnet 4.6.Older models: Use"type": "enabled"with"budget_tokens": N(must be <max_tokens, min 1024).
# Opus 4.6: adaptive thinking (recommended)
curl https://api.anthropic.com/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-6",
"max_tokens": 16000,
"thinking": {
"type": "adaptive"
},
"output_config": {
"effort": "high"
},
"messages": [{"role": "user", "content": "Solve this step by step..."}]
}'---
Required Headers
| Header | Value | Description |
|---|---|---|
Content-Type | application/json | Required |
x-api-key | Your API key | Authentication |
anthropic-version | 2023-06-01 | API version |
anthropic-beta | Beta feature IDs | Required for beta features |
Claude API — Go
Note: The Go SDK supports the Claude API and beta tool use with BetaToolRunner. Agent SDK is not yet available for Go.Installation
go get github.com/anthropics/anthropic-sdk-goClient Initialization
import (
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
// Default (uses ANTHROPIC_API_KEY env var)
client := anthropic.NewClient()
// Explicit API key
client := anthropic.NewClient(
option.WithAPIKey("your-api-key"),
)---
Basic Message Request
response, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_6,
MaxTokens: 16000,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the capital of France?")),
},
})
if err != nil {
log.Fatal(err)
}
for _, block := range response.Content {
switch variant := block.AsAny().(type) {
case anthropic.TextBlock:
fmt.Println(variant.Text)
}
}---
Streaming
stream := client.Messages.NewStreaming(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_6,
MaxTokens: 64000,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Write a haiku")),
},
})
for stream.Next() {
event := stream.Current()
switch eventVariant := event.AsAny().(type) {
case anthropic.ContentBlockDeltaEvent:
switch deltaVariant := eventVariant.Delta.AsAny().(type) {
case anthropic.TextDelta:
fmt.Print(deltaVariant.Text)
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}Accumulating the final message (there is no GetFinalMessage() on the stream):
stream := client.Messages.NewStreaming(ctx, params)
message := anthropic.Message{}
for stream.Next() {
message.Accumulate(stream.Current())
}
if err := stream.Err(); err != nil { log.Fatal(err) }
// message.Content now has the complete response---
Tool Use
Tool Runner (Beta — Recommended)
Beta: The Go SDK provides BetaToolRunner for automatic tool use loops via the toolrunner package.
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/toolrunner"
)
// Define tool input with jsonschema tags for automatic schema generation
type GetWeatherInput struct {
City string `json:"city" jsonschema:"required,description=The city name"`
}
// Create a tool with automatic schema generation from struct tags
weatherTool, err := toolrunner.NewBetaToolFromJSONSchema(
"get_weather",
"Get current weather for a city",
func(ctx context.Context, input GetWeatherInput) (anthropic.BetaToolResultBlockParamContentUnion, error) {
return anthropic.BetaToolResultBlockParamContentUnion{
OfText: &anthropic.BetaTextBlockParam{
Text: fmt.Sprintf("The weather in %s is sunny, 72°F", input.City),
},
}, nil
},
)
if err != nil {
log.Fatal(err)
}
// Create a tool runner that handles the conversation loop automatically
runner := client.Beta.Messages.NewToolRunner(
[]anthropic.BetaTool{weatherTool},
anthropic.BetaToolRunnerParams{
BetaMessageNewParams: anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_6,
MaxTokens: 16000,
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("What's the weather in Paris?")),
},
},
MaxIterations: 5,
},
)
// Run until Claude produces a final response
message, err := runner.RunToCompletion(context.Background())
if err != nil {
log.Fatal(err)
}
// RunToCompletion returns *BetaMessage; content is []BetaContentBlockUnion.
// Narrow via AsAny() switch — note the Beta-namespace types (BetaTextBlock,
// not TextBlock):
for _, block := range message.Content {
switch block := block.AsAny().(type) {
case anthropic.BetaTextBlock:
fmt.Println(block.Text)
}
}Key features of the Go tool runner:
- Automatic schema generation from Go structs via
jsonschematags RunToCompletion()for simple one-shot usageAll()iterator for processing each message in the conversationNextMessage()for step-by-step iteration- Streaming variant via
NewToolRunnerStreaming()withAllStreaming()
Manual Loop
For fine-grained control over the agentic loop, define tools with ToolParam, check StopReason, execute tools yourself, and feed tool_result blocks back. This is the pattern when you need to intercept, validate, or log tool calls.
Derived from anthropic-sdk-go/examples/tools/main.go.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
// 1. Define tools. ToolParam.InputSchema uses a map, no struct tags needed.
addTool := anthropic.ToolParam{
Name: "add",
Description: anthropic.String("Add two integers"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"a": map[string]any{"type": "integer"},
"b": map[string]any{"type": "integer"},
},
},
}
// ToolParam must be wrapped in ToolUnionParam for the Tools slice
tools := []anthropic.ToolUnionParam{{OfTool: &addTool}}
messages := []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is 2 + 3?")),
}
for {
resp, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 16000,
Messages: messages,
Tools: tools,
})
if err != nil {
log.Fatal(err)
}
// 2. Append the assistant response to history BEFORE processing tool calls.
// resp.ToParam() converts Message → MessageParam in one call.
messages = append(messages, resp.ToParam())
// 3. Walk content blocks. ContentBlockUnion is a flattened struct;
// use block.AsAny().(type) to switch on the actual variant.
toolResults := []anthropic.ContentBlockParamUnion{}
for _, block := range resp.Content {
switch variant := block.AsAny().(type) {
case anthropic.TextBlock:
fmt.Println(variant.Text)
case anthropic.ToolUseBlock:
// 4. Parse the tool input. Use variant.JSON.Input.Raw() to get the
// raw JSON — block.Input is json.RawMessage, not the parsed value.
var in struct {
A int `json:"a"`
B int `json:"b"`
}
if err := json.Unmarshal([]byte(variant.JSON.Input.Raw()), &in); err != nil {
log.Fatal(err)
}
result := fmt.Sprintf("%d", in.A+in.B)
// 5. NewToolResultBlock(toolUseID, content, isError) builds the
// ContentBlockParamUnion for you. block.ID is the tool_use_id.
toolResults = append(toolResults,
anthropic.NewToolResultBlock(block.ID, result, false))
}
}
// 6. Exit when Claude stops asking for tools
if resp.StopReason != anthropic.StopReasonToolUse {
break
}
// 7. Tool results go in a user message (variadic: all results in one turn)
messages = append(messages, anthropic.NewUserMessage(toolResults...))
}
}Key API surface:
| Symbol | Purpose |
|---|---|
resp.ToParam() | Convert Message response → MessageParam for history |
block.AsAny().(type) | Type-switch on ContentBlockUnion variants |
variant.JSON.Input.Raw() | Raw JSON string of tool input (for json.Unmarshal) |
anthropic.NewToolResultBlock(id, content, isError) | Build tool_result block |
anthropic.NewUserMessage(blocks...) | Wrap tool results as a user turn |
anthropic.StopReasonToolUse | StopReason constant to check loop termination |
anthropic.ToolUnionParam{OfTool: &t} | Wrap ToolParam in the union for Tools: |
---
Thinking
Enable Claude's internal reasoning by setting Thinking in MessageNewParams. The response will contain ThinkingBlock content before the final TextBlock.
Adaptive thinking is the recommended mode for Claude 4.6+ models. Claude decides dynamically when and how much to think. Combine with the effort parameter for cost-quality control.
Derived from anthropic-sdk-go/message.go (ThinkingConfigParamUnion, NewThinkingConfigAdaptiveParam).
// There is no ThinkingConfigParamOfAdaptive helper — construct the union
// struct-literal directly and take the address of the variant.
adaptive := anthropic.NewThinkingConfigAdaptiveParam()
params := anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamUnion{OfAdaptive: &adaptive},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("How many r's in strawberry?")),
},
}
resp, err := client.Messages.New(context.Background(), params)
if err != nil {
log.Fatal(err)
}
// ThinkingBlock(s) precede TextBlock in content
for _, block := range resp.Content {
switch b := block.AsAny().(type) {
case anthropic.ThinkingBlock:
fmt.Println("[thinking]", b.Thinking)
case anthropic.TextBlock:
fmt.Println(b.Text)
}
}Deprecated: ThinkingConfigParamOfEnabled(budgetTokens) (fixed-budget extended thinking) still works on Claude 4.6 but is deprecated. Use adaptive thinking above.To disable: anthropic.ThinkingConfigParamUnion{OfDisabled: &anthropic.ThinkingConfigDisabledParam{}}.
---
Server-Side Tools
Version-suffixed struct names with Param suffix. Name/Type are constant.* types — zero value marshals correctly, so {} works. Wrap in ToolUnionParam with the matching Of* field.
Tools: []anthropic.ToolUnionParam{
{OfWebSearchTool20260209: &anthropic.WebSearchTool20260209Param{}},
{OfBashTool20250124: &anthropic.ToolBash20250124Param{}},
{OfTextEditor20250728: &anthropic.ToolTextEditor20250728Param{}},
{OfCodeExecutionTool20260120: &anthropic.CodeExecutionTool20260120Param{}},
},Also available: WebFetchTool20260209Param, MemoryTool20250818Param, ToolSearchToolBm25_20251119Param, ToolSearchToolRegex20251119Param.
---
PDF / Document Input
NewDocumentBlock generic helper accepts any source type. MediaType/Type are auto-set.
b64 := base64.StdEncoding.EncodeToString(pdfBytes)
msg := anthropic.NewUserMessage(
anthropic.NewDocumentBlock(anthropic.Base64PDFSourceParam{Data: b64}),
anthropic.NewTextBlock("Summarize this document"),
)Other sources: URLPDFSourceParam{URL: "https://..."}, PlainTextSourceParam{Data: "..."}.
---
Files API (Beta)
Under client.Beta.Files. Method is `Upload` (NOT New/Create), params struct is BetaFileUploadParams. The File field takes an io.Reader; use anthropic.File() to attach a filename + content-type for the multipart encoding.
f, _ := os.Open("./upload_me.txt")
defer f.Close()
meta, err := client.Beta.Files.Upload(ctx, anthropic.BetaFileUploadParams{
File: anthropic.File(f, "upload_me.txt", "text/plain"),
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaFilesAPI2025_04_14},
})
// meta.ID is the file_id to reference in subsequent message requestsOther Beta.Files methods: List, Delete, Download, GetMetadata.
---
Context Editing / Compaction (Beta)
Use Beta.Messages.New with ContextManagement on BetaMessageNewParams. There is no NewBetaAssistantMessage — use .ToParam() for the round-trip.
params := anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_6, // also supported: ModelClaudeSonnet4_6
MaxTokens: 16000,
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{}},
},
},
Messages: []anthropic.BetaMessageParam{ /* ... */ },
}
resp, err := client.Beta.Messages.New(ctx, params)
if err != nil {
log.Fatal(err)
}
// Round-trip: append response to history via .ToParam()
params.Messages = append(params.Messages, resp.ToParam())
// Read compaction blocks from the response
for _, block := range resp.Content {
if c, ok := block.AsAny().(anthropic.BetaCompactionBlock); ok {
fmt.Println("compaction summary:", c.Content)
}
}Other edit types: BetaClearToolUses20250919EditParam, BetaClearThinking20251015EditParam.
Claude API — Java
Note: The Java SDK supports the Claude API and beta tool use with annotated classes. Agent SDK is not yet available for Java.
Installation
Maven:
<dependency>
<groupId>com.anthropic</groupId>
<artifactId>anthropic-java</artifactId>
<version>2.16.1</version>
</dependency>Gradle:
implementation("com.anthropic:anthropic-java:2.16.1")Client Initialization
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
// Default (reads ANTHROPIC_API_KEY from environment)
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Explicit API key
AnthropicClient client = AnthropicOkHttpClient.builder()
.apiKey("your-api-key")
.build();---
Basic Message Request
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_6)
.maxTokens(16000L)
.addUserMessage("What is the capital of France?")
.build();
Message response = client.messages().create(params);
response.content().stream()
.flatMap(block -> block.text().stream())
.forEach(textBlock -> System.out.println(textBlock.text()));---
Streaming
import com.anthropic.core.http.StreamResponse;
import com.anthropic.models.messages.RawMessageStreamEvent;
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_6)
.maxTokens(64000L)
.addUserMessage("Write a haiku")
.build();
try (StreamResponse<RawMessageStreamEvent> streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream()
.flatMap(event -> event.contentBlockDelta().stream())
.flatMap(deltaEvent -> deltaEvent.delta().text().stream())
.forEach(textDelta -> System.out.print(textDelta.text()));
}---
Thinking
Adaptive thinking is the recommended mode for Claude 4.6+ models. Claude decides dynamically when and how much to think. The builder has a direct .thinking(ThinkingConfigAdaptive) overload — no manual union wrapping.
import com.anthropic.models.messages.ContentBlock;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.ThinkingConfigAdaptive;
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(16000L)
.thinking(ThinkingConfigAdaptive.builder().build())
.addUserMessage("Solve this step by step: 27 * 453")
.build();
for (ContentBlock block : client.messages().create(params).content()) {
block.thinking().ifPresent(t -> System.out.println("[thinking] " + t.thinking()));
block.text().ifPresent(t -> System.out.println(t.text()));
}Deprecated:ThinkingConfigEnabled.builder().budgetTokens(N)(and the.enabledThinking(N)shortcut) still works on Claude 4.6 but is deprecated. Use adaptive thinking above.
ContentBlock narrowing: .thinking() / .text() return Optional<T> — use .ifPresent(...) or .stream().flatMap(...). Alternative: isThinking() / asThinking() boolean+unwrap pairs (throws on wrong variant).
---
Tool Use (Beta)
The Java SDK supports beta tool use with annotated classes. Tool classes implement Supplier<String> for automatic execution via BetaToolRunner.
Tool Runner (automatic loop)
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.helpers.BetaToolRunner;
import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import java.util.function.Supplier;
@JsonClassDescription("Get the weather in a given location")
static class GetWeather implements Supplier<String> {
@JsonPropertyDescription("The city and state, e.g. San Francisco, CA")
public String location;
@Override
public String get() {
return "The weather in " + location + " is sunny and 72°F";
}
}
BetaToolRunner toolRunner = client.beta().messages().toolRunner(
MessageCreateParams.builder()
.model("claude-opus-4-6")
.maxTokens(16000L)
.putAdditionalHeader("anthropic-beta", "structured-outputs-2025-11-13")
.addTool(GetWeather.class)
.addUserMessage("What's the weather in San Francisco?")
.build());
for (BetaMessage message : toolRunner) {
System.out.println(message);
}Memory Tool
The Java SDK provides BetaMemoryToolHandler for implementing the memory tool backend. You supply a handler that manages file storage, and the BetaToolRunner handles memory tool calls automatically.
import com.anthropic.helpers.BetaMemoryToolHandler;
import com.anthropic.helpers.BetaToolRunner;
import com.anthropic.models.beta.messages.BetaMemoryTool20250818;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.ToolRunnerCreateParams;
// Implement BetaMemoryToolHandler with your storage backend (e.g., filesystem)
BetaMemoryToolHandler memoryHandler = new FileSystemMemoryToolHandler(sandboxRoot);
MessageCreateParams createParams = MessageCreateParams.builder()
.model("claude-opus-4-6")
.maxTokens(4096L)
.addTool(BetaMemoryTool20250818.builder().build())
.addUserMessage("Remember that my favorite color is blue")
.build();
BetaToolRunner toolRunner = client.beta().messages().toolRunner(
ToolRunnerCreateParams.builder()
.betaMemoryToolHandler(memoryHandler)
.initialMessageParams(createParams)
.build());
for (BetaMessage message : toolRunner) {
System.out.println(message);
}See the shared memory tool concepts for more details on the memory tool.
Non-Beta Tool Declaration (manual JSON schema)
Tool.InputSchema.Properties is a freeform Map<String, JsonValue> wrapper — build property schemas via putAdditionalProperty. type: "object" is the default. The builder has a direct .addTool(Tool) overload that wraps in ToolUnion automatically.
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.Tool;
Tool tool = Tool.builder()
.name("get_weather")
.description("Get the current weather in a given location")
.inputSchema(Tool.InputSchema.builder()
.properties(Tool.InputSchema.Properties.builder()
.putAdditionalProperty("location", JsonValue.from(Map.of("type", "string")))
.build())
.required(List.of("location"))
.build())
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(16000L)
.addTool(tool)
.addUserMessage("Weather in Paris?")
.build();For manual tool loops, handle tool_use blocks in the response, send tool_result back, loop until stop_reason is "end_turn". See shared tool use concepts.
Building MessageParam with Content Blocks (Tool Result Round-Trip)
MessageParam.Content is an inner union class (string | list). Use the builder's .contentOfBlockParams(List<ContentBlockParam>) alias — there is NO separate MessageParamContent class with a static ofBlockParams:
import com.anthropic.models.messages.MessageParam;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.ToolResultBlockParam;
List<ContentBlockParam> results = List.of(
ContentBlockParam.ofToolResult(ToolResultBlockParam.builder()
.toolUseId(toolUseBlock.id())
.content(yourResultString)
.build())
);
MessageParam toolResultMsg = MessageParam.builder()
.role(MessageParam.Role.USER)
.contentOfBlockParams(results) // builder alias for Content.ofBlockParams(...)
.build();---
Effort Parameter
Effort is nested inside OutputConfig — there is NO .effort() directly on MessageCreateParams.Builder.
import com.anthropic.models.messages.OutputConfig;
.outputConfig(OutputConfig.builder()
.effort(OutputConfig.Effort.HIGH) // or LOW, MEDIUM, MAX
.build())Combine with Thinking = ThinkingConfigAdaptive for cost-quality control.
---
Prompt Caching
System message as a list of TextBlockParam with CacheControlEphemeral. Use .systemOfTextBlockParams(...) — the plain .system(String) overload can't carry cache control.
import com.anthropic.models.messages.TextBlockParam;
import com.anthropic.models.messages.CacheControlEphemeral;
.systemOfTextBlockParams(List.of(
TextBlockParam.builder()
.text(longSystemPrompt)
.cacheControl(CacheControlEphemeral.builder()
.ttl(CacheControlEphemeral.Ttl.TTL_1H) // optional; also TTL_5M
.build())
.build()))There's also a top-level .cacheControl(CacheControlEphemeral) on MessageCreateParams.Builder and on Tool.builder().
---
Token Counting
import com.anthropic.models.messages.MessageCountTokensParams;
long tokens = client.messages().countTokens(
MessageCountTokensParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.addUserMessage("Hello")
.build()
).inputTokens();---
Structured Output
The class-based overload auto-derives the JSON schema from your POJO and gives you a typed .text() return — no manual schema, no manual parsing.
import com.anthropic.models.messages.StructuredMessageCreateParams;
record Book(String title, String author) {}
record BookList(List<Book> books) {}
StructuredMessageCreateParams<BookList> params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(16000L)
.outputConfig(BookList.class) // returns a typed builder
.addUserMessage("List 3 classic novels")
.build();
client.messages().create(params).content().stream()
.flatMap(cb -> cb.text().stream())
.forEach(typed -> {
// typed.text() returns BookList, not String
for (Book b : typed.text().books()) System.out.println(b.title());
});Supports Jackson annotations: @JsonPropertyDescription, @JsonIgnore, @ArraySchema(minItems=...). Manual schema path: OutputConfig.builder().format(JsonOutputFormat.builder().schema(...).build()).
---
PDF / Document Input
DocumentBlockParam builder has source shortcuts. Wrap in ContentBlockParam.ofDocument() and pass via .addUserMessageOfBlockParams().
import com.anthropic.models.messages.DocumentBlockParam;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.TextBlockParam;
DocumentBlockParam doc = DocumentBlockParam.builder()
.base64Source(base64String) // or .urlSource("https://...") or .textSource("...")
.title("My Document") // optional
.build();
.addUserMessageOfBlockParams(List.of(
ContentBlockParam.ofDocument(doc),
ContentBlockParam.ofText(TextBlockParam.builder().text("Summarize this").build())))---
Server-Side Tools
Version-suffixed types; name/type auto-set by builder. Direct .addTool() overloads exist for every type — no manual ToolUnion wrapping.
import com.anthropic.models.messages.WebSearchTool20260209;
import com.anthropic.models.messages.ToolBash20250124;
import com.anthropic.models.messages.ToolTextEditor20250728;
import com.anthropic.models.messages.CodeExecutionTool20260120;
.addTool(WebSearchTool20260209.builder()
.maxUses(5L) // optional
.allowedDomains(List.of("example.com")) // optional
.build())
.addTool(ToolBash20250124.builder().build())
.addTool(ToolTextEditor20250728.builder().build())
.addTool(CodeExecutionTool20260120.builder().build())Also available: WebFetchTool20260209, MemoryTool20250818, ToolSearchToolBm25_20251119.
Beta namespace (MCP, compaction)
For beta-only features use com.anthropic.models.beta.messages.* — class names have a Beta prefix AND live in the beta package. The beta MessageCreateParams.Builder has direct .addTool(BetaToolBash20250124) overloads AND .addMcpServer():
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaToolBash20250124;
import com.anthropic.models.beta.messages.BetaCodeExecutionTool20260120;
import com.anthropic.models.beta.messages.BetaRequestMcpServerUrlDefinition;
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_6)
.maxTokens(16000L)
.addBeta("mcp-client-2025-11-20")
.addTool(BetaToolBash20250124.builder().build())
.addTool(BetaCodeExecutionTool20260120.builder().build())
.addMcpServer(BetaRequestMcpServerUrlDefinition.builder()
.name("my-server")
.url("https://example.com/mcp")
.build())
.addUserMessage("...")
.build();
client.beta().messages().create(params);BetaTool* types are NOT interchangeable with non-beta Tool* — pick one namespace per request.
Reading server-tool blocks in the response: ServerToolUseBlock has .id(), .name() (enum), and ._input() returning raw JsonValue — there is NO typed .input(). For code execution results, unwrap two levels:
for (ContentBlock block : response.content()) {
block.serverToolUse().ifPresent(stu -> {
System.out.println("tool: " + stu.name() + " input: " + stu._input());
});
block.codeExecutionToolResult().ifPresent(r -> {
r.content().resultBlock().ifPresent(result -> {
System.out.println("stdout: " + result.stdout());
System.out.println("stderr: " + result.stderr());
System.out.println("exit: " + result.returnCode());
});
});
}---
Files API (Beta)
Under client.beta().files(). File references in messages need the beta message types (non-beta DocumentBlockParam.Source has no file-ID variant).
import com.anthropic.models.beta.files.FileUploadParams;
import com.anthropic.models.beta.files.FileMetadata;
import com.anthropic.models.beta.messages.BetaRequestDocumentBlock;
import java.nio.file.Paths;
FileMetadata meta = client.beta().files().upload(
FileUploadParams.builder()
.file(Paths.get("/path/to/doc.pdf")) // or .file(InputStream) or .file(byte[])
.build());
// Reference in a beta message:
BetaRequestDocumentBlock doc = BetaRequestDocumentBlock.builder()
.fileSource(meta.id())
.build();Other methods: .list(), .delete(String fileId), .download(String fileId), .retrieveMetadata(String fileId).
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.Claude API — PHP
Note: The PHP SDK is the official Anthropic SDK for PHP. Tool runner and Agent SDK are not available. Bedrock, Vertex AI, and Foundry clients are supported.
Installation
composer require "anthropic-ai/sdk"Client Initialization
use Anthropic\Client;
// Using API key from environment variable
$client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));Amazon Bedrock
use Anthropic\Bedrock;
// Constructor is private — use the static factory. Reads AWS credentials from env.
$client = Bedrock\Client::fromEnvironment(region: 'us-east-1');Google Vertex AI
use Anthropic\Vertex;
// Constructor is private. Parameter is `location`, not `region`.
$client = Vertex\Client::fromEnvironment(
location: 'us-east5',
projectId: 'my-project-id',
);Anthropic Foundry
use Anthropic\Foundry;
// Constructor is private. baseUrl or resource is required.
$client = Foundry\Client::withCredentials(
authToken: getenv('ANTHROPIC_FOUNDRY_AUTH_TOKEN'),
baseUrl: 'https://<resource>.services.ai.azure.com/anthropic',
);---
Basic Message Request
$message = $client->messages->create(
model: 'claude-opus-4-6',
maxTokens: 16000,
messages: [
['role' => 'user', 'content' => 'What is the capital of France?'],
],
);
// content is an array of polymorphic blocks (TextBlock, ToolUseBlock,
// ThinkingBlock). Accessing ->text on content[0] without checking the block
// type will throw if the first block is not a TextBlock (e.g., when extended
// thinking is enabled and a ThinkingBlock comes first). Always guard:
foreach ($message->content as $block) {
if ($block->type === 'text') {
echo $block->text;
}
}If you only want the first text block:
foreach ($message->content as $block) {
if ($block->type === 'text') {
echo $block->text;
break;
}
}---
Streaming
Requires SDK v0.5.0+. v0.4.0 and earlier used a single$paramsarray; calling with named parameters throwsUnknown named parameter $model. Upgrade:composer require "anthropic-ai/sdk:^0.6"
use Anthropic\Messages\RawContentBlockDeltaEvent;
use Anthropic\Messages\TextDelta;
$stream = $client->messages->createStream(
model: 'claude-opus-4-6',
maxTokens: 64000,
messages: [
['role' => 'user', 'content' => 'Write a haiku'],
],
);
foreach ($stream as $event) {
if ($event instanceof RawContentBlockDeltaEvent && $event->delta instanceof TextDelta) {
echo $event->delta->text;
}
}---
Tool Use (Manual Loop)
Tools are passed as arrays. The SDK uses camelCase keys (inputSchema, toolUseID, stopReason) and auto-maps to the API's snake_case on the wire — since v0.5.0. See shared tool use concepts for the loop pattern.
use Anthropic\Messages\ToolUseBlock;
$tools = [
[
'name' => 'get_weather',
'description' => 'Get the current weather in a given location',
'inputSchema' => [ // camelCase, not input_schema
'type' => 'object',
'properties' => [
'location' => ['type' => 'string', 'description' => 'City and state'],
],
'required' => ['location'],
],
],
];
$messages = [['role' => 'user', 'content' => 'What is the weather in SF?']];
$response = $client->messages->create(
model: 'claude-opus-4-6',
maxTokens: 16000,
tools: $tools,
messages: $messages,
);
while ($response->stopReason === 'tool_use') { // camelCase property
$toolResults = [];
foreach ($response->content as $block) {
if ($block instanceof ToolUseBlock) {
// $block->name : string — tool name to dispatch on
// $block->input : array<string,mixed> — parsed JSON input
// $block->id : string — pass back as toolUseID
$result = executeYourTool($block->name, $block->input);
$toolResults[] = [
'type' => 'tool_result',
'toolUseID' => $block->id, // camelCase, not tool_use_id
'content' => $result,
];
}
}
// Append assistant turn + user turn with tool results
$messages[] = ['role' => 'assistant', 'content' => $response->content];
$messages[] = ['role' => 'user', 'content' => $toolResults];
$response = $client->messages->create(
model: 'claude-opus-4-6',
maxTokens: 16000,
tools: $tools,
messages: $messages,
);
}
// Final text response
foreach ($response->content as $block) {
if ($block->type === 'text') {
echo $block->text;
}
}$block->type === 'tool_use' also works; instanceof ToolUseBlock narrows for PHPStan.
---
Extended Thinking
Adaptive thinking is the recommended mode for Claude 4.6+ models. Claude decides dynamically when and how much to think.
use Anthropic\Messages\ThinkingBlock;
$message = $client->messages->create(
model: 'claude-opus-4-6',
maxTokens: 16000,
thinking: ['type' => 'adaptive'],
messages: [
['role' => 'user', 'content' => 'Solve: 27 * 453'],
],
);
// ThinkingBlock(s) precede TextBlock in content
foreach ($message->content as $block) {
if ($block instanceof ThinkingBlock) {
echo "Thinking:\n{$block->thinking}\n\n";
// $block->signature is an opaque string — preserve verbatim if
// passing thinking blocks back in multi-turn conversations
} elseif ($block->type === 'text') {
echo "Answer: {$block->text}\n";
}
}Deprecated: ['type' => 'enabled', 'budgetTokens' => N] (fixed-budget extended thinking) still works on Claude 4.6 but is deprecated. Use adaptive thinking above.$block->type === 'thinking' also works for the check; instanceof narrows for PHPStan.
---
Beta Features & Server-Side Tools
`betas:` is NOT a param on `$client->messages->create()` — it only exists on the beta namespace. Use it for features that need an explicit opt-in header:
use Anthropic\Beta\Messages\BetaRequestMCPServerURLDefinition;
$response = $client->beta->messages->create(
model: 'claude-opus-4-6',
maxTokens: 16000,
mcpServers: [
BetaRequestMCPServerURLDefinition::with(
name: 'my-server',
url: 'https://example.com/mcp',
),
],
betas: ['mcp-client-2025-11-20'], // only valid on ->beta->messages
messages: [['role' => 'user', 'content' => 'Use the MCP tools']],
);Server-side tools (bash, web_search, text_editor, code_execution) are GA and work on both paths — Anthropic\Messages\ToolBash20250124 / WebSearchTool20260209 / ToolTextEditor20250728 / CodeExecutionTool20260120 for non-beta, Anthropic\Beta\Messages\BetaToolBash20250124 / BetaWebSearchTool20260209 / BetaToolTextEditor20250728 / BetaCodeExecutionTool20260120 for beta. No betas: header needed for these.
Agent SDK Patterns — Python
Basic Agent
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def main():
async for message in query(
prompt="Explain what this repository does",
options=ClaudeAgentOptions(
cwd="/path/to/project",
allowed_tools=["Read", "Glob", "Grep"]
)
):
if isinstance(message, ResultMessage):
print(message.result)
anyio.run(main)---
Custom Tools
Custom tools require an MCP server. Use ClaudeSDKClient for full control (custom SDK MCP tools require ClaudeSDKClient — query() only supports external stdio/http MCP servers).
import anyio
from claude_agent_sdk import (
tool,
create_sdk_mcp_server,
ClaudeSDKClient,
ClaudeAgentOptions,
AssistantMessage,
TextBlock,
)
@tool("get_weather", "Get the current weather for a location", {"location": str})
async def get_weather(args):
location = args["location"]
return {"content": [{"type": "text", "text": f"The weather in {location} is sunny and 72°F."}]}
server = create_sdk_mcp_server("weather-tools", tools=[get_weather])
async def main():
options = ClaudeAgentOptions(mcp_servers={"weather": server})
async with ClaudeSDKClient(options=options) as client:
await client.query("What's the weather in Paris?")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
anyio.run(main)---
Hooks
After Tool Use Hook
Log file changes after any edit:
import anyio
from datetime import datetime
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher, ResultMessage
async def log_file_change(input_data, tool_use_id, context):
file_path = input_data.get('tool_input', {}).get('file_path', 'unknown')
with open('./audit.log', 'a') as f:
f.write(f"{datetime.now()}: modified {file_path}\n")
return {}
async def main():
async for message in query(
prompt="Refactor utils.py to improve readability",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Edit", "Write"],
permission_mode="acceptEdits",
hooks={
"PostToolUse": [HookMatcher(matcher="Edit|Write", hooks=[log_file_change])]
}
)
):
if isinstance(message, ResultMessage):
print(message.result)
anyio.run(main)---
Subagents
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition, ResultMessage
async def main():
async for message in query(
prompt="Use the code-reviewer agent to review this codebase",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep", "Agent"],
agents={
"code-reviewer": AgentDefinition(
description="Expert code reviewer for quality and security reviews.",
prompt="Analyze code quality and suggest improvements.",
tools=["Read", "Glob", "Grep"]
)
}
)
):
if isinstance(message, ResultMessage):
print(message.result)
anyio.run(main)---
MCP Server Integration
Browser Automation (Playwright)
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def main():
async for message in query(
prompt="Open example.com and describe what you see",
options=ClaudeAgentOptions(
mcp_servers={
"playwright": {"command": "npx", "args": ["@playwright/mcp@latest"]}
}
)
):
if isinstance(message, ResultMessage):
print(message.result)
anyio.run(main)Database Access (PostgreSQL)
import os
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def main():
async for message in query(
prompt="Show me the top 10 users by order count",
options=ClaudeAgentOptions(
mcp_servers={
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {"DATABASE_URL": os.environ["DATABASE_URL"]}
}
}
)
):
if isinstance(message, ResultMessage):
print(message.result)
anyio.run(main)---
Permission Modes
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions
async def main():
# Default: prompt for dangerous operations
async for message in query(
prompt="Delete all test files",
options=ClaudeAgentOptions(
allowed_tools=["Bash"],
permission_mode="default" # Will prompt before deleting
)
):
pass
# Plan: agent creates a plan before making changes
async for message in query(
prompt="Refactor the auth system",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Edit"],
permission_mode="plan"
)
):
pass
# Accept edits: auto-accept file edits
async for message in query(
prompt="Refactor this module",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Edit"],
permission_mode="acceptEdits"
)
):
pass
# Bypass: skip all prompts (use with caution)
async for message in query(
prompt="Set up the development environment",
options=ClaudeAgentOptions(
allowed_tools=["Bash", "Write"],
permission_mode="bypassPermissions"
)
):
pass
anyio.run(main)---
Error Recovery
import anyio
from claude_agent_sdk import (
query,
ClaudeAgentOptions,
CLINotFoundError,
CLIConnectionError,
ProcessError,
ResultMessage,
)
async def run_with_recovery():
try:
async for message in query(
prompt="Fix the failing tests",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Edit", "Bash"],
max_turns=10
)
):
if isinstance(message, ResultMessage):
print(message.result)
except CLINotFoundError:
print("Claude Code CLI not found. Install with: pip install claude-agent-sdk")
except CLIConnectionError as e:
print(f"Connection error: {e}")
except ProcessError as e:
print(f"Process error: {e}")
anyio.run(run_with_recovery)---
Session Resumption
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage, SystemMessage
async def main():
session_id = None
# First query: capture the session ID
async for message in query(
prompt="Read the authentication module",
options=ClaudeAgentOptions(allowed_tools=["Read", "Glob"])
):
if isinstance(message, SystemMessage) and message.subtype == "init":
session_id = message.data.get("session_id")
# Resume with full context from the first query
async for message in query(
prompt="Now find all places that call it", # "it" = auth module
options=ClaudeAgentOptions(resume=session_id)
):
if isinstance(message, ResultMessage):
print(message.result)
anyio.run(main)---
Session History
from claude_agent_sdk import list_sessions, get_session_messages
# List past sessions (sync function — no await)
sessions = list_sessions()
for session in sessions:
print(f"Session {session.session_id} in {session.cwd}")
# Retrieve messages from the most recent session (sync function — no await)
if sessions:
messages = get_session_messages(session_id=sessions[0].session_id)
for msg in messages:
print(msg)---
Session Mutations
from claude_agent_sdk import rename_session, tag_session
session_id = "your-session-id"
# Rename a session
rename_session(session_id=session_id, title="Refactoring auth module")
# Tag a session for filtering
tag_session(session_id=session_id, tag="experiment-v2")
# Clear a tag
tag_session(session_id=session_id, tag=None)
# Scope to a specific project directory
rename_session(session_id=session_id, title="New title", directory="/path/to/project")---
Custom System Prompt
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def main():
async for message in query(
prompt="Review this code",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep"],
system_prompt="""You are a senior code reviewer focused on:
1. Security vulnerabilities
2. Performance issues
3. Code maintainability
Always provide specific line numbers and suggestions for improvement."""
)
):
if isinstance(message, ResultMessage):
print(message.result)
anyio.run(main)Agent SDK — Python
The Claude Agent SDK provides a higher-level interface for building AI agents with built-in tools, safety features, and agentic capabilities.
Installation
pip install claude-agent-sdk---
Quick Start
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def main():
async for message in query(
prompt="Explain this codebase",
options=ClaudeAgentOptions(allowed_tools=["Read", "Glob", "Grep"])
):
if isinstance(message, ResultMessage):
print(message.result)
anyio.run(main)---
Built-in Tools
| Tool | Description |
|---|---|
| Read | Read files in the workspace |
| Write | Create new files |
| Edit | Make precise edits to existing files |
| Bash | Execute shell commands |
| Glob | Find files by pattern |
| Grep | Search files by content |
| WebSearch | Search the web for information |
| WebFetch | Fetch and analyze web pages |
| AskUserQuestion | Ask user clarifying questions |
| Agent | Spawn subagents |
---
Primary Interfaces
query() — Simple One-Shot Usage
The query() function is the simplest way to run an agent. It returns an async iterator of messages.
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async for message in query(
prompt="Explain this codebase",
options=ClaudeAgentOptions(allowed_tools=["Read", "Glob", "Grep"])
):
if isinstance(message, ResultMessage):
print(message.result)ClaudeSDKClient — Full Control
ClaudeSDKClient provides full control over the agent lifecycle. Use it when you need custom tools, hooks, streaming, or the ability to interrupt execution.
import anyio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, TextBlock
async def main():
options = ClaudeAgentOptions(allowed_tools=["Read", "Glob", "Grep"])
async with ClaudeSDKClient(options=options) as client:
await client.query("Explain this codebase")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
anyio.run(main)ClaudeSDKClient supports:
- Context manager (
async with) for automatic resource cleanup - `client.query(prompt)` to send a prompt to the agent
- `receive_response()` for streaming messages until completion
- `interrupt()` to stop agent execution mid-task
- Required for custom tools (via SDK MCP servers)
---
Permission System
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async for message in query(
prompt="Refactor the authentication module",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Edit", "Write"],
permission_mode="acceptEdits" # Auto-accept file edits
)
):
if isinstance(message, ResultMessage):
print(message.result)Permission modes:
"default": Prompt for dangerous operations"plan": Planning only, no execution"acceptEdits": Auto-accept file edits"bypassPermissions": Skip all prompts (use with caution)
---
MCP (Model Context Protocol) Support
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async for message in query(
prompt="Open example.com and describe what you see",
options=ClaudeAgentOptions(
mcp_servers={
"playwright": {"command": "npx", "args": ["@playwright/mcp@latest"]}
}
)
):
if isinstance(message, ResultMessage):
print(message.result)---
Hooks
Customize agent behavior with hooks using callback functions:
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher, ResultMessage
async def log_file_change(input_data, tool_use_id, context):
file_path = input_data.get('tool_input', {}).get('file_path', 'unknown')
print(f"Modified: {file_path}")
return {}
async for message in query(
prompt="Refactor utils.py",
options=ClaudeAgentOptions(
permission_mode="acceptEdits",
hooks={
"PostToolUse": [HookMatcher(matcher="Edit|Write", hooks=[log_file_change])]
}
)
):
if isinstance(message, ResultMessage):
print(message.result)Hook callback inputs for tool-lifecycle events (PreToolUse, PostToolUse, PostToolUseFailure) include agent_id and agent_type fields, allowing hooks to identify which agent (main or subagent) triggered the tool call.
Available hook events: PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, Stop, SubagentStop, PreCompact, Notification, SubagentStart, PermissionRequest
---
Common Options
query() takes a top-level prompt (string) and an options object (ClaudeAgentOptions):
async for message in query(prompt="...", options=ClaudeAgentOptions(...)):| Option | Type | Description |
|---|---|---|
cwd | string | Working directory for file operations |
allowed_tools | list | Tools the agent can use (e.g., ["Read", "Edit", "Bash"]) |
tools | list | Built-in tools to make available (restricts the default set) |
disallowed_tools | list | Tools to explicitly disallow |
permission_mode | string | How to handle permission prompts |
mcp_servers | dict | MCP servers to connect to |
hooks | dict | Hooks for customizing behavior |
system_prompt | string | Custom system prompt |
max_turns | int | Maximum agent turns before stopping |
max_budget_usd | float | Maximum budget in USD for the query |
model | string | Model ID (default: determined by CLI) |
agents | dict | Subagent definitions (dict[str, AgentDefinition]) |
output_format | dict | Structured output schema |
thinking | dict | Thinking/reasoning control |
betas | list | Beta features to enable (e.g., ["context-1m-2025-08-07"]) |
setting_sources | list | Settings to load (e.g., ["project"]). Default: none (no CLAUDE.md files) |
env | dict | Environment variables to set for the session |
---
Message Types
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage, SystemMessage
async for message in query(
prompt="Find TODO comments",
options=ClaudeAgentOptions(allowed_tools=["Read", "Glob", "Grep"])
):
if isinstance(message, ResultMessage):
print(message.result)
print(f"Stop reason: {message.stop_reason}") # e.g., "end_turn", "max_turns"
elif isinstance(message, SystemMessage) and message.subtype == "init":
session_id = message.data.get("session_id") # Capture for resuming laterTyped task message subclasses are available for better type safety when handling subagent task events:
TaskStartedMessage— emitted when a subagent task is registeredTaskProgressMessage— real-time progress updates with cumulative usage metricsTaskNotificationMessage— task completion notifications
RateLimitEvent is emitted when the rate limit status transitions (e.g., from allowed to allowed_warning or rejected). Use it to warn users or back off gracefully:
from claude_agent_sdk import query, ClaudeAgentOptions, RateLimitEvent
async for message in query(prompt="...", options=ClaudeAgentOptions()):
if isinstance(message, RateLimitEvent):
print(f"Rate limit status: {message.rate_limit_info.status}")
if message.rate_limit_info.resets_at:
print(f"Resets at: {message.rate_limit_info.resets_at}")---
Subagents
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition, ResultMessage
async for message in query(
prompt="Use the code-reviewer agent to review this codebase",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep", "Agent"],
agents={
"code-reviewer": AgentDefinition(
description="Expert code reviewer for quality and security reviews.",
prompt="Analyze code quality and suggest improvements.",
tools=["Read", "Glob", "Grep"]
)
}
)
):
if isinstance(message, ResultMessage):
print(message.result)---
Error Handling
from claude_agent_sdk import query, ClaudeAgentOptions, CLINotFoundError, CLIConnectionError, ResultMessage
try:
async for message in query(
prompt="...",
options=ClaudeAgentOptions(allowed_tools=["Read"])
):
if isinstance(message, ResultMessage):
print(message.result)
except CLINotFoundError:
print("Claude Code CLI not found. Install with: pip install claude-agent-sdk")
except CLIConnectionError as e:
print(f"Connection error: {e}")---
Session History
Retrieve past session data with top-level functions:
from claude_agent_sdk import list_sessions, get_session_messages
# List all past sessions (sync function — no await)
sessions = list_sessions()
for session in sessions:
print(f"{session.session_id}: {session.cwd}")
# Get messages from a specific session (sync function — no await)
messages = get_session_messages(session_id="...")
for msg in messages:
print(msg)Session Mutations
Rename or tag sessions (sync functions — no await):
from claude_agent_sdk import rename_session, tag_session
# Rename a session
rename_session(session_id="...", title="My refactoring session")
# Tag a session (tags are Unicode-sanitized automatically)
tag_session(session_id="...", tag="experiment")
# Clear a tag
tag_session(session_id="...", tag=None)
# Optionally scope to a specific project directory
rename_session(session_id="...", title="New title", directory="/path/to/project")---
MCP Server Management
Manage MCP servers at runtime using ClaudeSDKClient:
async with ClaudeSDKClient(options=options) as client:
# Reconnect a disconnected MCP server
await client.reconnect_mcp_server("my-server")
# Toggle an MCP server on/off
await client.toggle_mcp_server("my-server", enabled=False)
# Get status of all MCP servers
status = await client.get_mcp_status() # returns McpStatusResponse---
Best Practices
1. Always specify allowed_tools — Explicitly list which tools the agent can use 2. Set working directory — Always specify cwd for file operations 3. Use appropriate permission modes — Start with "default" and only escalate when needed 4. Handle all message types — Check for ResultMessage to get agent output 5. Limit max_turns — Prevent runaway agents with reasonable limits
Message Batches API — Python
The Batches API (POST /v1/messages/batches) processes Messages API requests asynchronously at 50% of standard prices.
Key Facts
- Up to 100,000 requests or 256 MB per batch
- Most batches complete within 1 hour; maximum 24 hours
- Results available for 29 days after creation
- 50% cost reduction on all token usage
- All Messages API features supported (vision, tools, caching, etc.)
---
Create a Batch
import anthropic
from anthropic.types.message_create_params import MessageCreateParamsNonStreaming
from anthropic.types.messages.batch_create_params import Request
client = anthropic.Anthropic()
message_batch = client.messages.batches.create(
requests=[
Request(
custom_id="request-1",
params=MessageCreateParamsNonStreaming(
model="claude-opus-4-6",
max_tokens=16000,
messages=[{"role": "user", "content": "Summarize climate change impacts"}]
)
),
Request(
custom_id="request-2",
params=MessageCreateParamsNonStreaming(
model="claude-opus-4-6",
max_tokens=16000,
messages=[{"role": "user", "content": "Explain quantum computing basics"}]
)
),
]
)
print(f"Batch ID: {message_batch.id}")
print(f"Status: {message_batch.processing_status}")---
Poll for Completion
import time
while True:
batch = client.messages.batches.retrieve(message_batch.id)
if batch.processing_status == "ended":
break
print(f"Status: {batch.processing_status}, processing: {batch.request_counts.processing}")
time.sleep(60)
print("Batch complete!")
print(f"Succeeded: {batch.request_counts.succeeded}")
print(f"Errored: {batch.request_counts.errored}")---
Retrieve Results
Note: Examples below usematch/casesyntax, requiring Python 3.10+. For earlier versions, useif/elifchains instead.
for result in client.messages.batches.results(message_batch.id):
match result.result.type:
case "succeeded":
msg = result.result.message
text = next((b.text for b in msg.content if b.type == "text"), "")
print(f"[{result.custom_id}] {text[:100]}")
case "errored":
if result.result.error.type == "invalid_request":
print(f"[{result.custom_id}] Validation error - fix request and retry")
else:
print(f"[{result.custom_id}] Server error - safe to retry")
case "canceled":
print(f"[{result.custom_id}] Canceled")
case "expired":
print(f"[{result.custom_id}] Expired - resubmit")---
Cancel a Batch
cancelled = client.messages.batches.cancel(message_batch.id)
print(f"Status: {cancelled.processing_status}") # "canceling"---
Batch with Prompt Caching
shared_system = [
{"type": "text", "text": "You are a literary analyst."},
{
"type": "text",
"text": large_document_text, # Shared across all requests
"cache_control": {"type": "ephemeral"}
}
]
message_batch = client.messages.batches.create(
requests=[
Request(
custom_id=f"analysis-{i}",
params=MessageCreateParamsNonStreaming(
model="claude-opus-4-6",
max_tokens=16000,
system=shared_system,
messages=[{"role": "user", "content": question}]
)
)
for i, question in enumerate(questions)
]
)---
Full End-to-End Example
import anthropic
import time
from anthropic.types.message_create_params import MessageCreateParamsNonStreaming
from anthropic.types.messages.batch_create_params import Request
client = anthropic.Anthropic()
# 1. Prepare requests
items_to_classify = [
"The product quality is excellent!",
"Terrible customer service, never again.",
"It's okay, nothing special.",
]
requests = [
Request(
custom_id=f"classify-{i}",
params=MessageCreateParamsNonStreaming(
model="claude-haiku-4-5",
max_tokens=50,
messages=[{
"role": "user",
"content": f"Classify as positive/negative/neutral (one word): {text}"
}]
)
)
for i, text in enumerate(items_to_classify)
]
# 2. Create batch
batch = client.messages.batches.create(requests=requests)
print(f"Created batch: {batch.id}")
# 3. Wait for completion
while True:
batch = client.messages.batches.retrieve(batch.id)
if batch.processing_status == "ended":
break
time.sleep(10)
# 4. Collect results
results = {}
for result in client.messages.batches.results(batch.id):
if result.result.type == "succeeded":
msg = result.result.message
results[result.custom_id] = next((b.text for b in msg.content if b.type == "text"), "")
for custom_id, classification in sorted(results.items()):
print(f"{custom_id}: {classification}")Files API — Python
The Files API uploads files for use in Messages API requests. Reference files via file_id in content blocks, avoiding re-uploads across multiple API calls.
Beta: Pass betas=["files-api-2025-04-14"] in your API calls (the SDK sets the required header automatically).
Key Facts
- Maximum file size: 500 MB
- Total storage: 100 GB per organization
- Files persist until deleted
- File operations (upload, list, delete) are free; content used in messages is billed as input tokens
- Not available on Amazon Bedrock or Google Vertex AI
---
Upload a File
import anthropic
client = anthropic.Anthropic()
uploaded = client.beta.files.upload(
file=("report.pdf", open("report.pdf", "rb"), "application/pdf"),
)
print(f"File ID: {uploaded.id}")
print(f"Size: {uploaded.size_bytes} bytes")---
Use a File in Messages
PDF / Text Document
response = client.beta.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "Summarize the key findings in this report."},
{
"type": "document",
"source": {"type": "file", "file_id": uploaded.id},
"title": "Q4 Report", # optional
"citations": {"enabled": True} # optional, enables citations
}
]
}],
betas=["files-api-2025-04-14"],
)
for block in response.content:
if block.type == "text":
print(block.text)Image
image_file = client.beta.files.upload(
file=("photo.png", open("photo.png", "rb"), "image/png"),
)
response = client.beta.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{
"type": "image",
"source": {"type": "file", "file_id": image_file.id}
}
]
}],
betas=["files-api-2025-04-14"],
)---
Manage Files
List Files
files = client.beta.files.list()
for f in files.data:
print(f"{f.id}: {f.filename} ({f.size_bytes} bytes)")Get File Metadata
file_info = client.beta.files.retrieve_metadata("file_011CNha8iCJcU1wXNR6q4V8w")
print(f"Filename: {file_info.filename}")
print(f"MIME type: {file_info.mime_type}")Delete a File
client.beta.files.delete("file_011CNha8iCJcU1wXNR6q4V8w")Download a File
Only files created by the code execution tool or skills can be downloaded (not user-uploaded files).
file_content = client.beta.files.download("file_011CNha8iCJcU1wXNR6q4V8w")
file_content.write_to_file("output.txt")---
Full End-to-End Example
Upload a document once, ask multiple questions about it:
import anthropic
client = anthropic.Anthropic()
# 1. Upload once
uploaded = client.beta.files.upload(
file=("contract.pdf", open("contract.pdf", "rb"), "application/pdf"),
)
print(f"Uploaded: {uploaded.id}")
# 2. Ask multiple questions using the same file_id
questions = [
"What are the key terms and conditions?",
"What is the termination clause?",
"Summarize the payment schedule.",
]
for question in questions:
response = client.beta.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
messages=[{
"role": "user",
"content": [
{"type": "text", "text": question},
{
"type": "document",
"source": {"type": "file", "file_id": uploaded.id}
}
]
}],
betas=["files-api-2025-04-14"],
)
print(f"\nQ: {question}")
text = next((b.text for b in response.content if b.type == "text"), "")
print(f"A: {text[:200]}")
# 3. Clean up when done
client.beta.files.delete(uploaded.id)Claude API — Python
Installation
pip install anthropicClient Initialization
import anthropic
# Default (uses ANTHROPIC_API_KEY env var)
client = anthropic.Anthropic()
# Explicit API key
client = anthropic.Anthropic(api_key="your-api-key")
# Async client
async_client = anthropic.AsyncAnthropic()---
Basic Message Request
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
messages=[
{"role": "user", "content": "What is the capital of France?"}
]
)
# response.content is a list of content block objects (TextBlock, ThinkingBlock,
# ToolUseBlock, ...). Check .type before accessing .text.
for block in response.content:
if block.type == "text":
print(block.text)---
System Prompts
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
system="You are a helpful coding assistant. Always provide examples in Python.",
messages=[{"role": "user", "content": "How do I read a JSON file?"}]
)---
Vision (Images)
Base64
import base64
with open("image.png", "rb") as f:
image_data = base64.standard_b64encode(f.read()).decode("utf-8")
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
messages=[{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": image_data
}
},
{"type": "text", "text": "What's in this image?"}
]
}]
)URL
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
messages=[{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "url",
"url": "https://example.com/image.png"
}
},
{"type": "text", "text": "Describe this image"}
]
}]
)---
Prompt Caching
Cache large context to reduce costs (up to 90% savings).
Automatic Caching (Recommended)
Use top-level cache_control to automatically cache the last cacheable block in the request — no need to annotate individual content blocks:
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
cache_control={"type": "ephemeral"}, # auto-caches the last cacheable block
system="You are an expert on this large document...",
messages=[{"role": "user", "content": "Summarize the key points"}]
)Manual Cache Control
For fine-grained control, add cache_control to specific content blocks:
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
system=[{
"type": "text",
"text": "You are an expert on this large document...",
"cache_control": {"type": "ephemeral"} # default TTL is 5 minutes
}],
messages=[{"role": "user", "content": "Summarize the key points"}]
)
# With explicit TTL (time-to-live)
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
system=[{
"type": "text",
"text": "You are an expert on this large document...",
"cache_control": {"type": "ephemeral", "ttl": "1h"} # 1 hour TTL
}],
messages=[{"role": "user", "content": "Summarize the key points"}]
)---
Extended Thinking
Opus 4.6 and Sonnet 4.6: Use adaptive thinking. budget_tokens is deprecated on both Opus 4.6 and Sonnet 4.6.Older models: Usethinking: {type: "enabled", budget_tokens: N}(must be <max_tokens, min 1024).
# Opus 4.6: adaptive thinking (recommended)
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
thinking={"type": "adaptive"},
output_config={"effort": "high"}, # low | medium | high | max
messages=[{"role": "user", "content": "Solve this step by step..."}]
)
# Access thinking and response
for block in response.content:
if block.type == "thinking":
print(f"Thinking: {block.thinking}")
elif block.type == "text":
print(f"Response: {block.text}")---
Error Handling
import anthropic
try:
response = client.messages.create(...)
except anthropic.BadRequestError as e:
print(f"Bad request: {e.message}")
except anthropic.AuthenticationError:
print("Invalid API key")
except anthropic.PermissionDeniedError:
print("API key lacks required permissions")
except anthropic.NotFoundError:
print("Invalid model or endpoint")
except anthropic.RateLimitError as e:
retry_after = int(e.response.headers.get("retry-after", "60"))
print(f"Rate limited. Retry after {retry_after}s.")
except anthropic.APIStatusError as e:
if e.status_code >= 500:
print(f"Server error ({e.status_code}). Retry later.")
else:
print(f"API error: {e.message}")
except anthropic.APIConnectionError:
print("Network error. Check internet connection.")---
Multi-Turn Conversations
The API is stateless — send the full conversation history each time.
class ConversationManager:
"""Manage multi-turn conversations with the Claude API."""
def __init__(self, client: anthropic.Anthropic, model: str, system: str = None):
self.client = client
self.model = model
self.system = system
self.messages = []
def send(self, user_message: str, **kwargs) -> str:
"""Send a message and get a response."""
self.messages.append({"role": "user", "content": user_message})
response = self.client.messages.create(
model=self.model,
max_tokens=kwargs.get("max_tokens", 16000),
system=self.system,
messages=self.messages,
**kwargs
)
assistant_message = next(
(b.text for b in response.content if b.type == "text"), ""
)
self.messages.append({"role": "assistant", "content": assistant_message})
return assistant_message
# Usage
conversation = ConversationManager(
client=anthropic.Anthropic(),
model="claude-opus-4-6",
system="You are a helpful assistant."
)
response1 = conversation.send("My name is Alice.")
response2 = conversation.send("What's my name?") # Claude remembers "Alice"Rules:
- Messages must alternate between
userandassistant - First message must be
user
---
Compaction (long conversations)
Beta, Opus 4.6 and Sonnet 4.6. When conversations approach the 200K context window, compaction automatically summarizes earlier context server-side. The API returns acompactionblock; you must pass it back on subsequent requests — appendresponse.content, not just the text.
import anthropic
client = anthropic.Anthropic()
messages = []
def chat(user_message: str) -> str:
messages.append({"role": "user", "content": user_message})
response = client.beta.messages.create(
betas=["compact-2026-01-12"],
model="claude-opus-4-6",
max_tokens=16000,
messages=messages,
context_management={
"edits": [{"type": "compact_20260112"}]
}
)
# Append full content — compaction blocks must be preserved
messages.append({"role": "assistant", "content": response.content})
return next(block.text for block in response.content if block.type == "text")
# Compaction triggers automatically when context grows large
print(chat("Help me build a Python web scraper"))
print(chat("Add support for JavaScript-rendered pages"))
print(chat("Now add rate limiting and error handling"))---
Stop Reasons
The stop_reason field in the response indicates why the model stopped generating:
| Value | Meaning |
|---|---|
end_turn | Claude finished its response naturally |
max_tokens | Hit the max_tokens limit — increase it or use streaming |
stop_sequence | Hit a custom stop sequence |
tool_use | Claude wants to call a tool — execute it and continue |
pause_turn | Model paused and can be resumed (agentic flows) |
refusal | Claude refused for safety reasons — output may not match your schema |
---
Cost Optimization Strategies
1. Use Prompt Caching for Repeated Context
# Automatic caching (simplest — caches the last cacheable block)
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
cache_control={"type": "ephemeral"},
system=large_document_text, # e.g., 50KB of context
messages=[{"role": "user", "content": "Summarize the key points"}]
)
# First request: full cost
# Subsequent requests: ~90% cheaper for cached portion2. Choose the Right Model
# Default to Opus for most tasks
response = client.messages.create(
model="claude-opus-4-6", # $5.00/$25.00 per 1M tokens
max_tokens=16000,
messages=[{"role": "user", "content": "Explain quantum computing"}]
)
# Use Sonnet for high-volume production workloads
standard_response = client.messages.create(
model="claude-sonnet-4-6", # $3.00/$15.00 per 1M tokens
max_tokens=16000,
messages=[{"role": "user", "content": "Summarize this document"}]
)
# Use Haiku only for simple, speed-critical tasks
simple_response = client.messages.create(
model="claude-haiku-4-5", # $1.00/$5.00 per 1M tokens
max_tokens=256,
messages=[{"role": "user", "content": "Classify this as positive or negative"}]
)3. Use Token Counting Before Requests
count_response = client.messages.count_tokens(
model="claude-opus-4-6",
messages=messages,
system=system
)
estimated_input_cost = count_response.input_tokens * 0.000005 # $5/1M tokens
print(f"Estimated input cost: ${estimated_input_cost:.4f}")---
Retry with Exponential Backoff
Note: The Anthropic SDK automatically retries rate limit (429) and server errors (5xx) with exponential backoff. You can configure this with max_retries (default: 2). Only implement custom retry logic if you need behavior beyond what the SDK provides.import time
import random
import anthropic
def call_with_retry(
client: anthropic.Anthropic,
max_retries: int = 5,
base_delay: float = 1.0,
max_delay: float = 60.0,
**kwargs
):
"""Call the API with exponential backoff retry."""
last_exception = None
for attempt in range(max_retries):
try:
return client.messages.create(**kwargs)
except anthropic.RateLimitError as e:
last_exception = e
except anthropic.APIStatusError as e:
if e.status_code >= 500:
last_exception = e
else:
raise # Client errors (4xx except 429) should not be retried
delay = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
print(f"Retry {attempt + 1}/{max_retries} after {delay:.1f}s")
time.sleep(delay)
raise last_exceptionClaude API — Ruby
Note: The Ruby SDK supports the Claude API. A tool runner is available in beta via client.beta.messages.tool_runner(). Agent SDK is not yet available for Ruby.Installation
gem install anthropicClient Initialization
require "anthropic"
# Default (uses ANTHROPIC_API_KEY env var)
client = Anthropic::Client.new
# Explicit API key
client = Anthropic::Client.new(api_key: "your-api-key")---
Basic Message Request
message = client.messages.create(
model: :"claude-opus-4-6",
max_tokens: 16000,
messages: [
{ role: "user", content: "What is the capital of France?" }
]
)
# content is an array of polymorphic block objects (TextBlock, ThinkingBlock,
# ToolUseBlock, ...). .type is a Symbol — compare with :text, not "text".
# .text raises NoMethodError on non-TextBlock entries.
message.content.each do |block|
puts block.text if block.type == :text
end---
Streaming
stream = client.messages.stream(
model: :"claude-opus-4-6",
max_tokens: 64000,
messages: [{ role: "user", content: "Write a haiku" }]
)
stream.text.each { |text| print(text) }---
Tool Use
The Ruby SDK supports tool use via raw JSON schema definitions and also provides a beta tool runner for automatic tool execution.
Tool Runner (Beta)
class GetWeatherInput < Anthropic::BaseModel
required :location, String, doc: "City and state, e.g. San Francisco, CA"
end
class GetWeather < Anthropic::BaseTool
doc "Get the current weather for a location"
input_schema GetWeatherInput
def call(input)
"The weather in #{input.location} is sunny and 72°F."
end
end
client.beta.messages.tool_runner(
model: :"claude-opus-4-6",
max_tokens: 16000,
tools: [GetWeather.new],
messages: [{ role: "user", content: "What's the weather in San Francisco?" }]
).each_message do |message|
puts message.content
endManual Loop
See the shared tool use concepts for the tool definition format and agentic loop pattern.