# Anthropic Developer Documentation - Full Content
This file provides comprehensive documentation with full rendered content.
## Root URL
Claude Developer Platform Console (Requires login)
https://platform.claude.com
## Available Languages on Website
The full documentation is available in the following languages on https://platform.claude.com/docs:
- English (en) - 1414 pages - ✓ Full content included below
- German (Deutsch) (de) - 132 pages - Visit website for content
- Spanish (Español) (es) - 132 pages - Visit website for content
- French (Français) (fr) - 132 pages - Visit website for content
- Italian (Italiano) (it) - 132 pages - Visit website for content
- Japanese (日本語) (ja) - 132 pages - Visit website for content
- Korean (한국어) (ko) - 132 pages - Visit website for content
- Portuguese (Português) (pt-BR) - 132 pages - Visit website for content
- Russian (Русский) (ru) - 132 pages - Visit website for content
- Chinese Simplified (简体中文) (zh-CN) - 132 pages - Visit website for content
- Chinese Traditional (繁體中文) (zh-TW) - 132 pages - Visit website for content
- Indonesian (Bahasa Indonesia) (id) - 132 pages - Visit website for content
---
# English Documentation - Full Content
## Messages
### First steps
---
# Get started with Claude
URL: https://platform.claude.com/docs/en/get-started
# Get started with Claude
Make your first API call to Claude and build a simple web search assistant.
---
## Prerequisites
- An Anthropic [Console account](/)
- An [API key](/settings/keys)
## Call the API
Get your API key from the [Claude Console](/settings/keys) and set it as an environment variable:
```bash
export ANTHROPIC_API_KEY='your-api-key-here'
```
To persist the key across shell sessions, add the line to your shell profile (such as `~/.zshrc` or `~/.bashrc`).
Run this command to create a simple web search assistant:
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1000,
"messages": [
{
"role": "user",
"content": "What should I search for to find the latest developments in renewable energy?"
}
]
}'
```
**Example output:**
```json Output
{
"id": "msg_01HCDu5LRGeP2o7s2xGmxyx8",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "Here are some effective search strategies to find the latest renewable energy developments:\n\n## Search Terms to Use:\n- \"renewable energy news 2024\"\n- \"clean energy breakthrough\"\n- \"solar/wind/battery technology advances\"\n- \"green energy innovations\"\n- \"climate tech developments\"\n- \"energy storage solutions\"\n\n## Best Sources to Check:\n\n**News & Industry Sites:**\n- Renewable Energy World\n- GreenTech Media (now Wood Mackenzie)\n- Energy Storage News\n- CleanTechnica\n- PV Magazine (for solar)\n- WindPower Engineering & Development..."
}
],
"model": "claude-opus-4-7",
"stop_reason": "end_turn",
"usage": {
"input_tokens": 21,
"output_tokens": 305
}
}
```
Get your API key from the [Claude Console](/settings/keys) and set it as an environment variable:
```bash
export ANTHROPIC_API_KEY='your-api-key-here'
```
To persist the key across shell sessions, add the line to your shell profile (such as `~/.zshrc` or `~/.bashrc`).
Install the Anthropic CLI with Homebrew:
```bash
brew install anthropics/tap/ant
```
For other installation methods, see [Installation](/docs/en/api/sdks/cli#installation) in the CLI reference.
Run this command to create a simple web search assistant:
```bash
ant messages create \
--model claude-opus-4-7 \
--max-tokens 1000 \
--message '{
role: user,
content: "What should I search for to find the latest developments in renewable energy?"
}'
```
**Example output:**
```json Output
{
"id": "msg_01HCDu5LRGeP2o7s2xGmxyx8",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "Here are some effective search strategies to find the latest renewable energy developments:\n\n## Search Terms to Use:\n- \"renewable energy news 2024\"\n- \"clean energy breakthrough\"\n- \"solar/wind/battery technology advances\"\n- \"green energy innovations\"\n- \"climate tech developments\"\n- \"energy storage solutions\"\n\n## Best Sources to Check:\n\n**News & Industry Sites:**\n- Renewable Energy World\n- GreenTech Media (now Wood Mackenzie)\n- Energy Storage News\n- CleanTechnica\n- PV Magazine (for solar)\n- WindPower Engineering & Development..."
}
],
"model": "claude-opus-4-7",
"stop_reason": "end_turn",
"usage": {
"input_tokens": 21,
"output_tokens": 305
}
}
```
Get your API key from the [Claude Console](/settings/keys) and set it as an environment variable:
```bash
export ANTHROPIC_API_KEY='your-api-key-here'
```
To persist the key across shell sessions, add the line to your shell profile (such as `~/.zshrc` or `~/.bashrc`).
Install the Anthropic Python SDK:
```bash
pip install anthropic
```
Save this as `quickstart.py`:
```python
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1000,
messages=[
{
"role": "user",
"content": "What should I search for to find the latest developments in renewable energy?",
}
],
)
print(message.content)
```
```bash
python quickstart.py
```
**Example output:**
```text Output
[
TextBlock(
text='Here are some effective search strategies for finding the latest renewable energy developments:\n\n**Search Terms to Use:**\n- "renewable energy news 2024"\n- "clean energy breakthroughs"\n- "solar/wind/battery technology advances"\n- "energy storage innovations"\n- "green hydrogen developments"\n- "renewable energy policy updates"\n\n**Reliable Sources to Check:**\n- **News & Analysis:** Reuters Energy, Bloomberg New Energy Finance, Greentech Media, Energy Storage News\n- **Industry Publications:** Renewable Energy World, PV Magazine, Wind Power Engineering\n- **Research Organizations:** International Energy Agency (IEA), National Renewable Energy Laboratory (NREL)\n- **Government Sources:** Department of Energy websites, EPA clean energy updates\n\n**Specific Topics to Explore:**\n- Perovskite and next-gen solar cells\n- Offshore wind expansion\n- Grid-scale battery storage\n- Green hydrogen production\n- Carbon capture technologies\n- Smart grid innovations\n- Energy policy changes and incentives...',
type="text",
)
]
```
Get your API key from the [Claude Console](/settings/keys) and set it as an environment variable:
```bash
export ANTHROPIC_API_KEY='your-api-key-here'
```
To persist the key across shell sessions, add the line to your shell profile (such as `~/.zshrc` or `~/.bashrc`).
Install the Anthropic TypeScript SDK:
```bash
npm install @anthropic-ai/sdk
```
Save this as `quickstart.ts`:
```typescript
import Anthropic from "@anthropic-ai/sdk";
async function main() {
const anthropic = new Anthropic();
const msg = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1000,
messages: [
{
role: "user",
content:
"What should I search for to find the latest developments in renewable energy?"
}
]
});
console.log(msg);
}
main().catch(console.error);
```
```bash
npx tsx quickstart.ts
```
**Example output:**
```javascript Output hidelines={1..2}
const _ =
// output
{
id: "msg_01ThFHzad6Bh4TpQ6cHux9t8",
type: "message",
role: "assistant",
model: "claude-opus-4-7",
content: [
{
type: "text",
text:
"Here are some effective search strategies to find the latest renewable energy developments:\n\n" +
"## Search Terms to Use:\n" +
'- "renewable energy news 2024"\n' +
'- "clean energy breakthroughs"\n' +
'- "solar wind technology advances"\n' +
'- "energy storage innovations"\n' +
'- "green hydrogen developments"\n' +
'- "offshore wind projects"\n' +
'- "battery technology renewable"\n\n' +
"## Best Sources to Check:\n\n" +
"**News & Industry Sites:**\n" +
"- Renewable Energy World\n" +
"- CleanTechnica\n" +
"- GreenTech Media (now Wood Mackenzie)\n" +
"- Energy Storage News\n" +
"- PV Magazine (for solar)..."
}
],
stop_reason: "end_turn",
usage: {
input_tokens: 21,
output_tokens: 302
}
}
```
Get your API key from the [Claude Console](/settings/keys) and set it as an environment variable:
```bash
export ANTHROPIC_API_KEY='your-api-key-here'
```
To persist the key across shell sessions, add the line to your shell profile (such as `~/.zshrc` or `~/.bashrc`).
You need a JDK (25 or later) and either [Gradle](https://gradle.org/install/) or [Maven](https://maven.apache.org/install.html) on your `PATH`. Create a directory for your project with a Java source directory inside it:
```bash
mkdir -p claude-quickstart/src/main/java && cd claude-quickstart
```
Then add a build file. Find the current SDK version on [Maven Central](https://central.sonatype.com/artifact/com.anthropic/anthropic-java).
Save this as `build.gradle.kts`:
```kotlin
plugins {
application
}
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
dependencies {
implementation("com.anthropic:anthropic-java:2.30.0")
}
application {
mainClass = "QuickStart"
}
```
Save this as `pom.xml`:
```xml
4.0.0com.examplequickstart1.0-SNAPSHOT25UTF-8com.anthropicanthropic-java2.30.0
```
Save this as `QuickStart.java` in your project's Java source directory (usually `src/main/java/`):
```java
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
static void main() {
var client = AnthropicOkHttpClient.fromEnv();
var params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1000)
.addUserMessage(
"What should I search for to find the latest developments in renewable energy?"
)
.build();
Message message = client.messages().create(params);
IO.println(message.content());
}
```
```bash
gradle run
```
```bash
mvn compile exec:java -Dexec.mainClass=QuickStart
```
**Example output:**
```text Output
[ContentBlock{text=TextBlock{text=Here are some effective search strategies to find the latest renewable energy developments:
## Search Terms to Use:
- "renewable energy news 2024"
- "clean energy breakthroughs"
- "solar/wind/battery technology advances"
- "energy storage innovations"
- "green hydrogen developments"
- "renewable energy policy updates"
## Best Sources to Check:
- **News & Analysis:** Reuters Energy, Bloomberg New Energy Finance, Greentech Media
- **Industry Publications:** Renewable Energy World, PV Magazine, Wind Power Engineering
- **Research Organizations:** International Energy Agency (IEA), National Renewable Energy Laboratory (NREL)
- **Government Sources:** Department of Energy websites, EPA clean energy updates
## Specific Topics to Explore:
- Perovskite and next-gen solar cells
- Offshore wind expansion
- Grid-scale battery storage
- Green hydrogen production..., type=text}}]
```
## Next steps
You made your first API call. Next, learn the Messages API patterns you'll use in every Claude integration.
Learn multi-turn conversations, system prompts, stop reasons, and other core patterns.
Once you're comfortable with the basics, explore further:
Compare Claude models by capability and cost.
Browse all Claude capabilities: tools, context management, structured outputs, and more.
Reference documentation for Python, TypeScript, Java, and other client libraries.
---
# Intro to Claude
URL: https://platform.claude.com/docs/en/intro
# Intro to Claude
Claude is a highly performant, trustworthy, and intelligent AI platform built by Anthropic. Claude excels at tasks involving language, reasoning, analysis, coding, and more.
---
The latest generation of Claude models:
**Claude Opus 4.7** - Our most capable model for complex reasoning and agentic coding, with a step-change jump over Claude Opus 4.6. [Learn more](https://www.anthropic.com/news/claude-opus-4-7).
**Claude Sonnet 4.6** - Frontier intelligence at scale—built for coding, agents, and enterprise workflows. [Learn more](https://www.anthropic.com/news/claude-sonnet-4-6).
**Claude Haiku 4.5** - Fastest model with near-frontier intelligence. [Learn more](https://www.anthropic.com/news/claude-haiku-4-5).
Looking to chat with Claude? Visit [claude.ai](https://www.claude.ai).
Anthropic offers two ways to build with Claude, each suited to different use cases:
| | Messages API | Claude Managed Agents |
|---|---|---|
| **What it is** | Direct model prompting access | Pre-built, configurable agent harness that runs in managed infrastructure |
| **Best for** | Custom agent loops and fine-grained control | Long-running tasks and asynchronous work |
| **Learn more** | [Messages API docs](/docs/en/build-with-claude/working-with-messages) | [Claude Managed Agents docs](/docs/en/managed-agents/overview) |
## Recommended path for new developers
Follow these steps to go from zero to a working Claude integration.
Set up your environment, install an SDK, and send your first message to Claude.
[Go to the quickstart](/docs/en/get-started)
Learn the core request and response structure, including multi-turn conversations, system prompts, and stop reasons.
[Read the Messages API guide](/docs/en/build-with-claude/working-with-messages)
Compare Claude models by capability and cost to pick the best fit for your use case.
[See the models overview](/docs/en/about-claude/models/overview)
Discover what Claude can do: extended thinking, web search, file handling, structured outputs, and more.
[Browse the features overview](/docs/en/build-with-claude/overview)
---
## Develop with Claude
Anthropic provides developer tools to help you build and scale applications with Claude.
Prototype and test prompts in your browser with the Workbench and prompt generator.
Explore the full Claude API and client SDK documentation.
Learn with interactive Jupyter notebooks covering PDFs, embeddings, and more.
---
## Key capabilities
Claude can assist with many tasks that involve text, code, and images.
Summarize text, answer questions, extract data, translate text, and explain and generate code.
Process and analyze visual input and generate text and code from images.
---
## Support
Find answers to frequently asked account and billing questions.
Check the status of Anthropic services.
### Building with Claude
---
# Features overview
URL: https://platform.claude.com/docs/en/build-with-claude/overview
# Features overview
Explore Claude's advanced features and capabilities.
---
Claude's API surface is organized into five areas:
- **Model capabilities:** Control how Claude reasons and formats responses.
- **Tools:** Let Claude take actions on the web or in your environment.
- **Tool infrastructure:** Handles discovery and orchestration at scale.
- **Context management:** Keeps long-running sessions efficient.
- **Files and assets:** Manage the documents and data you provide to Claude.
If you're new, start with [model capabilities](#model-capabilities) and [tools](#tools). Return to the other sections when you're ready to optimize cost, latency, or scale.
For administration and governance, see the [Admin API](/docs/en/manage-claude/admin-api), the [Usage and Cost API](/docs/en/manage-claude/usage-cost-api), and the [Compliance API](/docs/en/manage-claude/compliance-api).
## Feature availability
Features on the Claude Platform are assigned one of the following availability classifications per platform (shown in the Availability column of each following table). Not all features pass through every stage. A feature may enter at any classification and may skip stages.
| Classification | Description |
|----------------|-------------|
| **Beta*** | Preview features used for gathering feedback and iterating on a less mature use case. Availability may be limited, including through sign-up requirements or waitlists, and may not be publicly announced.
Features may change significantly or be discontinued based on feedback. Not guaranteed for ongoing production use. Breaking changes are possible with notice, and some platform-specific limitations may apply. Beta features on the Claude API and [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws) have a [beta header](/docs/en/api/beta-headers). |
| **Generally available (GA)** | Feature is stable, fully supported, and recommended for production use. Should not have a beta header or other indicator that the feature is in a preview state. Covered by standard API [versioning](/docs/en/api/versioning) guarantees. |
| **Deprecated** | Feature is still functional but no longer recommended. A migration path and removal timeline are provided. |
| **Retired** | Feature is no longer available. |
_* May carry a qualifier indicating narrower availability or added constraints (for example, "beta: research preview"). See the feature's page for details._
**Platform labels:** Claude API (Anthropic first-party) · [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws) (Anthropic-operated on AWS) · [Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock) (AWS-operated) · [Vertex AI](/docs/en/build-with-claude/claude-on-vertex-ai) (Google-operated) · [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry) (Anthropic-operated on Azure)
## Model capabilities
Ways to steer Claude and Claude's direct outputs, including response format, reasoning depth, and input modalities.
You can discover which capabilities a model supports programmatically. The [Models API](/docs/en/api/models/list) returns `max_input_tokens`, `max_tokens`, and a `capabilities` object for every available model.
| Feature | Description | Zero Data Retention (ZDR) | Availability |
|---------|-----------|----|--------------|
| [Context windows](/docs/en/build-with-claude/context-windows) | Up to 1M tokens for processing large documents, extensive code bases, and long conversations. | ZDR eligible | |
| [Adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) | Let Claude dynamically decide when and how much to think. The recommended thinking mode for Opus 4.7. Use the effort parameter to control thinking depth. | ZDR eligible | |
| [Batch processing](/docs/en/build-with-claude/batch-processing) | Process large volumes of requests asynchronously for cost savings. Send batches with a large number of queries per batch. Batch API calls cost 50% less than standard API calls. | Not ZDR eligible | |
| [Citations](/docs/en/build-with-claude/citations) | Ground Claude's responses in source documents. With Citations, Claude can provide detailed references to the exact sentences and passages it uses to generate responses, leading to more verifiable, trustworthy outputs. | ZDR eligible | |
| [Data residency](/docs/en/manage-claude/data-residency) | Control where model inference runs using geographic controls. Specify `"global"` or `"us"` routing per request through the `inference_geo` parameter. | ZDR eligible | |
| [Effort](/docs/en/build-with-claude/effort) | Control how many tokens Claude uses when responding with the effort parameter, trading off between response thoroughness and token efficiency. Supported on Opus 4.7, Opus 4.6, Sonnet 4.6, and Opus 4.5. | ZDR eligible | |
| [Extended thinking](/docs/en/build-with-claude/extended-thinking) | Enhanced reasoning capabilities for complex tasks, providing transparency into Claude's step-by-step thought process before delivering its final answer. | ZDR eligible | |
| [PDF support](/docs/en/build-with-claude/pdf-support) | Process and analyze text and visual content from PDF documents. | ZDR eligible | |
| [Search results](/docs/en/build-with-claude/search-results) | Enable natural citations for RAG applications by providing search results with proper source attribution. Achieve web search-quality citations for custom knowledge bases and tools. | ZDR eligible | |
| [Structured outputs](/docs/en/build-with-claude/structured-outputs) | Guarantee schema conformance with two approaches: JSON outputs for structured data responses, and strict tool use for validated tool inputs. | [ZDR eligible (qualified)](/docs/en/build-with-claude/structured-outputs#data-retention)* | |
## Tools
Built-in tools that Claude invokes through `tool_use`. Server-side tools are run by the platform; client-side tools are implemented and executed by you.
### Server-side tools
| Feature | Description | ZDR | Availability |
|---------|-----------|----|--------------|
| [Advisor tool](/docs/en/agents-and-tools/tool-use/advisor-tool) | Pair a faster executor model with a higher-intelligence advisor model that provides strategic guidance mid-generation for long-horizon agentic workloads. | ZDR eligible | |
| [Code execution](/docs/en/agents-and-tools/tool-use/code-execution-tool) | Run code in a sandboxed environment for advanced data analysis, calculations, and file processing. Free when used with web search or web fetch. | Not ZDR eligible | |
| [Web fetch](/docs/en/agents-and-tools/tool-use/web-fetch-tool) | Retrieve full content from specified web pages and PDF documents for in-depth analysis. | ZDR eligible* | |
| [Web search](/docs/en/agents-and-tools/tool-use/web-search-tool) | Augment Claude's comprehensive knowledge with current, real-world data from across the web. | ZDR eligible* | |
### Client-side tools
| Feature | Description | ZDR | Availability |
|---------|-----------|----|--------------|
| [Bash](/docs/en/agents-and-tools/tool-use/bash-tool) | Execute bash commands and scripts to interact with the system shell and perform command-line operations. | ZDR eligible | |
| [Computer use](/docs/en/agents-and-tools/tool-use/computer-use-tool) | Control computer interfaces by taking screenshots and issuing mouse and keyboard commands. | ZDR eligible | |
| [Memory](/docs/en/agents-and-tools/tool-use/memory-tool) | Enable Claude to store and retrieve information across conversations. Build knowledge bases over time, maintain project context, and learn from past interactions. | ZDR eligible | |
| [Text editor](/docs/en/agents-and-tools/tool-use/text-editor-tool) | Create and edit text files with a built-in text editor interface for file manipulation tasks. | ZDR eligible | |
## Tool infrastructure
Infrastructure that supports discovering, orchestrating, and scaling tool use.
| Feature | Description | ZDR | Availability |
|---------|-----------|----|--------------|
| [Agent Skills](/docs/en/agents-and-tools/agent-skills/overview) | Extend Claude's capabilities with Skills. Use pre-built Skills (PowerPoint, Excel, Word, PDF) or create custom Skills with instructions and scripts. Skills use progressive disclosure to efficiently manage context. | Not ZDR eligible | |
| [Fine-grained tool streaming](/docs/en/agents-and-tools/tool-use/fine-grained-tool-streaming) | Stream tool use parameters without buffering/JSON validation, reducing latency for receiving large parameters. | ZDR eligible | |
| [MCP connector](/docs/en/agents-and-tools/mcp-connector) | Connect to remote [MCP](/docs/en/mcp) servers directly from the Messages API without a separate MCP client. | Not ZDR eligible | |
| [Programmatic tool calling](/docs/en/agents-and-tools/tool-use/programmatic-tool-calling) | Enable Claude to call your tools programmatically from within code execution containers, reducing latency and token consumption for multi-tool workflows. | Not ZDR eligible | |
| [Tool search](/docs/en/agents-and-tools/tool-use/tool-search-tool) | Scale to thousands of tools by dynamically discovering and loading tools on-demand using regex-based search, optimizing context usage and improving tool selection accuracy. | ZDR eligible | |
## Context management
Infrastructure for controlling and optimizing Claude's context window.
| Feature | Description | ZDR | Availability |
|---------|-----------|----|--------------|
| [Compaction](/docs/en/build-with-claude/compaction) | Server-side context summarization for long-running conversations. When context approaches the window limit, the API automatically summarizes earlier parts of the conversation. Supported on Opus 4.7, Opus 4.6, and Sonnet 4.6. | ZDR eligible | |
| [Context editing](/docs/en/build-with-claude/context-editing) | Automatically manage conversation context with configurable strategies. Supports clearing tool results when approaching token limits and managing thinking blocks in extended thinking conversations. | ZDR eligible | |
| [Automatic prompt caching](/docs/en/build-with-claude/prompt-caching#automatic-caching) | Simplify prompt caching to a single API parameter. The system automatically caches the last cacheable block in your request, moving the cache point forward as conversations grow. | ZDR eligible | |
| [Prompt caching (5m)](/docs/en/build-with-claude/prompt-caching) | Provide Claude with more background knowledge and example outputs to reduce costs and latency. | ZDR eligible | |
| [Prompt caching (1hr)](/docs/en/build-with-claude/prompt-caching#1-hour-cache-duration) | Extended 1-hour cache duration for less frequently accessed but important context, complementing the standard 5-minute cache. | ZDR eligible | |
| [Token counting](/docs/en/build-with-claude/token-counting) | Token counting enables you to determine the number of tokens in a message before sending it to Claude, helping you make informed decisions about your prompts and usage. | ZDR eligible | |
## Files and assets
Manage files and assets for use with Claude.
| Feature | Description | ZDR | Availability |
|---------|-----------|----|--------------|
| [Files API](/docs/en/build-with-claude/files) | Upload and manage files to use with Claude without re-uploading content with each request. Supports PDFs, images, and text files. | Not ZDR eligible | |
\* **Structured outputs:** Your prompts and Claude's outputs are not stored. Only JSON schemas are cached, for up to 24 hours since last use. **Web search and web fetch:** ZDR-eligible except when [dynamic filtering](/docs/en/agents-and-tools/tool-use/web-search-tool#dynamic-filtering) is enabled. See [ZDR details](/docs/en/manage-claude/api-and-data-retention#feature-eligibility).
---
# Handling stop reasons
URL: https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons
# Handling stop reasons
---
When you make a request to the Messages API, Claude's response includes a `stop_reason` field that indicates why the model stopped generating its response. Understanding these values is crucial for building robust applications that handle different response types appropriately.
For details about `stop_reason` in the API response, see the [Messages API reference](/docs/en/api/messages/create).
## The stop_reason field
The `stop_reason` field is part of every successful Messages API response. Unlike errors, which indicate failures in processing your request, `stop_reason` tells you why Claude successfully completed its response generation.
```json Example response
{
"id": "msg_01234",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "Here's the answer to your question..."
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}
```
## Stop reason values
### end_turn
The most common stop reason. Indicates Claude finished its response naturally.
```python Python
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
if response.stop_reason == "end_turn":
# Process the complete response
print(response.content[0].text)
```
#### Empty responses with end_turn
Sometimes Claude returns an empty response (exactly 2-3 tokens with no content) with `stop_reason: "end_turn"`. This typically happens when Claude interprets that the assistant turn is complete, particularly after tool results.
**Common causes:**
- Adding text blocks immediately after tool results (Claude learns to expect the user to always insert text after tool results, so it ends its turn to follow the pattern)
- Sending Claude's completed response back without adding anything (Claude already decided it's done, so it will remain done)
**How to prevent empty responses:**
```python nocheck
# INCORRECT: Adding text immediately after tool_result
messages = [
{"role": "user", "content": "Calculate the sum of 1234 and 5678"},
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_123",
"name": "calculator",
"input": {"operation": "add", "a": 1234, "b": 5678},
}
],
},
{
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": "toolu_123", "content": "6912"},
{
"type": "text",
"text": "Here's the result", # Don't add text after tool_result
},
],
},
]
# CORRECT: Send tool results directly without additional text
messages = [
{"role": "user", "content": "Calculate the sum of 1234 and 5678"},
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_123",
"name": "calculator",
"input": {"operation": "add", "a": 1234, "b": 5678},
}
],
},
{
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": "toolu_123", "content": "6912"}
],
}, # Just the tool_result, no additional text
]
# If you still get empty responses after fixing the above:
def handle_empty_response(client, messages):
response = client.messages.create(
model="claude-opus-4-7", max_tokens=1024, messages=messages
)
# Check if response is empty
if response.stop_reason == "end_turn" and not response.content:
# INCORRECT: Don't just retry with the empty response
# This won't work because Claude already decided it's done
# CORRECT: Add a continuation prompt in a NEW user message
messages.append({"role": "user", "content": "Please continue"})
response = client.messages.create(
model="claude-opus-4-7", max_tokens=1024, messages=messages
)
return response
```
**Best practices:**
1. **Never add text blocks immediately after tool results** - This teaches Claude to expect user input after every tool use
2. **Don't retry empty responses without modification** - Simply sending the empty response back won't help
3. **Use continuation prompts as a last resort** - Only if the above fixes don't resolve the issue
### max_tokens
Claude stopped because it reached the `max_tokens` limit specified in your request.
```python Python
# Request with limited tokens
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=10,
messages=[{"role": "user", "content": "Explain quantum physics"}],
)
if response.stop_reason == "max_tokens":
# Response was truncated
print("Response was cut off at token limit")
# Consider making another request to continue
```
#### Incomplete tool use blocks
If Claude's response is cut off due to hitting the `max_tokens` limit, and the truncated response contains an incomplete tool use block, you'll need to retry the request with a higher `max_tokens` value to get the full tool use.
```bash CLI nocheck
RESPONSE=$(ant messages create --max-tokens 1024 \
--format jsonl < request.yaml)
# Check if the response was truncated mid tool use
STOP_REASON=$(jq -r '.stop_reason' <<<"$RESPONSE")
LAST_TYPE=$(jq -r '.content[-1].type' <<<"$RESPONSE")
if [ "$STOP_REASON" = "max_tokens" ] && [ "$LAST_TYPE" = "tool_use" ]; then
# Retry with a higher max_tokens
ant messages create --max-tokens 4096 < request.yaml
fi
```
```python Python nocheck hidelines={1..8}
import anthropic
client = anthropic.Anthropic()
tools: list[dict] = []
messages: list[dict] = []
response = client.messages.create(
model="claude-opus-4-7", max_tokens=1024, tools=tools, messages=messages
)
# Check if response was truncated during tool use
if response.stop_reason == "max_tokens":
# Check if the last content block is an incomplete tool_use
last_block = response.content[-1]
if last_block.type == "tool_use":
# Send the request with higher max_tokens
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096, # Increased limit
messages=messages,
tools=tools,
)
```
```typescript TypeScript nocheck
// Check if response was truncated during tool use
if (response.stop_reason === "max_tokens") {
// Check if the last content block is an incomplete tool_use
const lastBlock = response.content[response.content.length - 1];
if (lastBlock.type === "tool_use") {
// Send the request with higher max_tokens
response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096, // Increased limit
messages: messages,
tools: tools
});
}
}
```
```csharp C# nocheck
using System.Linq;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = messages,
Tools = tools
};
var response = await client.Messages.Create(parameters);
if (response.StopReason == "max_tokens")
{
var lastBlock = response.Content.Last();
if (lastBlock.Type == "tool_use")
{
parameters.MaxTokens = 4096;
response = await client.Messages.Create(parameters);
}
}
```
```go Go hidelines={1..15,-3..-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
tools := []anthropic.ToolUnionParam{}
messages := []anthropic.MessageParam{anthropic.NewUserMessage(anthropic.NewTextBlock("test"))}
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: messages,
Tools: tools,
})
if err != nil {
log.Fatal(err)
}
if response.StopReason == "max_tokens" {
lastBlock := response.Content[len(response.Content)-1]
switch lastBlock.AsAny().(type) {
case anthropic.ToolUseBlock:
response, err = client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: messages,
Tools: tools,
})
if err != nil {
log.Fatal(err)
}
}
}
fmt.Println(response)
}
```
```java Java nocheck hidelines={1..13}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.ContentBlock;
import java.util.List;
import com.anthropic.models.messages.StopReason;
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
List messages = List.of();
List tools = List.of();
Message response = client.messages().create(MessageCreateParams.builder().model(Model.CLAUDE_OPUS_4_7).maxTokens(1024L).addUserMessage("test").build());
// Check if response was truncated during tool use
if (response.stopReason().isPresent() && response.stopReason().get().equals(StopReason.MAX_TOKENS)) {
ContentBlock lastBlock = response.content().get(response.content().size() - 1);
if (lastBlock.toolUse().isPresent()) {
// Send the request with higher max_tokens
response = client.messages().create(
MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(4096L) // Increased limit
.messages(messages)
.tools(tools)
.build()
);
}
}
```
```php PHP hidelines={1..6} nocheck
messages->create(
maxTokens: 1024,
messages: $messages,
model: 'claude-opus-4-7',
tools: $tools,
);
if ($response->stopReason === 'max_tokens') {
$lastBlock = end($response->content);
if ($lastBlock->type === 'tool_use') {
$response = $client->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
tools: $tools,
);
}
}
```
```ruby Ruby hidelines={1..15}
require "anthropic"
client = Anthropic::Client.new
tools = [
{
name: "get_weather",
description: "Get the current weather in a given location",
input_schema: { type: "object", properties: { location: { type: "string" } }, required: ["location"] }
}
]
messages = [
{ role: "user", content: "What's the weather in San Francisco?" }
]
response = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: messages,
tools: tools
)
if response.stop_reason == :max_tokens
last_block = response.content.last
if last_block.type == :tool_use
response = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
messages: messages,
tools: tools
)
end
end
```
### stop_sequence
Claude encountered one of your custom stop sequences.
```python Python
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
stop_sequences=["END", "STOP"],
messages=[{"role": "user", "content": "Generate text until you say END"}],
)
if response.stop_reason == "stop_sequence":
print(f"Stopped at sequence: {response.stop_sequence}")
```
### tool_use
Claude is calling a tool and expects you to execute it.
For most tool use implementations, we recommend using the [tool runner](/docs/en/agents-and-tools/tool-use/tool-runner) which automatically handles tool execution, result formatting, and conversation management.
```python Python nocheck
from anthropic import Anthropic
client = Anthropic()
weather_tool = {
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City and state"},
},
"required": ["location"],
},
}
def execute_tool(name, tool_input):
"""Execute a tool and return the result."""
return f"Weather in {tool_input.get('location', 'unknown')}: 72°F"
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=[weather_tool],
messages=[{"role": "user", "content": "What's the weather?"}],
)
if response.stop_reason == "tool_use":
# Extract and execute the tool
for content in response.content:
if content.type == "tool_use":
result = execute_tool(content.name, content.input)
# Return result to Claude for final response
```
### pause_turn
Returned when the server-side sampling loop reaches its iteration limit while executing [server tools](/docs/en/agents-and-tools/tool-use/server-tools) like web search or web fetch. The default limit is 10 iterations per request.
When this happens, the response may contain a `server_tool_use` block without a corresponding `server_tool_result`. To let Claude finish processing, continue the conversation by sending the response back as-is.
```python Python nocheck
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
tools=[{"type": "web_search_20250305", "name": "web_search"}],
messages=[{"role": "user", "content": "Search for latest AI news"}],
)
if response.stop_reason == "pause_turn":
# Continue the conversation by sending the response back
messages = [
{"role": "user", "content": original_query},
{"role": "assistant", "content": response.content},
]
continuation = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=messages,
tools=[{"type": "web_search_20250305", "name": "web_search"}],
)
```
Your application should handle `pause_turn` in any agent loop that uses server tools. Simply add the assistant's response to your messages array and make another API request to let Claude continue.
### refusal
Claude refused to generate a response due to safety concerns.
```python Python
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{"role": "user", "content": "[Unsafe request]"}],
)
if response.stop_reason == "refusal":
# Claude declined to respond
print("Claude was unable to process this request")
# Consider rephrasing or modifying the request
```
If you encounter `refusal` stop reasons frequently while using Claude Sonnet 4.5 or Opus 4.1, you can try updating your API calls to use Haiku 4.5 (`claude-haiku-4-5-20251001`), which has different usage restrictions. Learn more about [understanding Sonnet 4.5's API safety filters](https://support.claude.com/en/articles/12449294-understanding-sonnet-4-5-s-api-safety-filters).
### model_context_window_exceeded
Claude stopped because it reached the model's context window limit. This allows you to request the maximum possible tokens without knowing the exact input size.
```python Python nocheck
# Request with maximum tokens to get as much as possible
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k (Opus 4.7 supports 128k with streaming)
messages=[
{"role": "user", "content": "Large input that uses most of context window..."}
],
)
if response.stop_reason == "model_context_window_exceeded":
# Response hit context window limit before max_tokens
print("Response reached model's context window limit")
# The response is still valid but was limited by context window
```
This stop reason is available by default in Sonnet 4.5 and newer models. For earlier models, use the beta header `model-context-window-exceeded-2025-08-26` to enable this behavior.
## Best practices for handling stop reasons
### 1. Always check stop_reason
Make it a habit to check the `stop_reason` in your response handling logic:
```python nocheck
def handle_response(response):
if response.stop_reason == "tool_use":
return handle_tool_use(response)
elif response.stop_reason == "max_tokens":
return handle_truncation(response)
elif response.stop_reason == "model_context_window_exceeded":
return handle_context_limit(response)
elif response.stop_reason == "pause_turn":
return handle_pause(response)
elif response.stop_reason == "refusal":
return handle_refusal(response)
else:
# Handle end_turn and other cases
return response.content[0].text
```
### 2. Handle truncated responses gracefully
When a response is truncated due to token limits or context window:
```python nocheck
def handle_truncated_response(response):
if response.stop_reason in ["max_tokens", "model_context_window_exceeded"]:
# Option 1: Warn the user about the specific limit
if response.stop_reason == "max_tokens":
message = "[Response truncated due to max_tokens limit]"
else:
message = "[Response truncated due to context window limit]"
return f"{response.content[0].text}\n\n{message}"
# Option 2: Continue generation
messages = [
{"role": "user", "content": original_prompt},
{"role": "assistant", "content": response.content[0].text},
]
continuation = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=messages + [{"role": "user", "content": "Please continue"}],
)
return response.content[0].text + continuation.content[0].text
```
### 3. Implement retry logic for pause_turn
When using [server tools](/docs/en/agents-and-tools/tool-use/server-tools), the API may return `pause_turn` if the server-side sampling loop reaches its iteration limit (default 10). Handle this by continuing the conversation:
```python nocheck
def handle_server_tool_conversation(client, user_query, tools, max_continuations=5):
"""
Handle server tool conversations that may require multiple continuations.
The server runs a sampling loop when executing server tools. If the loop
reaches its iteration limit, the API returns pause_turn. Continue the
conversation by sending the response back to let Claude finish.
"""
messages = [{"role": "user", "content": user_query}]
for _ in range(max_continuations):
response = client.messages.create(
model="claude-opus-4-7", max_tokens=1024, messages=messages, tools=tools
)
if response.stop_reason != "pause_turn":
# Claude finished processing - return the final response
return response
# pause_turn: replace the full message list to maintain alternating roles
messages = [
{"role": "user", "content": user_query},
{"role": "assistant", "content": response.content},
]
# Reached max continuations - return the last response
return response
```
## Stop reasons vs. errors
It's important to distinguish between `stop_reason` values and actual errors:
### Stop reasons (successful responses)
- Part of the response body
- Indicate why generation stopped normally
- Response contains valid content
### Errors (failed requests)
- HTTP status codes 4xx or 5xx
- Indicate request processing failures
- Response contains error details
```python Python
import anthropic
from anthropic import Anthropic
client = Anthropic()
try:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
# Handle successful response with stop_reason
if response.stop_reason == "max_tokens":
print("Response was truncated")
except anthropic.APIStatusError as e:
# Handle actual errors
if e.status_code == 429:
print("Rate limit exceeded")
elif e.status_code == 500:
print("Server error")
```
## Streaming considerations
When using streaming, `stop_reason` is:
- `null` in the initial `message_start` event
- Provided in the `message_delta` event
- Not provided in any other events
```python Python
from anthropic import Anthropic
client = Anthropic()
with client.messages.stream(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
) as stream:
for event in stream:
if event.type == "message_delta":
stop_reason = event.delta.stop_reason
if stop_reason:
print(f"Stream ended with: {stop_reason}")
```
## Common patterns
### Handling tool use workflows
**Simpler with tool runner**: The example below shows manual tool handling. For most use cases, the [tool runner](/docs/en/agents-and-tools/tool-use/tool-runner) automatically handles tool execution with much less code.
```python nocheck
def complete_tool_workflow(client, user_query, tools):
messages = [{"role": "user", "content": user_query}]
while True:
response = client.messages.create(
model="claude-opus-4-7", max_tokens=1024, messages=messages, tools=tools
)
if response.stop_reason == "tool_use":
# Execute tools and continue
tool_results = execute_tools(response.content)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
# Final response
return response
```
### Ensuring complete responses
```python nocheck
def get_complete_response(client, prompt, max_attempts=3):
messages = [{"role": "user", "content": prompt}]
full_response = ""
for _ in range(max_attempts):
response = client.messages.create(
model="claude-opus-4-7", messages=messages, max_tokens=4096
)
full_response += response.content[0].text
if response.stop_reason != "max_tokens":
break
# Continue from where it left off
messages = [
{"role": "user", "content": prompt},
{"role": "assistant", "content": full_response},
{"role": "user", "content": "Please continue from where you left off."},
]
return full_response
```
### Getting maximum tokens without knowing input size
With the `model_context_window_exceeded` stop reason, you can request the maximum possible tokens without calculating input size:
```python
def get_max_possible_tokens(client, prompt):
"""
Get as many tokens as possible within the model's context window
without needing to calculate input token count
"""
response = client.messages.create(
model="claude-opus-4-7",
messages=[{"role": "user", "content": prompt}],
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k
)
if response.stop_reason == "model_context_window_exceeded":
# Got the maximum possible tokens given input size
print(
f"Generated {response.usage.output_tokens} tokens (context limit reached)"
)
elif response.stop_reason == "max_tokens":
# Got exactly the requested tokens
print(f"Generated {response.usage.output_tokens} tokens (max_tokens reached)")
else:
# Natural completion
print(f"Generated {response.usage.output_tokens} tokens (natural completion)")
return response.content[0].text
```
By properly handling `stop_reason` values, you can build more robust applications that gracefully handle different response scenarios and provide better user experiences.
---
# Using the Messages API
URL: https://platform.claude.com/docs/en/build-with-claude/working-with-messages
# Using the Messages API
Practical patterns and examples for using the Messages API effectively
---
Anthropic offers two ways to build with Claude, each suited to different use cases:
| | Messages API | Claude Managed Agents |
|---|---|---|
| **What it is** | Direct model prompting access | Pre-built, configurable agent harness that runs in managed infrastructure |
| **Best for** | Custom agent loops and fine-grained control | Long-running tasks and asynchronous work |
| **Learn more** | [Messages API docs](/docs/en/build-with-claude/working-with-messages) | [Claude Managed Agents docs](/docs/en/managed-agents/overview) |
This guide covers common patterns for working with the Messages API, including basic requests, multi-turn conversations, prefill techniques, and vision capabilities. For complete API specifications, see the [Messages API reference](/docs/en/api/messages/create).
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
## Basic request and response
```bash cURL
#!/bin/sh
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hello, Claude"}
]
}'
```
```bash CLI
ant messages create \
--model claude-opus-4-7 \
--max-tokens 1024 \
--message '{role: user, content: "Hello, Claude"}'
```
```python Python hidelines={1..2}
import anthropic
message = anthropic.Anthropic().messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(message)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const message = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello, Claude" }]
});
console.log(message);
```
```csharp C#
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello, Claude" }]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, Claude")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..8,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
public class Main {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessage("Hello, Claude")
.build();
Message response = client.messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello, Claude']],
model: 'claude-opus-4-7',
);
echo $message->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "Hello, Claude" }
]
)
puts message
```
```json Output
{
"id": "msg_01XFDUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "Hello!"
}
],
"model": "claude-opus-4-7",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 12,
"output_tokens": 6
}
}
```
## Multiple conversational turns
The Messages API is stateless, which means that you always send the full conversational history to the API. You can use this pattern to build up a conversation over time. Earlier conversational turns don't necessarily need to actually originate from Claude. You can use synthetic `assistant` messages.
```bash cURL
#!/bin/sh
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hello, Claude"},
{"role": "assistant", "content": "Hello!"},
{"role": "user", "content": "Can you describe LLMs to me?"}
]
}'
```
```bash CLI
ant messages create \
--model claude-opus-4-7 \
--max-tokens 1024 \
--message '{role: user, content: "Hello, Claude"}' \
--message '{role: assistant, content: "Hello!"}' \
--message '{role: user, content: "Can you describe LLMs to me?"}'
```
```python Python hidelines={1..2}
import anthropic
message = anthropic.Anthropic().messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{"role": "user", "content": "Hello, Claude"},
{"role": "assistant", "content": "Hello!"},
{"role": "user", "content": "Can you describe LLMs to me?"},
],
)
print(message)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const message = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "Hello, Claude" },
{ role: "assistant", content: "Hello!" },
{ role: "user", content: "Can you describe LLMs to me?" }
]
});
console.log(message);
```
```csharp C#
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages =
[
new() { Role = Role.User, Content = "Hello, Claude" },
new() { Role = Role.Assistant, Content = "Hello!" },
new() { Role = Role.User, Content = "Can you describe LLMs to me?" }
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, Claude")),
anthropic.NewAssistantMessage(anthropic.NewTextBlock("Hello!")),
anthropic.NewUserMessage(anthropic.NewTextBlock("Can you describe LLMs to me?")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..8,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
public class MultiTurnConversation {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessage("Hello, Claude")
.addAssistantMessage("Hello!")
.addUserMessage("Can you describe LLMs to me?")
.build();
Message response = client.messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Hello, Claude'],
['role' => 'assistant', 'content' => 'Hello!'],
['role' => 'user', 'content' => 'Can you describe LLMs to me?'],
],
model: 'claude-opus-4-7',
);
echo $message->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "Hello, Claude" },
{ role: "assistant", content: "Hello!" },
{ role: "user", content: "Can you describe LLMs to me?" }
]
)
puts message
```
```json Output
{
"id": "msg_018gCsTGsXkYJVqYPxTgDHBU",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "Sure, I'd be happy to provide..."
}
],
"model": "claude-opus-4-7",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 30,
"output_tokens": 309
}
}
```
## Putting words in Claude's mouth
You can pre-fill part of Claude's response in the last position of the input messages list. This can be used to shape Claude's response. The example below uses `"max_tokens": 1` to get a single multiple choice answer from Claude.
Prefilling is not supported on [Claude Mythos Preview](https://anthropic.com/glasswing), Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6. Requests using prefill with these models return a 400 error. Use [structured outputs](/docs/en/build-with-claude/structured-outputs) or system prompt instructions instead. See the [migration guide](/docs/en/about-claude/models/migration-guide) for migration patterns.
```bash cURL
#!/bin/sh
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-sonnet-4-5",
"max_tokens": 1,
"messages": [
{"role": "user", "content": "What is latin for Ant? (A) Apoidea, (B) Rhopalocera, (C) Formicidae"},
{"role": "assistant", "content": "The answer is ("}
]
}'
```
```bash CLI
ant messages create <<'YAML'
model: claude-sonnet-4-5
max_tokens: 1
messages:
- role: user
content: "What is latin for Ant? (A) Apoidea, (B) Rhopalocera, (C) Formicidae"
- role: assistant
content: "The answer is ("
YAML
```
```python Python hidelines={1..2}
import anthropic
message = anthropic.Anthropic().messages.create(
model="claude-sonnet-4-5",
max_tokens=1,
messages=[
{
"role": "user",
"content": "What is latin for Ant? (A) Apoidea, (B) Rhopalocera, (C) Formicidae",
},
{"role": "assistant", "content": "The answer is ("},
],
)
print(message)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const message = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1,
messages: [
{
role: "user",
content: "What is latin for Ant? (A) Apoidea, (B) Rhopalocera, (C) Formicidae"
},
{ role: "assistant", content: "The answer is (" }
]
});
console.log(message);
```
```csharp C#
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_5,
MaxTokens = 1,
Messages = [
new() { Role = Role.User, Content = "What is latin for Ant? (A) Apoidea, (B) Rhopalocera, (C) Formicidae" },
new() { Role = Role.Assistant, Content = "The answer is (" }
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_5,
MaxTokens: 1,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is latin for Ant? (A) Apoidea, (B) Rhopalocera, (C) Formicidae")),
anthropic.NewAssistantMessage(anthropic.NewTextBlock("The answer is (")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..8,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
public class PrefillExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_5)
.maxTokens(1L)
.addUserMessage("What is latin for Ant? (A) Apoidea, (B) Rhopalocera, (C) Formicidae")
.addAssistantMessage("The answer is (")
.build();
Message response = client.messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1,
messages: [
['role' => 'user', 'content' => 'What is latin for Ant? (A) Apoidea, (B) Rhopalocera, (C) Formicidae'],
['role' => 'assistant', 'content' => 'The answer is ('],
],
model: 'claude-sonnet-4-5',
);
echo $message->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-sonnet-4-5",
max_tokens: 1,
messages: [
{
role: "user",
content: "What is latin for Ant? (A) Apoidea, (B) Rhopalocera, (C) Formicidae"
},
{ role: "assistant", content: "The answer is (" }
]
)
puts message
```
```json Output
{
"id": "msg_01Q8Faay6S7QPTvEUUQARt7h",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "C"
}
],
"model": "claude-sonnet-4-5",
"stop_reason": "max_tokens",
"stop_sequence": null,
"usage": {
"input_tokens": 42,
"output_tokens": 1
}
}
```
## Vision
Claude can read both text and images in requests. Images can be supplied using the `base64`, `url`, or `file` source types. The `file` source type references an image uploaded through the [Files API](/docs/en/build-with-claude/files). Supported media types are `image/jpeg`, `image/png`, `image/gif`, and `image/webp`. See the [vision guide](/docs/en/build-with-claude/vision) for more details.
```bash cURL
#!/bin/sh
# Option 1: Base64-encoded image
IMAGE_URL="https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
IMAGE_MEDIA_TYPE="image/jpeg"
IMAGE_BASE64=$(curl "$IMAGE_URL" | base64 | tr -d '\n')
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": [
{"type": "image", "source": {
"type": "base64",
"media_type": "'$IMAGE_MEDIA_TYPE'",
"data": "'$IMAGE_BASE64'"
}},
{"type": "text", "text": "What is in the above image?"}
]}
]
}'
# Option 2: URL-referenced image
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": [
{"type": "image", "source": {
"type": "url",
"url": "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
}},
{"type": "text", "text": "What is in the above image?"}
]}
]
}'
```
```bash CLI nocheck
IMAGE_URL="https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
# Option 1: Base64-encoded image (CLI auto-encodes binary @file refs)
curl -s "$IMAGE_URL" -o ./ant.jpg
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content:
- type: image
source:
type: base64
media_type: image/jpeg
data: "@./ant.jpg"
- type: text
text: What is in the above image?
YAML
# Option 2: URL-referenced image
ant messages create <
{
new ContentBlockParam(new ImageBlockParam(
new ImageBlockParamSource(new Base64ImageSource()
{
Data = imageData,
MediaType = MediaType.ImageJpeg,
})
)),
new ContentBlockParam(new TextBlockParam("What is in the above image?")),
}),
}
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
// Option 2: URL-referenced image
var parametersFromUrl = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages =
[
new()
{
Role = Role.User,
Content = new MessageParamContent(new List
{
new ContentBlockParam(new ImageBlockParam(
new ImageBlockParamSource(new UrlImageSource()
{
Url = new Uri("https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"),
})
)),
new ContentBlockParam(new TextBlockParam("What is in the above image?")),
}),
}
]
};
var messageFromUrl = await client.Messages.Create(parametersFromUrl);
Console.WriteLine(messageFromUrl);
```
```go Go nocheck hidelines={1..16,-1}
package main
import (
"context"
"encoding/base64"
"fmt"
"io"
"log"
"net/http"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
// Option 1: Base64-encoded image
imageURL := "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
req, err := http.NewRequest("GET", imageURL, nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("User-Agent", "AnthropicDocsBot/1.0")
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
imageBytes, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
imageData := base64.StdEncoding.EncodeToString(imageBytes)
message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(
anthropic.NewImageBlockBase64("image/jpeg", imageData),
anthropic.NewTextBlock("What is in the above image?"),
),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(message)
// Option 2: URL-referenced image
messageFromURL, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(
anthropic.NewImageBlock(anthropic.URLImageSourceParam{
URL: "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg",
}),
anthropic.NewTextBlock("What is in the above image?"),
),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(messageFromURL)
}
```
```java Java nocheck hidelines={1..12,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.*;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Base64;
import java.util.List;
public class VisionExample {
public static void main(String[] args) throws Exception {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Option 1: Base64-encoded image
String imageUrl = "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg";
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(imageUrl)).build();
HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
String imageData = Base64.getEncoder().encodeToString(response.body());
List base64Content = List.of(
ContentBlockParam.ofImage(
ImageBlockParam.builder()
.source(Base64ImageSource.builder()
.data(imageData)
.mediaType(Base64ImageSource.MediaType.IMAGE_JPEG)
.build())
.build()),
ContentBlockParam.ofText(
TextBlockParam.builder()
.text("What is in the above image?")
.build())
);
Message message = client.messages().create(
MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessageOfBlockParams(base64Content)
.build());
System.out.println(message);
// Option 2: URL-referenced image
List urlContent = List.of(
ContentBlockParam.ofImage(
ImageBlockParam.builder()
.source(UrlImageSource.builder()
.url("https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg")
.build())
.build()),
ContentBlockParam.ofText(
TextBlockParam.builder()
.text("What is in the above image?")
.build())
);
Message messageFromUrl = client.messages().create(
MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessageOfBlockParams(urlContent)
.build());
System.out.println(messageFromUrl);
}
}
```
```php PHP hidelines={1..4} nocheck
messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'image',
'source' => [
'type' => 'base64',
'media_type' => $image_media_type,
'data' => $image_data,
],
],
[
'type' => 'text',
'text' => 'What is in the above image?',
],
],
],
],
model: 'claude-opus-4-7',
);
echo $message;
// Option 2: URL-referenced image
$message_from_url = $client->messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'image',
'source' => [
'type' => 'url',
'url' => 'https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg',
],
],
[
'type' => 'text',
'text' => 'What is in the above image?',
],
],
],
],
model: 'claude-opus-4-7',
);
echo $message_from_url;
```
```ruby Ruby nocheck hidelines={1}
require "anthropic"
require "base64"
require "net/http"
client = Anthropic::Client.new
# Option 1: Base64-encoded image
image_url = "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
image_media_type = "image/jpeg"
image_data = Base64.strict_encode64(Net::HTTP.get(URI(image_url)))
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "image",
source: {
type: "base64",
media_type: image_media_type,
data: image_data
}
},
{
type: "text",
text: "What is in the above image?"
}
]
}
]
)
puts message
# Option 2: URL-referenced image
message_from_url = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "image",
source: {
type: "url",
url: "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
}
},
{
type: "text",
text: "What is in the above image?"
}
]
}
]
)
puts message_from_url
```
```json Output
{
"id": "msg_01EcyWo6m4hyW8KHs2y2pei5",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "This image shows an ant, specifically a close-up view of an ant. The ant is shown in detail, with its distinct head, antennae, and legs clearly visible. The image is focused on capturing the intricate details and features of the ant, likely taken with a macro lens to get an extreme close-up perspective."
}
],
"model": "claude-opus-4-7",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 1551,
"output_tokens": 71
}
}
```
## Tool use and computer use
See the [tool use guide](/docs/en/agents-and-tools/tool-use/overview) for examples of how to use tools with the Messages API.
See the [computer use guide](/docs/en/agents-and-tools/tool-use/computer-use-tool) for examples of how to control desktop computer environments with the Messages API.
For guaranteed JSON output, see [Structured Outputs](/docs/en/build-with-claude/structured-outputs).
### Model capabilities
---
# Adaptive thinking
URL: https://platform.claude.com/docs/en/build-with-claude/adaptive-thinking
# Adaptive thinking
Let Claude dynamically determine when and how much to use extended thinking with adaptive thinking mode.
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
Adaptive thinking is the recommended way to use [extended thinking](/docs/en/build-with-claude/extended-thinking) with Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6, and is the default mode on [Claude Mythos Preview](https://anthropic.com/glasswing) (where it auto-applies whenever `thinking` is unset). Instead of manually setting a thinking token budget, adaptive thinking lets Claude dynamically determine when and how much to use extended thinking based on the complexity of each request. On Claude Opus 4.7, adaptive thinking is the **only** supported thinking mode; manual `thinking: {type: "enabled", budget_tokens: N}` is no longer accepted.
Adaptive thinking can drive better performance than extended thinking with a fixed `budget_tokens` for many workloads, especially bimodal tasks and long-horizon agentic workflows. No beta header is required.
If your workload requires predictable latency or precise control over thinking costs, extended thinking with `budget_tokens` is still functional on Claude Opus 4.6 and Claude Sonnet 4.6 but is deprecated and no longer recommended. See the warning below.
## Supported models
Adaptive thinking is supported on the following models:
- Claude Mythos Preview (`claude-mythos-preview`), adaptive thinking is the default; `thinking: {type: "disabled"}` is not supported
- Claude Opus 4.7 (`claude-opus-4-7`), adaptive thinking is the only supported thinking mode. Thinking is off unless you explicitly set `thinking: {type: "adaptive"}` in your request; manual `thinking: {type: "enabled"}` is rejected with a 400 error.
- Claude Opus 4.6 (`claude-opus-4-6`)
- Claude Sonnet 4.6 (`claude-sonnet-4-6`)
`thinking.type: "enabled"` and `budget_tokens` are [**deprecated**](/docs/en/build-with-claude/overview#feature-availability) on Opus 4.6 and Sonnet 4.6 and will be removed in a future model release. Use `thinking.type: "adaptive"` with the `effort` parameter instead. Existing `budget_tokens` configurations are still functional but no longer recommended; plan to migrate.
Older models (Sonnet 4.5, Opus 4.5, etc.) do not support adaptive thinking and require `thinking.type: "enabled"` with `budget_tokens`.
## How adaptive thinking works
In adaptive mode, thinking is optional for the model. Claude evaluates the complexity of each request and determines whether and how much to use extended thinking. At the default effort level (`high`), Claude almost always thinks. At lower effort levels, Claude may skip thinking for simpler problems.
Adaptive thinking also automatically enables [interleaved thinking](/docs/en/build-with-claude/extended-thinking#interleaved-thinking). This means Claude can think between tool calls, making it especially effective for agentic workflows.
## How to use adaptive thinking
Set `thinking.type` to `"adaptive"` in your API request:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 16000,
"thinking": {
"type": "adaptive"
},
"messages": [
{
"role": "user",
"content": "Explain why the sum of two even numbers is always even."
}
]
}'
```
```bash CLI nocheck
ant messages create \
--model claude-opus-4-7 \
--max-tokens 16000 \
--thinking '{type: adaptive}' \
--message '{role: user, content: Explain why the sum of two even numbers is always even.}' \
--transform content --format jsonl |
jq -r '
if .type == "thinking" then "\nThinking: \(.thinking)"
elif .type == "text" then "\nResponse: \(.text)"
else empty end'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=16000,
thinking={"type": "adaptive"},
messages=[
{
"role": "user",
"content": "Explain why the sum of two even numbers is always even.",
}
],
)
for block in response.content:
if block.type == "thinking":
print(f"\nThinking: {block.thinking}")
elif block.type == "text":
print(f"\nResponse: {block.text}")
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 16000,
thinking: {
type: "adaptive"
},
messages: [
{
role: "user",
content: "Explain why the sum of two even numbers is always even."
}
]
});
for (const block of response.content) {
if (block.type === "thinking") {
console.log(`\nThinking: ${block.thinking}`);
} else if (block.type === "text") {
console.log(`\nResponse: ${block.text}`);
}
}
```
```csharp C#
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 16000,
Thinking = new ThinkingConfigAdaptive(),
Messages = [
new() {
Role = Role.User,
Content = "Explain why the sum of two even numbers is always even."
}
]
};
var message = await client.Messages.Create(parameters);
foreach (var block in message.Content)
{
if (block.TryPickThinking(out ThinkingBlock? thinking))
{
Console.WriteLine($"\nThinking: {thinking.Thinking}");
}
else if (block.TryPickText(out TextBlock? text))
{
Console.WriteLine($"\nResponse: {text.Text}");
}
}
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamUnion{
OfAdaptive: &anthropic.ThinkingConfigAdaptiveParam{},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Explain why the sum of two even numbers is always even.")),
},
})
if err != nil {
log.Fatal(err)
}
for _, block := range response.Content {
switch v := block.AsAny().(type) {
case anthropic.ThinkingBlock:
fmt.Printf("\nThinking: %s", v.Thinking)
case anthropic.TextBlock:
fmt.Printf("\nResponse: %s", v.Text)
}
}
}
```
```java Java hidelines={1..5,7..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.ThinkingConfigAdaptive;
public class ExtendedThinkingExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(16000L)
.thinking(ThinkingConfigAdaptive.builder().build())
.addUserMessage("Explain why the sum of two even numbers is always even.")
.build();
Message response = client.messages().create(params);
response.content().forEach(block -> {
block.thinking().ifPresent(thinkingBlock ->
System.out.println("\nThinking: " + thinkingBlock.thinking())
);
block.text().ifPresent(textBlock ->
System.out.println("\nResponse: " + textBlock.text())
);
});
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 16000,
messages: [
[
'role' => 'user',
'content' => 'Explain why the sum of two even numbers is always even.'
]
],
model: 'claude-opus-4-7',
thinking: ['type' => 'adaptive'],
);
foreach ($message->content as $block) {
if ($block->type === 'thinking') {
echo "\nThinking: " . $block->thinking;
} elseif ($block->type === 'text') {
echo "\nResponse: " . $block->text;
}
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 16000,
thinking: {
type: "adaptive"
},
messages: [
{
role: "user",
content: "Explain why the sum of two even numbers is always even."
}
]
)
message.content.each do |block|
case block.type
when :thinking
puts "\nThinking: #{block.thinking}"
when :text
puts "\nResponse: #{block.text}"
end
end
```
## Adaptive thinking with the effort parameter
You can combine adaptive thinking with the [effort parameter](/docs/en/build-with-claude/effort) to guide how much thinking Claude does. The effort level acts as soft guidance for Claude's thinking allocation:
| Effort level | Thinking behavior |
|:-------------|:------------------|
| `max` | Claude always thinks with no constraints on thinking depth. Available on Claude Mythos Preview, Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6. |
| `xhigh` | Claude always thinks deeply with extended exploration. Available on Claude Opus 4.7. |
| `high` (default) | Claude always thinks. Provides deep reasoning on complex tasks. |
| `medium` | Claude uses moderate thinking. May skip thinking for very simple queries. |
| `low` | Claude minimizes thinking. Skips thinking for simple tasks where speed matters most. |
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 16000,
"thinking": {
"type": "adaptive"
},
"output_config": {
"effort": "medium"
},
"messages": [
{
"role": "user",
"content": "What is the capital of France?"
}
]
}'
```
```bash CLI
ant messages create \
--transform 'content.0.text' --raw-output <<'YAML'
model: claude-opus-4-7
max_tokens: 16000
thinking:
type: adaptive
output_config:
effort: medium
messages:
- role: user
content: What is the capital of France?
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=16000,
thinking={"type": "adaptive"},
output_config={"effort": "medium"},
messages=[{"role": "user", "content": "What is the capital of France?"}],
)
print(response.content[0].text)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 16000,
thinking: {
type: "adaptive"
},
output_config: {
effort: "medium"
},
messages: [
{
role: "user",
content: "What is the capital of France?"
}
]
});
for (const block of response.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
```csharp C#
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 16000,
Thinking = new ThinkingConfigAdaptive(),
OutputConfig = new OutputConfig { Effort = Effort.Medium },
Messages = [new() { Role = Role.User, Content = "What is the capital of France?" }]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamUnion{
OfAdaptive: &anthropic.ThinkingConfigAdaptiveParam{},
},
OutputConfig: anthropic.OutputConfigParam{
Effort: anthropic.OutputConfigEffortMedium,
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the capital of France?")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Content[0].Text)
}
```
```java Java hidelines={1..5,8..10,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.OutputConfig;
import com.anthropic.models.messages.ThinkingConfigAdaptive;
public class Main {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(16000L)
.thinking(ThinkingConfigAdaptive.builder().build())
.outputConfig(OutputConfig.builder()
.effort(OutputConfig.Effort.MEDIUM)
.build())
.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()));
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 16000,
messages: [
['role' => 'user', 'content' => 'What is the capital of France?']
],
model: 'claude-opus-4-7',
thinking: ['type' => 'adaptive'],
outputConfig: ['effort' => 'medium'],
);
echo $message->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 16000,
thinking: {
type: "adaptive"
},
output_config: {
effort: "medium"
},
messages: [
{ role: "user", content: "What is the capital of France?" }
]
)
puts message.content.first.text
```
## Streaming with adaptive thinking
Adaptive thinking works seamlessly with [streaming](/docs/en/build-with-claude/streaming). Thinking blocks are streamed via `thinking_delta` events just like manual thinking mode:
```bash CLI
ant messages create --stream --format jsonl \
--model claude-opus-4-7 \
--max-tokens 16000 \
--thinking '{type: adaptive}' \
--message '{role: user, content: What is the greatest common divisor of 1071 and 462?}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-opus-4-7",
max_tokens=16000,
thinking={"type": "adaptive"},
messages=[
{
"role": "user",
"content": "What is the greatest common divisor of 1071 and 462?",
}
],
) as stream:
for event in stream:
if event.type == "content_block_start":
print(f"\nStarting {event.content_block.type} block...")
elif event.type == "content_block_delta":
if event.delta.type == "thinking_delta":
print(event.delta.thinking, end="", flush=True)
elif event.delta.type == "text_delta":
print(event.delta.text, end="", flush=True)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const stream = await client.messages.stream({
model: "claude-opus-4-7",
max_tokens: 16000,
thinking: { type: "adaptive" },
messages: [{ role: "user", content: "What is the greatest common divisor of 1071 and 462?" }]
});
for await (const event of stream) {
if (event.type === "content_block_start") {
console.log(`\nStarting ${event.content_block.type} block...`);
} else if (event.type === "content_block_delta") {
if (event.delta.type === "thinking_delta") {
process.stdout.write(event.delta.thinking);
} else if (event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
}
```
```csharp C#
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 16000,
Thinking = new ThinkingConfigAdaptive(),
Messages = [new() { Role = Role.User, Content = "What is the greatest common divisor of 1071 and 462?" }]
};
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
Console.Write(msg);
}
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamUnion{
OfAdaptive: &anthropic.ThinkingConfigAdaptiveParam{},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the greatest common divisor of 1071 and 462?")),
},
})
for stream.Next() {
event := stream.Current()
switch eventVariant := event.AsAny().(type) {
case anthropic.ContentBlockStartEvent:
fmt.Printf("\nStarting %s block...\n", eventVariant.ContentBlock.Type)
case anthropic.ContentBlockDeltaEvent:
switch deltaVariant := eventVariant.Delta.AsAny().(type) {
case anthropic.ThinkingDelta:
fmt.Print(deltaVariant.Thinking)
case anthropic.TextDelta:
fmt.Print(deltaVariant.Text)
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
}
```
```java Java hidelines={1..4,6..8,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.ThinkingConfigAdaptive;
public class StreamingThinkingExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(16000L)
.thinking(ThinkingConfigAdaptive.builder().build())
.addUserMessage("What is the greatest common divisor of 1071 and 462?")
.build();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
if (event.contentBlockStart().isPresent()) {
var startEvent = event.contentBlockStart().get();
var block = startEvent.contentBlock();
if (block.isThinking()) {
System.out.println("\nStarting thinking block...");
} else if (block.isText()) {
System.out.println("\nStarting text block...");
}
} else if (event.contentBlockDelta().isPresent()) {
var deltaEvent = event.contentBlockDelta().get();
deltaEvent.delta().thinking().ifPresent(td ->
System.out.print(td.thinking())
);
deltaEvent.delta().text().ifPresent(td ->
System.out.print(td.text())
);
}
});
}
}
}
```
```php PHP hidelines={1..4}
messages->createStream(
maxTokens: 16000,
messages: [
['role' => 'user', 'content' => 'What is the greatest common divisor of 1071 and 462?']
],
model: 'claude-opus-4-7',
thinking: ['type' => 'adaptive'],
);
foreach ($stream as $event) {
if ($event->type === 'content_block_start') {
echo "\nStarting {$event->contentBlock->type} block...\n";
} elseif ($event->type === 'content_block_delta') {
if ($event->delta->type === 'thinking_delta') {
echo $event->delta->thinking;
} elseif ($event->delta->type === 'text_delta') {
echo $event->delta->text;
}
}
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
stream = client.messages.stream(
model: "claude-opus-4-7",
max_tokens: 16000,
thinking: { type: "adaptive" },
messages: [
{ role: "user", content: "What is the greatest common divisor of 1071 and 462?" }
]
)
stream.each do |event|
case event
when Anthropic::Streaming::ThinkingEvent
print event.thinking
when Anthropic::Streaming::TextEvent
print event.text
end
end
```
## Adaptive vs manual vs disabled thinking
| Mode | Config | Availability | When to use |
|:-----|:-------|:-------------|:------------|
| **Adaptive** | `thinking: {type: "adaptive"}` | Claude Mythos Preview (default), Opus 4.7 (only mode), Opus 4.6, Sonnet 4.6 | Claude determines when and how much to use extended thinking. Use `effort` to guide. |
| **Manual** | `thinking: {type: "enabled", budget_tokens: N}` | All models except Claude Opus 4.7 (rejected). Deprecated on Opus 4.6 and Sonnet 4.6 (consider adaptive mode instead). | When you need precise control over thinking token spend. |
| **Disabled** | Omit `thinking` parameter or pass `{type: "disabled"}` | All models except Claude Mythos Preview | When you don't need extended thinking and want the lowest latency. |
Adaptive thinking is available on Claude Mythos Preview, Claude Opus 4.7, Opus 4.6, and Sonnet 4.6. On Mythos Preview, adaptive thinking is the default and applies automatically whenever `thinking` is unset. On Claude Opus 4.7, adaptive thinking is the only supported mode and `type: "enabled"` with `budget_tokens` is rejected. Older models only support `type: "enabled"` with `budget_tokens`. On Opus 4.6 and Sonnet 4.6, `type: "enabled"` with `budget_tokens` is still functional but deprecated.
**Interleaved thinking availability by mode:**
- **Adaptive mode:** Interleaved thinking is automatically enabled on Claude Mythos Preview, Claude Opus 4.7, Opus 4.6, and Sonnet 4.6. On Mythos Preview and Opus 4.7, inter-tool reasoning always lives inside thinking blocks.
- **Manual mode on Sonnet 4.6:** Interleaved thinking works via the `interleaved-thinking-2025-05-14` beta header.
- **Manual mode on Opus 4.6:** Interleaved thinking is not available. If your agentic workflow requires thinking between tool calls on Opus 4.6, use adaptive mode.
## Important considerations
### Validation changes
When using adaptive thinking, previous assistant turns don't need to start with thinking blocks. This is more flexible than manual mode, where the API enforces that thinking-enabled turns begin with a thinking block.
### Prompt caching
Consecutive requests using `adaptive` thinking preserve [prompt cache](/docs/en/build-with-claude/prompt-caching) breakpoints. However, switching between `adaptive` and `enabled`/`disabled` thinking modes breaks cache breakpoints for messages. System prompts and tool definitions remain cached regardless of mode changes.
### Tuning thinking behavior
Adaptive thinking's triggering behavior is promptable. If Claude is thinking more or less often than you'd like, you can add guidance to your system prompt:
```text
Extended thinking adds latency and should only be used when it
will meaningfully improve answer quality — typically for problems
that require multi-step reasoning. When in doubt, respond directly.
```
Steering Claude to think less often may reduce quality on tasks that benefit from reasoning. Measure the impact on your specific workloads before deploying prompt-based tuning to production. Consider testing with lower [effort levels](/docs/en/build-with-claude/effort) first.
### Cost control
Use `max_tokens` as a hard limit on total output (thinking + response text). The `effort` parameter provides additional soft guidance on how much thinking Claude allocates. Together, these give you effective control over cost.
At `high` and `max` effort levels, Claude may think more extensively and can be more likely to exhaust the `max_tokens` budget. If you observe `stop_reason: "max_tokens"` in responses, consider increasing `max_tokens` to give the model more room, or lowering the effort level.
## Working with thinking blocks
The following concepts apply to all models that support extended thinking, regardless of whether you use adaptive or manual mode.
### Summarized thinking
With extended thinking enabled, the Messages API for Claude 4 models returns a summary of Claude's full thinking process. Summarized thinking provides the full intelligence benefits of extended thinking, while preventing misuse. This is the default behavior on Claude 4 models when the `display` field on the thinking configuration is unset or set to `"summarized"`. On Claude Opus 4.7 and [Claude Mythos Preview](https://anthropic.com/glasswing), `display` defaults to `"omitted"` instead, so you must set `display: "summarized"` explicitly to receive summarized thinking.
Here are some important considerations for summarized thinking:
- You're charged for the full thinking tokens generated by the original request, not the summary tokens.
- The billed output token count will **not match** the count of tokens you see in the response.
- On Claude 4 models, the first few lines of thinking output are more verbose, providing detailed reasoning that's particularly helpful for prompt engineering purposes. [Claude Mythos Preview](https://anthropic.com/glasswing) summarizes from the first token, so its thinking blocks do not show this verbose preamble.
- As Anthropic seeks to improve the extended thinking feature, summarization behavior is subject to change.
- Summarization preserves the key ideas of Claude's thinking process with minimal added latency, enabling a streamable user experience.
- Summarization is processed by a different model than the one you target in your requests. The thinking model does not see the summarized output.
In rare cases where you need access to full thinking output for Claude 4 models, [contact our sales team](mailto:sales@anthropic.com).
### Controlling thinking display
The `display` field on the thinking configuration controls how thinking content is returned in API responses. It accepts two values:
- `"summarized"`: Thinking blocks contain summarized thinking text. See [Summarized thinking](#summarized-thinking) for details. This is the default on Claude Opus 4.6, Claude Sonnet 4.6, and earlier Claude 4 models.
- `"omitted"`: Thinking blocks are returned with an empty `thinking` field. The `signature` field still carries the encrypted full thinking for multi-turn continuity (see [Thinking encryption](#thinking-encryption)). This is the default on Claude Opus 4.7 and [Claude Mythos Preview](https://anthropic.com/glasswing).
Setting `display: "omitted"` is useful when your application doesn't surface thinking content to users. The primary benefit is **faster time-to-first-text-token when streaming:** The server skips streaming thinking tokens entirely and delivers only the signature, so the final text response begins streaming sooner.
Here are some important considerations for omitted thinking:
- You're still charged for the full thinking tokens. Omitting reduces latency, not cost.
- If you pass thinking blocks back in multi-turn conversations, pass them unchanged. The server decrypts the `signature` to reconstruct the original thinking for prompt construction (see [Preserving thinking blocks](/docs/en/build-with-claude/extended-thinking#preserving-thinking-blocks)). Any text you place in the `thinking` field of a round-tripped omitted block is ignored.
- `display` is invalid with `thinking.type: "disabled"` (there is nothing to display).
- When using `thinking.type: "adaptive"` and the model skips thinking for a simple request, no thinking block is produced regardless of `display`.
The `signature` field is identical whether `display` is `"summarized"` or `"omitted"`. Switching `display` values between turns in a conversation is supported.
On Claude Opus 4.7, `thinking.display` defaults to `"omitted"`. Thinking blocks still appear in the response stream, but their `thinking` field is empty unless you explicitly opt in. This is a silent change from Claude Opus 4.6, where the default was `"summarized"`. To restore summarized thinking text on Claude Opus 4.7, set `thinking.display` to `"summarized"` explicitly:
```python
thinking = {
"type": "adaptive",
"display": "summarized",
}
```
For code examples and streaming behavior with `display: "omitted"`, see [Controlling thinking display](/docs/en/build-with-claude/extended-thinking#controlling-thinking-display) on the extended thinking page. The examples there use `type: "enabled"`; with adaptive thinking, use:
```python
thinking = {"type": "adaptive", "display": "omitted"}
```
### Thinking encryption
Full thinking content is encrypted and returned in the `signature` field. This field is used to verify that thinking blocks were generated by Claude when passed back to the API.
It is only strictly necessary to send back thinking blocks when using [tools with extended thinking](/docs/en/build-with-claude/extended-thinking#extended-thinking-with-tool-use). Otherwise you can omit thinking blocks from previous turns. If you pass them back, whether the API keeps or strips them depends on the model: Opus 4.5+ and Sonnet 4.6+ keep them in context by default; earlier Opus/Sonnet models and all Haiku models strip them. See [context editing](/docs/en/build-with-claude/context-editing) to configure this.
If sending back thinking blocks, we recommend passing everything back as you received it for consistency and to avoid potential issues.
Here are some important considerations on thinking encryption:
- When [streaming responses](/docs/en/build-with-claude/extended-thinking#streaming-thinking), the signature is added via a `signature_delta` inside a `content_block_delta` event just before the `content_block_stop` event.
- `signature` values are significantly longer in Claude 4 models than in previous models.
- The `signature` field is an opaque field and should not be interpreted or parsed.
- `signature` values are compatible across platforms (Claude APIs, [Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock), and [Vertex AI](/docs/en/build-with-claude/claude-on-vertex-ai)). Values generated on one platform will be compatible with another.
### Pricing
For complete pricing information including base rates, cache writes, cache hits, and output tokens, see the [pricing page](/docs/en/about-claude/pricing).
The thinking process incurs charges for:
- Tokens used during thinking (output tokens)
- Thinking blocks from prior assistant turns kept in context: only the last turn on earlier Opus/Sonnet models and all Haiku models; all turns by default on Opus 4.5+ and Sonnet 4.6+ (input tokens)
- Standard text output tokens
When extended thinking is enabled, a specialized system prompt is automatically included to support this feature.
When using summarized thinking:
- **Input tokens:** Tokens in your original request (excludes thinking tokens from previous turns)
- **Output tokens (billed):** The original thinking tokens that Claude generated internally
- **Output tokens (visible):** The summarized thinking tokens you see in the response
- **No charge:** Tokens used to generate the summary
When using `display: "omitted"`:
- **Input tokens:** Tokens in your original request (same as summarized)
- **Output tokens (billed):** The original thinking tokens that Claude generated internally (same as summarized)
- **Output tokens (visible):** Zero thinking tokens (the `thinking` field is empty)
The billed output token count will **not** match the visible token count in the response. You are billed for the full thinking process, not the thinking content visible in the response.
### Additional topics
The extended thinking page covers several topics in more detail with mode-specific code examples:
- **[Tool use with thinking](/docs/en/build-with-claude/extended-thinking#extended-thinking-with-tool-use)**: The same rules apply for adaptive thinking: preserve thinking blocks between tool calls and be aware of `tool_choice` limitations when thinking is active.
- **[Prompt caching](/docs/en/build-with-claude/extended-thinking#extended-thinking-with-prompt-caching)**: With adaptive thinking, consecutive requests using the same thinking mode preserve cache breakpoints. Switching between `adaptive` and `enabled`/`disabled` modes breaks cache breakpoints for messages (system prompts and tool definitions remain cached).
- **[Context windows](/docs/en/build-with-claude/extended-thinking#max-tokens-and-context-window-size-with-extended-thinking)**: How thinking tokens interact with `max_tokens` and context window limits.
## Next steps
Learn more about extended thinking, including manual mode, tool use, and prompt caching.
Control how thoroughly Claude responds with the effort parameter.
---
# Batch processing
URL: https://platform.claude.com/docs/en/build-with-claude/batch-processing
# Batch processing
---
Batch processing is a powerful approach for handling large volumes of requests efficiently. Instead of processing requests one at a time with immediate responses, batch processing allows you to submit multiple requests together for asynchronous processing. This pattern is particularly useful when:
- You need to process large volumes of data
- Immediate responses are not required
- You want to optimize for cost efficiency
- You're running large-scale evaluations or analyses
The Message Batches API is Anthropic's first implementation of this pattern.
This feature is **not** eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). Data is retained according to the feature's standard retention policy.
---
# Message Batches API
The Message Batches API is a powerful, cost-effective way to asynchronously process large volumes of [Messages](/docs/en/api/messages/create) requests. This approach is well-suited to tasks that do not require immediate responses, with most batches finishing in less than 1 hour while reducing costs by 50% and increasing throughput.
You can [explore the API reference directly](/docs/en/api/creating-message-batches), in addition to this guide.
## How the Message Batches API works
When you send a request to the Message Batches API:
1. The system creates a new Message Batch with the provided Messages requests.
2. The batch is then processed asynchronously, with each request handled independently.
3. You can poll for the status of the batch and retrieve results when processing has ended for all requests.
This is especially useful for bulk operations that don't require immediate results, such as:
- Large-scale evaluations: Process thousands of test cases efficiently.
- Content moderation: Analyze large volumes of user-generated content asynchronously.
- Data analysis: Generate insights or summaries for large datasets.
- Bulk content generation: Create large amounts of text for various purposes (e.g., product descriptions, article summaries).
### Batch limitations
- A Message Batch is limited to either 100,000 Message requests or 256 MB in size, whichever is reached first.
- The system processes each batch as fast as possible, with most batches completing within 1 hour. You can access batch results when all messages have completed or after 24 hours, whichever comes first. Batches expire if processing does not complete within 24 hours.
- Batch results are available for 29 days after creation. After that, you may still view the Batch, but its results will no longer be available for download.
- Batches are scoped to a [Workspace](/settings/workspaces). You may view all batches (and their results) that were created within the Workspace that your API key belongs to.
- Rate limits apply to both Batches API HTTP requests and the number of requests within a batch waiting to be processed. See [Message Batches API rate limits](/docs/en/api/rate-limits#message-batches-api). Additionally, processing may be slowed down based on current demand and your request volume. In that case, you may see more requests expiring after 24 hours.
- Due to high throughput and concurrent processing, batches may go slightly over your Workspace's configured [spend limit](/settings/limits).
- Each batched request must have `max_tokens` of at least `1`. `max_tokens: 0` ([cache pre-warming](/docs/en/build-with-claude/prompt-caching#pre-warming-the-cache)) is not supported inside a batch, since an ephemeral cache entry written during batch processing would likely expire before the follow-up request runs.
### Supported models
All [active models](/docs/en/about-claude/models/overview) support the Message Batches API.
### What can be batched
Any request that you can make to the Messages API can be included in a batch. This includes:
- Vision
- Tool use
- System messages
- Multi-turn conversations
- Any beta features
Since each request in the batch is processed independently, you can mix different types of requests within a single batch.
Since batches can take longer than 5 minutes to process, consider using the [1-hour cache duration](/docs/en/build-with-claude/prompt-caching#1-hour-cache-duration) with prompt caching for better cache hit rates when processing batches with shared context.
---
## Pricing
The Batches API offers significant cost savings. All usage is charged at 50% of the standard API prices.
| Model | Batch input | Batch output |
|-------------------|------------------|-----------------|
| Claude Opus 4.7 | $2.50 / MTok | $12.50 / MTok |
| Claude Opus 4.6 | $2.50 / MTok | $12.50 / MTok |
| Claude Opus 4.5 | $2.50 / MTok | $12.50 / MTok |
| Claude Opus 4.1 | $7.50 / MTok | $37.50 / MTok |
| Claude Opus 4 ([deprecated](/docs/en/about-claude/model-deprecations)) | $7.50 / MTok | $37.50 / MTok |
| Claude Sonnet 4.6 | $1.50 / MTok | $7.50 / MTok |
| Claude Sonnet 4.5 | $1.50 / MTok | $7.50 / MTok |
| Claude Sonnet 4 ([deprecated](/docs/en/about-claude/model-deprecations)) | $1.50 / MTok | $7.50 / MTok |
| Claude Haiku 4.5 | $0.50 / MTok | $2.50 / MTok |
| Claude Haiku 3.5 ([retired, except on Bedrock and Vertex AI](/docs/en/about-claude/model-deprecations)) | $0.40 / MTok | $2 / MTok |
---
## How to use the Message Batches API
### Prepare and create your batch
A Message Batch is composed of a list of requests to create a Message. The shape of an individual request is comprised of:
- A unique `custom_id` for identifying the Messages request. Must be 1 to 64 characters and contain only alphanumeric characters, hyphens, and underscores (matching `^[a-zA-Z0-9_-]{1,64}$`).
- A `params` object with the standard [Messages API](/docs/en/api/messages/create) parameters
You can [create a batch](/docs/en/api/creating-message-batches) by passing this list into the `requests` parameter:
```bash cURL
curl https://api.anthropic.com/v1/messages/batches \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"requests": [
{
"custom_id": "my-first-request",
"params": {
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hello, world"}
]
}
},
{
"custom_id": "my-second-request",
"params": {
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hi again, friend"}
]
}
}
]
}'
```
```bash CLI
ant messages:batches create <<'YAML'
requests:
- custom_id: my-first-request
params:
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content: Hello, world
- custom_id: my-second-request
params:
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content: Hi again, friend
YAML
```
```python Python hidelines={1}
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="my-first-request",
params=MessageCreateParamsNonStreaming(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Hello, world",
}
],
),
),
Request(
custom_id="my-second-request",
params=MessageCreateParamsNonStreaming(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Hi again, friend",
}
],
),
),
]
)
print(message_batch)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const messageBatch = await anthropic.messages.batches.create({
requests: [
{
custom_id: "my-first-request",
params: {
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello, world" }]
}
},
{
custom_id: "my-second-request",
params: {
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Hi again, friend" }]
}
}
]
});
console.log(messageBatch);
```
```csharp C#
using Anthropic;
using Anthropic.Models.Messages;
using Anthropic.Models.Messages.Batches;
AnthropicClient client = new();
var batch = await client.Messages.Batches.Create(new BatchCreateParams
{
Requests =
[
new()
{
CustomID = "my-first-request",
Params = new()
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages =
[
new() { Role = Role.User, Content = "Hello, world" }
]
}
},
new()
{
CustomID = "my-second-request",
Params = new()
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages =
[
new() { Role = Role.User, Content = "Hi again, friend" }
]
}
}
]
});
Console.WriteLine(batch);
```
```go Go hidelines={1..10,-1}
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
batch, _ := client.Messages.Batches.New(context.Background(),
anthropic.MessageBatchNewParams{
Requests: []anthropic.MessageBatchNewParamsRequest{
{
CustomID: "my-first-request",
Params: anthropic.MessageBatchNewParamsRequestParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(
anthropic.NewTextBlock("Hello, world"),
),
},
},
},
{
CustomID: "my-second-request",
Params: anthropic.MessageBatchNewParamsRequestParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(
anthropic.NewTextBlock("Hi again, friend"),
),
},
},
},
},
})
fmt.Println(batch.ID)
}
```
```java Java hidelines={1..3,5..8,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.batches.*;
public class BatchExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
BatchCreateParams params = BatchCreateParams.builder()
.addRequest(
BatchCreateParams.Request.builder()
.customId("my-first-request")
.params(
BatchCreateParams.Request.Params.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addUserMessage("Hello, world")
.build()
)
.build()
)
.addRequest(
BatchCreateParams.Request.builder()
.customId("my-second-request")
.params(
BatchCreateParams.Request.Params.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addUserMessage("Hi again, friend")
.build()
)
.build()
)
.build();
MessageBatch messageBatch = client.messages().batches().create(params);
System.out.println(messageBatch);
}
}
```
```php PHP hidelines={1..4}
messages->batches->create(
requests: [
[
'custom_id' => 'my-first-request',
'params' => [
'model' => 'claude-opus-4-7',
'max_tokens' => 1024,
'messages' => [
['role' => 'user', 'content' => 'Hello, world']
]
]
],
[
'custom_id' => 'my-second-request',
'params' => [
'model' => 'claude-opus-4-7',
'max_tokens' => 1024,
'messages' => [
['role' => 'user', 'content' => 'Hi again, friend']
]
]
]
],
);
echo $batch->id;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
batch = client.messages.batches.create(
requests: [
{
custom_id: "my-first-request",
params: {
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "Hello, world" }
]
}
},
{
custom_id: "my-second-request",
params: {
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "Hi again, friend" }
]
}
}
]
)
puts batch
```
In this example, two separate requests are batched together for asynchronous processing. Each request has a unique `custom_id` and contains the standard parameters you'd use for a Messages API call.
**Test your batch requests with the Messages API**
Validation of the `params` object for each message request is performed asynchronously, and validation errors are returned when processing of the entire batch has ended. You can ensure that you are building your input correctly by verifying your request shape with the [Messages API](/docs/en/api/messages/create) first.
When a batch is first created, the response will have a processing status of `in_progress`.
```json Output
{
"id": "msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d",
"type": "message_batch",
"processing_status": "in_progress",
"request_counts": {
"processing": 2,
"succeeded": 0,
"errored": 0,
"canceled": 0,
"expired": 0
},
"ended_at": null,
"created_at": "2024-09-24T18:37:24.100435Z",
"expires_at": "2024-09-25T18:37:24.100435Z",
"cancel_initiated_at": null,
"results_url": null
}
```
### Tracking your batch
The Message Batch's `processing_status` field indicates the stage of processing the batch is in. It starts as `in_progress`, then updates to `ended` once all the requests in the batch have finished processing, and results are ready. You can monitor the state of your batch by visiting the [Console](/settings/workspaces/default/batches), or using the [retrieval endpoint](/docs/en/api/retrieving-message-batches).
#### Polling for Message Batch completion
To poll a Message Batch, you'll need its `id`, which is provided in the response when creating a batch or by listing batches. You can implement a polling loop that checks the batch status periodically until processing has ended:
```bash cURL hidelines={2..16,23}
#!/bin/sh
MESSAGE_BATCH_ID=$(curl -s https://api.anthropic.com/v1/messages/batches \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"requests": [{
"custom_id": "test-1",
"params": {
"model": "claude-opus-4-7",
"max_tokens": 100,
"messages": [{"role": "user", "content": "Hi"}]
}
}]
}' | jq -r '.id')
until [[ $(curl -s "https://api.anthropic.com/v1/messages/batches/$MESSAGE_BATCH_ID" \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
| grep -o '"processing_status":[[:space:]]*"[^"]*"' \
| cut -d'"' -f4) == "ended" ]]; do
echo "Batch $MESSAGE_BATCH_ID is still processing..."
break
sleep 60
done
echo "Batch $MESSAGE_BATCH_ID has finished processing"
```
```bash CLI hidelines={2..14,19}
#!/bin/bash
MESSAGE_BATCH_ID=$(ant messages:batches create \
--transform id --raw-output <<'YAML'
requests:
- custom_id: test-1
params:
model: claude-opus-4-7
max_tokens: 100
messages:
- role: user
content: Hi
YAML
)
until [[ $(ant messages:batches retrieve \
--message-batch-id "$MESSAGE_BATCH_ID" \
--transform processing_status --raw-output) == "ended" ]]; do
echo "Batch $MESSAGE_BATCH_ID is still processing..."
break
sleep 60
done
echo "Batch $MESSAGE_BATCH_ID has finished processing"
```
```python Python nocheck hidelines={1}
import anthropic
import time
client = anthropic.Anthropic()
MESSAGE_BATCH_ID = "msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d"
message_batch = None
while True:
message_batch = client.messages.batches.retrieve(MESSAGE_BATCH_ID)
if message_batch.processing_status == "ended":
break
print(f"Batch {MESSAGE_BATCH_ID} is still processing...")
time.sleep(60)
print(message_batch)
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const messageBatchId = "msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d";
let messageBatch;
while (true) {
messageBatch = await anthropic.messages.batches.retrieve(messageBatchId);
if (messageBatch.processing_status === "ended") {
break;
}
console.log(`Batch ${messageBatchId} is still processing... waiting`);
await new Promise((resolve) => setTimeout(resolve, 60_000));
}
console.log(messageBatch);
```
```csharp C# nocheck hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages.Batches;
AnthropicClient client = new();
string messageBatchId = Environment.GetEnvironmentVariable("MESSAGE_BATCH_ID");
MessageBatch messageBatch = null;
while (true)
{
messageBatch = await client.Messages.Batches.Retrieve(messageBatchId);
if (messageBatch.ProcessingStatus == "ended")
{
break;
}
Console.WriteLine($"Batch {messageBatchId} is still processing...");
await Task.Delay(60000);
}
Console.WriteLine(messageBatch);
```
```go Go nocheck hidelines={1..14,-1}
package main
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
messageBatchID := os.Getenv("MESSAGE_BATCH_ID")
var messageBatch *anthropic.MessageBatch
for {
var err error
messageBatch, err = client.Messages.Batches.Get(context.TODO(), messageBatchID)
if err != nil {
log.Fatal(err)
}
if messageBatch.ProcessingStatus == "ended" {
break
}
fmt.Printf("Batch %s is still processing...\n", messageBatchID)
time.Sleep(60 * time.Second)
}
fmt.Println(messageBatch)
}
```
```java Java nocheck hidelines={1..2,4..6,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.batches.MessageBatch;
public class MessageBatchPolling {
public static void main(String[] args) throws InterruptedException {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
String messageBatchId = "msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d";
MessageBatch messageBatch = null;
while (true) {
messageBatch = client.messages().batches().retrieve(messageBatchId);
if (messageBatch.processingStatus().equals(MessageBatch.ProcessingStatus.ENDED)) {
break;
}
System.out.println("Batch " + messageBatchId + " is still processing...");
Thread.sleep(60000);
}
System.out.println(messageBatch);
}
}
```
```php PHP hidelines={1..4} nocheck
messages->batches->retrieve(
messageBatchID: $messageBatchId,
);
if ($messageBatch->processingStatus === "ended") {
break;
}
echo "Batch {$messageBatchId} is still processing...\n";
sleep(60);
}
echo json_encode($messageBatch, JSON_PRETTY_PRINT);
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message_batch_id = ENV["MESSAGE_BATCH_ID"]
message_batch = nil
loop do
message_batch = client.messages.batches.retrieve(message_batch_id)
break if message_batch.processing_status == :ended
puts "Batch #{message_batch_id} is still processing..."
sleep 60
end
puts message_batch
```
### Listing all Message Batches
You can list all Message Batches in your Workspace using the [list endpoint](/docs/en/api/listing-message-batches). The API supports pagination, automatically fetching additional pages as needed:
```bash cURL
#!/bin/sh
if ! command -v jq &> /dev/null; then
echo "Error: This script requires jq. Please install it first."
exit 1
fi
BASE_URL="https://api.anthropic.com/v1/messages/batches"
has_more=true
after_id=""
while [ "$has_more" = true ]; do
# Construct URL with after_id if it exists
if [ -n "$after_id" ]; then
url="${BASE_URL}?limit=20&after_id=${after_id}"
else
url="$BASE_URL?limit=20"
fi
response=$(curl -s "$url" \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01")
# Extract values using jq
has_more=$(echo "$response" | jq -r '.has_more')
after_id=$(echo "$response" | jq -r '.last_id')
# Process and print each entry in the data array
echo "$response" | jq -c '.data[]' | while read -r entry; do
echo "$entry" | jq '.'
done
done
```
```bash CLI
# Automatically fetches more pages as needed
ant messages:batches list --limit 20
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
# Automatically fetches more pages as needed.
for message_batch in client.messages.batches.list(limit=20):
print(message_batch)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
// Automatically fetches more pages as needed.
for await (const messageBatch of anthropic.messages.batches.list({
limit: 20
})) {
console.log(messageBatch);
}
```
```csharp C# hidelines={1..11,-2..}
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages.Batches;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new BatchListParams
{
Limit = 20
};
// Automatically fetches more pages as needed
var page = await client.Messages.Batches.List(parameters);
await foreach (var messageBatch in page.Paginate())
{
Console.WriteLine(messageBatch);
}
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
// Automatically fetches more pages as needed
iter := client.Messages.Batches.ListAutoPaging(context.TODO(), anthropic.MessageBatchListParams{
Limit: anthropic.Int(20),
})
for iter.Next() {
messageBatch := iter.Current()
fmt.Println(messageBatch)
}
if err := iter.Err(); err != nil {
log.Fatal(err)
}
}
```
```java Java hidelines={1..2,4..7,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.batches.*;
public class BatchListExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Automatically fetches more pages as needed
for (MessageBatch messageBatch : client
.messages()
.batches()
.list(BatchListParams.builder().limit(20).build())
.autoPager()) {
System.out.println(messageBatch);
}
}
}
```
```php PHP hidelines={1..4} nocheck
messages->batches->list(limit: 20)->pagingEachItem() as $messageBatch) {
echo $messageBatch->id . "\n";
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
# Automatically fetches more pages as needed
client.messages.batches.list(limit: 20).auto_paging_each do |message_batch|
puts message_batch
end
```
### Retrieving batch results
Once batch processing has ended, each Messages request in the batch has a result. There are 4 result types:
| Result Type | Description |
|-------------|-------------|
| `succeeded` | Request was successful. Includes the message result. |
| `errored` | Request encountered an error and a message was not created. Possible errors include invalid requests and internal server errors. You will not be billed for these requests. |
| `canceled` | User canceled the batch before this request could be sent to the model. You will not be billed for these requests. |
| `expired` | Batch reached its 24 hour expiration before this request could be sent to the model. You will not be billed for these requests. |
You will see an overview of your results with the batch's `request_counts`, which shows how many requests reached each of these four states.
Results of the batch are available for download at the `results_url` property on the Message Batch, and if the organization permission allows, in the Console. Because of the potentially large size of the results, it's recommended to [stream results](/docs/en/api/retrieving-message-batch-results) back rather than download them all at once.
```bash cURL
#!/bin/sh
curl "https://api.anthropic.com/v1/messages/batches/msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_API_KEY" \
| grep -o '"results_url":[[:space:]]*"[^"]*"' \
| cut -d'"' -f4 \
| while read -r url; do
curl -s "$url" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_API_KEY" \
| sed 's/}{/}\n{/g' \
| while IFS= read -r line
do
result_type=$(echo "$line" | sed -n 's/.*"result":[[:space:]]*{[[:space:]]*"type":[[:space:]]*"\([^"]*\)".*/\1/p')
custom_id=$(echo "$line" | sed -n 's/.*"custom_id":[[:space:]]*"\([^"]*\)".*/\1/p')
error_type=$(echo "$line" | sed -n 's/.*"error":[[:space:]]*{[[:space:]]*"type":[[:space:]]*"\([^"]*\)".*/\1/p')
case "$result_type" in
"succeeded")
echo "Success! $custom_id"
;;
"errored")
if [ "$error_type" = "invalid_request_error" ]; then
# Request body must be fixed before re-sending request
echo "Validation error: $custom_id"
else
# Request can be retried directly
echo "Server error: $custom_id"
fi
;;
"expired")
echo "Expired: $line"
;;
esac
done
done
```
```bash CLI nocheck
ant messages:batches results \
--message-batch-id msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d \
--transform '{custom_id,"type":result.type,"error":result.error.error.type}' \
--format jsonl \
| while IFS= read -r line; do
custom_id=${line#*'"custom_id":"'}; custom_id=${custom_id%%'"'*}
case "$line" in
*'"type":"succeeded"'*)
printf 'Success! %s\n' "$custom_id" ;;
*'"type":"errored"'*)
case "$line" in
*'"error":"invalid_request_error"'*)
printf 'Validation error %s\n' "$custom_id" ;;
*)
printf 'Server error %s\n' "$custom_id" ;;
esac ;;
*'"type":"expired"'*)
printf 'Request expired %s\n' "$custom_id" ;;
esac
done
```
```python Python nocheck hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
# Stream results file in memory-efficient chunks, processing one at a time
for result in client.messages.batches.results(
"msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d",
):
match result.result.type:
case "succeeded":
print(f"Success! {result.custom_id}")
case "errored":
if result.result.error.error.type == "invalid_request_error":
# Request body must be fixed before re-sending request
print(f"Validation error {result.custom_id}")
else:
# Request can be retried directly
print(f"Server error {result.custom_id}")
case "expired":
print(f"Request expired {result.custom_id}")
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
// Stream results file in memory-efficient chunks, processing one at a time
for await (const result of await anthropic.messages.batches.results(
"msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d"
)) {
switch (result.result.type) {
case "succeeded":
console.log(`Success! ${result.custom_id}`);
break;
case "errored":
if (result.result.error.type === "invalid_request_error") {
// Request body must be fixed before re-sending request
console.log(`Validation error: ${result.custom_id}`);
} else {
// Request can be retried directly
console.log(`Server error: ${result.custom_id}`);
}
break;
case "expired":
console.log(`Request expired: ${result.custom_id}`);
break;
}
}
```
```csharp C# nocheck hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages.Batches;
AnthropicClient client = new();
await foreach (var result in client.Messages.Batches.ResultsStreaming("msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d"))
{
switch (result.Result.Type)
{
case "succeeded":
Console.WriteLine($"Success! {result.CustomID}");
break;
case "errored":
if (result.Result.Error?.Type == "invalid_request")
{
Console.WriteLine($"Validation error: {result.CustomID}");
}
else
{
Console.WriteLine($"Server error: {result.CustomID}");
}
break;
case "expired":
Console.WriteLine($"Request expired: {result.CustomID}");
break;
}
}
```
```go Go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
stream := client.Messages.Batches.ResultsStreaming(context.TODO(), "msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d")
for stream.Next() {
result := stream.Current()
switch variant := result.Result.AsAny().(type) {
case anthropic.MessageBatchSucceededResult:
fmt.Printf("Success! %s\n", result.CustomID)
case anthropic.MessageBatchErroredResult:
fmt.Printf("Error: %s - %s\n", result.CustomID, variant.Error.Error.Message)
case anthropic.MessageBatchExpiredResult:
fmt.Printf("Request expired: %s\n", result.CustomID)
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
}
```
```java Java nocheck hidelines={1..2,6..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.http.StreamResponse;
import com.anthropic.models.messages.batches.BatchResultsParams;
import com.anthropic.models.messages.batches.MessageBatchIndividualResponse;
public class BatchResultsExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Stream results file in memory-efficient chunks, processing one at a time
try (
StreamResponse streamResponse = client
.messages()
.batches()
.resultsStreaming(
BatchResultsParams.builder()
.messageBatchId("msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d")
.build()
)
) {
streamResponse
.stream()
.forEach(result -> {
if (result.result().isSucceeded()) {
System.out.println("Success! " + result.customId());
} else if (result.result().isErrored()) {
if (result.result().asErrored().error().error().isInvalidRequestError()) {
// Request body must be fixed before re-sending request
System.out.println("Validation error: " + result.customId());
} else {
// Request can be retried directly
System.out.println("Server error: " + result.customId());
}
} else if (result.result().isExpired()) {
System.out.println("Request expired: " + result.customId());
}
});
}
}
}
```
```php PHP hidelines={1..4} nocheck
messages->batches->resultsStream(messageBatchID: 'msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d') as $result) {
switch ($result->result->type) {
case "succeeded":
echo "Success! {$result->customID}\n";
break;
case "errored":
if ($result->result->error->error->type === "invalid_request_error") {
echo "Validation error: {$result->customID}\n";
} else {
echo "Server error: {$result->customID}\n";
}
break;
case "expired":
echo "Request expired: {$result->customID}\n";
break;
}
}
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
client.messages.batches.results_streaming("msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d").each do |result|
case result.result.type
when :succeeded
puts "Success! #{result.custom_id}"
when :errored
if result.result.error.type == :invalid_request
puts "Validation error: #{result.custom_id}"
else
puts "Server error: #{result.custom_id}"
end
when :expired
puts "Request expired: #{result.custom_id}"
end
end
```
The results are in `.jsonl` format, where each line is a valid JSON object representing the result of a single request in the Message Batch. For each streamed result, you can do something different depending on its `custom_id` and result type. Here is an example set of results:
```jsonl .jsonl file
{"custom_id":"my-second-request","result":{"type":"succeeded","message":{"id":"msg_014VwiXbi91y3JMjcpyGBHX5","type":"message","role":"assistant","model":"claude-opus-4-7","content":[{"type":"text","text":"Hello again! It's nice to see you. How can I assist you today? Is there anything specific you'd like to chat about or any questions you have?"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":11,"output_tokens":36}}}}
{"custom_id":"my-first-request","result":{"type":"succeeded","message":{"id":"msg_01FqfsLoHwgeFbguDgpz48m7","type":"message","role":"assistant","model":"claude-opus-4-7","content":[{"type":"text","text":"Hello! How can I assist you today? Feel free to ask me any questions or let me know if there's anything you'd like to chat about."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":34}}}}
```
If your result has an error, its `result.error` will be set to the standard [error shape](/docs/en/api/errors#error-shapes).
**Batch results may not match input order**
Batch results can be returned in any order, and may not match the ordering of requests when the batch was created. In the above example, the result for the second batch request is returned before the first. To correctly match results with their corresponding requests, always use the `custom_id` field.
### Canceling a Message Batch
You can cancel a Message Batch that is currently processing using the [cancel endpoint](/docs/en/api/canceling-message-batches). Immediately after cancellation, a batch's `processing_status` will be `canceling`. You can use the same polling technique described above to wait until cancellation is finalized. Canceled batches end up with a status of `ended` and may contain partial results for requests that were processed before cancellation.
```bash cURL hidelines={2..15}
#!/bin/sh
MESSAGE_BATCH_ID=$(curl -s https://api.anthropic.com/v1/messages/batches \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"requests": [{
"custom_id": "test-1",
"params": {
"model": "claude-opus-4-7",
"max_tokens": 100,
"messages": [{"role": "user", "content": "Hi"}]
}
}]
}' | jq -r '.id')
curl --request POST https://api.anthropic.com/v1/messages/batches/$MESSAGE_BATCH_ID/cancel \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01"
```
```bash CLI hidelines={2..13}
#!/bin/bash
MESSAGE_BATCH_ID=$(ant messages:batches create \
--transform id --raw-output <<'YAML'
requests:
- custom_id: test-1
params:
model: claude-opus-4-7
max_tokens: 100
messages:
- role: user
content: Hi
YAML
)
ant messages:batches cancel --message-batch-id "$MESSAGE_BATCH_ID"
```
```python Python nocheck hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
MESSAGE_BATCH_ID = "msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d"
message_batch = client.messages.batches.cancel(
MESSAGE_BATCH_ID,
)
print(message_batch)
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const messageBatch = await anthropic.messages.batches.cancel(MESSAGE_BATCH_ID);
console.log(messageBatch);
```
```csharp C# nocheck hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages.Batches;
AnthropicClient client = new();
string messageBatchId = Environment.GetEnvironmentVariable("MESSAGE_BATCH_ID");
var messageBatch = await client.Messages.Batches.Cancel(messageBatchId);
Console.WriteLine(messageBatch);
```
```go Go nocheck hidelines={1..12,-1}
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
messageBatchID := os.Getenv("MESSAGE_BATCH_ID")
messageBatch, err := client.Messages.Batches.Cancel(context.TODO(), messageBatchID)
if err != nil {
log.Fatal(err)
}
fmt.Println(messageBatch)
}
```
```java Java nocheck hidelines={1..2,4..7,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.batches.*;
public class BatchCancelExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageBatch messageBatch = client
.messages()
.batches()
.cancel("msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d");
System.out.println(messageBatch);
}
}
```
```php PHP hidelines={1..4} nocheck
messages->batches->cancel(
messageBatchID: 'msgbatch_example_id',
);
echo $messageBatch;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message_batch_id = ENV.fetch("MESSAGE_BATCH_ID")
message_batch = client.messages.batches.cancel(message_batch_id)
puts message_batch
```
The response will show the batch in a `canceling` state:
```json Output
{
"id": "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF",
"type": "message_batch",
"processing_status": "canceling",
"request_counts": {
"processing": 2,
"succeeded": 0,
"errored": 0,
"canceled": 0,
"expired": 0
},
"ended_at": null,
"created_at": "2024-09-24T18:37:24.100435Z",
"expires_at": "2024-09-25T18:37:24.100435Z",
"cancel_initiated_at": "2024-09-24T18:39:03.114875Z",
"results_url": null
}
```
### Using prompt caching with Message Batches
The Message Batches API supports prompt caching, allowing you to potentially reduce costs and processing time for batch requests. The pricing discounts from prompt caching and Message Batches can stack, providing even greater cost savings when both features are used together. However, since batch requests are processed asynchronously and concurrently, cache hits are provided on a best-effort basis. Users typically experience cache hit rates ranging from 30% to 98%, depending on their traffic patterns.
To maximize the likelihood of cache hits in your batch requests:
1. Include identical `cache_control` blocks in every Message request within your batch
2. Maintain a steady stream of requests to prevent cache entries from expiring after their 5-minute lifetime
3. Structure your requests to share as much cached content as possible
Example of implementing prompt caching in a batch:
```bash cURL
curl https://api.anthropic.com/v1/messages/batches \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"requests": [
{
"custom_id": "my-first-request",
"params": {
"model": "claude-opus-4-7",
"max_tokens": 1024,
"system": [
{
"type": "text",
"text": "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"
},
{
"type": "text",
"text": "",
"cache_control": {"type": "ephemeral"}
}
],
"messages": [
{"role": "user", "content": "Analyze the major themes in Pride and Prejudice."}
]
}
},
{
"custom_id": "my-second-request",
"params": {
"model": "claude-opus-4-7",
"max_tokens": 1024,
"system": [
{
"type": "text",
"text": "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"
},
{
"type": "text",
"text": "",
"cache_control": {"type": "ephemeral"}
}
],
"messages": [
{"role": "user", "content": "Write a summary of Pride and Prejudice."}
]
}
}
]
}'
```
```bash CLI
ant messages:batches create <<'YAML'
requests:
- custom_id: my-first-request
params:
model: claude-opus-4-7
max_tokens: 1024
system:
- type: text
text: >
You are an AI assistant tasked with analyzing literary works. Your
goal is to provide insightful commentary on themes, characters, and
writing style.
- type: text
text: ""
cache_control:
type: ephemeral
messages:
- role: user
content: Analyze the major themes in Pride and Prejudice.
- custom_id: my-second-request
params:
model: claude-opus-4-7
max_tokens: 1024
system:
- type: text
text: >
You are an AI assistant tasked with analyzing literary works. Your
goal is to provide insightful commentary on themes, characters, and
writing style.
- type: text
text: ""
cache_control:
type: ephemeral
messages:
- role: user
content: Write a summary of Pride and Prejudice.
YAML
```
```python Python hidelines={1}
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="my-first-request",
params=MessageCreateParamsNonStreaming(
model="claude-opus-4-7",
max_tokens=1024,
system=[
{
"type": "text",
"text": "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n",
},
{
"type": "text",
"text": "",
"cache_control": {"type": "ephemeral"},
},
],
messages=[
{
"role": "user",
"content": "Analyze the major themes in Pride and Prejudice.",
}
],
),
),
Request(
custom_id="my-second-request",
params=MessageCreateParamsNonStreaming(
model="claude-opus-4-7",
max_tokens=1024,
system=[
{
"type": "text",
"text": "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n",
},
{
"type": "text",
"text": "",
"cache_control": {"type": "ephemeral"},
},
],
messages=[
{
"role": "user",
"content": "Write a summary of Pride and Prejudice.",
}
],
),
),
]
)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const messageBatch = await anthropic.messages.batches.create({
requests: [
{
custom_id: "my-first-request",
params: {
model: "claude-opus-4-7",
max_tokens: 1024,
system: [
{
type: "text",
text: "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"
},
{
type: "text",
text: "",
cache_control: { type: "ephemeral" }
}
],
messages: [
{ role: "user", content: "Analyze the major themes in Pride and Prejudice." }
]
}
},
{
custom_id: "my-second-request",
params: {
model: "claude-opus-4-7",
max_tokens: 1024,
system: [
{
type: "text",
text: "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"
},
{
type: "text",
text: "",
cache_control: { type: "ephemeral" }
}
],
messages: [{ role: "user", content: "Write a summary of Pride and Prejudice." }]
}
}
]
});
```
```csharp C#
using Anthropic;
using Anthropic.Models.Messages;
using Anthropic.Models.Messages.Batches;
AnthropicClient client = new()
{
ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")
};
var messageBatch = await client.Messages.Batches.Create(new BatchCreateParams
{
Requests =
[
new()
{
CustomID = "my-first-request",
Params = new()
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
System = new List
{
new()
{
Text = "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"
},
new()
{
Text = "",
CacheControl = new()
}
},
Messages =
[
new() { Role = Role.User, Content = "Analyze the major themes in Pride and Prejudice." }
]
}
},
new()
{
CustomID = "my-second-request",
Params = new()
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
System = new List
{
new()
{
Text = "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"
},
new()
{
Text = "",
CacheControl = new()
}
},
Messages =
[
new() { Role = Role.User, Content = "Write a summary of Pride and Prejudice." }
]
}
}
]
});
```
```go Go hidelines={1..10,-1}
package main
import (
"context"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
messageBatch, err := client.Messages.Batches.New(context.TODO(), anthropic.MessageBatchNewParams{
Requests: []anthropic.MessageBatchNewParamsRequest{
{
CustomID: "my-first-request",
Params: anthropic.MessageBatchNewParamsRequestParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
System: []anthropic.TextBlockParam{
{
Text: "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n",
},
{
Text: "",
CacheControl: anthropic.NewCacheControlEphemeralParam(),
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the major themes in Pride and Prejudice.")),
},
},
},
{
CustomID: "my-second-request",
Params: anthropic.MessageBatchNewParamsRequestParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
System: []anthropic.TextBlockParam{
{
Text: "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n",
},
{
Text: "",
CacheControl: anthropic.NewCacheControlEphemeralParam(),
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Write a summary of Pride and Prejudice.")),
},
},
},
},
})
if err != nil {
log.Fatal(err)
}
log.Printf("%+v\n", messageBatch)
}
```
```java Java hidelines={1..2,4..5,7..11,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import com.anthropic.models.messages.batches.*;
import java.util.List;
public class BatchExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
BatchCreateParams createParams = BatchCreateParams.builder()
.addRequest(
BatchCreateParams.Request.builder()
.customId("my-first-request")
.params(
BatchCreateParams.Request.Params.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.systemOfTextBlockParams(
List.of(
TextBlockParam.builder()
.text(
"You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"
)
.build(),
TextBlockParam.builder()
.text("")
.cacheControl(CacheControlEphemeral.builder().build())
.build()
)
)
.addUserMessage("Analyze the major themes in Pride and Prejudice.")
.build()
)
.build()
)
.addRequest(
BatchCreateParams.Request.builder()
.customId("my-second-request")
.params(
BatchCreateParams.Request.Params.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.systemOfTextBlockParams(
List.of(
TextBlockParam.builder()
.text(
"You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"
)
.build(),
TextBlockParam.builder()
.text("")
.cacheControl(CacheControlEphemeral.builder().build())
.build()
)
)
.addUserMessage("Write a summary of Pride and Prejudice.")
.build()
)
.build()
)
.build();
MessageBatch messageBatch = client.messages().batches().create(createParams);
}
}
```
```php PHP hidelines={1..4}
messages->batches->create(
requests: [
[
'custom_id' => 'my-first-request',
'params' => [
'model' => 'claude-opus-4-7',
'max_tokens' => 1024,
'system' => [
[
'type' => 'text',
'text' => 'You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n'
],
[
'type' => 'text',
'text' => '',
'cache_control' => ['type' => 'ephemeral']
]
],
'messages' => [
['role' => 'user', 'content' => 'Analyze the major themes in Pride and Prejudice.']
]
]
],
[
'custom_id' => 'my-second-request',
'params' => [
'model' => 'claude-opus-4-7',
'max_tokens' => 1024,
'system' => [
[
'type' => 'text',
'text' => 'You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n'
],
[
'type' => 'text',
'text' => '',
'cache_control' => ['type' => 'ephemeral']
]
],
'messages' => [
['role' => 'user', 'content' => 'Write a summary of Pride and Prejudice.']
]
]
]
],
);
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message_batch = client.messages.batches.create(
requests: [
{
custom_id: "my-first-request",
params: {
model: "claude-opus-4-7",
max_tokens: 1024,
system: [
{
type: "text",
text: "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"
},
{
type: "text",
text: "",
cache_control: { type: "ephemeral" }
}
],
messages: [
{ role: "user", content: "Analyze the major themes in Pride and Prejudice." }
]
}
},
{
custom_id: "my-second-request",
params: {
model: "claude-opus-4-7",
max_tokens: 1024,
system: [
{
type: "text",
text: "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"
},
{
type: "text",
text: "",
cache_control: { type: "ephemeral" }
}
],
messages: [
{ role: "user", content: "Write a summary of Pride and Prejudice." }
]
}
}
]
)
```
In this example, both requests in the batch include identical system messages and the full text of Pride and Prejudice marked with `cache_control` to increase the likelihood of cache hits.
### Extended output (beta)
The `output-300k-2026-03-24` beta header raises the `max_tokens` cap to 300,000 for batch requests using Claude Opus 4.7, Claude Opus 4.6, or Claude Sonnet 4.6. Include the header to generate outputs far longer than the standard limit (64k to 128k depending on model) in a single turn.
Extended output is available on the Message Batches API only, not the synchronous Messages API. It is supported on the Claude API and Claude Platform on AWS, and is not available on Amazon Bedrock, Vertex AI, or Microsoft Foundry.
Use extended output for long-form generation such as book-length drafts and technical documentation, exhaustive structured data extraction, large code-generation scaffolds, and long reasoning chains.
A single 300k-token generation can take over an hour to complete, so plan your batch submissions with the 24-hour processing window in mind. Standard batch pricing (50% of standard API prices) applies.
```bash cURL
curl https://api.anthropic.com/v1/messages/batches \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "anthropic-beta: output-300k-2026-03-24" \
--header "content-type: application/json" \
--data \
'{
"requests": [
{
"custom_id": "long-form-request",
"params": {
"model": "claude-opus-4-7",
"max_tokens": 300000,
"messages": [
{"role": "user", "content": "Write a comprehensive technical guide to building distributed systems, covering architecture patterns, consistency models, fault tolerance, and operational best practices."}
]
}
}
]
}'
```
```bash CLI
ant beta:messages:batches create --beta output-300k-2026-03-24 <<'YAML'
requests:
- custom_id: long-form-request
params:
model: claude-opus-4-7
max_tokens: 300000
messages:
- role: user
content: >-
Write a comprehensive technical guide to building distributed
systems, covering architecture patterns, consistency models,
fault tolerance, and operational best practices.
YAML
```
```python Python hidelines={1}
import anthropic
from anthropic.types.beta.message_create_params import MessageCreateParamsNonStreaming
from anthropic.types.beta.messages.batch_create_params import Request
client = anthropic.Anthropic()
message_batch = client.beta.messages.batches.create(
betas=["output-300k-2026-03-24"],
requests=[
Request(
custom_id="long-form-request",
params=MessageCreateParamsNonStreaming(
model="claude-opus-4-7",
max_tokens=300_000,
messages=[
{
"role": "user",
"content": "Write a comprehensive technical guide to building distributed systems, covering architecture patterns, consistency models, fault tolerance, and operational best practices.",
}
],
),
),
],
)
print(message_batch)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const messageBatch = await anthropic.beta.messages.batches.create({
betas: ["output-300k-2026-03-24"],
requests: [
{
custom_id: "long-form-request",
params: {
model: "claude-opus-4-7",
max_tokens: 300000,
messages: [
{
role: "user",
content:
"Write a comprehensive technical guide to building distributed systems, covering architecture patterns, consistency models, fault tolerance, and operational best practices."
}
]
}
}
]
});
console.log(messageBatch);
```
```csharp C#
using Anthropic;
using Anthropic.Models.Beta.Messages;
using Anthropic.Models.Beta.Messages.Batches;
AnthropicClient client = new();
var batch = await client.Beta.Messages.Batches.Create(new BatchCreateParams
{
Betas = ["output-300k-2026-03-24"],
Requests =
[
new()
{
CustomID = "long-form-request",
Params = new()
{
Model = "claude-opus-4-7",
MaxTokens = 300_000,
Messages =
[
new() { Role = Role.User, Content = "Write a comprehensive technical guide to building distributed systems, covering architecture patterns, consistency models, fault tolerance, and operational best practices." }
]
}
}
]
});
Console.WriteLine(batch);
```
```go Go hidelines={1..10,-1}
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
batch, err := client.Beta.Messages.Batches.New(context.Background(),
anthropic.BetaMessageBatchNewParams{
Betas: []anthropic.AnthropicBeta{"output-300k-2026-03-24"},
Requests: []anthropic.BetaMessageBatchNewParamsRequest{
{
CustomID: "long-form-request",
Params: anthropic.BetaMessageBatchNewParamsRequestParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 300_000,
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(
anthropic.NewBetaTextBlock("Write a comprehensive technical guide to building distributed systems, covering architecture patterns, consistency models, fault tolerance, and operational best practices."),
),
},
},
},
},
})
if err != nil {
panic(err)
}
fmt.Println(batch.ID)
}
```
```java Java hidelines={1..3,5..6,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Model;
import com.anthropic.models.beta.messages.batches.*;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
BatchCreateParams params = BatchCreateParams.builder()
.addBeta("output-300k-2026-03-24")
.addRequest(
BatchCreateParams.Request.builder()
.customId("long-form-request")
.params(
BatchCreateParams.Request.Params.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(300_000L)
.addUserMessage("Write a comprehensive technical guide to building distributed systems, covering architecture patterns, consistency models, fault tolerance, and operational best practices.")
.build()
)
.build()
)
.build();
BetaMessageBatch messageBatch = client.beta().messages().batches().create(params);
IO.println(messageBatch);
}
```
```php PHP hidelines={1..4}
beta->messages->batches->create(
betas: ['output-300k-2026-03-24'],
requests: [
[
'custom_id' => 'long-form-request',
'params' => [
'model' => 'claude-opus-4-7',
'max_tokens' => 300_000,
'messages' => [
['role' => 'user', 'content' => 'Write a comprehensive technical guide to building distributed systems, covering architecture patterns, consistency models, fault tolerance, and operational best practices.']
]
]
]
],
);
echo $batch->id;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
batch = client.beta.messages.batches.create(
betas: ["output-300k-2026-03-24"],
requests: [
{
custom_id: "long-form-request",
params: {
model: "claude-opus-4-7",
max_tokens: 300_000,
messages: [
{ role: "user", content: "Write a comprehensive technical guide to building distributed systems, covering architecture patterns, consistency models, fault tolerance, and operational best practices." }
]
}
}
]
)
puts batch
```
### Best practices for effective batching
To get the most out of the Batches API:
- Monitor batch processing status regularly and implement appropriate retry logic for failed requests.
- Use meaningful `custom_id` values to easily match results with requests, since order is not guaranteed.
- Consider breaking very large datasets into multiple batches for better manageability.
- Dry run a single request shape with the Messages API to avoid validation errors.
### Troubleshooting common issues
If experiencing unexpected behavior:
- Verify that the total batch request size doesn't exceed 256 MB. If the request size is too large, you may get a 413 `request_too_large` error.
- Check that you're using [supported models](#supported-models) for all requests in the batch.
- Ensure each request in the batch has a unique `custom_id`.
- Ensure that it has been less than 29 days since batch `created_at` (not processing `ended_at`) time. If over 29 days have passed, results will no longer be viewable.
- Confirm that the batch has not been canceled.
Note that the failure of one request in a batch does not affect the processing of other requests.
---
## Batch storage and privacy
- **Workspace isolation**: Batches are isolated within the Workspace they are created in. They can only be accessed by API keys associated with that Workspace, or users with permission to view Workspace batches in the Console.
- **Result availability**: Batch results are available for 29 days after the batch is created, allowing ample time for retrieval and processing.
---
## Data retention
Batch processing stores request and response data for up to 29 days after batch creation. You can delete a message batch at any time after processing using the `DELETE /v1/messages/batches/{batch_id}` endpoint. To delete an in-progress batch, cancel it first. Asynchronous processing requires server-side storage of both inputs and outputs until batch completion and result retrieval.
For ZDR eligibility across all features, see [API and data retention](/docs/en/manage-claude/api-and-data-retention).
## FAQ
Batches may take up to 24 hours for processing, but many finish sooner. Actual processing time depends on the size of the batch, current demand, and your request volume. It is possible for a batch to expire and not complete within 24 hours.
See [above](#supported-models) for the list of supported models.
Yes, the Message Batches API supports all features available in the Messages API, including beta features. However, streaming is not supported for batch requests.
The Message Batches API offers a 50% discount on all usage compared to standard API prices. This applies to input tokens, output tokens, and any special tokens. For more on pricing, visit the [pricing page](https://claude.com/pricing#anthropic-api).
No, once a batch has been submitted, it cannot be modified. If you need to make changes, you should cancel the current batch and submit a new one. Note that cancellation may not take immediate effect.
The Message Batches API has HTTP requests-based rate limits in addition to limits on the number of requests in need of processing. See [Message Batches API rate limits](/docs/en/api/rate-limits#message-batches-api). Usage of the Batches API does not affect rate limits in the Messages API.
When you retrieve the results, each request will have a `result` field indicating whether it `succeeded`, `errored`, was `canceled`, or `expired`. For `errored` results, additional error information will be provided. View the error response object in the [API reference](/docs/en/api/creating-message-batches).
The Message Batches API is designed with strong privacy and data separation measures:
1. Batches and their results are isolated within the Workspace in which they were created. This means they can only be accessed by API keys from that same Workspace.
2. Each request within a batch is processed independently, with no data leakage between requests.
3. Results are only available for a limited time (29 days), and follow Anthropic's [data retention policy](https://support.claude.com/en/articles/7996866-how-long-do-you-store-personal-data).
4. Downloading batch results in the Console can be disabled on the organization-level or on a per-workspace basis.
Yes, it is possible to use prompt caching with Message Batches API. However, because asynchronous batch requests can be processed concurrently and in any order, cache hits are provided on a best-effort basis.
---
# Building with extended thinking
URL: https://platform.claude.com/docs/en/build-with-claude/extended-thinking
# Building with extended thinking
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
Extended thinking gives Claude enhanced reasoning capabilities for complex tasks, while providing varying levels of transparency into its step-by-step thought process before it delivers its final answer.
For Claude Opus 4.7, use [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) (`thinking: {type: "adaptive"}`) with the [effort parameter](/docs/en/build-with-claude/effort). Manual extended thinking (`thinking: {type: "enabled", budget_tokens: N}`) is no longer supported on Claude Opus 4.7 and returns a 400 error. For Claude Opus 4.6 and Claude Sonnet 4.6, adaptive thinking is also recommended; the manual configuration is still functional on these models but is deprecated and will be removed in a future model release.
## Supported models
Manual extended thinking (`thinking: {type: "enabled", budget_tokens: N}`) is supported on all current Claude models **except Claude Opus 4.7**, where it is no longer accepted and returns a 400 error. A few models have mode-specific behavior:
- **Claude Opus 4.7 (`claude-opus-4-7`):** manual extended thinking is no longer supported. Use [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) (`thinking: {type: "adaptive"}`) with the [effort parameter](/docs/en/build-with-claude/effort) instead.
- **[Claude Mythos Preview](https://anthropic.com/glasswing):** [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) is the default; `thinking: {type: "enabled", budget_tokens: N}` is also accepted. `thinking: {type: "disabled"}` is not supported, and `display` defaults to `"omitted"` rather than returning thinking content. Pass `display: "summarized"` to receive summaries.
- **Claude Opus 4.6 (`claude-opus-4-6`):** [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) recommended; manual mode (`type: "enabled"`) is deprecated but still functional.
- **Claude Sonnet 4.6 (`claude-sonnet-4-6`):** [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) recommended; manual mode (`type: "enabled"`) with [interleaved mode](#interleaved-thinking) is deprecated but still functional.
Thinking behavior differs across Claude model versions. See [Differences in thinking across model versions](#differences-in-thinking-across-model-versions) for details.
## How extended thinking works
When extended thinking is turned on, Claude creates `thinking` content blocks where it outputs its internal reasoning. Claude incorporates insights from this reasoning before crafting a final response.
The API response includes `thinking` content blocks, followed by `text` content blocks.
Here's an example of the default response format:
```json
{
"content": [
{
"type": "thinking",
"thinking": "Let me analyze this step by step...",
"signature": "WaUjzkypQ2mUEVM36O2TxuC06KN8xyfbJwyem2dw3URve/op91XWHOEBLLqIOMfFG/UvLEczmEsUjavL...."
},
{
"type": "text",
"text": "Based on my analysis..."
}
]
}
```
For more information about the response format of extended thinking, see the [Messages API Reference](/docs/en/api/messages/create).
## How to use extended thinking
Here is an example of using extended thinking in the Messages API:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-sonnet-4-6",
"max_tokens": 16000,
"thinking": {
"type": "enabled",
"budget_tokens": 10000
},
"messages": [
{
"role": "user",
"content": "Are there an infinite number of prime numbers such that n mod 4 == 3?"
}
]
}'
```
```bash CLI
ant messages create \
--transform content --format yaml \
--model claude-sonnet-4-6 \
--max-tokens 16000 \
--thinking '{type: enabled, budget_tokens: 10000}' \
--message '{role: user, content: Are there an infinite number of prime numbers such that n mod 4 == 3?}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=16000,
thinking={"type": "enabled", "budget_tokens": 10000},
messages=[
{
"role": "user",
"content": "Are there an infinite number of prime numbers such that n mod 4 == 3?",
}
],
)
# The response contains summarized thinking blocks and text blocks
for block in response.content:
if block.type == "thinking":
print(f"\nThinking summary: {block.thinking}")
elif block.type == "text":
print(f"\nResponse: {block.text}")
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 16000,
thinking: {
type: "enabled",
budget_tokens: 10000
},
messages: [
{
role: "user",
content: "Are there an infinite number of prime numbers such that n mod 4 == 3?"
}
]
});
// The response contains summarized thinking blocks and text blocks
for (const block of response.content) {
if (block.type === "thinking") {
console.log(`\nThinking summary: ${block.thinking}`);
} else if (block.type === "text") {
console.log(`\nResponse: ${block.text}`);
}
}
```
```csharp C# hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 16000,
Thinking = new ThinkingConfigEnabled(budgetTokens: 10000),
Messages = [
new() {
Role = Role.User,
Content = "Are there an infinite number of prime numbers such that n mod 4 == 3?"
}
]
};
var message = await client.Messages.Create(parameters);
foreach (var block in message.Content)
{
if (block.TryPickThinking(out ThinkingBlock? thinking))
{
Console.WriteLine($"\nThinking summary: {thinking.Thinking}");
}
else if (block.TryPickText(out TextBlock? text))
{
Console.WriteLine($"\nResponse: {text.Text}");
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamOfEnabled(10000),
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Are there an infinite number of prime numbers such that n mod 4 == 3?")),
},
})
if err != nil {
log.Fatal(err)
}
for _, block := range response.Content {
switch v := block.AsAny().(type) {
case anthropic.ThinkingBlock:
fmt.Printf("\nThinking summary: %s", v.Thinking)
case anthropic.TextBlock:
fmt.Printf("\nResponse: %s", v.Text)
}
}
}
```
```java Java hidelines={1..7,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(16000L)
.enabledThinking(10000L)
.addUserMessage("Are there an infinite number of prime numbers such that n mod 4 == 3?")
.build();
Message response = client.messages().create(params);
response.content().forEach(block -> {
block.thinking().ifPresent(thinkingBlock ->
IO.println("\nThinking summary: " + thinkingBlock.thinking())
);
block.text().ifPresent(textBlock ->
IO.println("\nResponse: " + textBlock.text())
);
});
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 16000,
messages: [
[
'role' => 'user',
'content' => 'Are there an infinite number of prime numbers such that n mod 4 == 3?'
]
],
model: 'claude-sonnet-4-6',
thinking: ['type' => 'enabled', 'budget_tokens' => 10000],
);
foreach ($message->content as $block) {
if ($block->type === 'thinking') {
echo "\nThinking summary: " . $block->thinking;
} elseif ($block->type === 'text') {
echo "\nResponse: " . $block->text;
}
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 16000,
thinking: {
type: "enabled",
budget_tokens: 10000
},
messages: [
{
role: "user",
content: "Are there an infinite number of prime numbers such that n mod 4 == 3?"
}
]
)
message.content.each do |block|
case block.type
when :thinking
puts "\nThinking summary: #{block.thinking}"
when :text
puts "\nResponse: #{block.text}"
end
end
```
To turn on extended thinking, add a `thinking` object, with the `type` parameter set to `enabled` and the `budget_tokens` to a specified token budget for extended thinking. For Claude Opus 4.6 and Claude Sonnet 4.6, use `type: "adaptive"` instead. See [Adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) for details. While `type: "enabled"` with `budget_tokens` is still functional on these models, it is deprecated and will be removed in a future release.
The `budget_tokens` parameter determines the maximum number of tokens Claude is allowed to use for its internal reasoning process. This limit applies to full thinking tokens, not to [the summarized output](#summarized-thinking). Larger budgets can improve response quality by enabling more thorough analysis for complex problems, although Claude may not use the entire budget allocated, especially at ranges above 32k.
`budget_tokens` is [deprecated](/docs/en/build-with-claude/overview#feature-availability) on Claude Opus 4.6 and Claude Sonnet 4.6 and will be removed in a future model release. Use [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) with the [effort parameter](/docs/en/build-with-claude/effort) to control thinking depth instead.
[Claude Mythos Preview](https://anthropic.com/glasswing), Claude Opus 4.7, and Claude Opus 4.6 support up to 128k output tokens. Claude Sonnet 4.6 and Claude Haiku 4.5 support up to 64k. See the [models overview](/docs/en/about-claude/models/overview) for limits on legacy models. On the [Message Batches API](/docs/en/build-with-claude/batch-processing#extended-output-beta), the `output-300k-2026-03-24` [beta header](/docs/en/api/beta-headers) raises the output limit to 300k for Opus 4.7, Opus 4.6, and Sonnet 4.6.
`budget_tokens` must be set to a value less than `max_tokens`. However, when using [interleaved thinking with tools](#interleaved-thinking), you can exceed this limit as the token limit becomes your entire context window. Because `budget_tokens` must be less than `max_tokens`, extended thinking cannot be combined with `max_tokens: 0` ([cache pre-warming](/docs/en/build-with-claude/prompt-caching#pre-warming-the-cache)).
### Summarized thinking
With extended thinking enabled, the Messages API for Claude 4 models returns a summary of Claude's full thinking process. Summarized thinking provides the full intelligence benefits of extended thinking, while preventing misuse. This is the default behavior on Claude 4 models when the `display` field on the thinking configuration is unset or set to `"summarized"`. On Claude Opus 4.7 and [Claude Mythos Preview](https://anthropic.com/glasswing), `display` defaults to `"omitted"` instead, so you must set `display: "summarized"` explicitly to receive summarized thinking.
Here are some important considerations for summarized thinking:
- You're charged for the full thinking tokens generated by the original request, not the summary tokens.
- The billed output token count will **not match** the count of tokens you see in the response.
- On Claude 4 models, the first few lines of thinking output are more verbose, providing detailed reasoning that's particularly helpful for prompt engineering purposes. [Claude Mythos Preview](https://anthropic.com/glasswing) summarizes from the first token, so its thinking blocks do not show this verbose preamble.
- As Anthropic seeks to improve the extended thinking feature, summarization behavior is subject to change.
- Summarization preserves the key ideas of Claude's thinking process with minimal added latency, enabling a streamable user experience.
- Summarization is processed by a different model than the one you target in your requests. The thinking model does not see the summarized output.
In rare cases where you need access to full thinking output for Claude 4 models, [contact our sales team](mailto:sales@anthropic.com).
### Controlling thinking display
The `display` field on the thinking configuration controls how thinking content is returned in API responses. It accepts two values:
- `"summarized"`: Thinking blocks contain summarized thinking text. See [Summarized thinking](#summarized-thinking) for details. This is the default on Claude Opus 4.6, Claude Sonnet 4.6, and earlier Claude 4 models.
- `"omitted"`: Thinking blocks are returned with an empty `thinking` field. The `signature` field still carries the encrypted full thinking for multi-turn continuity (see [Thinking encryption](#thinking-encryption)). This is the default on Claude Opus 4.7 and [Claude Mythos Preview](https://anthropic.com/glasswing).
Setting `display: "omitted"` is useful when your application doesn't surface thinking content to users. The primary benefit is **faster time-to-first-text-token when streaming:** The server skips streaming thinking tokens entirely and delivers only the signature, so the final text response begins streaming sooner.
Here are some important considerations for omitted thinking:
- You're still charged for the full thinking tokens. Omitting reduces latency, not cost.
- If you pass thinking blocks back in multi-turn conversations, pass them unchanged. The server decrypts the `signature` to reconstruct the original thinking for prompt construction (see [Preserving thinking blocks](/docs/en/build-with-claude/extended-thinking#preserving-thinking-blocks)). Any text you place in the `thinking` field of a round-tripped omitted block is ignored.
- `display` is invalid with `thinking.type: "disabled"` (there is nothing to display).
- When using `thinking.type: "adaptive"` and the model skips thinking for a simple request, no thinking block is produced regardless of `display`.
The `signature` field is identical whether `display` is `"summarized"` or `"omitted"`. Switching `display` values between turns in a conversation is supported.
On [Claude Mythos Preview](https://anthropic.com/glasswing), `display` defaults to `"omitted"`. The examples in this section pass `display` explicitly so they apply to all models, but on Mythos Preview you can leave it unset and receive the same behavior. To receive summarized thinking on Mythos Preview, set `display: "summarized"` explicitly.
Automated pipelines that never surface thinking content to end users can skip the overhead of receiving thinking tokens over the wire. Latency-sensitive applications get the same reasoning quality without waiting for thinking text to stream before the final response begins.
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-sonnet-4-6",
"max_tokens": 16000,
"thinking": {
"type": "enabled",
"budget_tokens": 10000,
"display": "omitted"
},
"messages": [
{
"role": "user",
"content": "What is 27 * 453?"
}
]
}'
```
```bash CLI
ant messages create \
--model claude-sonnet-4-6 \
--max-tokens 16000 \
--transform content --format yaml \
--thinking '{type: enabled, budget_tokens: 10000, display: omitted}' \
--message '{role: user, content: "What is 27 * 453?"}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=16000,
thinking={
"type": "enabled",
"budget_tokens": 10000,
"display": "omitted",
},
messages=[
{"role": "user", "content": "What is 27 * 453?"},
],
)
for block in response.content:
if block.type == "thinking":
if block.thinking:
print(f"Thinking: {block.thinking}")
else:
print("Thinking: [omitted]")
elif block.type == "text":
print(f"Response: {block.text}")
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 16000,
thinking: {
type: "enabled",
budget_tokens: 10000,
display: "omitted"
},
messages: [
{
role: "user",
content: "What is 27 * 453?"
}
]
});
for (const block of response.content) {
if (block.type === "thinking") {
if (block.thinking.length > 0) {
console.log(`Thinking: ${block.thinking}`);
} else {
console.log("Thinking: [omitted]");
}
} else if (block.type === "text") {
console.log(`Response: ${block.text}`);
}
}
```
```csharp C# hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var message = await client.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 16000,
Thinking = new ThinkingConfigEnabled
{
BudgetTokens = 10000,
Display = ThinkingConfigEnabledDisplay.Omitted
},
Messages =
[
new() { Role = Role.User, Content = "What is 27 * 453?" }
]
});
foreach (var block in message.Content)
{
if (block.TryPickThinking(out ThinkingBlock? thinking))
{
Console.WriteLine(string.IsNullOrEmpty(thinking.Thinking)
? "Thinking: [omitted]"
: $"Thinking: {thinking.Thinking}");
}
else if (block.TryPickText(out TextBlock? text))
{
Console.WriteLine($"Response: {text.Text}");
}
}
```
```go Go hidelines={1..12,-1}
package main
import (
"cmp"
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamUnion{
OfEnabled: &anthropic.ThinkingConfigEnabledParam{
BudgetTokens: 10000,
Display: anthropic.ThinkingConfigEnabledDisplayOmitted,
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is 27 * 453?")),
},
})
if err != nil {
log.Fatal(err)
}
for _, block := range response.Content {
switch v := block.AsAny().(type) {
case anthropic.ThinkingBlock:
fmt.Println("Thinking:", cmp.Or(v.Thinking, "[omitted]"))
case anthropic.TextBlock:
fmt.Println("Response:", v.Text)
}
}
}
```
```java Java hidelines={1..5,7}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.ThinkingConfigEnabled;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(16000L)
.thinking(ThinkingConfigEnabled.builder()
.budgetTokens(10000L)
.display(ThinkingConfigEnabled.Display.OMITTED)
.build())
.addUserMessage("What is 27 * 453?")
.build();
Message message = client.messages().create(params);
message.content().forEach(block -> {
block.thinking().ifPresent(thinkingBlock -> {
if (thinkingBlock.thinking().isEmpty()) {
IO.println("Thinking: [omitted]");
} else {
IO.println("Thinking: " + thinkingBlock.thinking());
}
});
block.text().ifPresent(textBlock ->
IO.println("Response: " + textBlock.text())
);
});
}
```
```php PHP hidelines={1..3,8}
messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 16000,
thinking: ThinkingConfigEnabled::with(
budgetTokens: 10000,
display: Display::OMITTED,
),
messages: [
['role' => 'user', 'content' => 'What is 27 * 453?'],
],
);
foreach ($response->content as $block) {
echo match (true) {
$block instanceof ThinkingBlock && $block->thinking === '' => "Thinking: [omitted]\n",
$block instanceof ThinkingBlock => "Thinking: {$block->thinking}\n",
$block instanceof TextBlock => "Response: {$block->text}\n",
default => '',
};
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 16000,
thinking: {
type: :enabled,
budget_tokens: 10000,
# The Ruby SDK uses `display_` (trailing underscore) to avoid
# shadowing Kernel#display; the wire field is still `display`.
display_: :omitted
},
messages: [{role: "user", content: "What is 27 * 453?"}]
)
response.content.each do |block|
case block.type
when :thinking
puts block.thinking.empty? ? "Thinking: [omitted]" : "Thinking: #{block.thinking}"
when :text
puts "Response: #{block.text}"
end
end
```
When `display: "omitted"` is set, the response contains `thinking` blocks with an empty `thinking` field:
```json Output
{
"content": [
{
"type": "thinking",
"thinking": "",
"signature": "EosnCkYICxIMMb3LzNrMu..."
},
{
"type": "text",
"text": "The answer is 12,231."
}
]
}
```
When streaming with `display: "omitted"`, no `thinking_delta` events are emitted; see [Streaming thinking](#streaming-thinking) below for the event sequence.
### Streaming thinking
You can stream extended thinking responses using [server-sent events (SSE)](https://developer.mozilla.org/en-US/Web/API/Server-sent%5Fevents/Using%5Fserver-sent%5Fevents).
When streaming is enabled for extended thinking, you receive thinking content via `thinking_delta` events.
When `display: "omitted"` is set, no `thinking_delta` events are emitted. See [Controlling thinking display](#controlling-thinking-display).
For more documentation on streaming via the Messages API, see [Streaming Messages](/docs/en/build-with-claude/streaming).
Here's how to handle streaming with thinking:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-sonnet-4-6",
"max_tokens": 16000,
"stream": true,
"thinking": {
"type": "enabled",
"budget_tokens": 10000
},
"messages": [
{
"role": "user",
"content": "What is the greatest common divisor of 1071 and 462?"
}
]
}'
```
```bash CLI
ant messages create --stream --format jsonl \
--model claude-sonnet-4-6 \
--max-tokens 16000 \
--thinking '{type: enabled, budget_tokens: 10000}' \
--message '{role: user, content: What is the greatest common divisor of 1071 and 462?}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=16000,
thinking={"type": "enabled", "budget_tokens": 10000},
messages=[
{
"role": "user",
"content": "What is the greatest common divisor of 1071 and 462?",
}
],
) as stream:
thinking_started = False
response_started = False
for event in stream:
if event.type == "content_block_start":
print(f"\nStarting {event.content_block.type} block...")
# Reset flags for each new block
thinking_started = False
response_started = False
elif event.type == "content_block_delta":
if event.delta.type == "thinking_delta":
if not thinking_started:
print("Thinking: ", end="", flush=True)
thinking_started = True
print(event.delta.thinking, end="", flush=True)
elif event.delta.type == "text_delta":
if not response_started:
print("Response: ", end="", flush=True)
response_started = True
print(event.delta.text, end="", flush=True)
elif event.type == "content_block_stop":
print("\nBlock complete.")
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const stream = await client.messages.stream({
model: "claude-sonnet-4-6",
max_tokens: 16000,
thinking: {
type: "enabled",
budget_tokens: 10000
},
messages: [
{
role: "user",
content: "What is the greatest common divisor of 1071 and 462?"
}
]
});
let thinkingStarted = false;
let responseStarted = false;
for await (const event of stream) {
if (event.type === "content_block_start") {
console.log(`\nStarting ${event.content_block.type} block...`);
// Reset flags for each new block
thinkingStarted = false;
responseStarted = false;
} else if (event.type === "content_block_delta") {
if (event.delta.type === "thinking_delta") {
if (!thinkingStarted) {
process.stdout.write("Thinking: ");
thinkingStarted = true;
}
process.stdout.write(event.delta.thinking);
} else if (event.delta.type === "text_delta") {
if (!responseStarted) {
process.stdout.write("Response: ");
responseStarted = true;
}
process.stdout.write(event.delta.text);
}
} else if (event.type === "content_block_stop") {
console.log("\nBlock complete.");
}
}
```
```csharp C# hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 16000,
Thinking = new ThinkingConfigEnabled(budgetTokens: 10000),
Messages = [new() { Role = Role.User, Content = "What is the greatest common divisor of 1071 and 462?" }]
};
bool thinkingStarted = false;
bool responseStarted = false;
await foreach (var streamEvent in client.Messages.CreateStreaming(parameters))
{
if (streamEvent.TryPickContentBlockStart(out var blockStart))
{
Console.WriteLine($"\nStarting {blockStart.ContentBlock.Type} block...");
thinkingStarted = false;
responseStarted = false;
}
else if (streamEvent.TryPickContentBlockDelta(out var blockDelta))
{
if (blockDelta.Delta.TryPickThinking(out var thinkingDelta))
{
if (!thinkingStarted)
{
Console.Write("Thinking: ");
thinkingStarted = true;
}
Console.Write(thinkingDelta.Thinking);
}
else if (blockDelta.Delta.TryPickText(out var textDelta))
{
if (!responseStarted)
{
Console.Write("Response: ");
responseStarted = true;
}
Console.Write(textDelta.Text);
}
}
else if (streamEvent.TryPickContentBlockStop(out _))
{
Console.WriteLine("\nBlock complete.");
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamOfEnabled(10000),
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the greatest common divisor of 1071 and 462?")),
},
})
thinkingStarted := false
responseStarted := false
for stream.Next() {
event := stream.Current()
switch eventVariant := event.AsAny().(type) {
case anthropic.ContentBlockStartEvent:
fmt.Printf("\nStarting %s block...\n", eventVariant.ContentBlock.Type)
thinkingStarted = false
responseStarted = false
case anthropic.ContentBlockDeltaEvent:
switch deltaVariant := eventVariant.Delta.AsAny().(type) {
case anthropic.ThinkingDelta:
if !thinkingStarted {
fmt.Print("Thinking: ")
thinkingStarted = true
}
fmt.Print(deltaVariant.Thinking)
case anthropic.TextDelta:
if !responseStarted {
fmt.Print("Response: ")
responseStarted = true
}
fmt.Print(deltaVariant.Text)
}
case anthropic.ContentBlockStopEvent:
fmt.Println("\nBlock complete.")
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
}
```
```java Java hidelines={1..6,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(16000L)
.enabledThinking(10000L)
.addUserMessage("What is the greatest common divisor of 1071 and 462?")
.build();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
event.contentBlockStart().ifPresent(startEvent ->
IO.println("\nStarting block...")
);
event.contentBlockDelta().ifPresent(deltaEvent -> {
deltaEvent.delta().thinking().ifPresent(td ->
IO.print(td.thinking())
);
deltaEvent.delta().text().ifPresent(td ->
IO.print(td.text())
);
});
event.contentBlockStop().ifPresent(stopEvent ->
IO.println("\nBlock complete.")
);
});
}
}
```
```php PHP hidelines={1..4}
messages->createStream(
maxTokens: 16000,
messages: [
['role' => 'user', 'content' => 'What is the greatest common divisor of 1071 and 462?']
],
model: 'claude-sonnet-4-6',
thinking: ['type' => 'enabled', 'budget_tokens' => 10000],
);
foreach ($stream as $event) {
if ($event->type === 'content_block_start') {
echo "\nStarting {$event->contentBlock->type} block...\n";
$thinkingStarted = false;
$responseStarted = false;
} elseif ($event->type === 'content_block_delta') {
if ($event->delta->type === 'thinking_delta') {
if (!$thinkingStarted) {
echo "Thinking: ";
$thinkingStarted = true;
}
echo $event->delta->thinking;
} elseif ($event->delta->type === 'text_delta') {
if (!$responseStarted) {
echo "Response: ";
$responseStarted = true;
}
echo $event->delta->text;
}
} elseif ($event->type === 'content_block_stop') {
echo "\nBlock complete.\n";
}
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
thinking_started = false
response_started = false
stream = client.messages.stream(
model: "claude-sonnet-4-6",
max_tokens: 16000,
thinking: {
type: "enabled",
budget_tokens: 10000
},
messages: [
{ role: "user", content: "What is the greatest common divisor of 1071 and 462?" }
]
)
stream.each do |event|
case event.type
when :content_block_start
puts "\nStarting #{event.content_block.type} block..."
thinking_started = false
response_started = false
when :content_block_delta
if event.delta.type == :thinking_delta
unless thinking_started
print "Thinking: "
thinking_started = true
end
print event.delta.thinking
elsif event.delta.type == :text_delta
unless response_started
print "Response: "
response_started = true
end
print event.delta.text
end
when :content_block_stop
puts "\nBlock complete."
end
end
```
Example streaming output:
```sse Output
event: message_start
data: {"type": "message_start", "message": {"id": "msg_01...", "type": "message", "role": "assistant", "content": [], "model": "claude-sonnet-4-6", "stop_reason": null, "stop_sequence": null}}
event: content_block_start
data: {"type": "content_block_start", "index": 0, "content_block": {"type": "thinking", "thinking": "", "signature": ""}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "I need to find the GCD of 1071 and 462 using the Euclidean algorithm.\n\n1071 = 2 × 462 + 147"}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "\n462 = 3 × 147 + 21\n147 = 7 × 21 + 0\n\nSo GCD(1071, 462) = 21"}}
// Additional thinking deltas...
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "signature_delta", "signature": "EqQBCgIYAhIM1gbcDa9GJwZA2b3hGgxBdjrkzLoky3dl1pkiMOYds..."}}
event: content_block_stop
data: {"type": "content_block_stop", "index": 0}
event: content_block_start
data: {"type": "content_block_start", "index": 1, "content_block": {"type": "text", "text": ""}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 1, "delta": {"type": "text_delta", "text": "The greatest common divisor of 1071 and 462 is **21**."}}
// Additional text deltas...
event: content_block_stop
data: {"type": "content_block_stop", "index": 1}
event: message_delta
data: {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence": null}}
event: message_stop
data: {"type": "message_stop"}
```
When `display: "omitted"` is set, the thinking block opens, a single `signature_delta` arrives, and the block closes without any `thinking_delta` events. Text streaming begins immediately after:
```sse Output
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"EosnCkYICxIMMb3LzNrMu..."}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}}
```
When using streaming with thinking enabled, you might notice that text sometimes arrives in larger chunks alternating with smaller, token-by-token delivery. This is expected behavior, especially for thinking content.
The streaming system needs to process content in batches for optimal performance, which can result in this "chunky" delivery pattern, with possible delays between streaming events.
## Extended thinking with tool use
Extended thinking can be used alongside [tool use](/docs/en/agents-and-tools/tool-use/overview), allowing Claude to reason through tool selection and results processing.
When using extended thinking with tool use, be aware of the following limitations:
1. **Tool choice limitation**: Tool use with thinking only supports `tool_choice: {"type": "auto"}` (the default) or `tool_choice: {"type": "none"}`. Using `tool_choice: {"type": "any"}` or `tool_choice: {"type": "tool", "name": "..."}` will result in an error because these options force tool use, which is incompatible with extended thinking.
2. **Preserving thinking blocks**: During tool use, you must pass `thinking` blocks back to the API for the last assistant message. Include the complete unmodified block back to the API to maintain reasoning continuity.
### Toggling thinking modes in conversations
You can't toggle thinking in the middle of an assistant turn, including during tool use loops. The entire assistant turn should operate in a single thinking mode:
- **If thinking is enabled**, the final assistant turn should start with a thinking block.
- **If thinking is disabled**, the final assistant turn shouldn't contain any thinking blocks
From the model's perspective, **tool use loops are part of the assistant turn**. An assistant turn doesn't complete until Claude finishes its full response, which may include multiple tool calls and results.
For example, this sequence is all part of a **single assistant turn**:
```text
User: "What's the weather in Paris?"
Assistant: [thinking] + [tool_use: get_weather]
User: [tool_result: "20°C, sunny"]
Assistant: [text: "The weather in Paris is 20°C and sunny"]
```
Even though there are multiple API messages, the tool use loop is conceptually part of one continuous assistant response.
#### Graceful thinking degradation
When a mid-turn thinking conflict occurs (such as toggling thinking on or off during a tool use loop), the API automatically disables thinking for that request. To preserve model quality and remain on-distribution, the API may:
- Strip thinking blocks from the conversation when they would create an invalid turn structure
- Disable thinking for the current request when the conversation history is incompatible with thinking being enabled
This means that attempting to toggle thinking mid-turn won't cause an error, but thinking will be silently disabled for that request. To confirm whether thinking was active, check for the presence of `thinking` blocks in the response.
#### Practical guidance
**Best practice**: Plan your thinking strategy at the start of each turn rather than trying to toggle mid-turn.
**Example: Toggling thinking after completing a turn**
```text
User: "What's the weather?"
Assistant: [tool_use] (thinking disabled)
User: [tool_result]
Assistant: [text: "It's sunny"]
User: "What about tomorrow?"
Assistant: [thinking] + [text: "..."] (thinking enabled - new turn)
```
By completing the assistant turn before toggling thinking, you ensure that thinking is actually enabled for the new request.
Toggling thinking modes also invalidates prompt caching for message history. For more details, see the [Extended thinking with prompt caching](#extended-thinking-with-prompt-caching) section.
Here's a practical example showing how to preserve thinking blocks when providing tool results:
```bash CLI
ant messages create --transform content <<'YAML'
model: claude-sonnet-4-6
max_tokens: 16000
thinking:
type: enabled
budget_tokens: 10000
tools:
- name: get_weather
description: Get current weather for a location
input_schema:
type: object
properties:
location:
type: string
required:
- location
messages:
- role: user
content: "What's the weather in Paris?"
YAML
```
```python Python hidelines={1}
import anthropic
client = anthropic.Anthropic()
weather_tool = {
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
}
# First request - Claude responds with thinking and tool request
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=16000,
thinking={"type": "enabled", "budget_tokens": 10000},
tools=[weather_tool],
messages=[{"role": "user", "content": "What's the weather in Paris?"}],
)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const weatherTool: Anthropic.Tool = {
name: "get_weather",
description: "Get current weather for a location",
input_schema: {
type: "object",
properties: {
location: { type: "string" }
},
required: ["location"]
}
};
// First request - Claude responds with thinking and tool request
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 16000,
thinking: {
type: "enabled",
budget_tokens: 10000
},
tools: [weatherTool],
messages: [{ role: "user", content: "What's the weather in Paris?" }]
});
```
```csharp C# hidelines={1..4}
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var weatherTool = new ToolUnion(new Tool()
{
Name = "get_weather",
Description = "Get current weather for a location",
InputSchema = new InputSchema()
{
Properties = new Dictionary
{
["location"] = JsonSerializer.SerializeToElement(new { type = "string" }),
},
Required = ["location"],
},
});
var parameters = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 16000,
Thinking = new ThinkingConfigEnabled(budgetTokens: 10000),
Tools = [weatherTool],
Messages = [new() { Role = Role.User, Content = "What's the weather in Paris?" }]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
weatherTool := anthropic.ToolUnionParam{
OfTool: &anthropic.ToolParam{
Name: "get_weather",
Description: anthropic.String("Get current weather for a location"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"location": map[string]any{
"type": "string",
},
},
Required: []string{"location"},
},
},
}
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamOfEnabled(10000),
Tools: []anthropic.ToolUnionParam{weatherTool},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather in Paris?")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..11,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.Tool;
import com.anthropic.core.JsonValue;
import java.util.List;
import java.util.Map;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(16000L)
.enabledThinking(10000L)
.addTool(Tool.builder()
.name("get_weather")
.description("Get current weather for a location")
.inputSchema(Tool.InputSchema.builder()
.properties(JsonValue.from(Map.of(
"location", Map.of("type", "string")
)))
.required(List.of("location"))
.build())
.build())
.addUserMessage("What's the weather in Paris?")
.build();
Message response = client.messages().create(params);
IO.println(response);
}
```
```php PHP hidelines={1..4}
'get_weather',
'description' => 'Get current weather for a location',
'input_schema' => [
'type' => 'object',
'properties' => [
'location' => ['type' => 'string']
],
'required' => ['location']
]
];
$message = $client->messages->create(
maxTokens: 16000,
messages: [
['role' => 'user', 'content' => "What's the weather in Paris?"]
],
model: 'claude-sonnet-4-6',
thinking: ['type' => 'enabled', 'budget_tokens' => 10000],
tools: [$weatherTool],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
weather_tool = {
name: "get_weather",
description: "Get current weather for a location",
input_schema: {
type: "object",
properties: {
location: { type: "string" }
},
required: ["location"]
}
}
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 16000,
thinking: {
type: "enabled",
budget_tokens: 10000
},
tools: [weather_tool],
messages: [
{ role: "user", content: "What's the weather in Paris?" }
]
)
puts message
```
The API response includes thinking, text, and tool_use blocks:
```json Output
{
"content": [
{
"type": "thinking",
"thinking": "The user wants to know the current weather in Paris. I have access to a function `get_weather`...",
"signature": "BDaL4VrbR2Oj0hO4XpJxT28J5TILnCrrUXoKiiNBZW9P+nr8XSj1zuZzAl4egiCCpQNvfyUuFFJP5CncdYZEQPPmLxYsNrcs...."
},
{
"type": "text",
"text": "I can help you get the current weather information for Paris. Let me check that for you"
},
{
"type": "tool_use",
"id": "toolu_01CswdEQBMshySk6Y9DFKrfq",
"name": "get_weather",
"input": {
"location": "Paris"
}
}
]
}
```
Now let's continue the conversation and use the tool
```bash CLI
# First turn: capture the assistant content array (thinking + tool_use,
# with signatures intact) as compact JSON.
ASSISTANT_CONTENT=$(ant messages create \
--transform content <<'YAML'
model: claude-sonnet-4-6
max_tokens: 16000
thinking:
type: enabled
budget_tokens: 10000
tools:
- name: get_weather
description: Get the current weather in a given location
input_schema:
type: object
properties:
location:
type: string
description: The city and state
required: [location]
messages:
- role: user
content: What's the weather in Paris?
YAML
)
TOOL_USE_ID=$(printf '%s' "$ASSISTANT_CONTENT" \
| grep -o 'toolu_[A-Za-z0-9]*')
# Second turn: pass the captured blocks back as the assistant message.
# The thinking block MUST accompany the tool_use block.
ant messages create < block.type === "thinking"
);
const toolUseBlock = response.content.find(
(block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
);
// Call your actual weather API, here is where your actual API call would go
// Let's pretend this is what we get back
const weatherData = { temperature: 88 };
if (thinkingBlock && toolUseBlock) {
// Second request - Include thinking block and tool result
// No new thinking blocks are generated in the response
const continuation = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 16000,
thinking: {
type: "enabled",
budget_tokens: 10000
},
tools: [weatherTool],
messages: [
{ role: "user", content: "What's the weather in Paris?" },
// notice that the thinkingBlock is passed in as well as the toolUseBlock
// if this is not passed in, an error is raised
{ role: "assistant", content: [thinkingBlock, toolUseBlock] },
{
role: "user",
content: [
{
type: "tool_result" as const,
tool_use_id: toolUseBlock.id,
content: `Current temperature: ${weatherData.temperature}°F`
}
]
}
]
});
console.log(continuation);
}
```
```csharp C# hidelines={1..4}
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var weatherTool = new ToolUnion(new Tool()
{
Name = "get_weather",
Description = "Get current weather for a location",
InputSchema = new InputSchema()
{
Properties = new Dictionary
{
["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "City name" }),
},
Required = ["location"],
},
});
var parameters = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 16000,
Thinking = new ThinkingConfigEnabled(budgetTokens: 10000),
Tools = [weatherTool],
Messages = [
new() { Role = Role.User, Content = "What is the weather in Paris?" }
]
};
var response = await client.Messages.Create(parameters);
// Extract the tool_use block to get its ID for the tool result
ToolUseBlock? toolUseBlock = null;
foreach (var block in response.Content)
{
if (block.TryPickToolUse(out var toolUse))
{
toolUseBlock = toolUse;
}
}
var weatherData = new { temperature = 88 };
// Build continuation with tool result
var continuationParams = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 16000,
Thinking = new ThinkingConfigEnabled(budgetTokens: 10000),
Tools = [weatherTool],
Messages = [
new() { Role = Role.User, Content = "What is the weather in Paris?" },
// response.Content includes the thinking blocks; passing them back is required
new() { Role = Role.Assistant, Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList() },
new() { Role = Role.User, Content = new MessageParamContent(new List
{
new ContentBlockParam(new ToolResultBlockParam()
{
ToolUseID = toolUseBlock?.ID ?? "",
Content = $"Current temperature: {weatherData.temperature}°F"
})
})}
]
};
var continuation = await client.Messages.Create(continuationParams);
Console.WriteLine(continuation);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
weatherTool := anthropic.ToolUnionParam{
OfTool: &anthropic.ToolParam{
Name: "get_weather",
Description: anthropic.String("Get current weather for a location"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"location": map[string]any{
"type": "string",
"description": "City name",
},
},
Required: []string{"location"},
},
},
}
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamOfEnabled(10000),
Tools: []anthropic.ToolUnionParam{weatherTool},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the weather in Paris?")),
},
})
if err != nil {
log.Fatal(err)
}
var toolUseBlock anthropic.ToolUseBlock
for _, block := range response.Content {
switch v := block.AsAny().(type) {
case anthropic.ToolUseBlock:
toolUseBlock = v
}
}
weatherData := map[string]int{"temperature": 88}
continuation, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamOfEnabled(10000),
Tools: []anthropic.ToolUnionParam{weatherTool},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the weather in Paris?")),
response.ToParam(),
anthropic.NewUserMessage(
anthropic.NewToolResultBlock(toolUseBlock.ID, fmt.Sprintf("Current temperature: %d°F", weatherData["temperature"]), false),
),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(continuation)
}
```
```java Java hidelines={1..10,13..16}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.ToolResultBlockParam;
import com.anthropic.models.messages.ToolUseBlock;
import com.anthropic.models.messages.ToolUseBlockParam;
import com.anthropic.models.messages.ThinkingBlock;
import com.anthropic.models.messages.ThinkingBlockParam;
import com.anthropic.core.JsonValue;
import java.util.List;
import java.util.Map;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
Tool weatherTool = Tool.builder()
.name("get_weather")
.description("Get current weather for a location")
.inputSchema(Tool.InputSchema.builder()
.properties(JsonValue.from(Map.of(
"location", Map.of("type", "string", "description", "City name")
)))
.required(List.of("location"))
.build())
.build();
MessageCreateParams initialParams = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(16000L)
.enabledThinking(10000L)
.addTool(weatherTool)
.addUserMessage("What is the weather in Paris?")
.build();
Message response = client.messages().create(initialParams);
ThinkingBlock thinkingBlock = null;
ToolUseBlock toolUseBlock = null;
for (var block : response.content()) {
if (block.thinking().isPresent()) {
thinkingBlock = block.thinking().get();
}
if (block.toolUse().isPresent()) {
toolUseBlock = block.toolUse().get();
}
}
int temperature = 88;
// Second request: pass back thinking block and tool result
MessageCreateParams continuationParams = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(16000L)
.enabledThinking(10000L)
.addTool(weatherTool)
.addUserMessage("What is the weather in Paris?")
.addAssistantMessageOfBlockParams(List.of(
ContentBlockParam.ofThinking(ThinkingBlockParam.builder()
.thinking(thinkingBlock.thinking())
.signature(thinkingBlock.signature())
.build()),
ContentBlockParam.ofToolUse(ToolUseBlockParam.builder()
.id(toolUseBlock.id())
.name(toolUseBlock.name())
.input(toolUseBlock._input())
.build())
))
.addUserMessageOfBlockParams(List.of(
ContentBlockParam.ofToolResult(
ToolResultBlockParam.builder()
.toolUseId(toolUseBlock.id())
.content("Current temperature: " + temperature + "°F")
.build()
)
))
.build();
Message continuation = client.messages().create(continuationParams);
IO.println(continuation);
}
```
```php PHP hidelines={1..4}
'get_weather',
'description' => 'Get current weather for a location',
'input_schema' => [
'type' => 'object',
'properties' => [
'location' => [
'type' => 'string',
'description' => 'City name'
]
],
'required' => ['location']
]
];
$response = $client->messages->create(
maxTokens: 16000,
messages: [
['role' => 'user', 'content' => 'What is the weather in Paris?']
],
model: 'claude-sonnet-4-6',
thinking: ['type' => 'enabled', 'budget_tokens' => 10000],
tools: [$weatherTool],
);
$thinkingBlock = null;
$toolUseBlock = null;
foreach ($response->content as $block) {
if ($block->type === 'thinking') {
$thinkingBlock = $block;
}
if ($block->type === 'tool_use') {
$toolUseBlock = $block;
}
}
$weatherData = ['temperature' => 88];
$continuation = $client->messages->create(
maxTokens: 16000,
messages: [
['role' => 'user', 'content' => 'What is the weather in Paris?'],
['role' => 'assistant', 'content' => [$thinkingBlock, $toolUseBlock]],
['role' => 'user', 'content' => [
[
'type' => 'tool_result',
'tool_use_id' => $toolUseBlock->id,
'content' => "Current temperature: {$weatherData['temperature']}°F"
]
]]
],
model: 'claude-sonnet-4-6',
thinking: ['type' => 'enabled', 'budget_tokens' => 10000],
tools: [$weatherTool],
);
echo $continuation;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
weather_tool = {
name: "get_weather",
description: "Get current weather for a location",
input_schema: {
type: "object",
properties: {
location: { type: "string", description: "City name" }
},
required: ["location"]
}
}
response = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 16000,
thinking: {
type: "enabled",
budget_tokens: 10000
},
tools: [weather_tool],
messages: [
{ role: "user", content: "What is the weather in Paris?" }
]
)
thinking_block = response.content.find { |block| block.type == :thinking }
tool_use_block = response.content.find { |block| block.type == :tool_use }
raise "No tool_use block found" unless tool_use_block
weather_data = { temperature: 88 }
continuation = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 16000,
thinking: {
type: "enabled",
budget_tokens: 10000
},
tools: [weather_tool],
messages: [
{ role: "user", content: "What is the weather in Paris?" },
{ role: "assistant", content: [thinking_block, tool_use_block] },
{ role: "user", content: [
{
type: "tool_result",
tool_use_id: tool_use_block.id,
content: "Current temperature: #{weather_data[:temperature]}°F"
}
] }
]
)
puts continuation
```
The API response now includes **only** text
```json Output
{
"content": [
{
"type": "text",
"text": "Currently in Paris, the temperature is 88°F (31°C)"
}
]
}
```
### Preserving thinking blocks
During tool use, you must pass `thinking` blocks back to the API, and you must include the complete unmodified block back to the API. This is critical for maintaining the model's reasoning flow and conversation integrity.
While you can omit `thinking` blocks from prior `assistant` role turns, always pass back all thinking blocks to the API for any multi-turn conversation. The API:
- Automatically filters the provided thinking blocks
- Uses the relevant thinking blocks necessary to preserve the model's reasoning
- Only bills for the input tokens for the blocks shown to Claude
Which blocks are kept depends on the model. See [Thinking block preservation by model](#thinking-block-preservation-in-claude-opus-45-and-later) for the per-class defaults. To override the default, use the [`clear_thinking_20251015` context-editing strategy](/docs/en/build-with-claude/context-editing#thinking-block-clearing).
When toggling thinking modes during a conversation, remember that the entire assistant turn (including tool use loops) must operate in a single thinking mode. For more details, see [Toggling thinking modes in conversations](#toggling-thinking-modes-in-conversations).
When Claude invokes tools, it is pausing its construction of a response to await external information. When tool results are returned, Claude continues building that existing response. This necessitates preserving thinking blocks during tool use, for a couple of reasons:
1. **Reasoning continuity**: The thinking blocks capture Claude's step-by-step reasoning that led to tool requests. When you post tool results, including the original thinking ensures Claude can continue its reasoning from where it left off.
2. **Context maintenance**: While tool results appear as user messages in the API structure, they're part of a continuous reasoning flow. Preserving thinking blocks maintains this conceptual flow across multiple API calls. For more information on context management, see the [guide on context windows](/docs/en/build-with-claude/context-windows).
**Important**: When providing `thinking` blocks, the entire sequence of consecutive `thinking` blocks must match the outputs generated by the model during the original request; you can't rearrange or modify the sequence of these blocks.
### Interleaved thinking
Extended thinking with tool use in Claude 4 models supports interleaved thinking, which enables Claude to think between tool calls and make more sophisticated reasoning after receiving tool results.
With interleaved thinking, Claude can:
- Reason about the results of a tool call before deciding what to do next
- Chain multiple tool calls with reasoning steps in between
- Make more nuanced decisions based on intermediate results
**Model support:**
- **[Claude Mythos Preview](https://anthropic.com/glasswing)**: Interleaved thinking happens automatically. Every inter-tool reasoning step moves into a thinking block instead of plain text, and thinking blocks are preserved across turns by default. No beta header is needed or supported.
- **Claude Opus 4.7**: Interleaved thinking is automatically enabled when using [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) (the only supported thinking mode on Opus 4.7). No beta header is needed.
- **Claude Opus 4.6**: Interleaved thinking is automatically enabled when using [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking). No beta header is needed. The `interleaved-thinking-2025-05-14` beta header is **deprecated** on Opus 4.6 and is safely ignored if included.
- **Claude Sonnet 4.6**: Interleaved thinking is automatically enabled when using [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) (recommended). The `interleaved-thinking-2025-05-14` beta header with manual extended thinking (`thinking: {type: "enabled"}`) is still functional but deprecated.
- **Other Claude 4 models** (Opus 4.5, Opus 4.1, Opus 4 (deprecated), Sonnet 4.5, Sonnet 4 (deprecated)): Add [the beta header](/docs/en/api/beta-headers) `interleaved-thinking-2025-05-14` to your API request to enable interleaved thinking.
Here are some important considerations for interleaved thinking:
- With interleaved thinking, the `budget_tokens` can exceed the `max_tokens` parameter, as it represents the total budget across all thinking blocks within one assistant turn.
- Interleaved thinking is only supported for [tools used via the Messages API](/docs/en/agents-and-tools/tool-use/overview).
- The Claude API and [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws) accept `interleaved-thinking-2025-05-14` in requests to any model without returning an error. On models that don't support interleaved thinking, the header is ignored. On Claude Opus 4.7 and Claude Opus 4.6, it's deprecated and safely ignored. On Claude Mythos Preview, it's not needed and safely ignored.
- On partner-operated platforms (for example, [Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock) and [Vertex AI](/docs/en/build-with-claude/claude-on-vertex-ai)), if you pass `interleaved-thinking-2025-05-14` to any model aside from Claude Opus 4.7, Claude Opus 4.6, Claude Sonnet 4.6, Claude Opus 4.5, Claude Opus 4.1, Opus 4 (deprecated), Sonnet 4.5, or Sonnet 4 (deprecated), your request will fail.
Without interleaved thinking, Claude thinks once at the start of the assistant turn. Subsequent responses after tool results continue without new thinking blocks.
```text
User: "What's the total revenue if we sold 150 units at $50 each,
and how does this compare to our average monthly revenue?"
Turn 1: [thinking] "I need to calculate 150 * $50, then check the database..."
[tool_use: calculator] { "expression": "150 * 50" }
↓ tool result: "7500"
Turn 2: [tool_use: database_query] { "query": "SELECT AVG(revenue)..." }
↑ no thinking block
↓ tool result: "5200"
Turn 3: [text] "The total revenue is $7,500, which is 44% above your
average monthly revenue of $5,200."
↑ no thinking block
```
With interleaved thinking enabled, Claude can think after receiving each tool result, allowing it to reason about intermediate results before continuing.
```text
User: "What's the total revenue if we sold 150 units at $50 each,
and how does this compare to our average monthly revenue?"
Turn 1: [thinking] "I need to calculate 150 * $50 first..."
[tool_use: calculator] { "expression": "150 * 50" }
↓ tool result: "7500"
Turn 2: [thinking] "Got $7,500. Now I should query the database to compare..."
[tool_use: database_query] { "query": "SELECT AVG(revenue)..." }
↑ thinking after receiving calculator result
↓ tool result: "5200"
Turn 3: [thinking] "$7,500 vs $5,200 average - that's a 44% increase..."
[text] "The total revenue is $7,500, which is 44% above your
average monthly revenue of $5,200."
↑ thinking before final answer
```
## Extended thinking with prompt caching
[Prompt caching](/docs/en/build-with-claude/prompt-caching) with thinking has several important considerations:
Extended thinking tasks often take longer than 5 minutes to complete. Consider using the [1-hour cache duration](/docs/en/build-with-claude/prompt-caching#1-hour-cache-duration) to maintain cache hits across longer thinking sessions and multi-step workflows.
**Thinking block context removal**
- On earlier Opus/Sonnet models and all Haiku models, thinking blocks from previous turns are removed from context, which can affect cache breakpoints. On Opus 4.5+ and Sonnet 4.6+, they are kept by default.
- When continuing conversations with tool use, thinking blocks are cached and count as input tokens when read from cache
- This creates a tradeoff: while thinking blocks don't consume context window space visually, they still count toward your input token usage when cached
- If thinking becomes disabled and you pass thinking content in the current tool use turn, the thinking content will be stripped and thinking will remain disabled for that request
**Cache invalidation patterns**
- Changes to thinking parameters (enabled/disabled or budget allocation) invalidate message cache breakpoints
- [Interleaved thinking](#interleaved-thinking) amplifies cache invalidation, as thinking blocks can occur between multiple [tool calls](#extended-thinking-with-tool-use)
- System prompts and tools remain cached despite thinking parameter changes or block removal
On earlier Opus/Sonnet models and all Haiku models, thinking blocks are removed for caching and context calculations; on Opus 4.5+ and Sonnet 4.6+, they are kept by default. In either case, they must be preserved when continuing conversations with [tool use](#extended-thinking-with-tool-use), especially with [interleaved thinking](#interleaved-thinking).
### Understanding thinking block caching behavior
When using extended thinking with tool use, thinking blocks exhibit specific caching behavior that affects token counting:
**How it works:**
1. Caching only occurs when you make a subsequent request that includes tool results
2. When the subsequent request is made, the previous conversation history (including thinking blocks) can be cached
3. These cached thinking blocks count as input tokens in your usage metrics when read from the cache
4. When a non-tool-result user block is included: on Opus 4.5+ and Sonnet 4.6+, previous thinking blocks are kept; on earlier Opus/Sonnet models and all Haiku models, all previous thinking blocks are ignored and stripped from context
**Detailed example flow:**
**Request 1:**
```text
User: "What's the weather in Paris?"
```
**Response 1:**
```text
[thinking_block_1] + [tool_use block 1]
```
**Request 2:**
```text
User: ["What's the weather in Paris?"],
Assistant: [thinking_block_1] + [tool_use block 1],
User: [tool_result_1, cache=True]
```
**Response 2:**
```text
[thinking_block_2] + [text block 2]
```
Request 2 writes a cache of the request content (not the response). The cache includes the original user message, the first thinking block, tool use block, and the tool result.
**Request 3:**
```text
User: ["What's the weather in Paris?"],
Assistant: [thinking_block_1] + [tool_use block 1],
User: [tool_result_1, cache=True],
Assistant: [thinking_block_2] + [text block 2],
User: [Text response, cache=True]
```
For Opus 4.5+ and Sonnet 4.6+, all previous thinking blocks are kept by default. For earlier Opus/Sonnet models and all Haiku models, because a non-tool-result user block was included, all previous thinking blocks are ignored and stripped from context. This request will be processed the same as:
```text
User: ["What's the weather in Paris?"],
Assistant: [tool_use block 1],
User: [tool_result_1, cache=True],
Assistant: [text block 2],
User: [Text response, cache=True]
```
**Key points:**
- This caching behavior happens automatically, even without explicit `cache_control` markers
- This behavior is consistent whether using regular thinking or interleaved thinking
```bash CLI
# Fetch ~10 kB of Pride and Prejudice for the cached system block
curl -s https://www.gutenberg.org/cache/epub/1342/pg1342.txt \
| head -c 10000 > pride.txt
# Emit a request body for the given thinking budget. Once CONTENT1
# is populated (after the first turn), the assistant reply and a
# follow-up user message are appended so the conversation grows.
build_body() {
cat <-
You are an AI assistant that is tasked with literary analysis.
Analyze the following text carefully.
- type: text
text: "@./pride.txt"
cache_control:
type: ephemeral
messages:
- role: user
content: Analyze the tone of this passage.
YAML
if [[ -n "${CONTENT1:-}" ]]; then
printf ' - role: assistant\n content: %s\n' "$CONTENT1"
printf ' - role: user\n'
printf ' content: Analyze the characters in this passage.\n'
fi
}
# First request (budget 4000): establishes the cache. Capture usage
# and content as two jsonl lines so the reply can be fed forward.
printf 'First request - establishing cache\n'
{
read -r USAGE1
read -r CONTENT1
} < <(build_body 4000 \
| ant messages create --transform '[usage,content]' --format jsonl)
printf 'First response usage: %s\n' "$USAGE1"
# Second request: same budget, system-prompt cache hit expected.
printf '\nSecond request - same thinking parameters (cache hit expected)\n'
USAGE2=$(build_body 4000 \
| ant messages create --transform usage --format jsonl)
printf 'Second response usage: %s\n' "$USAGE2"
# Third request: budget changed to 8000. The cached system prompt
# still hits; only message-block caching is invalidated.
printf '\nThird request - different thinking parameters (cache miss for messages)\n'
USAGE3=$(build_body 8000 \
| ant messages create --transform usage --format jsonl)
printf 'Third response usage: %s\n' "$USAGE3"
```
```python Python hidelines={1}
from anthropic import Anthropic
import requests
from bs4 import BeautifulSoup
client = Anthropic()
def fetch_article_content(url):
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")
# Remove script and style elements
for script in soup(["script", "style"]):
script.decompose()
# Get text
text = soup.get_text()
# Break into lines and remove leading and trailing space on each
lines = (line.strip() for line in text.splitlines())
# Break multi-headlines into a line each
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
# Drop blank lines
text = "\n".join(chunk for chunk in chunks if chunk)
return text
# Fetch the content of the article
book_url = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt"
book_content = fetch_article_content(book_url)
# Use just enough text for caching (first few chapters)
LARGE_TEXT = book_content[:10000]
SYSTEM_PROMPT = [
{
"type": "text",
"text": "You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully.",
},
{"type": "text", "text": LARGE_TEXT, "cache_control": {"type": "ephemeral"}},
]
MESSAGES = [{"role": "user", "content": "Analyze the tone of this passage."}]
# First request - establish cache
print("First request - establishing cache")
response1 = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=20000,
thinking={"type": "enabled", "budget_tokens": 4000},
system=SYSTEM_PROMPT,
messages=MESSAGES,
)
print(f"First response usage: {response1.usage}")
MESSAGES.append({"role": "assistant", "content": response1.content})
MESSAGES.append({"role": "user", "content": "Analyze the characters in this passage."})
# Second request - same thinking parameters (cache hit expected)
print("\nSecond request - same thinking parameters (cache hit expected)")
response2 = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=20000,
thinking={"type": "enabled", "budget_tokens": 4000},
system=SYSTEM_PROMPT,
messages=MESSAGES,
)
print(f"Second response usage: {response2.usage}")
# Third request - different thinking parameters (cache miss for messages)
print("\nThird request - different thinking parameters (cache miss for messages)")
response3 = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=20000,
thinking={
"type": "enabled",
"budget_tokens": 8000, # Changed thinking budget
},
system=SYSTEM_PROMPT, # System prompt remains cached
messages=MESSAGES, # Messages cache is invalidated
)
print(f"Third response usage: {response3.usage}")
```
```typescript TypeScript nocheck hidelines={1}
import Anthropic from "@anthropic-ai/sdk";
import axios from "axios";
import * as cheerio from "cheerio";
const client = new Anthropic();
async function fetchArticleContent(url: string): Promise {
const response = await axios.get(url);
const $ = cheerio.load(response.data);
$("script, style").remove();
let text = $.text();
const lines = text.split("\n").map((line) => line.trim());
text = lines.filter((line) => line.length > 0).join("\n");
return text;
}
const bookUrl = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt";
const bookContent = await fetchArticleContent(bookUrl);
const LARGE_TEXT = bookContent.slice(0, 10000);
const SYSTEM_PROMPT: Anthropic.TextBlockParam[] = [
{
type: "text",
text: "You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully."
},
{
type: "text",
text: LARGE_TEXT,
cache_control: { type: "ephemeral" }
}
];
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: "Analyze the tone of this passage." }
];
// First request - establish cache
console.log("First request - establishing cache");
const response1 = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 20000,
thinking: { type: "enabled", budget_tokens: 4000 },
system: SYSTEM_PROMPT,
messages
});
console.log(`First response usage: ${JSON.stringify(response1.usage)}`);
messages.push({
role: "assistant",
content: response1.content
});
messages.push({
role: "user",
content: "Analyze the characters in this passage."
});
// Second request - same thinking parameters (cache hit expected)
console.log("\nSecond request - same thinking parameters (cache hit expected)");
const response2 = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 20000,
thinking: { type: "enabled", budget_tokens: 4000 },
system: SYSTEM_PROMPT,
messages
});
console.log(`Second response usage: ${JSON.stringify(response2.usage)}`);
// Third request - different thinking parameters (cache miss for messages)
console.log("\nThird request - different thinking parameters (cache miss for messages)");
const response3 = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 20000,
thinking: { type: "enabled", budget_tokens: 8000 },
system: SYSTEM_PROMPT,
messages
});
console.log(`Third response usage: ${JSON.stringify(response3.usage)}`);
```
```csharp C# hidelines={1..4}
using System.Net.Http;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
// Fetch book content
using var httpClient = new HttpClient();
var bookContent = await httpClient.GetStringAsync("https://www.gutenberg.org/cache/epub/1342/pg1342.txt");
var largeText = bookContent.Substring(0, Math.Min(10000, bookContent.Length));
var systemPrompt = new MessageCreateParamsSystem(new List
{
new TextBlockParam()
{
Text = "You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully."
},
new TextBlockParam()
{
Text = largeText,
CacheControl = new CacheControlEphemeral(),
},
});
var messages = new List
{
new() { Role = Role.User, Content = "Analyze the tone of this passage." }
};
// First request - establish cache
Console.WriteLine("First request - establishing cache");
var parameters1 = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 20000,
Thinking = new ThinkingConfigEnabled(budgetTokens: 4000),
System = systemPrompt,
Messages = messages
};
var response1 = await client.Messages.Create(parameters1);
Console.WriteLine($"First response usage: {response1.Usage}");
messages.Add(new() { Role = Role.Assistant, Content = response1.Content.Select(block => new ContentBlockParam(block.Json)).ToList() });
messages.Add(new() { Role = Role.User, Content = "Analyze the characters in this passage." });
// Second request - same thinking parameters (cache hit expected)
Console.WriteLine("\nSecond request - same thinking parameters (cache hit expected)");
var parameters2 = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 20000,
Thinking = new ThinkingConfigEnabled(budgetTokens: 4000),
System = systemPrompt,
Messages = messages
};
var response2 = await client.Messages.Create(parameters2);
Console.WriteLine($"Second response usage: {response2.Usage}");
// Third request - different thinking parameters (cache miss for messages)
Console.WriteLine("\nThird request - different thinking parameters (cache miss for messages)");
var parameters3 = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 20000,
Thinking = new ThinkingConfigEnabled(budgetTokens: 8000),
System = systemPrompt,
Messages = messages
};
var response3 = await client.Messages.Create(parameters3);
Console.WriteLine($"Third response usage: {response3.Usage}");
```
```go Go hidelines={1..15,-6..-1}
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
// Fetch book content
resp, err := http.Get("https://www.gutenberg.org/cache/epub/1342/pg1342.txt")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
largeText := string(body)
if len(largeText) > 10000 {
largeText = largeText[:10000]
}
systemPrompt := []anthropic.TextBlockParam{
{Text: "You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully."},
{
Text: largeText,
CacheControl: anthropic.NewCacheControlEphemeralParam(),
},
}
messages := []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the tone of this passage.")),
}
// First request - establish cache
fmt.Println("First request - establishing cache")
response1, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 20000,
Thinking: anthropic.ThinkingConfigParamOfEnabled(4000),
System: systemPrompt,
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("First response usage: %s\n", response1.Usage.RawJSON())
messages = append(messages, response1.ToParam())
messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the characters in this passage.")))
// Second request - same thinking parameters (cache hit expected)
fmt.Println("\nSecond request - same thinking parameters (cache hit expected)")
response2, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 20000,
Thinking: anthropic.ThinkingConfigParamOfEnabled(4000),
System: systemPrompt,
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Second response usage: %s\n", response2.Usage.RawJSON())
// Third request - different thinking parameters (cache miss for messages)
fmt.Println("\nThird request - different thinking parameters (cache miss for messages)")
response3, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 20000,
Thinking: anthropic.ThinkingConfigParamOfEnabled(8000),
System: systemPrompt,
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Third response usage: %s\n", response3.Usage.RawJSON())
}
```
```java Java hidelines={1..2,4..13}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
void main() throws Exception {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Fetch book content
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.gutenberg.org/cache/epub/1342/pg1342.txt"))
.build();
HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
String bookContent = response.body();
String largeText = bookContent.substring(0, Math.min(10000, bookContent.length()));
List systemPrompt = List.of(
TextBlockParam.builder()
.text("You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully.")
.build(),
TextBlockParam.builder()
.text(largeText)
.cacheControl(CacheControlEphemeral.builder().build())
.build()
);
// First request - establish cache
IO.println("First request - establishing cache");
MessageCreateParams params1 = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(20000L)
.enabledThinking(4000L)
.systemOfTextBlockParams(systemPrompt)
.addUserMessage("Analyze the tone of this passage.")
.build();
Message response1 = client.messages().create(params1);
IO.println("First response usage: " + response1.usage());
// Second request - same thinking parameters (cache hit expected)
IO.println("\nSecond request - same thinking parameters (cache hit expected)");
MessageCreateParams params2 = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(20000L)
.enabledThinking(4000L)
.systemOfTextBlockParams(systemPrompt)
.addUserMessage("Analyze the tone of this passage.")
.addAssistantMessageOfBlockParams(response1.content().stream()
.map(block -> block.toParam())
.collect(java.util.stream.Collectors.toList()))
.addUserMessage("Analyze the characters in this passage.")
.build();
Message response2 = client.messages().create(params2);
IO.println("Second response usage: " + response2.usage());
// Third request - different thinking parameters (cache miss for messages)
IO.println("\nThird request - different thinking parameters (cache miss for messages)");
MessageCreateParams params3 = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(20000L)
.enabledThinking(8000L)
.systemOfTextBlockParams(systemPrompt)
.addUserMessage("Analyze the tone of this passage.")
.addAssistantMessageOfBlockParams(response1.content().stream()
.map(block -> block.toParam())
.collect(java.util.stream.Collectors.toList()))
.addUserMessage("Analyze the characters in this passage.")
.build();
Message response3 = client.messages().create(params3);
IO.println("Third response usage: " + response3.usage());
}
```
```php PHP hidelines={1..5}
'text',
'text' => 'You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully.'
],
[
'type' => 'text',
'text' => $largeText,
'cache_control' => ['type' => 'ephemeral']
]
];
$messages = [
['role' => 'user', 'content' => 'Analyze the tone of this passage.']
];
// First request - establish cache
echo "First request - establishing cache\n";
$response1 = $client->messages->create(
maxTokens: 20000,
messages: $messages,
model: 'claude-sonnet-4-6',
system: $systemPrompt,
thinking: ['type' => 'enabled', 'budget_tokens' => 4000],
);
echo "First response usage: " . json_encode($response1->usage) . "\n";
$messages[] = ['role' => 'assistant', 'content' => $response1->content];
$messages[] = ['role' => 'user', 'content' => 'Analyze the characters in this passage.'];
// Second request - same thinking parameters (cache hit expected)
echo "\nSecond request - same thinking parameters (cache hit expected)\n";
$response2 = $client->messages->create(
maxTokens: 20000,
messages: $messages,
model: 'claude-sonnet-4-6',
system: $systemPrompt,
thinking: ['type' => 'enabled', 'budget_tokens' => 4000],
);
echo "Second response usage: " . json_encode($response2->usage) . "\n";
// Third request - different thinking parameters (cache miss for messages)
echo "\nThird request - different thinking parameters (cache miss for messages)\n";
$response3 = $client->messages->create(
maxTokens: 20000,
messages: $messages,
model: 'claude-sonnet-4-6',
system: $systemPrompt,
thinking: ['type' => 'enabled', 'budget_tokens' => 8000],
);
echo "Third response usage: " . json_encode($response3->usage) . "\n";
```
```ruby Ruby hidelines={1}
require "anthropic"
require "net/http"
require "uri"
client = Anthropic::Client.new
# Fetch book content
uri = URI("https://www.gutenberg.org/cache/epub/1342/pg1342.txt")
response = Net::HTTP.get_response(uri)
book_content = response.body
large_text = book_content[0...10000]
system_prompt = [
{
type: "text",
text: "You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully."
},
{
type: "text",
text: large_text,
cache_control: { type: "ephemeral" }
}
]
messages = [
{ role: "user", content: "Analyze the tone of this passage." }
]
# First request - establish cache
puts "First request - establishing cache"
response1 = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 20000,
thinking: {
type: "enabled",
budget_tokens: 4000
},
system: system_prompt,
messages: messages
)
puts "First response usage: #{response1.usage}"
messages << { role: "assistant", content: response1.content }
messages << { role: "user", content: "Analyze the characters in this passage." }
# Second request - same thinking parameters (cache hit expected)
puts "\nSecond request - same thinking parameters (cache hit expected)"
response2 = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 20000,
thinking: {
type: "enabled",
budget_tokens: 4000
},
system: system_prompt,
messages: messages
)
puts "Second response usage: #{response2.usage}"
# Third request - different thinking parameters (cache miss for messages)
puts "\nThird request - different thinking parameters (cache miss for messages)"
response3 = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 20000,
thinking: {
type: "enabled",
budget_tokens: 8000
},
system: system_prompt,
messages: messages
)
puts "Third response usage: #{response3.usage}"
```
```bash CLI
# Fetch the first ~10 kB of Pride and Prejudice for the cached prefix
curl -sL 'https://www.gutenberg.org/cache/epub/1342/pg1342.txt' \
| head -c 10000 > book.txt
# Call 1: thinking budget 4000, writes the cache
USAGE=$(ant messages create \
--model claude-sonnet-4-6 --max-tokens 20000 \
--transform usage <<'YAML'
thinking:
type: enabled
budget_tokens: 4000
messages:
- role: user
content:
- type: text
text: "@./book.txt"
cache_control:
type: ephemeral
- type: text
text: "Give a one-sentence summary of this passage."
YAML
)
printf 'Call 1 (budget 4000):\n%s\n\n' "$USAGE"
# Call 2: same budget, conversation extended; expect cache HIT
USAGE=$(ant messages create \
--model claude-sonnet-4-6 --max-tokens 20000 \
--transform usage <<'YAML'
thinking:
type: enabled
budget_tokens: 4000
messages:
- role: user
content:
- type: text
text: "@./book.txt"
cache_control:
type: ephemeral
- type: text
text: "Give a one-sentence summary of this passage."
- role: assistant
content: "It opens Pride and Prejudice with the Bennet family."
- role: user
content: "Who is the protagonist?"
YAML
)
printf 'Call 2 (budget 4000):\n%s\n\n' "$USAGE"
# Call 3: budget changed to 8000; cache MISS even though prefix is identical
USAGE=$(ant messages create \
--model claude-sonnet-4-6 --max-tokens 20000 \
--transform usage <<'YAML'
thinking:
type: enabled
budget_tokens: 8000
messages:
- role: user
content:
- type: text
text: "@./book.txt"
cache_control:
type: ephemeral
- type: text
text: "Give a one-sentence summary of this passage."
- role: assistant
content: "It opens Pride and Prejudice with the Bennet family."
- role: user
content: "Who is the protagonist?"
- role: assistant
content: "Elizabeth Bennet is the protagonist."
- role: user
content: "What era is the story set in?"
YAML
)
printf 'Call 3 (budget 8000):\n%s\n' "$USAGE"
```
```python Python hidelines={1}
from anthropic import Anthropic
import requests
from bs4 import BeautifulSoup
client = Anthropic()
def fetch_article_content(url):
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")
# Remove script and style elements
for script in soup(["script", "style"]):
script.decompose()
# Get text
text = soup.get_text()
# Break into lines and remove leading and trailing space on each
lines = (line.strip() for line in text.splitlines())
# Break multi-headlines into a line each
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
# Drop blank lines
text = "\n".join(chunk for chunk in chunks if chunk)
return text
# Fetch the content of the article
book_url = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt"
book_content = fetch_article_content(book_url)
# Use just enough text for caching (first few chapters)
LARGE_TEXT = book_content[:10000]
# No system prompt - caching in messages instead
MESSAGES = [
{
"role": "user",
"content": [
{
"type": "text",
"text": LARGE_TEXT,
"cache_control": {"type": "ephemeral"},
},
{"type": "text", "text": "Analyze the tone of this passage."},
],
}
]
# First request - establish cache
print("First request - establishing cache")
response1 = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=20000,
thinking={"type": "enabled", "budget_tokens": 4000},
messages=MESSAGES,
)
print(f"First response usage: {response1.usage}")
MESSAGES.append({"role": "assistant", "content": response1.content})
MESSAGES.append({"role": "user", "content": "Analyze the characters in this passage."})
# Second request - same thinking parameters (cache hit expected)
print("\nSecond request - same thinking parameters (cache hit expected)")
response2 = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=20000,
thinking={
"type": "enabled",
"budget_tokens": 4000, # Same thinking budget
},
messages=MESSAGES,
)
print(f"Second response usage: {response2.usage}")
MESSAGES.append({"role": "assistant", "content": response2.content})
MESSAGES.append({"role": "user", "content": "Analyze the setting in this passage."})
# Third request - different thinking budget (cache miss expected)
print("\nThird request - different thinking budget (cache miss expected)")
response3 = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=20000,
thinking={
"type": "enabled",
"budget_tokens": 8000, # Different thinking budget breaks cache
},
messages=MESSAGES,
)
print(f"Third response usage: {response3.usage}")
```
```typescript TypeScript nocheck hidelines={1}
import Anthropic from "@anthropic-ai/sdk";
import axios from "axios";
import * as cheerio from "cheerio";
const client = new Anthropic();
async function fetchArticleContent(url: string): Promise {
const response = await axios.get(url);
const $ = cheerio.load(response.data);
// Remove script and style elements
$("script, style").remove();
// Get text
let text = $.text();
// Clean up text (break into lines, remove whitespace)
const lines = text.split("\n").map((line) => line.trim());
const chunks = lines.flatMap((line) => line.split(" ").map((phrase) => phrase.trim()));
text = chunks.filter((chunk) => chunk).join("\n");
return text;
}
const bookUrl = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt";
const bookContent = await fetchArticleContent(bookUrl);
const LARGE_TEXT = bookContent.substring(0, 10000);
// No system prompt - caching in messages instead
const messages: Anthropic.MessageParam[] = [
{
role: "user",
content: [
{
type: "text",
text: LARGE_TEXT,
cache_control: { type: "ephemeral" }
},
{
type: "text",
text: "Analyze the tone of this passage."
}
]
}
];
// First request - establish cache
console.log("First request - establishing cache");
const response1 = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 20000,
thinking: { type: "enabled", budget_tokens: 4000 },
messages
});
console.log("First response usage: ", response1.usage);
messages.push(
{ role: "assistant", content: response1.content },
{ role: "user", content: "Analyze the characters in this passage." }
);
// Second request - same thinking parameters (cache hit expected)
console.log("\nSecond request - same thinking parameters (cache hit expected)");
const response2 = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 20000,
thinking: { type: "enabled", budget_tokens: 4000 },
messages
});
console.log("Second response usage: ", response2.usage);
messages.push(
{ role: "assistant", content: response2.content },
{ role: "user", content: "Analyze the setting in this passage." }
);
// Third request - different thinking budget (cache miss expected)
console.log("\nThird request - different thinking budget (cache miss expected)");
const response3 = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 20000,
thinking: { type: "enabled", budget_tokens: 8000 },
messages
});
console.log("Third response usage: ", response3.usage);
```
```csharp C# hidelines={1..4}
using System.Net.Http;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
string bookUrl = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt";
string bookContent = await FetchArticleContent(bookUrl);
string largeText = bookContent.Substring(0, Math.Min(10000, bookContent.Length));
Console.WriteLine("First request - establishing cache");
var parameters1 = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 20000,
Thinking = new ThinkingConfigEnabled(budgetTokens: 4000),
Messages =
[
new()
{
Role = Role.User,
Content = new MessageParamContent(new List
{
new ContentBlockParam(new TextBlockParam()
{
Text = largeText,
CacheControl = new CacheControlEphemeral(),
}),
new ContentBlockParam(new TextBlockParam()
{
Text = "Analyze the tone of this passage."
}),
})
}
]
};
var response1 = await client.Messages.Create(parameters1);
Console.WriteLine($"First response usage: {response1.Usage}");
Console.WriteLine("\nSecond request - same thinking parameters (cache hit expected)");
var parameters2 = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 20000,
Thinking = new ThinkingConfigEnabled(budgetTokens: 4000),
Messages =
[
new()
{
Role = Role.User,
Content = new MessageParamContent(new List
{
new ContentBlockParam(new TextBlockParam()
{
Text = largeText,
CacheControl = new CacheControlEphemeral(),
}),
new ContentBlockParam(new TextBlockParam()
{
Text = "Analyze the tone of this passage."
}),
})
},
new()
{
Role = Role.Assistant,
Content = response1.Content.Select(block => new ContentBlockParam(block.Json)).ToList()
},
new()
{
Role = Role.User,
Content = "Analyze the characters in this passage."
}
]
};
var response2 = await client.Messages.Create(parameters2);
Console.WriteLine($"Second response usage: {response2.Usage}");
Console.WriteLine("\nThird request - different thinking budget (cache miss expected)");
var parameters3 = new MessageCreateParams
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 20000,
Thinking = new ThinkingConfigEnabled(budgetTokens: 8000),
Messages =
[
new()
{
Role = Role.User,
Content = new MessageParamContent(new List
{
new ContentBlockParam(new TextBlockParam()
{
Text = largeText,
CacheControl = new CacheControlEphemeral(),
}),
new ContentBlockParam(new TextBlockParam()
{
Text = "Analyze the tone of this passage."
}),
})
},
new()
{
Role = Role.Assistant,
Content = response1.Content.Select(block => new ContentBlockParam(block.Json)).ToList()
},
new()
{
Role = Role.User,
Content = "Analyze the characters in this passage."
},
new()
{
Role = Role.Assistant,
Content = response2.Content.Select(block => new ContentBlockParam(block.Json)).ToList()
},
new()
{
Role = Role.User,
Content = "Analyze the setting in this passage."
}
]
};
var response3 = await client.Messages.Create(parameters3);
Console.WriteLine($"Third response usage: {response3.Usage}");
static async Task FetchArticleContent(string url)
{
using HttpClient httpClient = new();
string content = await httpClient.GetStringAsync(url);
return content;
}
```
```go Go hidelines={1..41,-1}
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"strings"
"github.com/anthropics/anthropic-sdk-go"
)
func fetchArticleContent(url string) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
text := string(body)
lines := strings.Split(text, "\n")
var cleanedLines []string
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed != "" {
cleanedLines = append(cleanedLines, trimmed)
}
}
return strings.Join(cleanedLines, "\n"), nil
}
func main() {
client := anthropic.NewClient()
bookURL := "https://www.gutenberg.org/cache/epub/1342/pg1342.txt"
bookContent, err := fetchArticleContent(bookURL)
if err != nil {
log.Fatal(err)
}
largeText := bookContent
if len(largeText) > 10000 {
largeText = largeText[:10000]
}
// No system prompt - caching in messages instead
messages := []anthropic.MessageParam{
anthropic.NewUserMessage(
anthropic.ContentBlockParamUnion{OfText: &anthropic.TextBlockParam{
Text: largeText,
CacheControl: anthropic.NewCacheControlEphemeralParam(),
}},
anthropic.NewTextBlock("Analyze the tone of this passage."),
),
}
// First request - establish cache
fmt.Println("First request - establishing cache")
response1, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 20000,
Thinking: anthropic.ThinkingConfigParamOfEnabled(4000),
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("First response usage: %s\n", response1.Usage.RawJSON())
messages = append(messages, response1.ToParam())
messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the characters in this passage.")))
// Second request - same thinking parameters (cache hit expected)
fmt.Println("\nSecond request - same thinking parameters (cache hit expected)")
response2, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 20000,
Thinking: anthropic.ThinkingConfigParamOfEnabled(4000),
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Second response usage: %s\n", response2.Usage.RawJSON())
messages = append(messages, response2.ToParam())
messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the setting in this passage.")))
// Third request - different thinking budget (cache miss expected)
fmt.Println("\nThird request - different thinking budget (cache miss expected)")
response3, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 20000,
Thinking: anthropic.ThinkingConfigParamOfEnabled(8000),
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Third response usage: %s\n", response3.Usage.RawJSON())
}
```
```java Java hidelines={1..2,4..14}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
void main() throws Exception {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
String bookUrl = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt";
String bookContent = fetchArticleContent(bookUrl);
String largeText = bookContent.substring(0, Math.min(10000, bookContent.length()));
// First request - establishing cache
IO.println("First request - establishing cache");
MessageCreateParams params1 = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(20000L)
.enabledThinking(4000L)
.addUserMessageOfBlockParams(List.of(
ContentBlockParam.ofText(TextBlockParam.builder()
.text(largeText)
.cacheControl(CacheControlEphemeral.builder().build())
.build()),
ContentBlockParam.ofText(TextBlockParam.builder()
.text("Analyze the tone of this passage.")
.build())
))
.build();
Message response1 = client.messages().create(params1);
IO.println("First response usage: " + response1.usage());
// Second request - same thinking parameters (cache hit expected)
IO.println("\nSecond request - same thinking parameters (cache hit expected)");
MessageCreateParams params2 = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(20000L)
.enabledThinking(4000L)
.addUserMessageOfBlockParams(List.of(
ContentBlockParam.ofText(TextBlockParam.builder()
.text(largeText)
.cacheControl(CacheControlEphemeral.builder().build())
.build()),
ContentBlockParam.ofText(TextBlockParam.builder()
.text("Analyze the tone of this passage.")
.build())
))
.addAssistantMessageOfBlockParams(response1.content().stream()
.map(block -> block.toParam())
.collect(java.util.stream.Collectors.toList()))
.addUserMessage("Analyze the characters in this passage.")
.build();
Message response2 = client.messages().create(params2);
IO.println("Second response usage: " + response2.usage());
// Third request - different thinking budget (cache miss expected)
IO.println("\nThird request - different thinking budget (cache miss expected)");
MessageCreateParams params3 = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(20000L)
.enabledThinking(8000L)
.addUserMessageOfBlockParams(List.of(
ContentBlockParam.ofText(TextBlockParam.builder()
.text(largeText)
.cacheControl(CacheControlEphemeral.builder().build())
.build()),
ContentBlockParam.ofText(TextBlockParam.builder()
.text("Analyze the tone of this passage.")
.build())
))
.addAssistantMessageOfBlockParams(response1.content().stream()
.map(block -> block.toParam())
.collect(java.util.stream.Collectors.toList()))
.addUserMessage("Analyze the characters in this passage.")
.addAssistantMessageOfBlockParams(response2.content().stream()
.map(block -> block.toParam())
.collect(java.util.stream.Collectors.toList()))
.addUserMessage("Analyze the setting in this passage.")
.build();
Message response3 = client.messages().create(params3);
IO.println("Third response usage: " + response3.usage());
}
String fetchArticleContent(String url) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
```
```php PHP hidelines={1..6}
messages->create(
maxTokens: 20000,
messages: [[
'role' => 'user',
'content' => [
[
'type' => 'text',
'text' => $largeText,
'cache_control' => ['type' => 'ephemeral']
],
[
'type' => 'text',
'text' => 'Analyze the tone of this passage.'
]
]
]],
model: 'claude-sonnet-4-6',
thinking: ['type' => 'enabled', 'budget_tokens' => 4000],
);
echo "First response usage: " . json_encode($response1->usage) . "\n";
echo "\nSecond request - same thinking parameters (cache hit expected)\n";
$response2 = $client->messages->create(
maxTokens: 20000,
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'text',
'text' => $largeText,
'cache_control' => ['type' => 'ephemeral']
],
[
'type' => 'text',
'text' => 'Analyze the tone of this passage.'
]
]
],
[
'role' => 'assistant',
'content' => $response1->content
],
[
'role' => 'user',
'content' => 'Analyze the characters in this passage.'
]
],
model: 'claude-sonnet-4-6',
thinking: ['type' => 'enabled', 'budget_tokens' => 4000],
);
echo "Second response usage: " . json_encode($response2->usage) . "\n";
echo "\nThird request - different thinking budget (cache miss expected)\n";
$response3 = $client->messages->create(
maxTokens: 20000,
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'text',
'text' => $largeText,
'cache_control' => ['type' => 'ephemeral']
],
[
'type' => 'text',
'text' => 'Analyze the tone of this passage.'
]
]
],
[
'role' => 'assistant',
'content' => $response1->content
],
[
'role' => 'user',
'content' => 'Analyze the characters in this passage.'
],
[
'role' => 'assistant',
'content' => $response2->content
],
[
'role' => 'user',
'content' => 'Analyze the setting in this passage.'
]
],
model: 'claude-sonnet-4-6',
thinking: ['type' => 'enabled', 'budget_tokens' => 8000],
);
echo "Third response usage: " . json_encode($response3->usage) . "\n";
```
```ruby Ruby hidelines={1}
require "anthropic"
require "net/http"
require "uri"
def fetch_article_content(url)
uri = URI.parse(url)
response = Net::HTTP.get_response(uri)
text = response.body
lines = text.split("\n").map(&:strip)
lines.reject(&:empty?).join("\n")
end
client = Anthropic::Client.new
book_url = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt"
book_content = fetch_article_content(book_url)
large_text = book_content[0...10000]
puts "First request - establishing cache"
response1 = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 20000,
thinking: {
type: "enabled",
budget_tokens: 4000
},
messages: [{
role: "user",
content: [
{
type: "text",
text: large_text,
cache_control: { type: "ephemeral" }
},
{
type: "text",
text: "Analyze the tone of this passage."
}
]
}]
)
puts "First response usage: #{response1.usage}"
puts "\nSecond request - same thinking parameters (cache hit expected)"
response2 = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 20000,
thinking: {
type: "enabled",
budget_tokens: 4000
},
messages: [
{
role: "user",
content: [
{
type: "text",
text: large_text,
cache_control: { type: "ephemeral" }
},
{
type: "text",
text: "Analyze the tone of this passage."
}
]
},
{
role: "assistant",
content: response1.content
},
{
role: "user",
content: "Analyze the characters in this passage."
}
]
)
puts "Second response usage: #{response2.usage}"
puts "\nThird request - different thinking budget (cache miss expected)"
response3 = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 20000,
thinking: {
type: "enabled",
budget_tokens: 8000
},
messages: [
{
role: "user",
content: [
{
type: "text",
text: large_text,
cache_control: { type: "ephemeral" }
},
{
type: "text",
text: "Analyze the tone of this passage."
}
]
},
{
role: "assistant",
content: response1.content
},
{
role: "user",
content: "Analyze the characters in this passage."
},
{
role: "assistant",
content: response2.content
},
{
role: "user",
content: "Analyze the setting in this passage."
}
]
)
puts "Third response usage: #{response3.usage}"
```
Here is the output of the script (you may see slightly different numbers)
```text Output
First request - establishing cache
First response usage: { cache_creation_input_tokens: 1370, cache_read_input_tokens: 0, input_tokens: 17, output_tokens: 700 }
Second request - same thinking parameters (cache hit expected)
Second response usage: { cache_creation_input_tokens: 0, cache_read_input_tokens: 1370, input_tokens: 303, output_tokens: 874 }
Third request - different thinking budget (cache miss expected)
Third response usage: { cache_creation_input_tokens: 1370, cache_read_input_tokens: 0, input_tokens: 747, output_tokens: 619 }
```
This example demonstrates that when caching is set up in the messages array, changing the thinking parameters (budget_tokens increased from 4000 to 8000) **invalidates the cache**. The third request shows no cache hit with `cache_creation_input_tokens=1370` and `cache_read_input_tokens=0`, proving that message-based caching is invalidated when thinking parameters change.
## Max tokens and context window size with extended thinking
`max_tokens` (which includes your thinking budget when thinking is enabled) is enforced as a strict limit. On Claude 4.5 models and newer, if input tokens plus `max_tokens` exceeds the context window size, the API accepts the request. If generation then reaches the context window limit, it stops with `stop_reason: "model_context_window_exceeded"`. On earlier models, the API returns a validation error instead. See [Handling stop reasons](/docs/en/build-with-claude/handling-stop-reasons).
You can read through the [guide on context windows](/docs/en/build-with-claude/context-windows) for a more thorough deep dive.
### The context window with extended thinking
When calculating context window usage with thinking enabled, there are some considerations to be aware of:
- On Opus 4.5+ and Sonnet 4.6+, thinking blocks from previous turns are kept and count towards your context window; on earlier Opus/Sonnet models and all Haiku models, they are stripped and not counted
- Current turn thinking counts towards your `max_tokens` limit for that turn
The diagram below demonstrates the specialized token management when extended thinking is enabled:

The effective context window is calculated as:
```text
context window =
(current input tokens - previous thinking tokens) +
(thinking tokens + encrypted thinking tokens + text output tokens)
```
Use the [token counting API](/docs/en/build-with-claude/token-counting) to get accurate token counts for your specific use case, especially when working with multi-turn conversations that include thinking.
### The context window with extended thinking and tool use
When using extended thinking with tool use, thinking blocks must be explicitly preserved and returned with the tool results.
The effective context window calculation for extended thinking with tool use becomes:
```text
context window =
(current input tokens + previous thinking tokens + tool use tokens) +
(thinking tokens + encrypted thinking tokens + text output tokens)
```
The diagram below illustrates token management for extended thinking with tool use:

### Managing tokens with extended thinking
Given the context window and `max_tokens` behavior with extended thinking, you may need to:
- More actively monitor and manage your token usage
- Adjust `max_tokens` values as your prompt length changes
- Potentially use the [token counting endpoints](/docs/en/build-with-claude/token-counting) more frequently
- Be aware that previous thinking blocks don't accumulate in your context window
## Thinking encryption
Full thinking content is encrypted and returned in the `signature` field. This field is used to verify that thinking blocks were generated by Claude when passed back to the API.
It is only strictly necessary to send back thinking blocks when using [tools with extended thinking](/docs/en/build-with-claude/extended-thinking#extended-thinking-with-tool-use). Otherwise you can omit thinking blocks from previous turns. If you pass them back, whether the API keeps or strips them depends on the model: Opus 4.5+ and Sonnet 4.6+ keep them in context by default; earlier Opus/Sonnet models and all Haiku models strip them. See [context editing](/docs/en/build-with-claude/context-editing) to configure this.
If sending back thinking blocks, we recommend passing everything back as you received it for consistency and to avoid potential issues.
Here are some important considerations on thinking encryption:
- When [streaming responses](/docs/en/build-with-claude/extended-thinking#streaming-thinking), the signature is added via a `signature_delta` inside a `content_block_delta` event just before the `content_block_stop` event.
- `signature` values are significantly longer in Claude 4 models than in previous models.
- The `signature` field is an opaque field and should not be interpreted or parsed.
- `signature` values are compatible across platforms (Claude APIs, [Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock), and [Vertex AI](/docs/en/build-with-claude/claude-on-vertex-ai)). Values generated on one platform will be compatible with another.
## Redacted thinking blocks
In addition to regular `thinking` blocks, the API may return `redacted_thinking` blocks. A `redacted_thinking` block contains encrypted thinking content in a `data` field, with no readable summary:
```json
{
"type": "redacted_thinking",
"data": "..."
}
```
The `data` field is opaque and encrypted. Like the `signature` field on regular thinking blocks, you should pass `redacted_thinking` blocks back to the API unchanged when continuing a multi-turn conversation with [tools](/docs/en/build-with-claude/extended-thinking#extended-thinking-with-tool-use).
If your code filters content blocks by type (for example, `block.type == "thinking"`) when round-tripping responses with tool use, also include `redacted_thinking` blocks. Filtering on `block.type == "thinking"` alone silently drops `redacted_thinking` blocks and breaks the multi-turn protocol described above.
`redacted_thinking` blocks are a distinct content block type returned by the API when portions of thinking are safety-redacted. This is separate from the [`display: "omitted"`](#controlling-thinking-display) option, which returns regular `thinking` blocks with an empty `thinking` field.
## Differences in thinking across model versions
The Messages API handles thinking differently across Claude model versions. The following table gives a condensed comparison:
| Feature | Claude 4 models (pre-Opus 4.5) | Claude Opus 4.5 | Claude Sonnet 4.6 | Claude Opus 4.6 ([adaptive thinking](/docs/en/build-with-claude/adaptive-thinking)) | Claude Opus 4.7 ([adaptive thinking](/docs/en/build-with-claude/adaptive-thinking)) | [Claude Mythos Preview](https://anthropic.com/glasswing) ([adaptive thinking](/docs/en/build-with-claude/adaptive-thinking)) |
|---------|-------------------------------|--------------------------|------------------|--------------------------|--------------------------|--------------------------|
| **Thinking output** | Returns summarized thinking | Returns summarized thinking | Returns summarized thinking | Returns summarized thinking | Omitted by default; set `display: "summarized"` to receive summarized thinking | Omitted by default; set `display: "summarized"` to receive summarized thinking. Raw thinking tokens are never returned. |
| **Interleaved thinking** | Supported with `interleaved-thinking-2025-05-14` beta header | Supported with `interleaved-thinking-2025-05-14` beta header | Supported with `interleaved-thinking-2025-05-14` beta header or automatic with [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) | Automatic with adaptive thinking (beta header deprecated and safely ignored) | Automatic with adaptive thinking (beta header deprecated and safely ignored) | Automatic with adaptive thinking (beta header not needed and safely ignored). Inter-tool reasoning moves into thinking blocks on this model. |
| **Thinking block preservation** | Not preserved across turns | **Preserved by default** | **Preserved by default** | **Preserved by default** | **Preserved by default** | **Preserved by default.** Blocks are stripped when continuing the conversation on a model that does not support the Mythos thinking format. |
### Thinking block preservation by model
Whether thinking blocks from previous assistant turns are preserved in context by default depends on the model class. **Opus**: Claude Opus 4.5 and later Opus models keep all prior thinking blocks; Claude Opus 4.1 and earlier Opus models keep only the last assistant turn's thinking. **Sonnet**: Claude Sonnet 4.6 and later Sonnet models keep all; Claude Sonnet 4.5 and earlier Sonnet models keep only the last turn. **Haiku**: all Haiku models through Claude Haiku 4.5 keep only the last turn. [Claude Mythos Preview](https://anthropic.com/glasswing) also keeps all prior thinking blocks.
**Benefits of thinking block preservation:**
- **Cache optimization**: When using tool use, preserved thinking blocks enable cache hits as they are passed back with tool results and cached incrementally across the assistant turn, resulting in token savings in multi-step workflows
- **No intelligence impact**: Preserving thinking blocks has no negative effect on model performance
**Important considerations:**
- **Context usage**: Long conversations will consume more context space since thinking blocks are retained in context
- **Automatic behavior**: This is the default for each model as listed above. No code changes or beta headers are required
- **Backward compatibility**: To leverage this feature, continue passing complete, unmodified thinking blocks back to the API as you would for tool use
For earlier models (Claude Sonnet 4.5, Opus 4.1, etc.), thinking blocks from previous turns continue to be removed from context. The existing behavior described in the [Extended thinking with prompt caching](#extended-thinking-with-prompt-caching) section applies to those models.
## Pricing
For complete pricing information including base rates, cache writes, cache hits, and output tokens, see the [pricing page](/docs/en/about-claude/pricing).
The thinking process incurs charges for:
- Tokens used during thinking (output tokens)
- Thinking blocks from prior assistant turns kept in context: only the last turn on earlier Opus/Sonnet models and all Haiku models; all turns by default on Opus 4.5+ and Sonnet 4.6+ (input tokens)
- Standard text output tokens
When extended thinking is enabled, a specialized system prompt is automatically included to support this feature.
When using summarized thinking:
- **Input tokens:** Tokens in your original request (excludes thinking tokens from previous turns)
- **Output tokens (billed):** The original thinking tokens that Claude generated internally
- **Output tokens (visible):** The summarized thinking tokens you see in the response
- **No charge:** Tokens used to generate the summary
When using `display: "omitted"`:
- **Input tokens:** Tokens in your original request (same as summarized)
- **Output tokens (billed):** The original thinking tokens that Claude generated internally (same as summarized)
- **Output tokens (visible):** Zero thinking tokens (the `thinking` field is empty)
The billed output token count will **not** match the visible token count in the response. You are billed for the full thinking process, not the thinking content visible in the response.
## Best practices and considerations for extended thinking
### Working with thinking budgets
- **Budget optimization:** The minimum budget is 1,024 tokens. Start at the minimum and increase the thinking budget incrementally to find the optimal range for your use case. Higher token counts enable more comprehensive reasoning but with diminishing returns depending on the task. Increasing the budget can improve response quality at the tradeoff of increased latency. For critical tasks, test different settings to find the optimal balance. Note that the thinking budget is a target rather than a strict limit. Actual token usage may vary based on the task.
- **Starting points:** Start with larger thinking budgets (16k+ tokens) for complex tasks and adjust based on your needs.
- **Large budgets:** For thinking budgets above 32k, use [batch processing](/docs/en/build-with-claude/batch-processing) to avoid networking issues. Requests pushing the model to think above 32k tokens causes long running requests that might run up against system timeouts and open connection limits.
- **Token usage tracking:** Monitor thinking token usage to optimize costs and performance.
### Performance considerations
- **Response times:** Be prepared for longer response times due to additional processing. Generating thinking blocks increases overall response time.
- **Streaming requirements:** The SDKs require streaming when `max_tokens` is greater than 21,333 to avoid HTTP timeouts on long-running requests. This is a client-side validation, not an API restriction. If you don't need to process events incrementally, use `.stream()` with `.get_final_message()` (Python) or `.finalMessage()` (TypeScript) to get the complete `Message` object without handling individual events. See [Streaming Messages](/docs/en/build-with-claude/streaming#get-the-final-message-without-handling-events) for details. When streaming, be prepared to handle both thinking and text content blocks as they arrive.
- **Omitting thinking for latency:** If your application doesn't display thinking content, set `display: "omitted"` on the thinking configuration to reduce time-to-first-text-token. See [Controlling thinking display](#controlling-thinking-display).
### Feature compatibility
- Thinking isn't compatible with `temperature` or `top_k` modifications as well as [forced tool use](/docs/en/agents-and-tools/tool-use/define-tools#forcing-tool-use).
- When thinking is enabled, you can set `top_p` to values between 1 and 0.95.
- You can't pre-fill responses when thinking is enabled.
- Changes to the thinking budget invalidate cached prompt prefixes that include messages. However, cached system prompts and tool definitions will continue to work when thinking parameters change.
### Usage guidelines
- **Task selection:** Use extended thinking for particularly complex tasks that benefit from step-by-step reasoning, like math, coding, and analysis.
- **Context handling:** You don't need to remove previous thinking blocks yourself. On Opus 4.5+ and Sonnet 4.6+, the Claude API keeps thinking blocks from previous turns by default; on earlier Opus/Sonnet models and all Haiku models, it automatically ignores them and they aren't included when calculating context usage.
- **Prompt engineering:** Review the [extended thinking prompting tips](/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#leverage-thinking-and-interleaved-thinking-capabilities) if you want to maximize Claude's thinking capabilities.
## Next steps
Explore practical examples of thinking in the cookbook.
Learn prompt engineering best practices for extended thinking.
---
# Citations
URL: https://platform.claude.com/docs/en/build-with-claude/citations
# Citations
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
Claude is capable of providing detailed citations when answering questions about documents, helping you track and verify information sources in responses.
All [active models](/docs/en/about-claude/models/overview) support citations, with the exception of Haiku 3.
Share your feedback and suggestions about the citations feature using this [form](https://forms.gle/9n9hSrKnKe3rpowH9).
Here's an example of how to use citations with the Messages API:
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": "The grass is green. The sky is blue."
},
"title": "My Document",
"context": "This is a trustworthy document.",
"citations": {"enabled": true}
},
{
"type": "text",
"text": "What color is the grass and sky?"
}
]
}
]
}'
```
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content:
- type: document
source:
type: text
media_type: text/plain
data: The grass is green. The sky is blue.
title: My Document
context: This is a trustworthy document.
citations:
enabled: true
- type: text
text: What color is the grass and sky?
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": "The grass is green. The sky is blue.",
},
"title": "My Document",
"context": "This is a trustworthy document.",
"citations": {"enabled": True},
},
{"type": "text", "text": "What color is the grass and sky?"},
],
}
],
)
print(response)
```
```java Java hidelines={1..8,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.*;
import java.util.List;
public class DocumentExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
PlainTextSource source = PlainTextSource.builder()
.data("The grass is green. The sky is blue.")
.build();
DocumentBlockParam documentParam = DocumentBlockParam.builder()
.source(source)
.title("My Document")
.context("This is a trustworthy document.")
.citations(CitationsConfigParam.builder().enabled(true).build())
.build();
TextBlockParam textBlockParam = TextBlockParam.builder()
.text("What color is the grass and sky?")
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addUserMessageOfBlockParams(
List.of(
ContentBlockParam.ofDocument(documentParam),
ContentBlockParam.ofText(textBlockParam)
)
)
.build();
Message message = client.messages().create(params);
System.out.println(message);
}
}
```
**Comparison with prompt-based approaches**
In comparison with prompt-based citations solutions, the citations feature has the following advantages:
- **Cost savings:** If your prompt-based approach asks Claude to output direct quotes, you may see cost savings due to the fact that `cited_text` does not count towards your output tokens.
- **Better citation reliability:** Because citations are parsed into the respective response formats mentioned above and `cited_text` is extracted, citations are guaranteed to contain valid pointers to the provided documents.
- **Improved citation quality:** In evaluations, the citations feature was found to be significantly more likely to cite the most relevant quotes from documents as compared to purely prompt-based approaches.
---
## How citations work
Integrate citations with Claude in these steps:
- Include documents in any of the supported formats: [PDFs](#pdf-documents), [plain text](#plain-text-documents), or [custom content](#custom-content-documents) documents
- Set `citations.enabled=true` on each of your documents. Currently, citations must be enabled on all or none of the documents within a request.
- Note that only text citations are currently supported and image citations are not yet possible.
- Document contents are "chunked" in order to define the minimum granularity of possible citations. For example, sentence chunking would allow Claude to cite a single sentence or chain together multiple consecutive sentences to cite a paragraph (or longer)!
- **For PDFs:** Text is extracted as described in [PDF Support](/docs/en/build-with-claude/pdf-support) and content is chunked into sentences. Citing images from PDFs is not currently supported.
- **For plain text documents:** Content is chunked into sentences that can be cited from.
- **For custom content documents:** Your provided content blocks are used as-is and no further chunking is done.
- Responses may now include multiple text blocks where each text block can contain a claim that Claude is making and a list of citations that support the claim.
- Citations reference specific locations in source documents. The format of these citations are dependent on the type of document being cited from.
- **For PDFs:** Citations include the page number range (1-indexed).
- **For plain text documents:** Citations include the character index range (0-indexed).
- **For custom content documents:** Citations include the content block index range (0-indexed) corresponding to the original content list provided.
- Document indices are provided to indicate the reference source and are 0-indexed according to the list of all documents in your original request.
**Automatic chunking vs custom content**
By default, plain text and PDF documents are automatically chunked into sentences. If you need more control over citation granularity (e.g., for bullet points or transcripts), use custom content documents instead. See [Document Types](#document-types) for more details.
For example, if you want Claude to be able to cite specific sentences from your RAG chunks, you should put each RAG chunk into a plain text document. Otherwise, if you do not want any further chunking to be done, or if you want to customize any additional chunking, you can put RAG chunks into custom content document(s).
### Citable vs non-citable content
- Text found within a document's `source` content can be cited from.
- `title` and `context` are optional fields that will be passed to the model but not used towards cited content.
- `title` is limited in length so you may find the `context` field to be useful in storing any document metadata as text or stringified json.
### Citation indices
- Document indices are 0-indexed from the list of all document content blocks in the request (spanning across all messages).
- Character indices are 0-indexed with exclusive end indices.
- Page numbers are 1-indexed with exclusive end page numbers.
- Content block indices are 0-indexed with exclusive end indices from the `content` list provided in the custom content document.
### Token costs
- Enabling citations incurs a slight increase in input tokens due to system prompt additions and document chunking.
- However, the citations feature is very efficient with output tokens. Under the hood, the model is outputting citations in a standardized format that are then parsed into cited text and document location indices. The `cited_text` field is provided for convenience and does not count towards output tokens.
- When passed back in subsequent conversation turns, `cited_text` is also not counted towards input tokens.
### Feature compatibility
Citations works in conjunction with other API features including [prompt caching](/docs/en/build-with-claude/prompt-caching), [token counting](/docs/en/build-with-claude/token-counting) and [batch processing](/docs/en/build-with-claude/batch-processing).
**Citations and Structured Outputs are incompatible**
Citations cannot be used together with [Structured Outputs](/docs/en/build-with-claude/structured-outputs). If you enable citations on any user-provided document (Document blocks or RequestSearchResultBlock) and also include the `output_config.format` parameter (or the deprecated `output_format` parameter), the API will return a 400 error.
This is because citations require interleaving citation blocks with text output, which is incompatible with the strict JSON schema constraints of structured outputs.
#### Using Prompt Caching with Citations
Citations and prompt caching can be used together effectively.
The citation blocks generated in responses cannot be cached directly, but the source documents they reference can be cached. To optimize performance, apply `cache_control` to your top-level document content blocks.
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": "This is a very long document with thousands of words..."
},
"citations": {"enabled": true},
"cache_control": {"type": "ephemeral"}
},
{
"type": "text",
"text": "What does this document say about API features?"
}
]
}
]
}'
```
```bash CLI
ant messages create \
--model claude-opus-4-7 \
--max-tokens 1024 <<'YAML'
messages:
- role: user
content:
- type: document
source:
type: text
media_type: text/plain
data: This is a very long document with thousands of words...
citations:
enabled: true
cache_control:
type: ephemeral
- type: text
text: What does this document say about API features?
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
# Long document content (e.g., technical documentation)
long_document = (
"This is a very long document with thousands of words..." + " ... " * 1000
) # Minimum cacheable length
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": long_document,
},
"citations": {"enabled": True},
"cache_control": {
"type": "ephemeral"
}, # Cache the document content
},
{
"type": "text",
"text": "What does this document say about API features?",
},
],
}
],
)
print(response)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// Long document content (e.g., technical documentation)
const longDocument =
"This is a very long document with thousands of words..." + " ... ".repeat(1000); // Minimum cacheable length
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "document",
source: {
type: "text",
media_type: "text/plain",
data: longDocument
},
citations: { enabled: true },
cache_control: { type: "ephemeral" } // Cache the document content
},
{
type: "text",
text: "What does this document say about API features?"
}
]
}
]
});
```
In this example:
- The document content is cached using `cache_control` on the document block
- Citations are enabled on the document
- Claude can generate responses with citations while benefiting from cached document content
- Subsequent requests using the same document will benefit from the cached content
## Document Types
### Choosing a document type
Three document types are supported for citations. Documents can be provided directly in the message (base64, text, or URL) or uploaded via the [Files API](/docs/en/build-with-claude/files) and referenced by `file_id`:
| Type | Best for | Chunking | Citation format |
| :--- | :--- | :--- | :--- |
| Plain text | Simple text documents, prose | Sentence | Character indices (0-indexed) |
| PDF | PDF files with text content | Sentence | Page numbers (1-indexed) |
| Custom content | Lists, transcripts, special formatting, more granular citations | No additional chunking | Block indices (0-indexed) |
.csv, .xlsx, .docx, .md, and .txt files are not supported as document blocks. Convert these to plain text and include directly in message content. See [Working with other file formats](/docs/en/build-with-claude/files#working-with-other-file-formats).
### Plain text documents
Plain text documents are automatically chunked into sentences. You can provide them inline or by reference with their `file_id`:
```python
{
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": "Plain text content...",
},
"title": "Document Title", # optional
"context": "Context about the document that will not be cited from", # optional
"citations": {"enabled": True},
}
```
```python
{
"type": "document",
"source": {"type": "file", "file_id": "file_011CNvxoj286tYUAZFiZMf1U"},
"title": "Document Title", # optional
"context": "Context about the document that will not be cited from", # optional
"citations": {"enabled": True},
}
```
```python
{
"type": "char_location",
"cited_text": "The exact text being cited", # not counted towards output tokens
"document_index": 0,
"document_title": "Document Title",
"start_char_index": 0, # 0-indexed
"end_char_index": 50, # exclusive
}
```
### PDF documents
PDF documents can be provided as base64-encoded data or by `file_id`. PDF text is extracted and chunked into sentences. As image citations are not yet supported, PDFs that are scans of documents and do not contain extractable text will not be citable.
```python
{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": base64_encoded_pdf_data,
},
"title": "Document Title", # optional
"context": "Context about the document that will not be cited from", # optional
"citations": {"enabled": True},
}
```
```python
{
"type": "document",
"source": {"type": "file", "file_id": "file_011CNvxoj286tYUAZFiZMf1U"},
"title": "Document Title", # optional
"context": "Context about the document that will not be cited from", # optional
"citations": {"enabled": True},
}
```
```python
{
"type": "page_location",
"cited_text": "The exact text being cited", # not counted towards output tokens
"document_index": 0,
"document_title": "Document Title",
"start_page_number": 1, # 1-indexed
"end_page_number": 2, # exclusive
}
```
### Custom content documents
Custom content documents give you control over citation granularity. No additional chunking is done and chunks are provided to the model according to the content blocks provided.
```python
{
"type": "document",
"source": {
"type": "content",
"content": [
{"type": "text", "text": "First chunk"},
{"type": "text", "text": "Second chunk"},
],
},
"title": "Document Title", # optional
"context": "Context about the document that will not be cited from", # optional
"citations": {"enabled": True},
}
```
```python
{
"type": "content_block_location",
"cited_text": "The exact text being cited", # not counted towards output tokens
"document_index": 0,
"document_title": "Document Title",
"start_block_index": 0, # 0-indexed
"end_block_index": 1, # exclusive
}
```
---
## Response Structure
When citations are enabled, responses include multiple text blocks with citations:
```python
{
"content": [
{"type": "text", "text": "According to the document, "},
{
"type": "text",
"text": "the grass is green",
"citations": [
{
"type": "char_location",
"cited_text": "The grass is green.",
"document_index": 0,
"document_title": "Example Document",
"start_char_index": 0,
"end_char_index": 20,
}
],
},
{"type": "text", "text": " and "},
{
"type": "text",
"text": "the sky is blue",
"citations": [
{
"type": "char_location",
"cited_text": "The sky is blue.",
"document_index": 0,
"document_title": "Example Document",
"start_char_index": 20,
"end_char_index": 36,
}
],
},
{
"type": "text",
"text": ". Information from page 5 states that ",
},
{
"type": "text",
"text": "water is essential",
"citations": [
{
"type": "page_location",
"cited_text": "Water is essential for life.",
"document_index": 1,
"document_title": "PDF Document",
"start_page_number": 5,
"end_page_number": 6,
}
],
},
{
"type": "text",
"text": ". The custom document mentions ",
},
{
"type": "text",
"text": "important findings",
"citations": [
{
"type": "content_block_location",
"cited_text": "These are important findings.",
"document_index": 2,
"document_title": "Custom Content Document",
"start_block_index": 0,
"end_block_index": 1,
}
],
},
]
}
```
### Streaming Support
For streaming responses, a `citations_delta` type is included that contains a single citation to be added to the `citations` list on the current `text` content block.
```sse
event: message_start
data: {"type": "message_start", ...}
event: content_block_start
data: {"type": "content_block_start", "index": 0, ...}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0,
"delta": {"type": "text_delta", "text": "According to..."}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0,
"delta": {"type": "citations_delta",
"citation": {
"type": "char_location",
"cited_text": "...",
"document_index": 0,
...
}}}
event: content_block_stop
data: {"type": "content_block_stop", "index": 0}
event: message_stop
data: {"type": "message_stop"}
```
---
# Effort
URL: https://platform.claude.com/docs/en/build-with-claude/effort
# Effort
Control how many tokens Claude uses when responding with the effort parameter, trading off between response thoroughness and token efficiency.
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
The effort parameter allows you to control how eager Claude is about spending tokens when responding to requests. This gives you the ability to trade off between response thoroughness and token efficiency, all with a single model. The effort parameter is available on all supported models with no beta header required.
The effort parameter is supported by [Claude Mythos Preview](https://anthropic.com/glasswing), Claude Opus 4.7, Claude Opus 4.6, Claude Sonnet 4.6, and Claude Opus 4.5.
For Claude Opus 4.6 and Sonnet 4.6, effort replaces `budget_tokens` as the recommended way to control thinking depth. Combine effort with [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) (`thinking: {type: "adaptive"}`) for the best experience. While `budget_tokens` is still accepted on Opus 4.6 and Sonnet 4.6, it is deprecated and will be removed in a future model release. At `high` (default) and `max` effort, Claude will almost always think. At lower effort levels, it may skip thinking for simpler problems.
## How effort works
By default, Claude uses high effort, spending as many tokens as needed for excellent results. You can raise the effort level to `max` for the absolute highest capability, or lower it to be more conservative with token usage, optimizing for speed and cost while accepting some reduction in capability.
Setting `effort` to `"high"` produces exactly the same behavior as omitting the `effort` parameter entirely.
The effort parameter affects **all tokens** in the response, including:
- Text responses and explanations
- Tool calls and function arguments
- Extended thinking (when enabled)
This approach has two major advantages:
1. It doesn't require thinking to be enabled in order to use it.
2. It can affect all token spend including tool calls. For example, lower effort would mean Claude makes fewer tool calls. This gives a much greater degree of control over efficiency.
### Effort levels
| Level | Description | Typical use case |
| -------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `max` | Absolute maximum capability with no constraints on token spending. Available on Claude Mythos Preview, Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6. | Tasks requiring the deepest possible reasoning and most thorough analysis |
| `xhigh` | Extended capability for long-horizon work. Available on Claude Opus 4.7. | Long-running agentic and coding tasks (over 30 minutes) with token budgets in the millions |
| `high` | High capability. Equivalent to not setting the parameter. | Complex reasoning, difficult coding problems, agentic tasks |
| `medium` | Balanced approach with moderate token savings. | Agentic tasks that require a balance of speed, cost, and performance |
| `low` | Most efficient. Significant token savings with some capability reduction. | Simpler tasks that need the best speed and lowest costs, such as subagents |
Effort is a behavioral signal, not a strict token budget. At lower effort levels, Claude will still think on sufficiently difficult problems, but it will think less than it would at higher effort levels for the same problem.
### Recommended effort levels for Sonnet 4.6
Sonnet 4.6 defaults to `high` effort. Explicitly set effort when using Sonnet 4.6 to avoid unexpected latency:
- **Medium effort** (recommended default): Best balance of speed, cost, and performance for most applications. Suitable for agentic coding, tool-heavy workflows, and code generation.
- **Low effort:** For high-volume or latency-sensitive workloads. Suitable for chat and non-coding use cases where faster turnaround is prioritized.
- **High effort:** For complex reasoning and tasks where quality matters more than speed or cost.
- **Max effort:** For tasks requiring the absolute highest capability with no constraints on token spending.
### Recommended effort levels for Claude Opus 4.7
**Start with `xhigh` for coding and agentic use cases**, and use `high` as the minimum for most intelligence-sensitive workloads. Step down to `medium` for cost-sensitive workloads, or up to `max` only when your evals show measurable headroom at `xhigh`.
The API default is `high`. To use `xhigh`, set `effort` explicitly; the value you pass overrides the default.
| Effort | Guidance for Claude Opus 4.7 |
|--------|------------------------------|
| `low` | Efficient, but best for short, scoped tasks. Pair `low` with explicit checklists if your task has multiple sections. |
| `medium` | The drop-in for the average workflow where you want good results while reducing costs. |
| `high` | Advanced use cases that still need a balance of intelligence and token consumption. This is often the sweet spot balancing quality and token efficiency. |
| `xhigh` | The recommended starting point for coding and agentic work, and for exploratory tasks such as repeated tool calling, detailed web search, and knowledge-base search. Expect meaningfully higher token usage than `high`. |
| `max` | Reserve for genuinely frontier problems. On most workloads `max` adds significant cost for relatively small quality gains, and on some structured-output or less intelligence-sensitive tasks it can lead to overthinking. |
Claude Opus 4.7 also respects effort levels more strictly than Claude Opus 4.6, especially at `low` and `medium`. At lower effort levels, the model scopes its work to what was asked rather than going above and beyond. If you observe shallow reasoning on complex problems with Claude Opus 4.7, raise effort rather than prompting around it. If you must keep effort low for latency, add targeted guidance like "This task involves multi-step reasoning. Think carefully before responding."
When running Claude Opus 4.7 at `xhigh` or `max` effort, set a large `max_tokens` so the model has room to think and act across subagents and tool calls. Starting at 64k tokens and tuning from there is a reasonable default.
## Basic usage
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"messages": [{
"role": "user",
"content": "Analyze the trade-offs between microservices and monolithic architectures"
}],
"output_config": {
"effort": "medium"
}
}'
```
```bash CLI
ant messages create --transform 'content.0.text' --raw-output <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: Analyze the trade-offs between microservices and monolithic architectures
output_config:
effort: medium
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
messages=[
{
"role": "user",
"content": "Analyze the trade-offs between microservices and monolithic architectures",
}
],
output_config={"effort": "medium"},
)
print(response.content[0].text)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{
role: "user",
content: "Analyze the trade-offs between microservices and monolithic architectures"
}
],
output_config: {
effort: "medium"
}
});
const textBlock = response.content.find(
(block): block is Anthropic.TextBlock => block.type === "text"
);
console.log(textBlock?.text);
```
```csharp C#
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Messages = [new() { Role = Role.User, Content = "Analyze the trade-offs between microservices and monolithic architectures" }],
OutputConfig = new OutputConfig
{
Effort = Effort.Medium
}
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the trade-offs between microservices and monolithic architectures")),
},
OutputConfig: anthropic.OutputConfigParam{
Effort: anthropic.OutputConfigEffortMedium,
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Content[0].Text)
}
```
```java Java hidelines={1..5,7..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.OutputConfig;
public class Main {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(4096L)
.addUserMessage("Analyze the trade-offs between microservices and monolithic architectures")
.outputConfig(OutputConfig.builder()
.effort(OutputConfig.Effort.MEDIUM)
.build())
.build();
Message response = client.messages().create(params);
response.content().stream()
.flatMap(block -> block.text().stream())
.forEach(textBlock -> System.out.println(textBlock.text()));
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Analyze the trade-offs between microservices and monolithic architectures']
],
model: 'claude-opus-4-7',
outputConfig: ['effort' => 'medium'],
);
echo $message->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{ role: "user", content: "Analyze the trade-offs between microservices and monolithic architectures" }
],
output_config: {
effort: "medium"
}
)
puts message.content.first.text
```
## When to adjust the effort parameter
- Use **max effort** when you need the absolute highest capability with no constraints: the most thorough reasoning and deepest analysis. Available on Claude Mythos Preview, Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6.
- Use **xhigh effort** for advanced coding and complex agentic work requiring extended exploration, such as repeated tool calling and detailed search. Available on Claude Opus 4.7.
- Use **high effort** (the default) for complex reasoning, nuanced analysis, difficult coding problems, or any task where quality matters more than speed or cost.
- Use **medium effort** as a balanced option when you want solid performance without the full token expenditure of high effort.
- Use **low effort** when you're optimizing for speed (because Claude answers with fewer tokens) or cost. For example, simple classification tasks, quick lookups, or high-volume use cases where marginal quality improvements don't justify additional latency or spend.
## Effort with tool use
When using tools, the effort parameter affects both the explanations around tool calls and the tool calls themselves. Lower effort levels tend to:
- Combine multiple operations into fewer tool calls
- Make fewer tool calls
- Proceed directly to action without preamble
- Use terse confirmation messages after completion
Higher effort levels may:
- Make more tool calls
- Explain the plan before taking action
- Provide detailed summaries of changes
- Include more comprehensive code comments
## Effort with extended thinking
The effort parameter works alongside extended thinking. Its behavior depends on the model:
- **Claude Mythos Preview** uses [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) by default (no `thinking` configuration required). `thinking: {type: "disabled"}` is rejected. Effort controls thinking depth the same way as on Opus 4.7 and Opus 4.6.
- **Claude Opus 4.7** uses [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) (`thinking: {type: "adaptive"}`), where effort is the recommended control for thinking depth. Manual extended thinking (`thinking: {type: "enabled", budget_tokens: N}`) is no longer supported on Opus 4.7; use adaptive thinking with effort instead. At `high`, `xhigh`, and `max` effort, Claude almost always thinks deeply. At lower levels, it may skip thinking for simpler problems.
- **Claude Opus 4.6** uses [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) (`thinking: {type: "adaptive"}`), where effort is the recommended control for thinking depth. While `budget_tokens` is still accepted on Opus 4.6, it is deprecated and will be removed in a future release. At `high` and `max` effort, Claude almost always thinks deeply. At lower levels, it may skip thinking for simpler problems.
- **Claude Sonnet 4.6** uses [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) (where effort controls thinking depth). Manual thinking with [interleaved mode](/docs/en/build-with-claude/extended-thinking#interleaved-thinking) (`thinking: {type: "enabled", budget_tokens: N}`) is still functional but deprecated.
- **Claude Opus 4.5** uses manual thinking (`thinking: {type: "enabled", budget_tokens: N}`), where effort works alongside the thinking token budget. Set the effort level for your task, then set the thinking token budget based on task complexity.
The effort parameter can be used with or without extended thinking enabled. When used without thinking, it still controls overall token spend for text responses and tool calls.
## Best practices
1. **Set effort explicitly:** The API defaults to `high`, but the right starting point depends on your model and workload.
2. **Use low for speed-sensitive or simple tasks:** When latency matters or tasks are straightforward, low effort can significantly reduce response times and costs.
3. **Test your use case:** The impact of effort levels varies by task type. Evaluate performance on your specific use cases before deploying.
4. **Consider dynamic effort:** Adjust effort based on task complexity. Simple queries may warrant low effort while agentic coding and complex reasoning benefit from high effort.
---
# Embeddings
URL: https://platform.claude.com/docs/en/build-with-claude/embeddings
# Embeddings
Text embeddings are numerical representations of text that enable measuring semantic similarity. This guide introduces embeddings, their applications, and how to use embedding models for tasks like search, recommendations, and anomaly detection.
---
## Before implementing embeddings
When selecting an embeddings provider, there are several factors you can consider depending on your needs and preferences:
- Dataset size & domain specificity: size of the model training dataset and its relevance to the domain you want to embed. Larger or more domain-specific data generally produces better in-domain embeddings
- Inference performance: embedding lookup speed and end-to-end latency. This is a particularly important consideration for large scale production deployments
- Customization: options for continued training on private data, or specialization of models for very specific domains. This can improve performance on unique vocabularies
## How to get embeddings with Anthropic
Anthropic does not offer its own embedding model. One embeddings provider that has a wide variety of options and capabilities encompassing all of the above considerations is Voyage AI.
Voyage AI makes state-of-the-art embedding models and offers customized models for specific industry domains such as finance and healthcare, or bespoke fine-tuned models for individual customers.
The rest of this guide is for Voyage AI, but you should assess a variety of embeddings vendors to find the best fit for your specific use case.
## Available models
Voyage recommends using the following text embedding models:
**Voyage 4 (latest generation)**
| Model | Context Length | Embedding Dimension | Description |
| --- | --- | --- | --- |
| `voyage-4-large` | 32,000 | 1024 (default), 256, 512, 2048 | The best general-purpose and multilingual retrieval quality. See [blog post](https://blog.voyageai.com/2026/01/15/voyage-4/) for details. |
| `voyage-4` | 32,000 | 1024 (default), 256, 512, 2048 | Optimized for general-purpose and multilingual retrieval quality. Balances quality and efficiency. See [blog post](https://blog.voyageai.com/2026/01/15/voyage-4/) for details. |
| `voyage-4-lite` | 32,000 | 1024 (default), 256, 512, 2048 | Optimized for latency and cost. See [blog post](https://blog.voyageai.com/2026/01/15/voyage-4/) for details. |
| `voyage-4-nano` | 32,000 | 1024 (default), 256, 512, 2048 | Open-weight model (Apache 2.0 license) available on Hugging Face. See [blog post](https://blog.voyageai.com/2026/01/15/voyage-4/) for details. |
**Previous generation**
| Model | Context Length | Embedding Dimension | Description |
| --- | --- | --- | --- |
| `voyage-3-large` | 32,000 | 1024 (default), 256, 512, 2048 | The best general-purpose and multilingual retrieval quality. See [blog post](https://blog.voyageai.com/2025/01/07/voyage-3-large/) for details. |
| `voyage-3.5` | 32,000 | 1024 (default), 256, 512, 2048 | Optimized for general-purpose and multilingual retrieval quality. See [blog post](https://blog.voyageai.com/2025/05/20/voyage-3-5/) for details. |
| `voyage-3.5-lite` | 32,000 | 1024 (default), 256, 512, 2048 | Optimized for latency and cost. See [blog post](https://blog.voyageai.com/2025/05/20/voyage-3-5/) for details. |
| `voyage-code-3` | 32,000 | 1024 (default), 256, 512, 2048 | Optimized for **code** retrieval. See [blog post](https://blog.voyageai.com/2024/12/04/voyage-code-3/) for details. |
| `voyage-finance-2` | 32,000 | 1024 | Optimized for **finance** retrieval and RAG. See [blog post](https://blog.voyageai.com/2024/06/03/domain-specific-embeddings-finance-edition-voyage-finance-2/) for details. |
| `voyage-law-2` | 16,000 | 1024 | Optimized for **legal** and **long-context** retrieval and RAG. Also improved performance across all domains. See [blog post](https://blog.voyageai.com/2024/04/15/domain-specific-embeddings-and-retrieval-legal-edition-voyage-law-2/) for details. |
Additionally, the following multimodal embedding models are recommended:
| Model | Context Length | Embedding Dimension | Description |
| --- | --- | --- | --- |
| `voyage-multimodal-3.5` | 32,000 | 1024 (default), 256, 512, 2048 | Rich multimodal embedding model that can vectorize interleaved text, images, and videos. Includes video support as the first production-grade video embedding model. See [blog post](https://blog.voyageai.com/2026/01/15/voyage-multimodal-3-5/) for details. |
| `voyage-multimodal-3` | 32,000 | 1024 | Rich multimodal embedding model that can vectorize interleaved text and content-rich images, such as screenshots of PDFs, slides, tables, figures, and more. See [blog post](https://blog.voyageai.com/2024/11/12/voyage-multimodal-3/) for details. |
Need help deciding which text embedding model to use? Check out the [FAQ](https://docs.voyageai.com/docs/faq#what-embedding-models-are-available-and-which-one-should-i-use&ref=anthropic).
## Getting started with Voyage AI
To access Voyage embeddings:
1. Sign up on Voyage AI's website
2. Obtain an API key
3. Set the API key as an environment variable for convenience:
```bash
export VOYAGE_API_KEY=""
```
You can obtain the embeddings by either using the official [`voyageai` Python package](https://github.com/voyage-ai/voyageai-python) or HTTP requests, as described below.
### Voyage Python library
The `voyageai` package can be installed using the following command:
```bash
pip install -U voyageai
```
Then, you can create a client object and start using it to embed your texts:
```python nocheck
import voyageai
vo = voyageai.Client()
# This will automatically use the environment variable VOYAGE_API_KEY.
# Alternatively, you can use vo = voyageai.Client(api_key="")
texts = ["Sample text 1", "Sample text 2"]
result = vo.embed(texts, model="voyage-4", input_type="document")
print(result.embeddings[0])
print(result.embeddings[1])
```
`result.embeddings` will be a list of two embedding vectors, each containing 1024 floating-point numbers. After running the above code, the two embeddings will be printed on the screen:
```text
[-0.013131560757756233, 0.019828535616397858, ...] # embedding for "Sample text 1"
[-0.0069352793507277966, 0.020878976210951805, ...] # embedding for "Sample text 2"
```
When creating the embeddings, you can specify a few other arguments to the `embed()` function.
For more information on the Voyage python package, see [the Voyage documentation](https://docs.voyageai.com/docs/embeddings#python-api).
### Voyage HTTP API
You can also get embeddings by requesting Voyage HTTP API. For example, you can send an HTTP request through the `curl` command in a terminal:
```bash cURL
curl https://api.voyageai.com/v1/embeddings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $VOYAGE_API_KEY" \
-d '{
"input": ["Sample text 1", "Sample text 2"],
"model": "voyage-4"
}'
```
The response you would get is a JSON object containing the embeddings and the token usage:
```json
{
"object": "list",
"data": [
{
"embedding": [-0.013131560757756233, 0.019828535616397858 /* ... */],
"index": 0
},
{
"embedding": [-0.0069352793507277966, 0.020878976210951805 /* ... */],
"index": 1
}
],
"model": "voyage-4",
"usage": {
"total_tokens": 10
}
}
```
For more information on the Voyage HTTP API, see [the Voyage documentation](https://docs.voyageai.com/reference/embeddings-api).
### AWS Marketplace
Voyage embeddings are available on [AWS Marketplace](https://aws.amazon.com/marketplace/seller-profile?id=seller-snt4gb6fd7ljg). Instructions for accessing Voyage on AWS are available in the [Voyage AWS Marketplace documentation](https://docs.voyageai.com/docs/aws-marketplace-model-package?ref=anthropic).
## Quickstart example
The following brief example shows how to use embeddings.
Suppose you have a small corpus of six documents to retrieve from
```python nocheck
documents = [
"The Mediterranean diet emphasizes fish, olive oil, and vegetables, believed to reduce chronic diseases.",
"Photosynthesis in plants converts light energy into glucose and produces essential oxygen.",
"20th-century innovations, from radios to smartphones, centered on electronic advancements.",
"Rivers provide water, irrigation, and habitat for aquatic species, vital for ecosystems.",
"Apple's conference call to discuss fourth fiscal quarter results and business updates is scheduled for Thursday, November 2, 2023 at 2:00 p.m. PT / 5:00 p.m. ET.",
"Shakespeare's works, like 'Hamlet' and 'A Midsummer Night's Dream,' endure in literature.",
]
```
First, use Voyage to convert each document into an embedding vector
```python nocheck
import voyageai
vo = voyageai.Client()
# Embed the documents
doc_embds = vo.embed(documents, model="voyage-4", input_type="document").embeddings
```
The embeddings allow you to do semantic search / retrieval in the vector space. Given an example query,
```python
query = "When is Apple's conference call scheduled?"
```
Next, convert it into an embedding and conduct a nearest neighbor search to find the most relevant document based on the distance in the embedding space.
```python nocheck
import numpy as np
# Embed the query
query_embd = vo.embed([query], model="voyage-4", input_type="query").embeddings[0]
# Compute the similarity
# Voyage embeddings are normalized to length 1, therefore dot-product
# and cosine similarity are the same.
similarities = np.dot(doc_embds, query_embd)
retrieved_id = np.argmax(similarities)
print(documents[retrieved_id])
```
Note that `input_type="document"` and `input_type="query"` are used for embedding the document and query, respectively. More specification can be found in [Voyage Python library](/docs/en/build-with-claude/embeddings#voyage-python-library).
The output would be the 5th document, which is indeed the most relevant to the query:
```text
Apple's conference call to discuss fourth fiscal quarter results and business updates is scheduled for Thursday, November 2, 2023 at 2:00 p.m. PT / 5:00 p.m. ET.
```
If you are looking for a detailed set of cookbooks on how to do RAG with embeddings, including vector databases, check out the [RAG cookbook](https://platform.claude.com/cookbook/third-party-pinecone-rag-using-pinecone).
## FAQ
Embedding models rely on powerful neural networks to capture and compress semantic context, similar to generative models. Voyage's team of experienced AI researchers optimizes every component of the embedding process, including:
- Model architecture
- Data collection
- Loss functions
- Optimizer selection
Learn more about Voyage's technical approach on their [blog](https://blog.voyageai.com/).
For general-purpose embedding, the recommended models are:
- `voyage-4-large`: Best quality
- `voyage-4-lite`: Lowest latency and cost
- `voyage-4`: Balanced performance
For retrieval, use the `input_type` parameter to specify whether the text is a query or document type.
Domain-specific models:
- Legal tasks: `voyage-law-2`
- Code and programming documentation: `voyage-code-3`
- Finance-related tasks: `voyage-finance-2`
You can use Voyage embeddings with either dot-product similarity, cosine similarity, or Euclidean distance. An explanation about embedding similarity can be found in this [vector similarity guide](https://www.pinecone.io/learn/vector-similarity/).
Voyage AI embeddings are normalized to length 1, which means that:
- Cosine similarity is equivalent to dot-product similarity, while the latter can be computed more quickly.
- Cosine similarity and Euclidean distance will result in the identical rankings.
See this [page](https://docs.voyageai.com/docs/tokenization?ref=anthropic).
For all retrieval tasks and use cases (for example, RAG), use the `input_type` parameter to specify whether the input text is a query or document. Do not omit `input_type` or set `input_type=None`. Specifying whether input text is a query or document can create better dense vector representations for retrieval, which can lead to better retrieval quality.
When using the `input_type` parameter, special prompts are prepended to the input text prior to embedding. Specifically:
> 📘 **Prompts associated with `input_type`**
>
> - For a query, the prompt is “Represent the query for retrieving supporting documents: “.
> - For a document, the prompt is “Represent the document for retrieval: “.
> - Example
> - When `input_type="query"`, a query like "When is Apple's conference call scheduled?" will become "**Represent the query for retrieving supporting documents:** When is Apple's conference call scheduled?"
> - When `input_type="document"`, a query like "Apple's conference call to discuss fourth fiscal quarter results and business updates is scheduled for Thursday, November 2, 2023 at 2:00 p.m. PT / 5:00 p.m. ET." will become "**Represent the document for retrieval:** Apple's conference call to discuss fourth fiscal quarter results and business updates is scheduled for Thursday, November 2, 2023 at 2:00 p.m. PT / 5:00 p.m. ET."
`voyage-large-2-instruct`, as the name suggests, is trained to be responsive to additional instructions that are prepended to the input text. For classification, clustering, or other [MTEB](https://huggingface.co/mteb) subtasks, please use the [voyage-large-2-instruct instructions](https://github.com/voyage-ai/voyage-large-2-instruct).
Quantization in embeddings converts high-precision values, like 32-bit single-precision floating-point numbers, to lower-precision formats such as 8-bit integers or 1-bit binary values, reducing storage, memory, and costs by 4x and 32x, respectively. Supported Voyage models enable quantization by specifying the output data type with the `output_dtype` parameter:
- `float`: Each returned embedding is a list of 32-bit (4-byte) single-precision floating-point numbers. This is the default and provides the highest precision / retrieval accuracy.
- `int8` and `uint8`: Each returned embedding is a list of 8-bit (1-byte) integers ranging from -128 to 127 and 0 to 255, respectively.
- `binary` and `ubinary`: Each returned embedding is a list of 8-bit integers that represent bit-packed, quantized single-bit embedding values: `int8` for `binary` and `uint8` for `ubinary`. The length of the returned list of integers is 1/8 of the actual dimension of the embedding. The binary type uses the offset binary method, which you can learn more about in the FAQ below.
> **Binary quantization example**
>
> Consider the following eight embedding values: -0.03955078, 0.006214142, -0.07446289, -0.039001465, 0.0046463013, 0.00030612946, -0.08496094, and 0.03994751. With binary quantization, values less than or equal to zero will be quantized to a binary zero, and positive values to a binary one, resulting in the following binary sequence: 0, 1, 0, 0, 1, 1, 0, 1. These eight bits are then packed into a single 8-bit integer, 01001101 (with the leftmost bit as the most significant bit).
> - `ubinary`: The binary sequence is directly converted and represented as the unsigned integer (`uint8`) 77.
> - `binary`: The binary sequence is represented as the signed integer (`int8`) -51, calculated using the offset binary method (77 - 128 = -51).
Matryoshka learning creates embeddings with coarse-to-fine representations within a single vector. Voyage models, such as `voyage-code-3`, that support multiple output dimensions generate such Matryoshka embeddings. You can truncate these vectors by keeping the leading subset of dimensions. For example, the following Python code demonstrates how to truncate 1024-dimensional vectors to 256 dimensions:
```python nocheck
import voyageai
import numpy as np
def embd_normalize(v: np.ndarray) -> np.ndarray:
"""
Normalize the rows of a 2D numpy array to unit vectors by dividing each row by its Euclidean
norm. Raises a ValueError if any row has a norm of zero to prevent division by zero.
"""
row_norms = np.linalg.norm(v, axis=1, keepdims=True)
if np.any(row_norms == 0):
raise ValueError("Cannot normalize rows with a norm of zero.")
return v / row_norms
vo = voyageai.Client()
# Generate voyage-code-3 vectors, which by default are 1024-dimensional floating-point numbers
embd = vo.embed(["Sample text 1", "Sample text 2"], model="voyage-code-3").embeddings
# Set shorter dimension
short_dim = 256
# Resize and normalize vectors to shorter dimension
resized_embd = embd_normalize(np.array(embd)[:, :short_dim]).tolist()
```
## Pricing
Visit Voyage's [pricing page](https://docs.voyageai.com/docs/pricing?ref=anthropic) for the most up to date pricing details.
---
# Fast mode (beta: research preview)
URL: https://platform.claude.com/docs/en/build-with-claude/fast-mode
# Fast mode (beta: research preview)
Higher output speed for Claude Opus 4.6 and Claude Opus 4.7, delivering significantly faster token generation for latency-sensitive and agentic workflows.
---
Fast mode provides significantly faster output token generation for Claude Opus 4.6 and Claude Opus 4.7. By setting `speed: "fast"` in your API request, you get up to 2.5x higher output tokens per second from the same model at premium pricing.
Fast mode is in beta (research preview). [Join the waitlist](https://claude.com/fast-mode) to request access. Availability is limited while Anthropic gathers feedback.
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
## Supported models
Fast mode is supported on the following models:
- Claude Opus 4.7 (`claude-opus-4-7`)
- Claude Opus 4.6 (`claude-opus-4-6`)
## How fast mode works
Fast mode runs the same model with a faster inference configuration. There is no change to intelligence or capabilities.
- Up to 2.5x higher output tokens per second compared to standard speed
- Speed benefits are focused on output tokens per second (OTPS), not time to first token (TTFT)
- Same model weights and behavior (not a different model)
## Basic usage
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "anthropic-beta: fast-mode-2026-02-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"speed": "fast",
"messages": [{
"role": "user",
"content": "Refactor this module to use dependency injection"
}]
}'
```
```bash CLI
ant beta:messages create \
--beta fast-mode-2026-02-01 \
--transform 'content.0.text' --raw-output <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
speed: fast
messages:
- role: user
content: Refactor this module to use dependency injection
YAML
```
```python Python nocheck hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
speed="fast",
betas=["fast-mode-2026-02-01"],
messages=[
{"role": "user", "content": "Refactor this module to use dependency injection"}
],
)
print(response.content[0].text)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
speed: "fast",
betas: ["fast-mode-2026-02-01"],
messages: [
{
role: "user",
content: "Refactor this module to use dependency injection"
}
]
});
const textBlock = response.content.find(
(block): block is Anthropic.Beta.Messages.BetaTextBlock => block.type === "text"
);
console.log(textBlock?.text);
```
```csharp C# hidelines={1..5}
using Anthropic;
using Anthropic.Models.Beta.Messages;
AnthropicClient client = new();
var response = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 4096,
Speed = Speed.Fast,
Betas = ["fast-mode-2026-02-01"],
Messages = [
new() { Role = Role.User, Content = "Refactor this module to use dependency injection" }
],
});
Console.WriteLine(response);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
anthropic "github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Speed: anthropic.BetaMessageNewParamsSpeedFast,
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaFastMode2026_02_01},
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Refactor this module to use dependency injection")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Content[0].AsText().Text)
}
```
```java Java hidelines={1..8,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
BetaMessage response = client.beta().messages().create(
MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(4096L)
.speed(MessageCreateParams.Speed.FAST)
.addBeta(AnthropicBeta.FAST_MODE_2026_02_01)
.addUserMessage("Refactor this module to use dependency injection")
.build());
IO.println(response.content().get(0).text().get().text());
}
```
```php PHP hidelines={1..4}
beta->messages->create(
model: 'claude-opus-4-7',
maxTokens: 4096,
speed: 'fast',
betas: ['fast-mode-2026-02-01'],
messages: [
['role' => 'user', 'content' => 'Refactor this module to use dependency injection'],
],
);
echo $response->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
speed: "fast",
betas: ["fast-mode-2026-02-01"],
messages: [{role: "user", content: "Refactor this module to use dependency injection"}]
)
puts response.content[0].text
```
## Pricing
Fast mode is priced at 6x standard Opus rates across the full context window, including requests over 200k input tokens. The following table shows pricing for Claude Opus 4.6 and Claude Opus 4.7 with fast mode:
| Input | Output |
|:------|:-------|
| $30 / MTok | $150 / MTok |
Fast mode pricing stacks with other pricing modifiers:
- [Prompt caching multipliers](/docs/en/about-claude/pricing#prompt-caching) apply on top of fast mode pricing
- [Data residency](/docs/en/manage-claude/data-residency) multipliers apply on top of fast mode pricing
For complete pricing details, see the [pricing page](/docs/en/about-claude/pricing#fast-mode-pricing).
## Rate limits
Fast mode has a dedicated rate limit that is separate from standard Opus rate limits. When your fast mode rate limit is exceeded, the API returns a `429` error with a `retry-after` header indicating when capacity will be available.
The response includes headers that indicate your fast mode rate limit status:
| Header | Description |
|:-------|:------------|
| `anthropic-fast-input-tokens-limit` | Maximum fast mode input tokens per minute |
| `anthropic-fast-input-tokens-remaining` | Remaining fast mode input tokens |
| `anthropic-fast-input-tokens-reset` | Time when the fast mode input token limit resets |
| `anthropic-fast-output-tokens-limit` | Maximum fast mode output tokens per minute |
| `anthropic-fast-output-tokens-remaining` | Remaining fast mode output tokens |
| `anthropic-fast-output-tokens-reset` | Time when the fast mode output token limit resets |
For tier-specific rate limits, see the [rate limits page](/docs/en/api/rate-limits).
## Checking which speed was used
The response `usage` object includes a `speed` field that indicates which speed was used, either `"fast"` or `"standard"`:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "anthropic-beta: fast-mode-2026-02-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"speed": "fast",
"messages": [{"role": "user", "content": "Hello"}]
}'
```
```bash CLI
ant beta:messages create --beta fast-mode-2026-02-01 \
--transform usage.speed --raw-output <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
speed: fast
messages:
- role: user
content: Hello
YAML
```
```python Python nocheck
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
speed="fast",
betas=["fast-mode-2026-02-01"],
messages=[{"role": "user", "content": "Hello"}],
)
print(response.usage.speed) # "fast" or "standard"
```
```typescript TypeScript
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
speed: "fast",
betas: ["fast-mode-2026-02-01"],
messages: [{ role: "user", content: "Hello" }]
});
console.log(response.usage.speed); // "fast" or "standard"
```
```csharp C# hidelines={1..5}
using Anthropic;
using Anthropic.Models.Beta.Messages;
AnthropicClient client = new();
var response = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 1024,
Speed = Speed.Fast,
Betas = ["fast-mode-2026-02-01"],
Messages = [new() { Role = Role.User, Content = "Hello" }],
});
Console.WriteLine(response.Usage.Speed); // "fast" or "standard"
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
anthropic "github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Speed: anthropic.BetaMessageNewParamsSpeedFast,
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaFastMode2026_02_01},
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Hello")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Usage.Speed) // "fast" or "standard"
}
```
```java Java hidelines={1..8,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.speed(MessageCreateParams.Speed.FAST)
.addBeta(AnthropicBeta.FAST_MODE_2026_02_01)
.addUserMessage("Hello")
.build();
BetaMessage response = client.beta().messages().create(params);
IO.println(response.usage().speed()); // "fast" or "standard"
}
```
```php PHP hidelines={1..4}
beta->messages->create(
model: 'claude-opus-4-7',
maxTokens: 1024,
speed: 'fast',
betas: ['fast-mode-2026-02-01'],
messages: [['role' => 'user', 'content' => 'Hello']],
);
echo $response->usage->speed; // "fast" or "standard"
```
```ruby Ruby nocheck
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
speed: "fast",
betas: ["fast-mode-2026-02-01"],
messages: [{ role: "user", content: "Hello" }]
)
puts(response.usage.speed) # "fast" or "standard"
```
```json Output hidelines={5..8}
{
"id": "msg_01XFDUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"content": [{ "type": "text", "text": "Hello!" }],
"model": "claude-opus-4-7",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 8,
"output_tokens": 12,
"speed": "fast"
}
}
```
To track fast mode usage and costs across your organization, see the [Usage and Cost API](/docs/en/manage-claude/usage-cost-api).
## Retries and fallback
### Automatic retries
When fast mode rate limits are exceeded, the API returns a `429` error with a `retry-after` header. The Anthropic SDKs automatically retry these requests up to 2 times by default (configurable via `max_retries`), waiting for the server-specified delay before each retry. Since fast mode uses continuous token replenishment, the `retry-after` delay is typically short and requests succeed once capacity is available.
### Falling back to standard speed
If you'd prefer to fall back to standard speed rather than wait for fast mode capacity, catch the rate limit error and retry without `speed: "fast"`. Set `max_retries` to `0` on the initial fast request to skip automatic retries and fail immediately on rate limit errors.
Falling back from fast to standard speed will result in a [prompt cache](/docs/en/build-with-claude/prompt-caching) miss. Requests at different speeds do not share cached prefixes.
Since setting `max_retries` to `0` also disables retries for other transient errors (overloaded, internal server errors), the examples below re-issue the original request with default retries for those cases.
```bash CLI
# `ant` retries 429/5xx automatically and has no per-request max_retries
# override, so on a fast-mode 429 the fallback runs after the built-in
# retries exhaust. --transform-error surfaces error.type for branching.
create_message_with_fast_fallback() {
local speed="$1" max_attempts="${2:-3}" body out
body=${3:-$(cat)}
out=$(
ant beta:messages create --beta fast-mode-2026-02-01 \
${speed:+--speed "$speed"} \
--transform-error error.type --format-error yaml <<<"$body" 2>/dev/null
) && { printf '%s\n' "$out"; return; }
case "$out" in
rate_limit_error)
if [[ -n "$speed" ]]; then
create_message_with_fast_fallback "" "$max_attempts" "$body"
return
fi ;;
overloaded_error | api_error | "")
if (( max_attempts > 1 )); then
create_message_with_fast_fallback "$speed" $((max_attempts - 1)) "$body"
return
fi ;;
esac
printf '%s\n' "${out:-connection_error}" >&2
return 1
}
MESSAGE=$(
create_message_with_fast_fallback fast <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content: Hello
YAML
)
```
```python Python nocheck hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
def create_message_with_fast_fallback(max_retries=None, max_attempts=3, **params):
try:
return client.beta.messages.create(**params, max_retries=max_retries)
except anthropic.RateLimitError:
if params.get("speed") == "fast":
del params["speed"]
return create_message_with_fast_fallback(**params)
raise
except (
anthropic.APIStatusError,
anthropic.APIConnectionError,
) as error:
if isinstance(error, anthropic.APIStatusError) and error.status_code < 500:
raise
if max_attempts > 1:
return create_message_with_fast_fallback(
max_attempts=max_attempts - 1, **params
)
raise
message = create_message_with_fast_fallback(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
betas=["fast-mode-2026-02-01"],
speed="fast",
max_retries=0,
)
```
```typescript TypeScript hidelines={1..3,-1}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
(async () => {
async function createMessageWithFastFallback(
params: Anthropic.Beta.MessageCreateParams,
requestOptions?: Anthropic.RequestOptions,
maxAttempts: number = 3
): Promise {
try {
return (await client.beta.messages.create(
params,
requestOptions
)) as Anthropic.Beta.Messages.BetaMessage;
} catch (e) {
if (e instanceof Anthropic.RateLimitError && params.speed === "fast") {
const { speed, ...rest } = params;
return createMessageWithFastFallback(rest);
}
if (
e instanceof Anthropic.InternalServerError ||
e instanceof Anthropic.APIConnectionError
) {
if (maxAttempts > 1) {
return createMessageWithFastFallback(params, undefined, maxAttempts - 1);
}
}
throw e;
}
}
const message = await createMessageWithFastFallback(
{
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
betas: ["fast-mode-2026-02-01"],
speed: "fast"
},
{ maxRetries: 0 }
);
})();
```
```csharp C# hidelines={1..6}
using Anthropic;
using Anthropic.Exceptions;
using Anthropic.Models.Beta.Messages;
AnthropicClient client = new();
async Task CreateMessageWithFastFallback(
MessageCreateParams parameters,
int? maxRetries = null,
int maxAttempts = 3)
{
try
{
var requestClient = maxRetries is int retries
? client.WithOptions(options => options with { MaxRetries = retries })
: client;
return await requestClient.Beta.Messages.Create(parameters);
}
catch (AnthropicRateLimitException)
{
if (parameters.Speed is not null)
{
return await CreateMessageWithFastFallback(
parameters with { Speed = null });
}
throw;
}
catch (Anthropic5xxException)
{
if (maxAttempts > 1)
{
return await CreateMessageWithFastFallback(
parameters, maxAttempts: maxAttempts - 1);
}
throw;
}
}
var message = await CreateMessageWithFastFallback(
new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello" }],
Betas = ["fast-mode-2026-02-01"],
Speed = Speed.Fast,
},
maxRetries: 0);
```
```go Go hidelines={1..11}
package main
import (
"context"
"errors"
"fmt"
anthropic "github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func createMessageWithFastFallback(
ctx context.Context,
client *anthropic.Client,
params anthropic.BetaMessageNewParams,
maxAttempts int,
opts ...option.RequestOption,
) (*anthropic.BetaMessage, error) {
message, err := client.Beta.Messages.New(ctx, params, opts...)
if err != nil {
var apierr *anthropic.Error
if errors.As(err, &apierr) && apierr.StatusCode == 429 && params.Speed != "" {
params.Speed = ""
return createMessageWithFastFallback(ctx, client, params, maxAttempts)
}
if (errors.As(err, &apierr) && apierr.StatusCode >= 500) || !errors.As(err, &apierr) {
if maxAttempts > 1 {
return createMessageWithFastFallback(ctx, client, params, maxAttempts-1)
}
}
return nil, err
}
return message, nil
}
func main() {
client := anthropic.NewClient()
message, err := createMessageWithFastFallback(
context.TODO(),
&client,
anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Hello")),
},
Speed: "fast",
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaFastMode2026_02_01},
},
3,
option.WithMaxRetries(0),
)
if err != nil {
panic(err)
}
fmt.Println(message)
}
```
```java Java hidelines={1..2,5..10}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.errors.InternalServerException;
import com.anthropic.errors.RateLimitException;
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import java.util.Optional;
// Disable SDK auto-retry so the fallback logic below handles it
AnthropicClient client =
AnthropicOkHttpClient.builder().fromEnv().maxRetries(0).build();
BetaMessage createMessageWithFastFallback(
MessageCreateParams params, int maxAttempts) {
try {
return client.beta().messages().create(params);
} catch (RateLimitException e) {
if (params.speed().isPresent()) {
MessageCreateParams retryParams = params.toBuilder()
.speed(Optional.empty())
.build();
return createMessageWithFastFallback(retryParams, maxAttempts);
}
throw e;
} catch (InternalServerException e) {
if (maxAttempts > 1) {
return createMessageWithFastFallback(params, maxAttempts - 1);
}
throw e;
}
}
void main() {
BetaMessage message = createMessageWithFastFallback(
MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessage("Hello")
.addBeta(AnthropicBeta.FAST_MODE_2026_02_01)
.speed(MessageCreateParams.Speed.FAST)
.build(),
3);
IO.println(message.content().get(0).text().get().text());
}
```
```php PHP hidelines={1..3,8}
beta->messages->create(
...$params,
requestOptions: $requestOptions,
);
} catch (RateLimitException $e) {
if (isset($params['speed'])) {
unset($params['speed']);
return createMessageWithFastFallback($client, $params);
}
throw $e;
} catch (InternalServerException | APIConnectionException $e) {
if ($maxAttempts > 1) {
return createMessageWithFastFallback(
$client, $params, maxAttempts: $maxAttempts - 1
);
}
throw $e;
}
}
$message = createMessageWithFastFallback(
$client,
[
'model' => 'claude-opus-4-7',
'maxTokens' => 1024,
'messages' => [['role' => 'user', 'content' => 'Hello']],
'betas' => ['fast-mode-2026-02-01'],
'speed' => 'fast',
],
RequestOptions::with(maxRetries: 0),
);
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
anthropic = Anthropic::Client.new
def create_message_with_fast_fallback(client, request_options: {}, max_attempts: 3, **params)
client.beta.messages.create(**params, request_options: request_options)
rescue Anthropic::Errors::RateLimitError
raise unless params[:speed] == "fast"
params.delete(:speed)
create_message_with_fast_fallback(client, **params)
rescue Anthropic::Errors::InternalServerError, Anthropic::Errors::APIConnectionError
raise unless max_attempts > 1
create_message_with_fast_fallback(client, max_attempts: max_attempts - 1, **params)
end
message = create_message_with_fast_fallback(
anthropic,
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
betas: ["fast-mode-2026-02-01"],
speed: "fast",
request_options: { max_retries: 0 }
)
```
## Considerations
- **Prompt caching:** Switching between fast and standard speed invalidates the prompt cache. Requests at different speeds do not share cached prefixes.
- **Supported models:** Fast mode is supported on Claude Opus 4.6 and Claude Opus 4.7. Sending `speed: "fast"` with an unsupported model returns an error.
- **TTFT:** Fast mode's benefits are focused on output tokens per second (OTPS), not time to first token (TTFT).
- **Batch API:** Fast mode is not available with the [Batch API](/docs/en/build-with-claude/batch-processing).
- **Priority Tier:** Fast mode is not available with [Priority Tier](/docs/en/api/service-tiers).
- **Claude Platform on AWS:** Fast mode is not available on [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws).
## Next steps
View detailed fast mode pricing information.
Check rate limit tiers for fast mode.
Control token usage with the effort parameter.
---
# Multilingual support
URL: https://platform.claude.com/docs/en/build-with-claude/multilingual-support
# Multilingual support
Claude excels at tasks across multiple languages, maintaining strong cross-lingual performance relative to English.
---
## Overview
Claude demonstrates robust multilingual capabilities, with particularly strong performance in zero-shot tasks across languages. The model maintains consistent relative performance across both widely-spoken and lower-resource languages, making it a reliable choice for multilingual applications.
Note that Claude is capable in many languages beyond those benchmarked below. Consider testing with any languages relevant to your specific use cases.
## Performance data
Below are the zero-shot chain-of-thought evaluation scores for Claude models across different languages, shown as a percent relative to English performance (100%):
| Language | Claude Opus 4.11 | Claude Opus 4 (deprecated)1 | Claude Sonnet 4.51 | Claude Sonnet 4 (deprecated)1 | Claude Haiku 4.51 |
|----------|---------------|---------------|---------------|-----------------|------------------|
| English (baseline, fixed to 100%) | 100% | 100% | 100% | 100% | 100% |
| Spanish | 98.1% | 98.0% | 98.2% | 97.5% | 96.4% |
| Portuguese (Brazil) | 97.8% | 97.3% | 97.8% | 97.2% | 96.1% |
| Italian | 97.7% | 97.5% | 97.9% | 97.3% | 96.0% |
| French | 97.9% | 97.7% | 97.5% | 97.1% | 95.7% |
| Indonesian | 97.3% | 97.2% | 97.3% | 96.2% | 94.2% |
| German | 97.7% | 97.1% | 97.0% | 94.7% | 94.3% |
| Arabic | 97.1% | 96.9% | 97.2% | 96.1% | 92.5% |
| Chinese (Simplified) | 97.1% | 96.7% | 96.9% | 95.9% | 94.2% |
| Korean | 96.6% | 96.4% | 96.7% | 95.9% | 93.3% |
| Japanese | 96.9% | 96.2% | 96.8% | 95.6% | 93.5% |
| Hindi | 96.8% | 96.7% | 96.7% | 95.8% | 92.4% |
| Bengali | 95.7% | 95.2% | 95.4% | 94.4% | 90.4% |
| Swahili | 89.8% | 89.5% | 91.1% | 87.1% | 78.3% |
| Yoruba | 80.3% | 78.9% | 79.7% | 76.4% | 52.7% |
1 With [extended thinking](/docs/en/build-with-claude/extended-thinking).
These metrics are based on [MMLU (Massive Multitask Language Understanding)](https://en.wikipedia.org/wiki/MMLU) English test sets that were translated into 14 additional languages by professional human translators, as documented in [OpenAI's simple-evals repository](https://github.com/openai/simple-evals/blob/main/multilingual_mmlu_benchmark_results.md). The use of human translators for this evaluation ensures high-quality translations, particularly important for languages with fewer digital resources.
***
## Best practices
When working with multilingual content:
1. **Provide clear language context**: While Claude can detect the target language automatically, explicitly stating the desired input/output language improves reliability. For enhanced fluency, you can prompt Claude to use "idiomatic speech as if it were a native speaker."
2. **Use native scripts**: Submit text in its native script rather than transliteration for optimal results
3. **Consider cultural context**: Effective communication often requires cultural and regional awareness beyond pure translation
Also follow the general [prompt engineering guidelines](/docs/en/build-with-claude/prompt-engineering/overview) to better improve Claude's performance.
***
## Language support considerations
- Claude processes input and generates output in most world languages that use standard Unicode characters
- Performance varies by language, with particularly strong capabilities in widely-spoken languages
- Even in languages with fewer digital resources, Claude maintains meaningful capabilities
Master the art of prompt crafting to get the most out of Claude.
---
# Search results
URL: https://platform.claude.com/docs/en/build-with-claude/search-results
# Search results
Enable natural citations for RAG applications by providing search results with source attribution
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
Search result content blocks enable natural citations with proper source attribution, bringing web search-quality citations to your custom applications. This feature is particularly powerful for RAG (Retrieval-Augmented Generation) applications where you need Claude to cite sources accurately.
The search results feature is available on the following models:
- Claude Opus 4.7 (`claude-opus-4-7`)
- Claude Opus 4.6 (`claude-opus-4-6`)
- Claude Sonnet 4.6 (`claude-sonnet-4-6`)
- Claude Sonnet 4.5 (`claude-sonnet-4-5-20250929`)
- Claude Opus 4.5 (`claude-opus-4-5-20251101`)
- Claude Opus 4.1 (`claude-opus-4-1-20250805`)
- Claude Opus 4 ([deprecated](/docs/en/about-claude/model-deprecations)) (`claude-opus-4-20250514`)
- Claude Sonnet 4 ([deprecated](/docs/en/about-claude/model-deprecations)) (`claude-sonnet-4-20250514`)
- Claude Haiku 4.5 (`claude-haiku-4-5-20251001`)
- Claude Haiku 3.5 ([retired, except on Bedrock and Vertex AI](/docs/en/about-claude/model-deprecations)) (`claude-3-5-haiku-20241022`)
## Key benefits
- **Natural citations:** Achieve the same citation quality as web search for any content
- **Flexible integration:** Use in tool returns for dynamic RAG or as top-level content for pre-fetched data
- **Proper source attribution:** Each result includes source and title information for clear attribution
- **No document workarounds needed:** Eliminates the need for document-based workarounds
- **Consistent citation format:** Matches the citation quality and format of Claude's web search functionality
## How it works
Search results can be provided in two ways:
1. **From tool calls:** Your custom tools return search results, enabling dynamic RAG applications
2. **As top-level content:** You provide search results directly in user messages for pre-fetched or cached content
In both cases, Claude can automatically cite information from the search results with proper source attribution.
### Search result schema
Search results use the following structure:
```json
{
"type": "search_result",
"source": "https://example.com/article", // Required: Source URL or identifier
"title": "Article Title", // Required: Title of the result
"content": [
// Required: Array of text blocks
{
"type": "text",
"text": "The actual content of the search result..."
}
],
"citations": {
// Optional: Citation configuration
"enabled": true // Enable/disable citations for this result
}
}
```
### Required fields
| Field | Type | Description |
|-------|------|-------------|
| `type` | string | Must be `"search_result"` |
| `source` | string | The source URL or identifier for the content |
| `title` | string | A descriptive title for the search result |
| `content` | array | An array of text blocks containing the actual content |
### Optional fields
| Field | Type | Description |
|-------|------|-------------|
| `citations` | object | Citation configuration with `enabled` boolean field |
| `cache_control` | object | Cache control settings (e.g., `{"type": "ephemeral"}`) |
Each item in the `content` array must be a text block with:
- `type`: Must be `"text"`
- `text`: The actual text content (non-empty string)
## Method 1: Search results from tool calls
The most powerful use case is returning search results from your custom tools. This enables dynamic RAG applications where tools fetch and return relevant content with automatic citations.
### Example: Knowledge base tool
```python Python nocheck hidelines={1}
from anthropic import Anthropic
from anthropic.types import (
MessageParam,
TextBlockParam,
SearchResultBlockParam,
ToolResultBlockParam,
)
client = Anthropic()
# Define a knowledge base search tool
knowledge_base_tool = {
"name": "search_knowledge_base",
"description": "Search the company knowledge base for information",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string", "description": "The search query"}},
"required": ["query"],
},
}
# Function to handle the tool call
def search_knowledge_base(query):
# Your search logic here
# Returns search results in the correct format
return [
SearchResultBlockParam(
type="search_result",
source="https://docs.company.com/product-guide",
title="Product Configuration Guide",
content=[
TextBlockParam(
type="text",
text="To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs.",
)
],
citations={"enabled": True},
),
SearchResultBlockParam(
type="search_result",
source="https://docs.company.com/troubleshooting",
title="Troubleshooting Guide",
content=[
TextBlockParam(
type="text",
text="If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values.",
)
],
citations={"enabled": True},
),
]
# Create a message with the tool
response = client.messages.create(
model="claude-opus-4-7", # Works with all supported models
max_tokens=1024,
tools=[knowledge_base_tool],
messages=[
MessageParam(role="user", content="How do I configure the timeout settings?")
],
)
# When Claude calls the tool, provide the search results
if response.content[0].type == "tool_use":
tool_result = search_knowledge_base(response.content[0].input["query"])
# Send the tool result back
final_response = client.messages.create(
model="claude-opus-4-7", # Works with all supported models
max_tokens=1024,
messages=[
MessageParam(
role="user", content="How do I configure the timeout settings?"
),
MessageParam(role="assistant", content=response.content),
MessageParam(
role="user",
content=[
ToolResultBlockParam(
type="tool_result",
tool_use_id=response.content[0].id,
content=tool_result, # Search results go here
)
],
),
],
)
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
// Define a knowledge base search tool
const knowledgeBaseTool: Anthropic.Messages.Tool = {
name: "search_knowledge_base",
description: "Search the company knowledge base for information",
input_schema: {
type: "object" as const,
properties: {
query: {
type: "string",
description: "The search query"
}
},
required: ["query"]
}
};
// Function to handle the tool call
function searchKnowledgeBase(query: string) {
// Your search logic here
// Returns search results in the correct format
return [
{
type: "search_result" as const,
source: "https://docs.company.com/product-guide",
title: "Product Configuration Guide",
content: [
{
type: "text" as const,
text: "To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs."
}
],
citations: { enabled: true }
},
{
type: "search_result" as const,
source: "https://docs.company.com/troubleshooting",
title: "Troubleshooting Guide",
content: [
{
type: "text" as const,
text: "If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values."
}
],
citations: { enabled: true }
}
];
}
// Create a message with the tool
const response = await anthropic.messages.create({
model: "claude-opus-4-7", // Works with all supported models
max_tokens: 1024,
tools: [knowledgeBaseTool],
messages: [
{
role: "user",
content: "How do I configure the timeout settings?"
}
]
});
// Handle tool use and provide results
if (response.content[0].type === "tool_use") {
const input = response.content[0].input as { query: string };
const toolResult = searchKnowledgeBase(input.query);
const finalResponse = await anthropic.messages.create({
model: "claude-opus-4-7", // Works with all supported models
max_tokens: 1024,
messages: [
{ role: "user", content: "How do I configure the timeout settings?" },
{ role: "assistant", content: response.content },
{
role: "user",
content: [
{
type: "tool_result" as const,
tool_use_id: response.content[0].id,
content: toolResult // Search results go here
}
]
}
]
});
}
```
```csharp C# nocheck
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
public class Program
{
public static async Task Main(string[] args)
{
AnthropicClient client = new();
var knowledgeBaseTool = new Tool
{
Name = "search_knowledge_base",
Description = "Search the company knowledge base for information",
InputSchema = new
{
type = "object",
properties = new
{
query = new
{
type = "string",
description = "The search query"
}
},
required = new[] { "query" }
}
};
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Tools = new[] { knowledgeBaseTool },
Messages = new[]
{
new MessageParam
{
Role = Role.User,
Content = "How do I configure the timeout settings?"
}
}
};
var response = await client.Messages.Create(parameters);
if (response.Content[0] is ToolUseBlock toolUse)
{
var toolResult = SearchKnowledgeBase(toolUse.Input["query"].ToString());
var finalParameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = new[]
{
new MessageParam { Role = Role.User, Content = "How do I configure the timeout settings?" },
new MessageParam { Role = Role.Assistant, Content = response.Content },
new MessageParam
{
Role = Role.User,
Content = new[]
{
new ToolResultBlockParam
{
ToolUseID = toolUse.Id,
Content = toolResult
}
}
}
}
};
var finalResponse = await client.Messages.Create(finalParameters);
Console.WriteLine(finalResponse);
}
}
private static List SearchKnowledgeBase(string query)
{
return new List
{
new SearchResultBlockParam
{
Source = "https://docs.company.com/product-guide",
Title = "Product Configuration Guide",
Content = new[]
{
new TextBlockParam
{
Text = "To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs."
}
},
Citations = new CitationsConfigParam { Enabled = true }
},
new SearchResultBlockParam
{
Source = "https://docs.company.com/troubleshooting",
Title = "Troubleshooting Guide",
Content = new[]
{
new TextBlockParam
{
Text = "If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values."
}
},
Citations = new CitationsConfigParam { Enabled = true }
}
};
}
}
```
```go Go nocheck hidelines={1..12,77..78}
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
knowledgeBaseTool := anthropic.ToolUnionParam{
OfTool: &anthropic.ToolParam{
Name: "search_knowledge_base",
Description: anthropic.String("Search the company knowledge base for information"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"query": map[string]any{
"type": "string",
"description": "The search query",
},
},
Required: []string{"query"},
},
},
}
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Tools: []anthropic.ToolUnionParam{knowledgeBaseTool},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("How do I configure the timeout settings?")),
},
})
if err != nil {
log.Fatal(err)
}
for _, block := range response.Content {
switch variant := block.AsAny().(type) {
case anthropic.ToolUseBlock:
var input struct {
Query string `json:"query"`
}
if err := json.Unmarshal(variant.Input, &input); err != nil {
log.Fatal(err)
}
toolResults := searchKnowledgeBase(input.Query)
// Build assistant message from the response
assistantParam := response.ToParam()
finalResponse, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("How do I configure the timeout settings?")),
assistantParam,
anthropic.NewUserMessage(anthropic.ContentBlockParamUnion{
OfToolResult: &anthropic.ToolResultBlockParam{
ToolUseID: variant.ID,
Content: toolResults,
},
}),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(finalResponse)
}
}
}
func searchKnowledgeBase(query string) []anthropic.ToolResultBlockParamContentUnion {
return []anthropic.ToolResultBlockParamContentUnion{
{OfSearchResult: &anthropic.SearchResultBlockParam{
Content: []anthropic.TextBlockParam{
{Text: "To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs."},
},
Source: "https://docs.company.com/product-guide",
Title: "Product Configuration Guide",
Citations: anthropic.CitationsConfigParam{Enabled: anthropic.Bool(true)},
}},
{OfSearchResult: &anthropic.SearchResultBlockParam{
Content: []anthropic.TextBlockParam{
{Text: "If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values."},
},
Source: "https://docs.company.com/troubleshooting",
Title: "Troubleshooting Guide",
Citations: anthropic.CitationsConfigParam{Enabled: anthropic.Bool(true)},
}},
}
}
```
```java Java nocheck hidelines={1..3,5..7,9..19,75..76,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.CitationsConfigParam;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.SearchResultBlockParam;
import com.anthropic.models.messages.TextBlockParam;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.ToolResultBlockParam;
import com.anthropic.models.messages.ToolUseBlock;
import com.anthropic.models.messages.ToolUseBlockParam;
import com.anthropic.core.JsonValue;
import java.util.List;
import java.util.Map;
public class SearchKnowledgeBaseExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
Tool knowledgeBaseTool = Tool.builder()
.name("search_knowledge_base")
.description("Search the company knowledge base for information")
.inputSchema(Tool.InputSchema.builder()
.properties(JsonValue.from(Map.of(
"query", Map.of(
"type", "string",
"description", "The search query"
)
)))
.putAdditionalProperty("required", JsonValue.from(List.of("query")))
.build())
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addTool(knowledgeBaseTool)
.addUserMessage("How do I configure the timeout settings?")
.build();
Message response = client.messages().create(params);
response.content().get(0).toolUse().ifPresent(toolUse -> {
List toolResult = searchKnowledgeBase(
(String) ((Map, ?>) toolUse._input()).get("query")
);
MessageCreateParams finalParams = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessage("How do I configure the timeout settings?")
.addAssistantMessageOfBlockParams(List.of(
ContentBlockParam.ofToolUse(ToolUseBlockParam.builder()
.id(toolUse.id())
.name(toolUse.name())
.input(toolUse._input())
.build())
))
.addUserMessageOfBlockParams(List.of(
ContentBlockParam.ofToolResult(
ToolResultBlockParam.builder()
.toolUseId(toolUse.id())
.contentOfBlockParams(toolResult)
.build()
)
))
.build();
Message finalResponse = client.messages().create(finalParams);
System.out.println(finalResponse);
});
}
private static List searchKnowledgeBase(String query) {
return List.of(
ContentBlockParam.ofSearchResult(
SearchResultBlockParam.builder()
.source("https://docs.company.com/product-guide")
.title("Product Configuration Guide")
.content(List.of(
TextBlockParam.builder()
.text("To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs.")
.build()
))
.citations(CitationsConfigParam.builder().enabled(true).build())
.build()
),
ContentBlockParam.ofSearchResult(
SearchResultBlockParam.builder()
.source("https://docs.company.com/troubleshooting")
.title("Troubleshooting Guide")
.content(List.of(
TextBlockParam.builder()
.text("If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values.")
.build()
))
.citations(CitationsConfigParam.builder().enabled(true).build())
.build()
)
);
}
}
```
```php PHP nocheck hidelines={1..4}
'search_knowledge_base',
'description' => 'Search the company knowledge base for information',
'input_schema' => [
'type' => 'object',
'properties' => [
'query' => [
'type' => 'string',
'description' => 'The search query'
]
],
'required' => ['query']
]
];
function searchKnowledgeBase($query) {
return [
[
'type' => 'search_result',
'source' => 'https://docs.company.com/product-guide',
'title' => 'Product Configuration Guide',
'content' => [
[
'type' => 'text',
'text' => 'To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs.'
]
],
'citations' => ['enabled' => true]
],
[
'type' => 'search_result',
'source' => 'https://docs.company.com/troubleshooting',
'title' => 'Troubleshooting Guide',
'content' => [
[
'type' => 'text',
'text' => 'If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values.'
]
],
'citations' => ['enabled' => true]
]
];
}
$response = $client->messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'How do I configure the timeout settings?']
],
model: 'claude-opus-4-7',
tools: [$knowledgeBaseTool],
);
$toolUseBlock = null;
foreach ($response->content as $block) {
if ($block->type === 'tool_use') {
$toolUseBlock = $block;
break;
}
}
if ($toolUseBlock !== null) {
$toolResult = searchKnowledgeBase($toolUseBlock->input['query']);
$finalResponse = $client->messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'How do I configure the timeout settings?'],
['role' => 'assistant', 'content' => $response->content],
[
'role' => 'user',
'content' => [
[
'type' => 'tool_result',
'tool_use_id' => $toolUseBlock->id,
'content' => $toolResult
]
]
]
],
model: 'claude-opus-4-7',
);
echo $finalResponse;
} else {
echo $response;
}
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
knowledge_base_tool = {
name: "search_knowledge_base",
description: "Search the company knowledge base for information",
input_schema: {
type: "object",
properties: {
query: { type: "string", description: "The search query" }
},
required: ["query"]
}
}
def search_knowledge_base(query)
[
{
type: "search_result",
source: "https://docs.company.com/product-guide",
title: "Product Configuration Guide",
content: [
{
type: "text",
text: "To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs."
}
],
citations: { enabled: true }
},
{
type: "search_result",
source: "https://docs.company.com/troubleshooting",
title: "Troubleshooting Guide",
content: [
{
type: "text",
text: "If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values."
}
],
citations: { enabled: true }
}
]
end
response = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [knowledge_base_tool],
messages: [
{ role: "user", content: "How do I configure the timeout settings?" }
]
)
if response.content.first.type == :tool_use
tool_result = search_knowledge_base(response.content.first.input["query"])
final_response = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "How do I configure the timeout settings?" },
{ role: "assistant", content: response.content },
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: response.content.first.id,
content: tool_result
}
]
}
]
)
puts final_response
end
```
## Method 2: Search results as top-level content
You can also provide search results directly in user messages. This is useful for:
- Pre-fetched content from your search infrastructure
- Cached search results from previous queries
- Content from external search services
- Testing and development
### Example: Direct search results
```bash cURL
#!/bin/sh
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": [
{
"type": "search_result",
"source": "https://docs.company.com/api-reference",
"title": "API Reference - Authentication",
"content": [
{
"type": "text",
"text": "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium."
}
],
"citations": {
"enabled": true
}
},
{
"type": "search_result",
"source": "https://docs.company.com/quickstart",
"title": "Getting Started Guide",
"content": [
{
"type": "text",
"text": "To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key."
}
],
"citations": {
"enabled": true
}
},
{
"type": "text",
"text": "Based on these search results, how do I authenticate API requests and what are the rate limits?"
}
]
}
]
}'
```
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content:
- type: search_result
source: https://docs.company.com/api-reference
title: API Reference - Authentication
content:
- type: text
text: >-
All API requests must include an API key in the Authorization
header. Keys can be generated from the dashboard. Rate limits:
1000 requests per hour for standard tier, 10000 for premium.
citations:
enabled: true
- type: search_result
source: https://docs.company.com/quickstart
title: Getting Started Guide
content:
- type: text
text: >-
To get started: 1) Sign up for an account, 2) Generate an API
key from the dashboard, 3) Install our SDK using pip install
company-sdk, 4) Initialize the client with your API key.
citations:
enabled: true
- type: text
text: >-
Based on these search results, how do I authenticate API requests
and what are the rate limits?
YAML
```
```python Python hidelines={1}
from anthropic import Anthropic
from anthropic.types import MessageParam, TextBlockParam, SearchResultBlockParam
client = Anthropic()
# Provide search results directly in the user message
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
MessageParam(
role="user",
content=[
SearchResultBlockParam(
type="search_result",
source="https://docs.company.com/api-reference",
title="API Reference - Authentication",
content=[
TextBlockParam(
type="text",
text="All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium.",
)
],
citations={"enabled": True},
),
SearchResultBlockParam(
type="search_result",
source="https://docs.company.com/quickstart",
title="Getting Started Guide",
content=[
TextBlockParam(
type="text",
text="To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key.",
)
],
citations={"enabled": True},
),
TextBlockParam(
type="text",
text="Based on these search results, how do I authenticate API requests and what are the rate limits?",
),
],
)
],
)
print(response)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
// Provide search results directly in the user message
const response = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "search_result" as const,
source: "https://docs.company.com/api-reference",
title: "API Reference - Authentication",
content: [
{
type: "text" as const,
text: "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium."
}
],
citations: { enabled: true }
},
{
type: "search_result" as const,
source: "https://docs.company.com/quickstart",
title: "Getting Started Guide",
content: [
{
type: "text" as const,
text: "To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key."
}
],
citations: { enabled: true }
},
{
type: "text" as const,
text: "Based on these search results, how do I authenticate API requests and what are the rate limits?"
}
]
}
]
});
console.log(response);
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages =
[
new()
{
Role = Role.User,
Content =
[
new SearchResultBlockParam
{
Source = "https://docs.company.com/api-reference",
Title = "API Reference - Authentication",
Content =
[
new TextBlockParam
{
Text = "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium."
}
],
Citations = new CitationsConfigParam { Enabled = true }
},
new SearchResultBlockParam
{
Source = "https://docs.company.com/quickstart",
Title = "Getting Started Guide",
Content =
[
new TextBlockParam
{
Text = "To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key."
}
],
Citations = new CitationsConfigParam { Enabled = true }
},
new TextBlockParam
{
Text = "Based on these search results, how do I authenticate API requests and what are the rate limits?"
}
]
}
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(
anthropic.ContentBlockParamUnion{OfSearchResult: &anthropic.SearchResultBlockParam{
Content: []anthropic.TextBlockParam{
{Text: "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium."},
},
Source: "https://docs.company.com/api-reference",
Title: "API Reference - Authentication",
Citations: anthropic.CitationsConfigParam{Enabled: anthropic.Bool(true)},
}},
anthropic.ContentBlockParamUnion{OfSearchResult: &anthropic.SearchResultBlockParam{
Content: []anthropic.TextBlockParam{
{Text: "To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key."},
},
Source: "https://docs.company.com/quickstart",
Title: "Getting Started Guide",
Citations: anthropic.CitationsConfigParam{Enabled: anthropic.Bool(true)},
}},
anthropic.NewTextBlock("Based on these search results, how do I authenticate API requests and what are the rate limits?"),
),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..3,5..7,9..13,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.CitationsConfigParam;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.SearchResultBlockParam;
import com.anthropic.models.messages.TextBlockParam;
import java.util.List;
public class SearchResultExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessageOfBlockParams(List.of(
ContentBlockParam.ofSearchResult(
SearchResultBlockParam.builder()
.source("https://docs.company.com/api-reference")
.title("API Reference - Authentication")
.content(List.of(
TextBlockParam.builder()
.text("All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium.")
.build()
))
.citations(CitationsConfigParam.builder().enabled(true).build())
.build()
),
ContentBlockParam.ofSearchResult(
SearchResultBlockParam.builder()
.source("https://docs.company.com/quickstart")
.title("Getting Started Guide")
.content(List.of(
TextBlockParam.builder()
.text("To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key.")
.build()
))
.citations(CitationsConfigParam.builder().enabled(true).build())
.build()
),
ContentBlockParam.ofText(
TextBlockParam.builder()
.text("Based on these search results, how do I authenticate API requests and what are the rate limits?")
.build()
)
))
.build();
Message response = client.messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'search_result',
'source' => 'https://docs.company.com/api-reference',
'title' => 'API Reference - Authentication',
'content' => [
[
'type' => 'text',
'text' => 'All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium.'
]
],
'citations' => ['enabled' => true]
],
[
'type' => 'search_result',
'source' => 'https://docs.company.com/quickstart',
'title' => 'Getting Started Guide',
'content' => [
[
'type' => 'text',
'text' => 'To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key.'
]
],
'citations' => ['enabled' => true]
],
[
'type' => 'text',
'text' => 'Based on these search results, how do I authenticate API requests and what are the rate limits?'
]
]
]
],
model: 'claude-opus-4-7',
);
echo json_encode($message, JSON_PRETTY_PRINT);
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "search_result",
source: "https://docs.company.com/api-reference",
title: "API Reference - Authentication",
content: [
{
type: "text",
text: "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium."
}
],
citations: { enabled: true }
},
{
type: "search_result",
source: "https://docs.company.com/quickstart",
title: "Getting Started Guide",
content: [
{
type: "text",
text: "To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key."
}
],
citations: { enabled: true }
},
{
type: "text",
text: "Based on these search results, how do I authenticate API requests and what are the rate limits?"
}
]
}
]
)
puts message
```
## Claude's response with citations
Regardless of how search results are provided, Claude automatically includes citations when using information from them:
```json
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard.",
"citations": [
{
"type": "search_result_location",
"cited_text": "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium.",
"source": "https://docs.company.com/api-reference",
"title": "API Reference - Authentication",
"search_result_index": 0,
"start_block_index": 0,
"end_block_index": 1
}
]
},
{
"type": "text",
"text": "\n\nTo set this up from scratch, you'll need to "
},
{
"type": "text",
"text": "sign up for an account, generate an API key from the dashboard, install the SDK using `pip install company-sdk`, and initialize the client with your API key.",
"citations": [
{
"type": "search_result_location",
"cited_text": "To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key.",
"source": "https://docs.company.com/quickstart",
"title": "Getting Started Guide",
"search_result_index": 1,
"start_block_index": 0,
"end_block_index": 1
}
]
}
]
}
```
### Citation fields
Each citation includes:
| Field | Type | Description |
|-------|------|-------------|
| `type` | string | Always `"search_result_location"` for search result citations |
| `source` | string | The source from the original search result |
| `title` | string or null | The title from the original search result |
| `cited_text` | string | The full text of the cited block(s), concatenated. Equals the contents of `content[start_block_index:end_block_index]` joined together. Not counted toward output tokens. |
| `search_result_index` | integer | 0-based index of the cited search result among all `search_result` blocks in the request, in the order they appear (across all messages and tool results). |
| `start_block_index` | integer | 0-based index of the first cited block in the search result's `content` array. |
| `end_block_index` | integer | Exclusive end index of the cited block range in the search result's `content` array. Always greater than `start_block_index`. |
The block indices identify a slice of the search result's `content` array, and `cited_text` is the full text of that slice. The text block is the minimal citable unit: Claude cites whole blocks, not substrings within a block. To get finer-grained citations, split your search result content into smaller blocks (see [Multiple content blocks](#multiple-content-blocks)).
## Multiple content blocks
Search results can contain multiple text blocks in the `content` array:
```json
{
"type": "search_result",
"source": "https://docs.company.com/api-guide",
"title": "API Documentation",
"content": [
{
"type": "text",
"text": "Authentication: All API requests require an API key."
},
{
"type": "text",
"text": "Rate Limits: The API allows 1000 requests per hour per key."
},
{
"type": "text",
"text": "Error Handling: The API returns standard HTTP status codes."
}
]
}
```
A citation referencing the rate limits block looks like:
```json
{
"type": "search_result_location",
"cited_text": "Rate Limits: The API allows 1000 requests per hour per key.",
"source": "https://docs.company.com/api-guide",
"title": "API Documentation",
"search_result_index": 0,
"start_block_index": 1,
"end_block_index": 2
}
```
When this search result is cited, `start_block_index` and `end_block_index` identify which of these blocks the citation covers, and `cited_text` contains exactly those blocks' text. Splitting content into smaller, focused blocks gives Claude finer citation boundaries; combining content into one block means every citation returns the full text. This is the same model used by [custom content documents](/docs/en/build-with-claude/citations#custom-content-documents) in the Citations feature.
## Advanced usage
### Combining both methods
You can use both tool-based and top-level search results in the same conversation:
```python nocheck
from anthropic.types import MessageParam, SearchResultBlockParam, TextBlockParam
# First message with top-level search results
messages = [
MessageParam(
role="user",
content=[
SearchResultBlockParam(
type="search_result",
source="https://docs.company.com/overview",
title="Product Overview",
content=[
TextBlockParam(
type="text", text="Our product helps teams collaborate..."
)
],
citations={"enabled": True},
),
TextBlockParam(
type="text",
text="Tell me about this product and search for pricing information",
),
],
)
]
# Claude might respond and call a tool to search for pricing
# Then you provide tool results with more search results
```
### Combining with other content types
Both methods support mixing search results with other content:
```python nocheck
from anthropic.types import SearchResultBlockParam, TextBlockParam
# In tool results
tool_result = [
SearchResultBlockParam(
type="search_result",
source="https://docs.company.com/guide",
title="User Guide",
content=[TextBlockParam(type="text", text="Configuration details...")],
citations={"enabled": True},
),
TextBlockParam(
type="text", text="Additional context: This applies to version 2.0 and later."
),
]
# In top-level content
user_content = [
SearchResultBlockParam(
type="search_result",
source="https://research.com/paper",
title="Research Paper",
content=[TextBlockParam(type="text", text="Key findings...")],
citations={"enabled": True},
),
{
"type": "image",
"source": {"type": "url", "url": "https://example.com/chart.png"},
},
TextBlockParam(
type="text", text="How does the chart relate to the research findings?"
),
]
```
### Cache control
Add cache control for better performance:
```json
{
"type": "search_result",
"source": "https://docs.company.com/guide",
"title": "User Guide",
"content": [{ "type": "text", "text": "..." }],
"cache_control": {
"type": "ephemeral"
}
}
```
### Citation control
By default, citations are disabled for search results. You can enable citations by explicitly setting the `citations` configuration:
```json
{
"type": "search_result",
"source": "https://docs.company.com/guide",
"title": "User Guide",
"content": [{ "type": "text", "text": "Important documentation..." }],
"citations": {
"enabled": true // Enable citations for this result
}
}
```
When `citations.enabled` is set to `true`, Claude includes citation references when using information from the search result. This enables:
- Natural citations for your custom RAG applications
- Source attribution when interfacing with proprietary knowledge bases
- Web search-quality citations for any custom tool that returns search results
Citations are all-or-nothing: either all search results in a request must have citations enabled, or all must have them disabled. Mixing search results with different citation settings results in an error.
## Best practices
### For tool-based search (Method 1)
- **Dynamic content:** Use for real-time searches and dynamic RAG applications
- **Error handling:** Return appropriate messages when searches fail
- **Result limits:** Return only the most relevant results to avoid context overflow
### For top-level search (Method 2)
- **Pre-fetched content:** Use when you already have search results
- **Batch processing:** Ideal for processing multiple search results at once
- **Testing:** Great for testing citation behavior with known content
### General best practices
1. **Structure results effectively:**
- Use clear, permanent source URLs
- Provide descriptive titles
- Break long content into logical text blocks to give Claude finer citation boundaries
2. **Maintain consistency:**
- Use consistent source formats across your application
- Ensure titles accurately reflect content
- Keep formatting consistent
3. **Handle errors gracefully:**
```python nocheck
def search_with_fallback(query):
try:
results = perform_search(query)
if not results:
return {"type": "text", "text": "No results found."}
return format_as_search_results(results)
except Exception as e:
return {"type": "text", "text": f"Search error: {str(e)}"}
```
## Limitations
- Search result content blocks are available on Claude API, Amazon Bedrock, and Google Cloud's Vertex AI
- Only text content is supported within search results (no images or other media)
- The `content` array must contain at least one text block
---
# Streaming messages
URL: https://platform.claude.com/docs/en/build-with-claude/streaming
# Streaming messages
---
When creating a Message, you can set `"stream": true` to incrementally stream the response using [server-sent events](https://developer.mozilla.org/en-US/Web/API/Server-sent%5Fevents/Using%5Fserver-sent%5Fevents) (SSE).
## Streaming with SDKs
The [Python](https://github.com/anthropics/anthropic-sdk-python) and [TypeScript](https://github.com/anthropics/anthropic-sdk-typescript) SDKs offer multiple ways of streaming. The [PHP](https://github.com/anthropics/anthropic-sdk-php) SDK provides streaming via `createStream()`. The Python SDK allows both sync and async streams. See the documentation in each SDK for details.
```bash CLI
ant messages create --stream --format jsonl \
--model claude-opus-4-7 \
--max-tokens 1024 \
--message '{role: user, content: "Hello"}' \
| while IFS= read -r event; do
[[ $event == *'"text_delta"'* ]] || continue
text=${event#*'"text":"'}
printf '%b' "${text%\"*}"
done
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
with client.messages.stream(
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
model="claude-opus-4-7",
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
await client.messages
.stream({
messages: [{ role: "user", content: "Hello" }],
model: "claude-opus-4-7",
max_tokens: 1024
})
.on("text", (text) => {
console.log(text);
});
```
```csharp C#
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello" }]
};
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
Console.Write(msg);
}
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")),
},
})
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)
}
}
```
```java Java hidelines={1..6,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
public class StreamingExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(1024L)
.addUserMessage("Hello")
.build();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
event.contentBlockDelta().ifPresent(deltaEvent ->
deltaEvent.delta().text().ifPresent(td ->
System.out.print(td.text())
)
);
});
}
}
}
```
```php PHP hidelines={1..4}
messages->createStream(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Hello']
],
model: 'claude-opus-4-7',
);
foreach ($stream as $message) {
echo $message;
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
stream = client.messages.stream(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }]
)
stream.text.each { |text| print(text) }
```
## Get the final message without handling events
If you don't need to process text as it arrives, the SDKs provide a way to use streaming under the hood while returning the complete `Message` object, identical to what `.create()` returns. This is especially useful for requests with large `max_tokens` values, where the SDKs require streaming to avoid HTTP timeouts.
```bash CLI
# The ant CLI's --stream flag emits one event per line and does not
# accumulate into a final Message. For long generations, stream the
# raw events:
ant messages create --stream --format jsonl <<'YAML'
model: claude-opus-4-7
max_tokens: 128000
messages:
- role: user
content: Write a detailed analysis...
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
with client.messages.stream(
max_tokens=128000,
messages=[{"role": "user", "content": "Write a detailed analysis..."}],
model="claude-opus-4-7",
) as stream:
message = stream.get_final_message()
print(message.content[0].text)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const stream = client.messages.stream({
max_tokens: 128000,
messages: [{ role: "user", content: "Write a detailed analysis..." }],
model: "claude-opus-4-7"
});
const message = await stream.finalMessage();
const textBlock = message.content.find((block) => block.type === "text");
if (textBlock && textBlock.type === "text") {
console.log(textBlock.text);
}
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main()
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 128000,
Messages = [new() { Role = Role.User, Content = "Write a detailed analysis..." }]
};
var fullText = "";
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
fullText += msg;
}
Console.WriteLine(fullText);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 128000,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Write a detailed analysis...")),
},
})
message := anthropic.Message{}
for stream.Next() {
event := stream.Current()
if err := message.Accumulate(event); err != nil {
log.Fatal(err)
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
fmt.Println(message.Content[0].Text)
}
```
```java Java hidelines={1..2,4..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.helpers.MessageAccumulator;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
public class StreamingExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(128000L)
.addUserMessage("Write a detailed analysis...")
.build();
MessageAccumulator accumulator = MessageAccumulator.create();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(accumulator::accumulate);
}
Message message = accumulator.message();
message.content().get(0).text().ifPresent(tb -> System.out.println(tb.text()));
}
}
```
```php PHP hidelines={1..4}
messages->createStream(
maxTokens: 128000,
messages: [
['role' => 'user', 'content' => 'Write a detailed analysis...']
],
model: 'claude-opus-4-7',
);
$fullText = '';
foreach ($stream as $event) {
if ($event->type === 'content_block_delta') {
$fullText .= $event->delta->text;
}
}
echo $fullText;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.stream(
model: "claude-opus-4-7",
max_tokens: 128000,
messages: [{ role: "user", content: "Write a detailed analysis..." }]
).accumulated_message
puts message.content.first.text
```
The `.stream()` call keeps the HTTP connection alive with server-sent events, then `.get_final_message()` (Python) or `.finalMessage()` (TypeScript) accumulates all events and returns the complete `Message` object. In Go, you call `message.Accumulate(event)` inside the stream loop to build the same complete `Message`. In Java, use `MessageAccumulator.create()` and call `accumulator.accumulate(event)` on each event. In Ruby, call `.accumulated_message` on the stream. In the PHP SDK, you iterate over stream events manually to accumulate the response.
## Event types
Each server-sent event includes a named event type and associated JSON data. Each event uses an SSE event name (e.g. `event: message_stop`), and includes the matching event `type` in its data.
Each stream uses the following event flow:
1. `message_start`: contains a `Message` object with empty `content`.
2. A series of content blocks, each of which have a `content_block_start`, one or more `content_block_delta` events, and a `content_block_stop` event. Each content block has an `index` that corresponds to its index in the final Message `content` array.
3. One or more `message_delta` events, indicating top-level changes to the final `Message` object.
4. A final `message_stop` event.
The token counts shown in the `usage` field of the `message_delta` event are *cumulative*.
### Ping events
Event streams may also include any number of `ping` events.
### Error events
The API may occasionally send [errors](/docs/en/api/errors) in the event stream. For example, during periods of high usage, you may receive an `overloaded_error`, which would normally correspond to an HTTP 529 in a non-streaming context:
```sse Example error
event: error
data: {"type": "error", "error": {"type": "overloaded_error", "message": "Overloaded"}}
```
### Other events
In accordance with the [versioning policy](/docs/en/api/versioning), new event types may be added, and your code should handle unknown event types gracefully.
## Content block delta types
Each `content_block_delta` event contains a `delta` of a type that updates the `content` block at a given `index`.
### Text delta
A `text` content block delta looks like:
```sse Text delta
event: content_block_delta
data: {"type": "content_block_delta","index": 0,"delta": {"type": "text_delta", "text": "ello frien"}}
```
### Input JSON delta
The deltas for `tool_use` content blocks correspond to updates for the `input` field of the block. To support maximum granularity, the deltas are _partial JSON strings_, whereas the final `tool_use.input` is always an _object_.
You can accumulate the string deltas and parse the JSON once you receive a `content_block_stop` event, by using a library like [Pydantic](https://docs.pydantic.dev/latest/concepts/json/#partial-json-parsing) to do partial JSON parsing, or by using the [SDKs](/docs/en/api/client-sdks), which provide helpers to access parsed incremental values.
A `tool_use` content block delta looks like:
```sse Input JSON delta
event: content_block_delta
data: {"type": "content_block_delta","index": 1,"delta": {"type": "input_json_delta","partial_json": "{\"location\": \"San Fra"}}}
```
Note: Current models only support emitting one complete key and value property from `input` at a time. As such, when using tools, there may be delays between streaming events while the model is working. Once an `input` key and value are accumulated, they are emitted as multiple `content_block_delta` events with chunked partial json so that the format can automatically support finer granularity in future models.
### Thinking delta
When using [extended thinking](/docs/en/build-with-claude/extended-thinking#streaming-thinking) with streaming enabled, you'll receive thinking content via `thinking_delta` events. These deltas correspond to the `thinking` field of the `thinking` content blocks.
For thinking content, a special `signature_delta` event is sent just before the `content_block_stop` event. This signature is used to verify the integrity of the thinking block.
When `display: "omitted"` is set on the thinking configuration, no `thinking_delta` events are sent. The thinking block opens, receives a single `signature_delta`, and closes. See [Controlling thinking display](/docs/en/build-with-claude/extended-thinking#controlling-thinking-display).
A typical thinking delta looks like:
```sse Thinking delta
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "I need to find the GCD of 1071 and 462 using the Euclidean algorithm.\n\n1071 = 2 × 462 + 147"}}
```
The signature delta looks like:
```sse Signature delta
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "signature_delta", "signature": "EqQBCgIYAhIM1gbcDa9GJwZA2b3hGgxBdjrkzLoky3dl1pkiMOYds..."}}
```
## Full HTTP stream response
Use the [client SDKs](/docs/en/api/client-sdks) when using streaming mode. However, if you are building a direct API integration, you need to handle these events yourself.
A stream response consists of:
1. A `message_start` event
2. Potentially multiple content blocks, each of which contains:
- A `content_block_start` event
- Potentially multiple `content_block_delta` events
- A `content_block_stop` event
3. One or more `message_delta` events
4. A `message_stop` event
There may be `ping` events dispersed throughout the response as well. See [Event types](#event-types) for more details on the format.
### Basic streaming request
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--data \
'{
"model": "claude-opus-4-7",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 256,
"stream": true
}'
```
```bash CLI
ant messages create --stream --format jsonl \
--model claude-opus-4-7 \
--max-tokens 256 \
--message '{role: user, content: Hello}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-opus-4-7",
messages=[{"role": "user", "content": "Hello"}],
max_tokens=256,
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const stream = client.messages.stream({
model: "claude-opus-4-7",
messages: [{ role: "user", content: "Hello" }],
max_tokens: 256
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
```
```csharp C#
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 256,
Messages = [new() { Role = Role.User, Content = "Hello" }]
};
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
Console.Write(msg);
}
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 256,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")),
},
})
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)
}
}
```
```java Java hidelines={1..7,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
public class StreamingExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(256L)
.addUserMessage("Hello")
.build();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
event.contentBlockDelta().ifPresent(deltaEvent ->
deltaEvent.delta().text().ifPresent(td ->
System.out.print(td.text())
)
);
});
}
}
}
```
```php PHP hidelines={1..4}
messages->createStream(
maxTokens: 256,
messages: [
['role' => 'user', 'content' => 'Hello']
],
model: 'claude-opus-4-7',
);
foreach ($stream as $message) {
echo $message;
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
stream = client.messages.stream(
model: "claude-opus-4-7",
messages: [{ role: "user", content: "Hello" }],
max_tokens: 256
)
stream.text.each { |text| print(text) }
```
```sse Response
event: message_start
data: {"type": "message_start", "message": {"id": "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY", "type": "message", "role": "assistant", "content": [], "model": "claude-opus-4-7", "stop_reason": null, "stop_sequence": null, "usage": {"input_tokens": 25, "output_tokens": 1}}}
event: content_block_start
data: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
event: ping
data: {"type": "ping"}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "Hello"}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "!"}}
event: content_block_stop
data: {"type": "content_block_stop", "index": 0}
event: message_delta
data: {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence":null}, "usage": {"output_tokens": 15}}
event: message_stop
data: {"type": "message_stop"}
```
### Streaming request with tool use
Tool use supports [fine-grained streaming](/docs/en/agents-and-tools/tool-use/fine-grained-tool-streaming) for parameter values. Enable it per tool with `eager_input_streaming`.
This request asks Claude to use a tool to report the weather.
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"tools": [
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
}
},
"required": ["location"]
}
}
],
"tool_choice": {"type": "any"},
"messages": [
{
"role": "user",
"content": "What is the weather like in San Francisco?"
}
],
"stream": true
}'
```
```bash CLI
ant messages create --stream --format jsonl <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
tools:
- name: get_weather
description: Get the current weather in a given location
input_schema:
type: object
properties:
location:
type: string
description: The city and state, e.g. San Francisco, CA
required:
- location
tool_choice:
type: any
messages:
- role: user
content: What is the weather like in San Francisco?
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
}
},
"required": ["location"],
},
}
]
with client.messages.stream(
model="claude-opus-4-7",
max_tokens=1024,
tools=tools,
tool_choice={"type": "any"},
messages=[
{"role": "user", "content": "What is the weather like in San Francisco?"}
],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "get_weather",
description: "Get the current weather in a given location",
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
}
},
required: ["location"]
}
}
];
const stream = client.messages.stream({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: tools,
tool_choice: { type: "any" },
messages: [
{
role: "user",
content: "What is the weather like in San Francisco?"
}
]
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
```
```csharp C#
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Tools = [
new ToolUnion(new Tool()
{
Name = "get_weather",
Description = "Get the current weather in a given location",
InputSchema = new InputSchema()
{
Properties = new Dictionary
{
["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "The city and state, e.g. San Francisco, CA" }),
},
Required = ["location"],
},
}),
],
ToolChoice = new ToolChoiceAny(),
Messages = [
new() { Role = Role.User, Content = "What is the weather like in San Francisco?" }
]
};
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
Console.Write(msg);
}
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Tools: []anthropic.ToolUnionParam{
{OfTool: &anthropic.ToolParam{
Name: "get_weather",
Description: anthropic.String("Get the current weather in a given location"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"location": map[string]any{
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
},
Required: []string{"location"},
},
}},
},
ToolChoice: anthropic.ToolChoiceUnionParam{OfAny: &anthropic.ToolChoiceAnyParam{}},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the weather like in San Francisco?")),
},
})
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)
}
}
```
```java Java hidelines={1..13,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.ToolChoice;
import com.anthropic.models.messages.ToolChoiceAny;
import com.anthropic.models.messages.Tool;
import com.anthropic.core.JsonValue;
import java.util.Map;
import java.util.List;
public class StreamingToolUse {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addTool(Tool.builder()
.name("get_weather")
.description("Get the current weather in a given location")
.inputSchema(Tool.InputSchema.builder()
.properties(JsonValue.from(Map.of(
"location", Map.of(
"type", "string",
"description", "The city and state, e.g. San Francisco, CA"
)
)))
.putAdditionalProperty("required", JsonValue.from(List.of("location")))
.build())
.build())
.toolChoice(ToolChoice.ofAny(ToolChoiceAny.builder().build()))
.addUserMessage("What is the weather like in San Francisco?")
.build();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
event.contentBlockDelta().ifPresent(deltaEvent ->
deltaEvent.delta().text().ifPresent(td ->
System.out.print(td.text())
)
);
});
}
}
}
```
```php PHP hidelines={1..4}
messages->createStream(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'What is the weather like in San Francisco?']
],
model: 'claude-opus-4-7',
toolChoice: ['type' => 'any'],
tools: [
[
'name' => 'get_weather',
'description' => 'Get the current weather in a given location',
'input_schema' => [
'type' => 'object',
'properties' => [
'location' => [
'type' => 'string',
'description' => 'The city and state, e.g. San Francisco, CA'
]
],
'required' => ['location']
]
]
],
);
foreach ($stream as $message) {
echo $message;
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
tools = [
{
name: "get_weather",
description: "Get the current weather in a given location",
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
}
},
required: ["location"]
}
}
]
stream = client.messages.stream(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: tools,
tool_choice: { type: "any" },
messages: [
{ role: "user", content: "What is the weather like in San Francisco?" }
]
)
stream.text.each { |text| print(text) }
```
```sse Response
event: message_start
data: {"type":"message_start","message":{"id":"msg_014p7gG3wDgGV9EUtLvnow3U","type":"message","role":"assistant","model":"claude-opus-4-7","stop_sequence":null,"usage":{"input_tokens":472,"output_tokens":2},"content":[],"stop_reason":null}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: ping
data: {"type": "ping"}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Okay"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":","}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" let"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'s"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" check"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" the"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" weather"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" for"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" San"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Francisco"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":","}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" CA"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":":"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_01T1x1fJ34qAmk2tNTrN7Up6","name":"get_weather","input":{}}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"location\":"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" \"San"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" Francisc"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"o,"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" CA\"}"}}
event: content_block_stop
data: {"type":"content_block_stop","index":1}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"output_tokens":89}}
event: message_stop
data: {"type":"message_stop"}
```
### Streaming request with extended thinking
This request enables extended thinking with streaming. The `display: "summarized"` setting streams a condensed summary of Claude's reasoning rather than the full chain of thought.
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 20000,
"stream": true,
"thinking": {
"type": "adaptive",
"display": "summarized"
},
"messages": [
{
"role": "user",
"content": "What is the greatest common divisor of 1071 and 462?"
}
]
}'
```
```bash CLI
ant messages create --stream --format jsonl \
--model claude-opus-4-7 \
--max-tokens 20000 \
--thinking '{type: adaptive, display: summarized}' \
--message '{role: user, content: What is the greatest common divisor of 1071 and 462?}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-opus-4-7",
max_tokens=20000,
thinking={"type": "adaptive", "display": "summarized"},
messages=[
{
"role": "user",
"content": "What is the greatest common divisor of 1071 and 462?",
}
],
) as stream:
for event in stream:
if event.type == "content_block_delta":
if event.delta.type == "thinking_delta":
print(event.delta.thinking, end="", flush=True)
elif event.delta.type == "text_delta":
print(event.delta.text, end="", flush=True)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const stream = client.messages.stream({
model: "claude-opus-4-7",
max_tokens: 20000,
thinking: { type: "adaptive", display: "summarized" },
messages: [
{
role: "user",
content: "What is the greatest common divisor of 1071 and 462?"
}
]
});
for await (const event of stream) {
if (event.type === "content_block_delta") {
if (event.delta.type === "thinking_delta") {
process.stdout.write(event.delta.thinking);
} else if (event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
}
```
```csharp C#
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 20000,
Thinking = new ThinkingConfigAdaptive { Display = Display.Summarized },
Messages = [new() { Role = Role.User, Content = "What is the greatest common divisor of 1071 and 462?" }]
};
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
Console.Write(msg);
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 20000,
Thinking: anthropic.ThinkingConfigParamUnion{
OfAdaptive: &anthropic.ThinkingConfigAdaptiveParam{
Display: anthropic.ThinkingConfigAdaptiveDisplaySummarized,
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the greatest common divisor of 1071 and 462?")),
},
})
for stream.Next() {
event := stream.Current()
switch eventVariant := event.AsAny().(type) {
case anthropic.ContentBlockDeltaEvent:
switch deltaVariant := eventVariant.Delta.AsAny().(type) {
case anthropic.ThinkingDelta:
fmt.Print(deltaVariant.Thinking)
case anthropic.TextDelta:
fmt.Print(deltaVariant.Text)
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
}
```
```java Java hidelines={1..7,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.ThinkingConfigAdaptive;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(20000L)
.thinking(ThinkingConfigAdaptive.builder()
.display(ThinkingConfigAdaptive.Display.SUMMARIZED)
.build())
.addUserMessage("What is the greatest common divisor of 1071 and 462?")
.build();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
event.contentBlockDelta().ifPresent(deltaEvent -> {
deltaEvent.delta().thinking().ifPresent(td ->
IO.print(td.thinking())
);
deltaEvent.delta().text().ifPresent(td ->
IO.print(td.text())
);
});
});
}
}
```
```php PHP hidelines={1..4}
messages->createStream(
maxTokens: 20000,
messages: [
['role' => 'user', 'content' => 'What is the greatest common divisor of 1071 and 462?']
],
model: 'claude-opus-4-7',
thinking: ['type' => 'adaptive', 'display' => 'summarized'],
);
foreach ($stream as $message) {
echo $message;
}
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
stream = client.messages.stream(
model: "claude-opus-4-7",
max_tokens: 20000,
thinking: { type: "adaptive", display: "summarized" },
messages: [
{ role: "user", content: "What is the greatest common divisor of 1071 and 462?" }
]
)
stream.each do |event|
if event.type == :content_block_delta
if event.delta.type == :thinking_delta
print(event.delta.thinking)
elsif event.delta.type == :text_delta
print(event.delta.text)
end
end
end
```
```sse Response
event: message_start
data: {"type": "message_start", "message": {"id": "msg_01...", "type": "message", "role": "assistant", "content": [], "model": "claude-opus-4-7", "stop_reason": null, "stop_sequence": null}}
event: content_block_start
data: {"type": "content_block_start", "index": 0, "content_block": {"type": "thinking", "thinking": "", "signature": ""}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "I need to find the GCD of 1071 and 462 using the Euclidean algorithm.\n\n1071 = 2 × 462 + 147"}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "\n462 = 3 × 147 + 21"}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "\n147 = 7 × 21 + 0"}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "\nThe remainder is 0, so GCD(1071, 462) = 21."}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "signature_delta", "signature": "EqQBCgIYAhIM1gbcDa9GJwZA2b3hGgxBdjrkzLoky3dl1pkiMOYds..."}}
event: content_block_stop
data: {"type": "content_block_stop", "index": 0}
event: content_block_start
data: {"type": "content_block_start", "index": 1, "content_block": {"type": "text", "text": ""}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 1, "delta": {"type": "text_delta", "text": "The greatest common divisor of 1071 and 462 is **21**."}}
event: content_block_stop
data: {"type": "content_block_stop", "index": 1}
event: message_delta
data: {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence": null}}
event: message_stop
data: {"type": "message_stop"}
```
### Streaming request with web search tool use
This request asks Claude to search the web for current weather information.
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"stream": true,
"tools": [
{
"type": "web_search_20250305",
"name": "web_search",
"max_uses": 5
}
],
"messages": [
{
"role": "user",
"content": "What is the weather like in New York City today?"
}
]
}'
```
```bash CLI
ant messages create --stream --format jsonl \
--model claude-opus-4-7 \
--max-tokens 1024 \
--tool '{type: web_search_20250305, name: web_search, max_uses: 5}' \
--message '{role: user, content: What is the weather like in New York City today?}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-opus-4-7",
max_tokens=1024,
tools=[{"type": "web_search_20250305", "name": "web_search", "max_uses": 5}],
messages=[
{"role": "user", "content": "What is the weather like in New York City today?"}
],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const stream = client.messages.stream({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [{ type: "web_search_20250305", name: "web_search", max_uses: 5 }],
messages: [{ role: "user", content: "What is the weather like in New York City today?" }]
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
```
```csharp C#
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Tools = [new ToolUnion(new WebSearchTool20250305() { MaxUses = 5 })],
Messages = [new() { Role = Role.User, Content = "What is the weather like in New York City today?" }]
};
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
Console.Write(msg);
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Tools: []anthropic.ToolUnionParam{
{
OfWebSearchTool20250305: &anthropic.WebSearchTool20250305Param{
MaxUses: anthropic.Int(5),
},
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the weather like in New York City today?")),
},
})
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)
}
}
```
```java Java hidelines={1..4,6..8,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.WebSearchTool20250305;
public class WebSearchStreaming {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addTool(WebSearchTool20250305.builder()
.maxUses(5L)
.build())
.addUserMessage("What is the weather like in New York City today?")
.build();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
event.contentBlockDelta().ifPresent(deltaEvent ->
deltaEvent.delta().text().ifPresent(td ->
System.out.print(td.text())
)
);
});
}
}
}
```
```php PHP hidelines={1..4}
messages->createStream(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'What is the weather like in New York City today?']
],
model: 'claude-opus-4-7',
tools: [
['type' => 'web_search_20250305', 'name' => 'web_search', 'max_uses' => 5]
],
);
foreach ($stream as $message) {
echo $message;
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
stream = client.messages.stream(
model: :"claude-opus-4-7",
max_tokens: 1024,
tools: [
{
type: "web_search_20250305",
name: "web_search",
max_uses: 5
}
],
messages: [
{
role: "user",
content: "What is the weather like in New York City today?"
}
]
)
stream.text.each { |text| print(text) }
```
```sse Response
event: message_start
data: {"type":"message_start","message":{"id":"msg_01G...","type":"message","role":"assistant","model":"claude-opus-4-7","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":2679,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":3}}}
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":"I'll check"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" the current weather in New York City for you"}}
event: ping
data: {"type": "ping"}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"."}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"server_tool_use","id":"srvtoolu_014hJH82Qum7Td6UV8gDXThB","name":"web_search","input":{}}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"query"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"\":"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" \"weather"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" NY"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"C to"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"day\"}"}}
event: content_block_stop
data: {"type":"content_block_stop","index":1 }
event: content_block_start
data: {"type":"content_block_start","index":2,"content_block":{"type":"web_search_tool_result","tool_use_id":"srvtoolu_014hJH82Qum7Td6UV8gDXThB","content":[{"type":"web_search_result","title":"Weather in New York City in May 2025 (New York) - detailed Weather Forecast for a month","url":"https://world-weather.info/forecast/usa/new_york/may-2025/","encrypted_content":"Ev0DCioIAxgCIiQ3NmU4ZmI4OC1k...","page_age":null},...]}}
event: content_block_stop
data: {"type":"content_block_stop","index":2}
event: content_block_start
data: {"type":"content_block_start","index":3,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":3,"delta":{"type":"text_delta","text":"Here's the current weather information for New York"}}
event: content_block_delta
data: {"type":"content_block_delta","index":3,"delta":{"type":"text_delta","text":" City:\n\n# Weather"}}
event: content_block_delta
data: {"type":"content_block_delta","index":3,"delta":{"type":"text_delta","text":" in New York City"}}
event: content_block_delta
data: {"type":"content_block_delta","index":3,"delta":{"type":"text_delta","text":"\n\n"}}
...
event: content_block_stop
data: {"type":"content_block_stop","index":17}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":10682,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":510,"server_tool_use":{"web_search_requests":1}}}
event: message_stop
data: {"type":"message_stop"}
```
## Error recovery
### Claude 4.5 and earlier
For Claude 4.5 models and earlier, you can recover a streaming request that was interrupted due to network issues, timeouts, or other errors by resuming from where the stream was interrupted. This approach saves you from re-processing the entire response.
The basic recovery strategy involves:
1. **Capture the partial response:** Save all content that was successfully received before the error occurred
2. **Construct a continuation request:** Create a new API request that includes the partial assistant response as the beginning of a new assistant message
3. **Resume streaming:** Continue receiving the rest of the response from where it was interrupted
### Claude 4.6 and later
For Claude 4.6 and later models, the same capture-and-resume strategy applies, but step 2 changes: instead of placing the partial response in an assistant message, add a user message that instructs the model to continue from where it left off.
1. **Capture the partial response:** Save all content that was successfully received before the error occurred
2. **Construct a continuation request:** Create a new API request with a user message containing the partial response and an instruction to continue, for example:
```text Sample prompt
Your previous response was interrupted and ended with [previous_response]. Continue from where you left off.
```
3. **Resume streaming:** Continue receiving the rest of the response from where it was interrupted
### Error recovery best practices
1. **Use SDK features:** Leverage the SDK's built-in message accumulation and error handling capabilities
2. **Handle content types:** Be aware that messages can contain multiple content blocks (`text`, `tool_use`, `thinking`). Tool use and extended thinking blocks cannot be partially recovered. You can resume streaming from the most recent text block.
---
# Streaming refusals
URL: https://platform.claude.com/docs/en/test-and-evaluate/strengthen-guardrails/handle-streaming-refusals
# Streaming refusals
---
Starting with Claude 4 models, streaming responses from Claude's API return **`stop_reason`: `"refusal"`** when streaming classifiers intervene to handle potential policy violations. This new safety feature helps maintain content compliance during real-time streaming.
To learn more about refusals triggered by API safety filters for Claude Sonnet 4.5, see [Understanding Sonnet 4.5's API Safety Filters](https://support.claude.com/en/articles/12449294-understanding-sonnet-4-5-s-api-safety-filters).
## API response format
When streaming classifiers detect content that violates Anthropic's policies, the API returns this response:
```json
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "Hello.."
}
],
"stop_reason": "refusal"
}
```
No additional refusal message is included. You must handle the response and provide appropriate user-facing messaging.
## Reset context after refusal
When you receive **`stop_reason`: `refusal`**, you must reset the conversation context before continuing. You can remove or rephrase the turn that triggered the refusal, or clear the conversation history entirely. Attempting to continue without resetting will result in continued refusals.
Usage metrics are still provided in the response for billing purposes, even when the response is refused.
You will be billed for output tokens up until the refusal.
If you encounter `refusal` stop reasons frequently while using Claude Sonnet 4.5 or Opus 4.1, you can try updating your API calls to use Haiku 4.5 (`claude-haiku-4-5-20251001`), which has different usage restrictions. Learn more about [understanding Sonnet 4.5's API safety filters](https://support.claude.com/en/articles/12449294-understanding-sonnet-4-5-s-api-safety-filters).
## Implementation guide
Here's how to detect and handle streaming refusals in your application:
```bash cURL
# Stream request and check for refusal
response=$(curl -N https://api.anthropic.com/v1/messages \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--data '{
"model": "claude-opus-4-7",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 1024,
"stream": true
}')
# Check for refusal in the stream
if echo "$response" | grep -q '"stop_reason":"refusal"'; then
echo "Response refused - resetting conversation context"
# Reset your conversation state here
fi
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
messages = []
def reset_conversation():
"""Reset conversation context after refusal"""
global messages
messages = []
print("Conversation reset due to refusal")
try:
with client.messages.stream(
max_tokens=1024,
messages=messages + [{"role": "user", "content": "Hello"}],
model="claude-opus-4-7",
) as stream:
for event in stream:
# Check for refusal in message delta
if event.type == "message_delta":
if event.delta.stop_reason == "refusal":
reset_conversation()
break
except Exception as e:
print(f"Error: {e}")
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
let messages: any[] = [];
function resetConversation() {
// Reset conversation context after refusal
messages = [];
console.log("Conversation reset due to refusal");
}
try {
const stream = await client.messages.stream({
messages: [...messages, { role: "user", content: "Hello" }],
model: "claude-opus-4-7",
max_tokens: 1024
});
for await (const event of stream) {
// Check for refusal in message delta
if (event.type === "message_delta" && event.delta.stop_reason === "refusal") {
resetConversation();
break;
}
}
} catch (error) {
console.error("Error:", error);
}
```
```csharp C# nocheck
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
private static List messages = new();
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello" }]
};
try
{
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
if (msg.Type == "message_delta" && msg.Delta?.StopReason == "refusal")
{
ResetConversation();
break;
}
}
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
private static void ResetConversation()
{
messages.Clear();
Console.WriteLine("Conversation reset due to refusal");
}
}
```
```go Go nocheck hidelines={1..10,17..18,-1..}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
var messages []anthropic.MessageParam
func resetConversation() {
messages = []anthropic.MessageParam{}
fmt.Println("Conversation reset due to refusal")
}
func main() {
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")),
},
})
streamLoop:
for stream.Next() {
event := stream.Current()
switch eventVariant := event.AsAny().(type) {
case anthropic.MessageDeltaEvent:
if eventVariant.Delta.StopReason == "refusal" {
resetConversation()
break streamLoop
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
}
```
```java Java hidelines={1..5,9..10}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.MessageParam;
import com.anthropic.models.messages.Model;
import com.anthropic.core.http.StreamResponse;
import com.anthropic.models.messages.RawMessageStreamEvent;
import com.anthropic.models.messages.StopReason;
import java.util.ArrayList;
import java.util.List;
List messages = new ArrayList<>();
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessage("Hello")
.build();
try (StreamResponse stream = client.messages().createStreaming(params)) {
stream.stream().forEach(event -> {
event.messageDelta().ifPresent(deltaEvent -> {
deltaEvent.delta().stopReason().ifPresent(stopReason -> {
if (stopReason.equals(StopReason.REFUSAL)) {
resetConversation();
}
});
});
});
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
void resetConversation() {
messages.clear();
IO.println("Conversation reset due to refusal");
}
```
```php PHP nocheck hidelines={1..4}
messages->createStream(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Hello']
],
model: 'claude-opus-4-7',
);
foreach ($stream as $event) {
if (isset($event->type) && $event->type === 'message_delta') {
if (isset($event->delta->stopReason) && $event->delta->stopReason === 'refusal') {
resetConversation($messages);
break;
}
}
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
messages = []
def reset_conversation(messages)
messages.clear
puts "Conversation reset due to refusal"
end
begin
stream = client.messages.stream(
model: :"claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }]
)
stream.each do |event|
if event.type == :message_delta && event.delta.stop_reason == :refusal
reset_conversation(messages)
break
end
end
rescue => e
puts "Error: #{e.message}"
end
```
## Current refusal types
The API currently handles refusals in three different ways:
| Refusal Type | Response Format | When It Occurs |
|-------------|----------------|----------------|
| Streaming classifier refusals | **`stop_reason`: `refusal`** | During streaming when content violates policies |
| API input and copyright validation | 400 error codes | When input fails validation checks |
| Model-generated refusals | Standard text responses | When the model itself decides to refuse |
Future API versions will expand the **`stop_reason`: `refusal`** pattern to unify refusal handling across all types.
## Best practices
- **Monitor for refusals**: Include **`stop_reason`: `refusal`** checks in your error handling
- **Reset automatically**: Implement automatic context reset when refusals are detected
- **Provide custom messaging**: Create user-friendly messages for better UX when refusals occur
- **Track refusal patterns**: Monitor refusal frequency to identify potential issues with your prompts
## Migration notes
- Future models will expand this pattern to other refusal types
- Plan your error handling to accommodate future unification of refusal responses
---
# Structured outputs
URL: https://platform.claude.com/docs/en/build-with-claude/structured-outputs
# Structured outputs
Get validated JSON results from agent workflows
---
Structured outputs constrain Claude's responses to follow a specific schema, ensuring valid, parseable output for downstream processing. Structured outputs provide two complementary features:
- **JSON outputs** (`output_config.format`): Get Claude's response in a specific JSON format
- **Strict tool use** (`strict: true`): Guarantee schema validation on tool names and inputs
You can use these features independently or together in the same request.
Structured outputs are generally available on the Claude API for [Claude Mythos Preview](https://anthropic.com/glasswing), Claude Opus 4.7, Claude Opus 4.6, Claude Sonnet 4.6, Claude Sonnet 4.5, Claude Opus 4.5, and Claude Haiku 4.5. On Amazon Bedrock, structured outputs are generally available for Claude Opus 4.6, Claude Sonnet 4.6, Claude Sonnet 4.5, Claude Opus 4.5, and Claude Haiku 4.5; Claude Opus 4.7 and Claude Mythos Preview are available through [Claude in Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock) (the Messages-API Bedrock endpoint). Structured outputs are available on [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws) and in beta on [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry). On [Vertex AI](/docs/en/build-with-claude/claude-on-vertex-ai), structured outputs are generally available for Claude Mythos Preview, Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6.
This feature qualifies for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention) with limited technical retention. See the [Data retention](#data-retention) section for details on what is retained and why.
**Migrating from beta?** The `output_format` parameter has moved to `output_config.format`, and beta headers are no longer required. The old beta header (`structured-outputs-2025-11-13`) and `output_format` parameter will continue working for a transition period. See the following code examples for the updated API shape.
## Why use structured outputs
Without structured outputs, Claude can generate malformed JSON responses or invalid tool inputs that break your applications. Even with careful prompting, you may encounter:
- Parsing errors from invalid JSON syntax
- Missing required fields
- Inconsistent data types
- Schema violations requiring error handling and retries
Structured outputs guarantee schema-compliant responses through constrained decoding:
- **Always valid:** No more `JSON.parse()` errors
- **Type safe:** Guaranteed field types and required fields
- **Reliable:** No retries needed for schema violations
## JSON outputs
JSON outputs control Claude's response format, ensuring Claude returns valid JSON matching your schema. Use JSON outputs when you need to:
- Control Claude's response format
- Extract data from images or text
- Generate structured reports
- Format API responses
### Quick start
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
}
],
"output_config": {
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"plan_interest": {"type": "string"},
"demo_requested": {"type": "boolean"}
},
"required": ["name", "email", "plan_interest", "demo_requested"],
"additionalProperties": false
}
}
}
}'
```
```bash CLI
ant messages create \
--transform 'content.0.text|@fromstr' \
--format jsonl <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content: >-
Extract the key information from this email: John Smith
(john@example.com) is interested in our Enterprise plan and wants
to schedule a demo for next Tuesday at 2pm.
output_config:
format:
type: json_schema
schema:
type: object
properties:
name: {type: string}
email: {type: string}
plan_interest: {type: string}
demo_requested: {type: boolean}
required: [name, email, plan_interest, demo_requested]
additionalProperties: false
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.",
}
],
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"plan_interest": {"type": "string"},
"demo_requested": {"type": "boolean"},
},
"required": ["name", "email", "plan_interest", "demo_requested"],
"additionalProperties": False,
},
}
},
)
print(response.content[0].text)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content:
"Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
}
],
output_config: {
format: {
type: "json_schema",
schema: {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string" },
plan_interest: { type: "string" },
demo_requested: { type: "boolean" }
},
required: ["name", "email", "plan_interest", "demo_requested"],
additionalProperties: false
}
}
}
});
for (const block of response.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
```csharp C#
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan." }],
OutputConfig = new OutputConfig
{
Format = new JsonOutputFormat
{
Schema = new Dictionary
{
["type"] = JsonSerializer.SerializeToElement("object"),
["properties"] = JsonSerializer.SerializeToElement(new
{
name = new { type = "string" },
email = new { type = "string" },
plan_interest = new { type = "string" },
demo_requested = new { type = "boolean" },
}),
["required"] = JsonSerializer.SerializeToElement(new[] { "name", "email", "plan_interest", "demo_requested" }),
["additionalProperties"] = JsonSerializer.SerializeToElement(false),
},
},
},
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..10,-1}
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, _ := client.Messages.New(context.Background(),
anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(
anthropic.NewTextBlock("Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan."),
),
},
OutputConfig: anthropic.OutputConfigParam{
Format: anthropic.JSONOutputFormatParam{
Schema: map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]string{"type": "string"},
"email": map[string]string{"type": "string"},
"plan_interest": map[string]string{"type": "string"},
"demo_requested": map[string]string{"type": "boolean"},
},
"required": []string{"name", "email", "plan_interest", "demo_requested"},
"additionalProperties": false,
},
},
},
})
fmt.Println(response.Content[0].Text)
}
```
```java Java hidelines={1..7}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.StructuredMessage;
import com.anthropic.models.messages.StructuredMessageCreateParams;
import com.anthropic.models.messages.Model;
static class ContactInfo {
public String name;
public String email;
public String plan_interest;
public boolean demo_requested;
}
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
StructuredMessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addUserMessage("Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan.")
.outputConfig(ContactInfo.class)
.build();
StructuredMessage response = client.messages().create(params);
ContactInfo contact = response.content().stream()
.flatMap(block -> block.text().stream())
.findFirst().orElseThrow().text();
IO.println(contact.name + " (" + contact.email + ")");
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => 'Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan.'
]
],
model: 'claude-opus-4-7',
outputConfig: [
'format' => [
'type' => 'json_schema',
'schema' => [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
'email' => ['type' => 'string'],
'plan_interest' => ['type' => 'string'],
'demo_requested' => ['type' => 'boolean']
],
'required' => ['name', 'email', 'plan_interest', 'demo_requested'],
'additionalProperties' => false
]
]
],
);
echo $response->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan."
}
],
output_config: {
format: {
type: "json_schema",
schema: {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string" },
plan_interest: { type: "string" },
demo_requested: { type: "boolean" }
},
required: ["name", "email", "plan_interest", "demo_requested"],
additionalProperties: false
}
}
}
)
puts response.content[0].text
```
**Response format:** Valid JSON matching your schema in `response.content[0].text`
```json Output
{
"name": "John Smith",
"email": "john@example.com",
"plan_interest": "Enterprise",
"demo_requested": true
}
```
### How it works
Create a JSON schema that describes the structure you want Claude to follow. The schema uses standard JSON Schema format with some limitations (see [JSON Schema limitations](#json-schema-limitations)).
Include the `output_config.format` parameter in your API request with `type: "json_schema"` and your schema definition.
Claude's response is valid JSON matching your schema, returned in `response.content[0].text`.
### Working with JSON outputs in SDKs
The SDKs provide helpers that make it easier to work with JSON outputs, including schema transformation, automatic validation, and integration with popular schema libraries.
The Python SDK's `client.messages.parse()` still accepts `output_format` as a convenience parameter and translates it to `output_config.format` internally. Other SDKs require `output_config` directly. The following examples show the SDK helper syntax.
#### Using native schema definitions
Instead of writing raw JSON schemas, you can use familiar schema definition tools in your language:
- **Python:** [Pydantic](https://docs.pydantic.dev/) models with `client.messages.parse()`
- **TypeScript:** [Zod](https://zod.dev/) schemas with `zodOutputFormat()` or typed JSON Schema literals with `jsonSchemaOutputFormat()`
- **Java:** Plain Java classes with automatic schema derivation through `outputConfig(Class)`
- **Ruby:** `Anthropic::BaseModel` classes with `output_config: {format: Model}`
- **PHP:** Classes implementing `StructuredOutputModel` with `outputConfig: ['format' => MyClass::class]`
- **CLI**, **C#**, **Go:** Raw JSON schemas passed through `output_config`
```bash CLI
{ read -r _ NAME; read -r _ EMAIL; } < <(
ant messages create \
--transform 'content.0.text|@fromstr|{name,email}' --format yaml <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content: >-
Extract the key information from this email: John Smith
(john@example.com) is interested in our Enterprise plan and wants
to schedule a demo for next Tuesday at 2pm.
output_config:
format:
type: json_schema
schema:
type: object
properties:
name: {type: string}
email: {type: string}
plan_interest: {type: string}
demo_requested: {type: boolean}
required: [name, email, plan_interest, demo_requested]
additionalProperties: false
YAML
)
printf '%s (%s)\n' "$NAME" "$EMAIL"
```
```python Python
from pydantic import BaseModel
from anthropic import Anthropic
class ContactInfo(BaseModel):
name: str
email: str
plan_interest: str
demo_requested: bool
client = Anthropic()
response = client.messages.parse(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.",
}
],
output_format=ContactInfo,
)
print(response.parsed_output)
```
```typescript TypeScript hidelines={1}
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
const ContactInfoSchema = z.object({
name: z.string(),
email: z.string(),
plan_interest: z.string(),
demo_requested: z.boolean()
});
const client = new Anthropic();
const response = await client.messages.parse({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content:
"Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
}
],
output_config: { format: zodOutputFormat(ContactInfoSchema) }
});
// Automatically parsed and validated
console.log(response.parsed_output);
```
```csharp C#
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
var client = new AnthropicClient();
var response = await client.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() {
Role = Role.User,
Content = "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
}],
OutputConfig = new OutputConfig
{
Format = new JsonOutputFormat
{
Schema = new Dictionary
{
["type"] = JsonSerializer.SerializeToElement("object"),
["properties"] = JsonSerializer.SerializeToElement(new
{
name = new { type = "string" },
email = new { type = "string" },
plan_interest = new { type = "string" },
demo_requested = new { type = "boolean" },
}),
["required"] = JsonSerializer.SerializeToElement(
new[] { "name", "email", "plan_interest", "demo_requested" }),
["additionalProperties"] = JsonSerializer.SerializeToElement(false),
},
},
},
});
if (response.Content[0].TryPickText(out var textBlock))
{
// JSON is guaranteed to match the schema
var contact = JsonSerializer.Deserialize>(textBlock.Text)!;
Console.WriteLine($"{contact["name"]} ({contact["email"]})");
}
```
```go Go hidelines={1..2,4..7,27..29,-1}
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/invopop/jsonschema"
)
type ContactInfo struct {
Name string `json:"name" jsonschema:"description=Full name"`
Email string `json:"email" jsonschema:"description=Email address"`
PlanInterest string `json:"plan_interest" jsonschema:"description=Plan type"`
DemoRequested bool `json:"demo_requested" jsonschema:"description=Whether a demo was requested"`
}
func generateSchema(v any) map[string]any {
r := jsonschema.Reflector{AllowAdditionalProperties: false, DoNotReference: true}
s := r.Reflect(v)
b, _ := json.Marshal(s)
var m map[string]any
json.Unmarshal(b, &m)
return m
}
func main() {
client := anthropic.NewClient()
schema := generateSchema(&ContactInfo{})
message, _ := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(
"Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.",
)),
},
OutputConfig: anthropic.OutputConfigParam{
Format: anthropic.JSONOutputFormatParam{
Schema: schema,
},
},
})
for _, block := range message.Content {
switch variant := block.AsAny().(type) {
case anthropic.TextBlock:
var contact ContactInfo
json.Unmarshal([]byte(variant.Text), &contact)
fmt.Printf("%s (%s)\n", contact.Name, contact.Email)
}
}
}
```
```java Java hidelines={1..7}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.StructuredMessage;
import com.anthropic.models.messages.StructuredMessageCreateParams;
import com.anthropic.models.messages.Model;
static class ContactInfo {
public String name;
public String email;
public String planInterest;
public boolean demoRequested;
}
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
StructuredMessageCreateParams createParams = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.outputConfig(ContactInfo.class)
.addUserMessage("Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.")
.build();
StructuredMessage response = client.messages().create(createParams);
ContactInfo contact = response.content().stream()
.flatMap(block -> block.text().stream())
.findFirst().orElseThrow().text();
IO.println(contact.name + " (" + contact.email + ")");
}
```
```php PHP hidelines={1..3}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.'],
],
model: 'claude-opus-4-7',
outputConfig: ['format' => ContactInfo::class],
);
$contact = $message->parsedOutput();
if ($contact instanceof ContactInfo) {
echo "{$contact->name} ({$contact->email})\n";
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
class ContactInfo < Anthropic::BaseModel
required :name, String
required :email, String
required :plan_interest, String
required :demo_requested, Anthropic::Boolean
end
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{
role: "user",
content: "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
}],
output_config: {format: ContactInfo}
)
contact = message.parsed_output
puts "#{contact.name} (#{contact.email})"
```
#### SDK-specific methods
Each SDK provides helpers that make working with structured outputs easier. See individual SDK pages for full details.
**Raw JSON schemas through heredoc body**
The CLI passes raw JSON schemas as a YAML heredoc body. Use the GJSON `@fromstr` modifier with `--transform` to parse the JSON string returned in `content[0].text` and project specific fields.
```bash
ant messages create \
--transform 'content.0.text|@fromstr|{name,email}' \
--format yaml <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content: >-
Extract contact info: John Smith, john@example.com,
interested in the Pro plan
output_config:
format:
type: json_schema
schema:
type: object
properties:
name: {type: string}
email: {type: string}
plan_interest: {type: string}
required: [name, email, plan_interest]
additionalProperties: false
YAML
```
```yaml Output
name: John Smith
email: john@example.com
```
**`client.messages.parse()` (Recommended)**
The `parse()` method automatically transforms your Pydantic model, validates the response, and returns a `parsed_output` attribute.
```python hidelines={2..4,9..12}
from pydantic import BaseModel
import anthropic
class ContactInfo(BaseModel):
name: str
email: str
plan_interest: str
client = anthropic.Anthropic()
response = client.messages.parse(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Extract contact info: John Smith, john@example.com, interested in the Pro plan",
}
],
output_format=ContactInfo,
)
# Access the parsed output directly
contact = response.parsed_output
print(contact.name, contact.email)
```
**`transform_schema()` helper**
For when you need to manually transform schemas before sending, or when you want to modify a Pydantic-generated schema. Unlike `client.messages.parse()`, which transforms provided schemas automatically, this gives you the transformed schema so you can further customize it.
```python nocheck
from anthropic import transform_schema
from pydantic import TypeAdapter
# First convert Pydantic model to JSON schema, then transform
schema = TypeAdapter(ContactInfo).json_schema()
schema = transform_schema(schema)
# Modify schema if needed
schema["properties"]["custom_field"] = {"type": "string"}
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{"role": "user", "content": "..."}],
output_config={
"format": {"type": "json_schema", "schema": schema},
},
)
```
**`client.messages.parse()` with `zodOutputFormat()`**
The `parse()` method accepts a Zod schema, validates the response, and returns a `parsed_output` attribute with the inferred TypeScript type matching the schema.
```typescript hidelines={1}
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
const ContactInfo = z.object({
name: z.string(),
email: z.string(),
planInterest: z.string()
});
const client = new Anthropic();
const response = await client.messages.parse({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: "Extract contact info: John Smith, john@example.com, interested in the Pro plan"
}
],
output_config: { format: zodOutputFormat(ContactInfo) }
});
// Guaranteed type-safe
console.log(response.parsed_output!.email);
```
**`client.messages.parse()` with `jsonSchemaOutputFormat()`**
The `jsonSchemaOutputFormat()` helper accepts a JSON Schema object and integrates it with `parse()` without requiring Zod. Zod is an optional peer dependency you install separately; `jsonSchemaOutputFormat()` works out of the box because the SDK bundles `json-schema-to-ts` directly.
For **inline schema literals** (declared with `as const` in your source), you also get compile-time type inference: `parsed_output` is typed to match the schema structure. For **imported or generated schemas** (from a JSON file or OpenAPI codegen), the helper still sends the schema and parses the response, but the inferred type is `unknown` because `as const` can only apply to literal expressions.
```typescript hidelines={1}
import Anthropic from "@anthropic-ai/sdk";
import { jsonSchemaOutputFormat } from "@anthropic-ai/sdk/helpers/json-schema";
const client = new Anthropic();
const response = await client.messages.parse({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: "Extract contact info: John Smith, john@example.com, interested in the Pro plan"
}
],
output_config: {
format: jsonSchemaOutputFormat({
type: "object",
properties: {
name: { type: "string" },
email: { type: "string" },
planInterest: { type: "string" }
},
required: ["name", "email", "planInterest"],
additionalProperties: false
} as const)
}
});
// response.parsed_output is typed as { name: string; email: string; planInterest: string } | null
console.log(response.parsed_output!.email);
```
**Type inference requires `as const`.** Use a literal object expression with a `const` assertion so TypeScript can narrow the property types. Without `as const`, the inferred type collapses to `unknown`.
**Schema transformation.** By default, the helper transforms the schema the same way `zodOutputFormat()` does: removing unsupported constraints, adding `additionalProperties: false` to objects, and filtering string formats. Pass `jsonSchemaOutputFormat(schema, { transform: false })` to send your schema to the API unchanged. See [How SDK transformation works](#how-sdk-transformation-works).
**Raw JSON schemas through `OutputConfig`**
The C# SDK uses raw JSON schemas built programmatically with `JsonSerializer.SerializeToElement`. Deserialize the response JSON with `JsonSerializer.Deserialize`.
```csharp
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
var client = new AnthropicClient();
var response = await client.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() {
Role = Role.User,
Content = "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan."
}],
OutputConfig = new OutputConfig
{
Format = new JsonOutputFormat
{
Schema = new Dictionary
{
["type"] = JsonSerializer.SerializeToElement("object"),
["properties"] = JsonSerializer.SerializeToElement(new
{
name = new { type = "string" },
email = new { type = "string" },
plan_interest = new { type = "string" },
}),
["required"] = JsonSerializer.SerializeToElement(
new[] { "name", "email", "plan_interest" }),
["additionalProperties"] = JsonSerializer.SerializeToElement(false),
},
},
},
});
if (response.Content[0].TryPickText(out var textBlock))
{
// JSON is guaranteed to match the schema
var contact = JsonSerializer.Deserialize>(textBlock.Text)!;
Console.WriteLine($"{contact["name"]} ({contact["email"]})");
}
```
**Raw JSON schemas through `OutputConfigParam`**
The Go SDK works with raw JSON schemas. Define a Go struct with json tags, generate the JSON schema (for example, using `invopop/jsonschema`), and unmarshal the response text into your struct.
```go hidelines={1..2,4..7,26..28,-1}
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/invopop/jsonschema"
)
type ContactInfo struct {
Name string `json:"name" jsonschema:"description=Full name"`
Email string `json:"email" jsonschema:"description=Email address"`
PlanInterest string `json:"plan_interest" jsonschema:"description=Plan type"`
}
func generateSchema(v any) map[string]any {
r := jsonschema.Reflector{AllowAdditionalProperties: false, DoNotReference: true}
s := r.Reflect(v)
b, _ := json.Marshal(s)
var m map[string]any
json.Unmarshal(b, &m)
return m
}
func main() {
client := anthropic.NewClient()
schema := generateSchema(&ContactInfo{})
message, _ := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(
"Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan.",
)),
},
OutputConfig: anthropic.OutputConfigParam{
Format: anthropic.JSONOutputFormatParam{
Schema: schema,
},
},
})
for _, block := range message.Content {
switch variant := block.AsAny().(type) {
case anthropic.TextBlock:
var contact ContactInfo
json.Unmarshal([]byte(variant.Text), &contact)
fmt.Printf("%s (%s)\n", contact.Name, contact.Email)
}
}
}
```
Java examples on this page use [JDK 25 compact source file](https://openjdk.org/jeps/512) syntax; see the [Java SDK requirements](/docs/en/api/sdks/java#requirements) for the substitution on earlier JDKs.
**`outputConfig(Class)` method**
Pass a Java class to `outputConfig()` and the SDK automatically derives a JSON schema, validates it, and returns a `StructuredMessageCreateParams`. Access the parsed result through `response.content().stream().flatMap(block -> block.text().stream()).findFirst().orElseThrow().text()`.
Declare your schema classes as top-level classes or `static` nested classes. This requirement comes from the Jackson Databind library (`com.fasterxml.jackson.databind`), which the SDK uses to deserialize JSON responses into your class instances and cannot instantiate non-static inner classes.
```java hidelines={1..7}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.StructuredMessage;
import com.anthropic.models.messages.StructuredMessageCreateParams;
import com.anthropic.models.messages.Model;
static class ContactInfo {
public String name;
public String email;
public String planInterest;
}
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
StructuredMessageCreateParams createParams = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.outputConfig(ContactInfo.class)
.addUserMessage("Extract contact info: John Smith, john@example.com, interested in the Pro plan")
.build();
StructuredMessage response = client.messages().create(createParams);
ContactInfo contact = response.content().stream()
.flatMap(block -> block.text().stream())
.findFirst().orElseThrow().text();
IO.println(contact.name + " (" + contact.email + ")");
}
```
Java retains generic type information for fields in the class's metadata, but generic type erasure applies in other scopes. While a JSON schema can be derived from a `BookList.books` field with type `List`, a valid JSON schema cannot be derived from a local variable of that same type.
If an error occurs while converting a JSON response to a Java class instance, the error message includes the JSON response to assist in diagnosis. If your JSON response may contain sensitive information, avoid logging it directly, or ensure that you redact any sensitive details from the error message.
Structured outputs support a [subset of the JSON Schema language](/docs/en/build-with-claude/structured-outputs#json-schema-limitations). The SDK generates schemas automatically from classes to align with this subset. The `outputConfig(Class)` method performs a validation check on the schema derived from the specified class.
Key points:
- **Local validation** occurs without sending requests to the remote AI model.
- **Remote validation** is also performed by the AI model upon receiving the JSON schema.
- **Version compatibility:** Local validation may fail while remote validation succeeds if the SDK version is outdated.
- **Disabling local validation:** Pass `JsonSchemaLocalValidation.NO` if you encounter compatibility issues:
```java hidelines={2..4}
import com.anthropic.core.JsonSchemaLocalValidation;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.StructuredMessageCreateParams;
import com.anthropic.models.messages.Model;
static class BookList {
public List books;
}
void main() {
StructuredMessageCreateParams createParams = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(2048)
.outputConfig(BookList.class, JsonSchemaLocalValidation.NO)
.addUserMessage("List some famous late twentieth century novels.")
.build();
}
```
Structured outputs also work with streaming. As responses arrive in stream events, you need to accumulate the full response before deserializing the JSON.
Use `MessageAccumulator` to collect the JSON strings from the stream. Once accumulated, call `MessageAccumulator.message(Class)` to convert the accumulated `Message` into a `StructuredMessage`, which automatically deserializes the JSON into your Java class.
When the SDK derives a JSON schema from your Java classes, it includes all properties represented by `public` fields or `public` getter methods by default and excludes non-`public` fields and getter methods.
You can control visibility with annotations:
- `@JsonIgnore` excludes a `public` field or getter method
- `@JsonProperty` includes a non-`public` field or getter method
If you define `private` fields with `public` getter methods, the SDK derives the property name from the getter (for example, `private` field `myValue` with `public` method `getMyValue()` produces a `"myValue"` property). To use a non-conventional getter name, annotate the method with `@JsonProperty`.
Each class must define at least one property for the JSON schema. A validation error occurs if no fields or getter methods can produce schema properties, such as when:
- There are no fields or getter methods in the class
- All `public` members are annotated with `@JsonIgnore`
- All non-`public` members lack `@JsonProperty` annotations
- A field uses a `Map` type, which produces an empty `"properties"` field
Your Java classes can use composition and inheritance to share structure when defining JSON schemas. Each pattern affects the output structure differently.
**Composition** produces nested JSON output. Deriving a schema from class `Composed` that composes `A` and `B`:
```java hidelines={1..7,20..35}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.StructuredMessage;
import com.anthropic.models.messages.StructuredMessageCreateParams;
static class A {
public String a;
}
static class B {
public String b;
}
static class Composed {
public A composedA;
public B composedB;
}
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
StructuredMessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.outputConfig(Composed.class)
.addUserMessage("Populate field a with 'hello' and field b with 'world'.")
.build();
StructuredMessage response = client.messages().create(params);
Composed result = response.content().stream()
.flatMap(block -> block.text().stream())
.findFirst().orElseThrow().text();
IO.println("composedA.a=" + result.composedA.a);
IO.println("composedB.b=" + result.composedB.b);
}
```
The JSON output has this nested structure:
```json
{
"composedA": { "a": "hello" },
"composedB": { "b": "world" }
}
```
**Inheritance** produces flat JSON output. Deriving a schema from class `Derived` that extends `Base`:
```java hidelines={1..7,15..30}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.StructuredMessage;
import com.anthropic.models.messages.StructuredMessageCreateParams;
static class Base {
public String a;
}
static class Derived extends Base {
public String b;
}
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
StructuredMessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.outputConfig(Derived.class)
.addUserMessage("Populate field a with 'hello' and field b with 'world'.")
.build();
StructuredMessage response = client.messages().create(params);
Derived result = response.content().stream()
.flatMap(block -> block.text().stream())
.findFirst().orElseThrow().text();
IO.println("a=" + result.a);
IO.println("b=" + result.b);
}
```
The JSON output has this flat structure:
```json
{
"a": "hello",
"b": "world"
}
```
You can use Jackson Databind annotations to enrich the JSON schema derived from your Java classes:
```java hidelines={-2..}
import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
static class Person {
@JsonPropertyDescription("The first name and surname of the person")
public String name;
public int birthYear;
@JsonPropertyDescription("The year the person died, or 'present' if the person is living.")
public String deathYear;
}
@JsonClassDescription("The details of one published book")
static class Book {
public String title;
public Person author;
@JsonPropertyDescription("The year in which the book was first published.")
public int publicationYear;
@JsonIgnore
public String genre;
}
static class BookList {
public List books;
}
void main() {}
```
Annotation summary:
- `@JsonClassDescription`: Add a description to a class
- `@JsonPropertyDescription`: Add a description to a field or getter method
- `@JsonIgnore`: Exclude a `public` field or getter from the schema
- `@JsonProperty`: Include a non-`public` field or getter in the schema
If you use `@JsonProperty(required = false)`, the SDK ignores the `false` value. Class-derived schemas always mark all properties as required.
You can also use Swagger Core (OpenAPI 3) `@Schema` and `@ArraySchema` annotations for type-specific constraints:
```java hidelines={-2..}
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
static class Article {
@ArraySchema(minItems = 1)
public List authors;
public String title;
@Schema(format = "date")
public String publicationDate;
public int pageCount;
}
void main() {}
```
Local validation checks that you haven't used any unsupported constraint keywords, but constraint values aren't validated locally. For example, an unsupported `"format"` value may pass local validation but cause a remote error.
If you use both Jackson and Swagger annotations to set the same schema field, the Jackson annotation takes precedence.
Class-based schema derivation is the most convenient path, but for direct control over the schema structure you can build a `JsonOutputFormat.Schema` manually and wrap it in an `OutputConfig`.
```java hidelines={1..2,5..6}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.JsonOutputFormat;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.OutputConfig;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
JsonOutputFormat.Schema schema = JsonOutputFormat.Schema.builder()
.putAdditionalProperty("type", JsonValue.from("object"))
.putAdditionalProperty("properties", JsonValue.from(Map.of(
"name", Map.of("type", "string"),
"email", Map.of("type", "string"),
"plan_interest", Map.of("type", "string"))))
.putAdditionalProperty("required", JsonValue.from(
List.of("name", "email", "plan_interest")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build();
OutputConfig outputConfig = OutputConfig.builder()
.format(JsonOutputFormat.builder().schema(schema).build())
.build();
MessageCreateParams createParams = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.outputConfig(outputConfig)
.addUserMessage(
"John Smith (john@example.com) is interested in our Enterprise plan.")
.build();
client.messages().create(createParams).content().stream()
.flatMap(contentBlock -> contentBlock.text().stream())
.forEach(textBlock -> IO.println(textBlock.text()));
}
```
For a more extensive example that builds a nested schema with arrays and descriptions, see [`StructuredOutputsRawExample.java`](https://github.com/anthropics/anthropic-sdk-java/blob/main/anthropic-java-example/src/main/java/com/anthropic/example/StructuredOutputsRawExample.java) in the SDK repository.
**Classes through the `StructuredOutputModel` interface**
Define a PHP class implementing `StructuredOutputModel` (using `StructuredOutputModelTrait`) and pass the class name to `outputConfig: ['format' => MyClass::class]`. The SDK derives a JSON schema from your native PHP 8 property types and returns a typed instance through `$message->parsedOutput()`.
`parsedOutput()` returns your model instance on success, or `null` (or an error array) if parsing fails. Use `instanceof` to narrow the type before accessing fields.
```php hidelines={1..3}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan.'],
],
model: 'claude-opus-4-7',
outputConfig: ['format' => ContactInfo::class],
);
$contact = $message->parsedOutput();
if ($contact instanceof ContactInfo) {
echo "{$contact->name} ({$contact->email})\n";
}
```
The SDK maps native PHP 8 property types to JSON Schema:
| PHP type | JSON Schema |
|---|---|
| `string` | `"string"` |
| `int` | `"integer"` |
| `float` | `"number"` |
| `bool` | `"boolean"` |
| `array` | `"array"` (see the following note) |
| `?type` (nullable) | Optional field |
| Class implementing `StructuredOutputModel` | Nested object |
For `array` properties, the SDK adds an `items` schema only when the element type is a nested `StructuredOutputModel`, declared with `#[Constrained(itemClass: MyModel::class)]` or a `/** @var MyModel[] */` docblock. Arrays of scalars (`string[]`, `int[]`) emit an unconstrained `{"type":"array"}`.
All non-nullable properties become required fields.
Add constraints with the `#[Constrained]` attribute:
```php hidelines={..2} highlight={3}
For schemas that PHP type hints can't express, pass a raw associative array through `OutputConfig::with()`. This path skips the `parsedOutput()` helper; decode the response with `json_decode()`:
```php hidelines={1..3}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan.'],
],
model: 'claude-opus-4-7',
outputConfig: OutputConfig::with(format: JSONOutputFormat::with(schema: [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
'email' => ['type' => 'string'],
'plan_interest' => ['type' => 'string'],
],
'required' => ['name', 'email', 'plan_interest'],
'additionalProperties' => false,
])),
);
$contact = json_decode($message->content[0]->text, associative: true);
echo "{$contact['name']} ({$contact['email']})\n";
```
**`output_config: {format: Model}` with `parsed_output`**
Define a model class extending `Anthropic::BaseModel` and pass it as the format to `messages.create()`. The response includes a `parsed_output` attribute with a typed Ruby object.
```ruby hidelines={1..2}
require "anthropic"
class ContactInfo < Anthropic::BaseModel
required :name, String
required :email, String
required :plan_interest, String
end
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: "Extract contact info: John Smith, john@example.com, interested in the Pro plan"
}
],
output_config: {format: ContactInfo}
)
contact = message.parsed_output
puts "#{contact.name} (#{contact.email})"
```
The Ruby SDK supports additional model definition features for richer schemas:
- **`doc:` keyword:** Add descriptions to fields for more informative schema output
- **`Anthropic::ArrayOf[T]`:** Typed arrays. Pass array-level constraints (`min_items:`, `max_items:`) as keywords on `required`/`optional`, not on `ArrayOf` itself
- **`Anthropic::EnumOf[:a, :b]`:** Enum fields with constrained values
- **`Anthropic::UnionOf[T1, T2]`:** Union types mapped to `anyOf`
```ruby
class FamousNumber < Anthropic::BaseModel
required :value, Float
optional :reason, String, doc: "why is this number mathematically significant?"
end
class Output < Anthropic::BaseModel
required :numbers, Anthropic::ArrayOf[FamousNumber], min_items: 3, max_items: 5
end
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{role: "user", content: "give me some famous numbers"}],
output_config: {format: Output}
)
message.parsed_output
# => #
#### How SDK transformation works
The Python, TypeScript, Ruby, and PHP SDKs automatically transform schemas with unsupported features:
1. **Remove unsupported constraints** (for example, `minimum`, `maximum`, `minLength`, `maxLength`)
2. **Update descriptions** with constraint info (for example, "Must be at least 100"), when the constraint is not directly supported with structured outputs
3. **Add `additionalProperties: false`** to all objects
4. **Filter string formats** to supported list only
5. **Validate responses** against your original schema (with all constraints)
This means Claude receives a simplified schema, but your code still enforces all constraints through validation.
**Example:** A Pydantic field with `minimum: 100` becomes a plain integer in the sent schema, but the SDK updates the description to "Must be at least 100" and validates the response against the original constraint.
### Common use cases
Extract structured data from unstructured text:
```bash CLI
ant messages create \
--transform 'content.0.text|@fromstr' --format jsonl <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: "Extract invoice data from: Invoice #12345, Date: 2024-01-15, Total: $500.00"
output_config:
format:
type: json_schema
schema:
type: object
properties:
invoice_number: {type: string}
date: {type: string}
total_amount: {type: number}
line_items:
type: array
items: {type: object, additionalProperties: false}
customer_name: {type: string}
required: [invoice_number, date, total_amount, line_items, customer_name]
additionalProperties: false
YAML
```
```python Python hidelines={1}
import anthropic
from pydantic import BaseModel
class Invoice(BaseModel):
invoice_number: str
date: str
total_amount: float
line_items: list[dict]
customer_name: str
client = anthropic.Anthropic()
invoice_text = "Invoice #12345, Date: 2024-01-15, Total: $500.00"
response = client.messages.parse(
model="claude-opus-4-7",
max_tokens=4096,
output_format=Invoice,
messages=[
{"role": "user", "content": f"Extract invoice data from: {invoice_text}"}
],
)
print(response.parsed_output)
```
```typescript TypeScript hidelines={1}
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
const client = new Anthropic();
const InvoiceSchema = z.object({
invoice_number: z.string(),
date: z.string(),
total_amount: z.number(),
line_items: z.array(z.record(z.string(), z.any())),
customer_name: z.string()
});
const invoiceText = "Invoice #12345, Date: 2024-01-15, Total: $500.00";
const response = await client.messages.parse({
model: "claude-opus-4-7",
max_tokens: 4096,
output_config: { format: zodOutputFormat(InvoiceSchema) },
messages: [{ role: "user", content: `Extract invoice data from: ${invoiceText}` }]
});
console.log(response.parsed_output);
```
```csharp C# hidelines={1..4}
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
string invoiceText = "Invoice #12345, Date: 2024-01-15, Total: $500.00";
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
OutputConfig = new OutputConfig
{
Format = new JsonOutputFormat
{
Schema = new Dictionary
{
["type"] = JsonSerializer.SerializeToElement("object"),
["properties"] = JsonSerializer.SerializeToElement(new
{
invoice_number = new { type = "string" },
date = new { type = "string" },
total_amount = new { type = "number" },
line_items = new
{
type = "array",
items = new
{
type = "object",
additionalProperties = false,
},
},
customer_name = new { type = "string" },
}),
["required"] = JsonSerializer.SerializeToElement(new[] { "invoice_number", "date", "total_amount", "line_items", "customer_name" }),
["additionalProperties"] = JsonSerializer.SerializeToElement(false),
},
},
},
Messages = [new() { Role = Role.User, Content = $"Extract invoice data from: {invoiceText}" }]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
invoiceText := "Invoice #12345, Date: 2024-01-15, Total: $500.00"
schema := map[string]any{
"type": "object",
"additionalProperties": false,
"properties": map[string]any{
"invoice_number": map[string]any{"type": "string"},
"date": map[string]any{"type": "string"},
"total_amount": map[string]any{"type": "number"},
"line_items": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"additionalProperties": false,
"properties": map[string]any{
"description": map[string]any{"type": "string"},
"quantity": map[string]any{"type": "number"},
"unit_price": map[string]any{"type": "number"},
},
"required": []string{"description", "quantity", "unit_price"},
},
},
"customer_name": map[string]any{"type": "string"},
},
"required": []string{"invoice_number", "date", "total_amount", "line_items", "customer_name"},
}
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
OutputConfig: anthropic.OutputConfigParam{
Format: anthropic.JSONOutputFormatParam{
Schema: schema,
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(fmt.Sprintf("Extract invoice data from: %s", invoiceText))),
},
})
if err != nil {
log.Fatal(err)
}
for _, block := range response.Content {
switch variant := block.AsAny().(type) {
case anthropic.TextBlock:
fmt.Println(variant.Text)
}
}
}
```
```java Java hidelines={1..6}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.StructuredMessage;
import com.anthropic.models.messages.StructuredMessageCreateParams;
import com.anthropic.models.messages.Model;
import com.fasterxml.jackson.annotation.JsonProperty;
static class LineItem {
@JsonProperty("description")
public String description;
@JsonProperty("quantity")
public int quantity;
@JsonProperty("unit_price")
public double unitPrice;
}
static class Invoice {
@JsonProperty("invoice_number")
public String invoiceNumber;
@JsonProperty("date")
public String date;
@JsonProperty("total_amount")
public double totalAmount;
@JsonProperty("line_items")
public List lineItems;
@JsonProperty("customer_name")
public String customerName;
}
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
String invoiceText = "Invoice #12345, Date: 2024-01-15, Total: $500.00";
StructuredMessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(4096L)
.outputConfig(Invoice.class)
.addUserMessage("Extract invoice data from: " + invoiceText)
.build();
StructuredMessage response = client.messages().create(params);
Invoice invoice = response.content().stream()
.flatMap(block -> block.text().stream())
.findFirst().orElseThrow().text();
IO.println(invoice.invoiceNumber + ": $" + invoice.totalAmount);
}
```
```php PHP hidelines={1..3}
messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => "Extract invoice data from: $invoiceText"]
],
model: 'claude-opus-4-7',
outputConfig: ['format' => Invoice::class],
);
$invoice = $message->parsedOutput();
if ($invoice instanceof Invoice) {
echo "Invoice {$invoice->invoice_number}: \${$invoice->total_amount}\n";
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
class LineItem < Anthropic::BaseModel
required :description, String
required :amount, Float
end
class Invoice < Anthropic::BaseModel
required :invoice_number, String
required :date, String
required :total_amount, Float
required :line_items, Anthropic::ArrayOf[LineItem]
required :customer_name, String
end
invoice_text = "Invoice #12345, Date: 2024-01-15, Total: $500.00"
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
output_config: {format: Invoice},
messages: [
{role: "user", content: "Extract invoice data from: #{invoice_text}"}
]
)
invoice = message.parsed_output
puts "Invoice #{invoice.invoice_number}: $#{invoice.total_amount}"
```
Classify content with structured categories:
```bash CLI
ant messages create \
--transform 'content.0.text|@fromstr' --format jsonl <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content: "Classify this feedback: Great product, fast shipping!"
output_config:
format:
type: json_schema
schema:
type: object
properties:
category:
type: string
confidence:
type: number
tags:
type: array
items:
type: string
sentiment:
type: string
required:
- category
- confidence
- tags
- sentiment
additionalProperties: false
YAML
```
```python Python hidelines={1}
from anthropic import Anthropic
from pydantic import BaseModel
client = Anthropic()
class Classification(BaseModel):
category: str
confidence: float
tags: list[str]
sentiment: str
feedback_text = "Great product, but the delivery was slow."
response = client.messages.parse(
model="claude-opus-4-7",
max_tokens=1024,
output_format=Classification,
messages=[{"role": "user", "content": f"Classify this feedback: {feedback_text}"}],
)
print(response.parsed_output)
```
```typescript TypeScript hidelines={1}
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
const client = new Anthropic();
const ClassificationSchema = z.object({
category: z.string(),
confidence: z.number(),
tags: z.array(z.string()),
sentiment: z.string()
});
const feedbackText = "Great product, but the delivery was slow.";
const response = await client.messages.parse({
model: "claude-opus-4-7",
max_tokens: 1024,
output_config: { format: zodOutputFormat(ClassificationSchema) },
messages: [{ role: "user", content: `Classify this feedback: ${feedbackText}` }]
});
console.log(response.parsed_output);
```
```csharp C# hidelines={1..6}
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
string feedbackText = "Great product, fast shipping!";
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = $"Classify this feedback: {feedbackText}" }],
OutputConfig = new OutputConfig
{
Format = new JsonOutputFormat
{
Schema = new Dictionary
{
["type"] = JsonSerializer.SerializeToElement("object"),
["properties"] = JsonSerializer.SerializeToElement(new
{
category = new { type = "string" },
confidence = new { type = "number" },
tags = new { type = "array", items = new { type = "string" } },
sentiment = new { type = "string" },
}),
["required"] = JsonSerializer.SerializeToElement(new[] { "category", "confidence", "tags", "sentiment" }),
["additionalProperties"] = JsonSerializer.SerializeToElement(false),
},
},
},
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..14,-1}
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
feedbackText := "Great product, fast shipping!"
schema := map[string]any{
"type": "object",
"properties": map[string]any{
"category": map[string]any{"type": "string"},
"confidence": map[string]any{"type": "number"},
"tags": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
"sentiment": map[string]any{"type": "string"},
},
"required": []string{"category", "confidence", "tags", "sentiment"},
"additionalProperties": false,
}
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
OutputConfig: anthropic.OutputConfigParam{
Format: anthropic.JSONOutputFormatParam{
Schema: schema,
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(fmt.Sprintf("Classify this feedback: %s", feedbackText))),
},
})
if err != nil {
log.Fatal(err)
}
for _, block := range response.Content {
switch variant := block.AsAny().(type) {
case anthropic.TextBlock:
var result map[string]any
json.Unmarshal([]byte(variant.Text), &result)
fmt.Println(result)
}
}
}
```
```java Java hidelines={1..6}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.StructuredMessage;
import com.anthropic.models.messages.StructuredMessageCreateParams;
import com.anthropic.models.messages.Model;
import com.fasterxml.jackson.annotation.JsonProperty;
static class Classification {
@JsonProperty("category")
public String category;
@JsonProperty("confidence")
public double confidence;
@JsonProperty("tags")
public List tags;
@JsonProperty("sentiment")
public String sentiment;
}
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
String feedbackText = "Great product, fast shipping!";
StructuredMessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.outputConfig(Classification.class)
.addUserMessage("Classify this feedback: " + feedbackText)
.build();
StructuredMessage response = client.messages().create(params);
Classification result = response.content().stream()
.flatMap(block -> block.text().stream())
.findFirst().orElseThrow().text();
IO.println(result.category + " (" + result.confidence + ")");
}
```
```php PHP hidelines={1..3}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => "Classify this feedback: {$feedbackText}"]
],
model: 'claude-opus-4-7',
outputConfig: ['format' => Classification::class],
);
$result = $message->parsedOutput();
if ($result instanceof Classification) {
echo "{$result->category} ({$result->confidence}): {$result->sentiment}\n";
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
class Classification < Anthropic::BaseModel
required :category, String
required :confidence, Float
required :tags, Anthropic::ArrayOf[String]
required :sentiment, String
end
feedback_text = "Great product, fast shipping!"
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
output_config: {format: Classification},
messages: [
{role: "user", content: "Classify this feedback: #{feedback_text}"}
]
)
puts message.parsed_output
```
Generate API-ready responses:
```bash CLI
ant messages create \
--transform 'content.0.text' --raw-output <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
output_config:
format:
type: json_schema
schema:
type: object
properties:
status:
type: string
data:
type: object
additionalProperties: false
errors:
type: array
items:
type: object
additionalProperties: false
metadata:
type: object
additionalProperties: false
required:
- status
- data
- metadata
additionalProperties: false
messages:
- role: user
content: "Process this request: ..."
YAML
```
```python Python hidelines={1}
from anthropic import Anthropic
from pydantic import BaseModel
client = Anthropic()
class APIResponse(BaseModel):
status: str
data: dict
errors: list[dict] | None
metadata: dict
response = client.messages.parse(
model="claude-opus-4-7",
max_tokens=1024,
output_format=APIResponse,
messages=[{"role": "user", "content": "Process this request: ..."}],
)
print(response.parsed_output)
```
```typescript TypeScript hidelines={1}
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
const client = new Anthropic();
const APIResponseSchema = z.object({
status: z.string(),
data: z.record(z.string(), z.any()),
errors: z.array(z.record(z.string(), z.any())).optional(),
metadata: z.record(z.string(), z.any())
});
const response = await client.messages.parse({
model: "claude-opus-4-7",
max_tokens: 1024,
output_config: { format: zodOutputFormat(APIResponseSchema) },
messages: [{ role: "user", content: "Process this request..." }]
});
console.log(response.parsed_output);
```
```csharp C# hidelines={1..6}
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Process this request: ..." }],
OutputConfig = new OutputConfig
{
Format = new JsonOutputFormat
{
Schema = new Dictionary
{
["type"] = JsonSerializer.SerializeToElement("object"),
["properties"] = JsonSerializer.SerializeToElement(new
{
status = new { type = "string" },
data = new { type = "object", additionalProperties = false },
errors = new
{
type = "array",
items = new { type = "object", additionalProperties = false },
},
metadata = new { type = "object", additionalProperties = false },
}),
["required"] = JsonSerializer.SerializeToElement(new[] { "status", "data", "metadata" }),
["additionalProperties"] = JsonSerializer.SerializeToElement(false),
},
},
},
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
OutputConfig: anthropic.OutputConfigParam{
Format: anthropic.JSONOutputFormatParam{
Schema: map[string]any{
"type": "object",
"additionalProperties": false,
"properties": map[string]any{
"status": map[string]any{
"type": "string",
},
"data": map[string]any{
"type": "object",
"additionalProperties": false,
},
"errors": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"additionalProperties": false,
},
},
"metadata": map[string]any{
"type": "object",
"additionalProperties": false,
},
},
"required": []string{"status", "data", "metadata"},
},
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Process this request: ...")),
},
})
if err != nil {
log.Fatal(err)
}
for _, block := range response.Content {
switch variant := block.AsAny().(type) {
case anthropic.TextBlock:
fmt.Println(variant.Text)
}
}
}
```
```java Java hidelines={1..6}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.StructuredMessage;
import com.anthropic.models.messages.StructuredMessageCreateParams;
import com.anthropic.models.messages.Model;
import com.fasterxml.jackson.annotation.JsonProperty;
static class APIData {
@JsonProperty("message")
public String message;
@JsonProperty("resource_id")
public String resourceId;
}
static class APIError {
@JsonProperty("code")
public String code;
@JsonProperty("message")
public String message;
}
static class APIMetadata {
@JsonProperty("request_id")
public String requestId;
@JsonProperty("timestamp")
public String timestamp;
}
static class APIResponse {
@JsonProperty("status")
public String status;
@JsonProperty("data")
public APIData data;
@JsonProperty("errors")
public List errors;
@JsonProperty("metadata")
public APIMetadata metadata;
}
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
StructuredMessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.outputConfig(APIResponse.class)
.addUserMessage("Process this request: ...")
.build();
StructuredMessage response = client.messages().create(params);
APIResponse result = response.content().stream()
.flatMap(block -> block.text().stream())
.findFirst().orElseThrow().text();
IO.println(result.status);
}
```
```php PHP hidelines={1..3}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Process this request: ...']
],
model: 'claude-opus-4-7',
outputConfig: ['format' => APIResponse::class],
);
$result = $message->parsedOutput();
if ($result instanceof APIResponse) {
echo "{$result->status}: {$result->data->message}\n";
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
class Payload < Anthropic::BaseModel
required :message, String
end
class APIError < Anthropic::BaseModel
required :code, String
required :detail, String
end
class Metadata < Anthropic::BaseModel
required :request_id, String
end
class APIResponse < Anthropic::BaseModel
required :status, String
required :data, Payload
optional :errors, Anthropic::ArrayOf[APIError]
required :metadata, Metadata
end
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
output_config: {format: APIResponse},
messages: [
{role: "user", content: "Process this request: ..."}
]
)
puts message.parsed_output
```
## Strict tool use
For enforcing JSON Schema compliance on tool inputs with grammar-constrained sampling, see [Strict tool use](/docs/en/agents-and-tools/tool-use/strict-tool-use).
## Using both features together
JSON outputs and strict tool use solve different problems and work together:
- **JSON outputs** control Claude's response format (what Claude says)
- **Strict tool use** validates tool parameters (how Claude calls your functions)
When combined, Claude can call tools with guaranteed-valid parameters AND return structured JSON responses. This is useful for agentic workflows where you need both reliable tool calls and structured final outputs.
```bash CLI nocheck
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content: Help me plan a trip to Paris departing May 15, 2026
# JSON outputs: structured response format
output_config:
format:
type: json_schema
schema:
type: object
properties:
summary:
type: string
next_steps:
type: array
items:
type: string
required: [summary, next_steps]
additionalProperties: false
# Strict tool use: guaranteed tool parameters
tools:
- name: search_flights
strict: true
input_schema:
type: object
properties:
destination:
type: string
date:
type: string
format: date
required: [destination, date]
additionalProperties: false
YAML
```
```python Python hidelines={1..4}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Help me plan a trip to Paris departing May 15, 2026",
}
],
# JSON outputs: structured response format
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"summary": {"type": "string"},
"next_steps": {"type": "array", "items": {"type": "string"}},
},
"required": ["summary", "next_steps"],
"additionalProperties": False,
},
}
},
# Strict tool use: guaranteed tool parameters
tools=[
{
"name": "search_flights",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"destination": {"type": "string"},
"date": {"type": "string", "format": "date"},
},
"required": ["destination", "date"],
"additionalProperties": False,
},
}
],
)
print(response)
```
```typescript TypeScript hidelines={1..4}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Help me plan a trip to Paris departing May 15, 2026" }],
// JSON outputs: structured response format
output_config: {
format: {
type: "json_schema",
schema: {
type: "object",
properties: {
summary: { type: "string" },
next_steps: { type: "array", items: { type: "string" } }
},
required: ["summary", "next_steps"],
additionalProperties: false
}
}
},
// Strict tool use: guaranteed tool parameters
tools: [
{
name: "search_flights",
description: "Search for available flights to a destination on a specific date",
strict: true,
input_schema: {
type: "object",
properties: {
destination: { type: "string" },
date: { type: "string", format: "date" }
},
required: ["destination", "date"],
additionalProperties: false
}
}
]
});
// Claude may call the tool first (tool_use) or respond with JSON (text)
console.log("Stop reason:", response.stop_reason);
for (const block of response.content) {
if (block.type === "tool_use") {
console.log(`Tool call: ${block.name}(${JSON.stringify(block.input)})`);
} else if (block.type === "text") {
console.log("Response:", block.text);
}
}
```
```csharp C# hidelines={1..6}
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Help me plan a trip to Paris departing May 15, 2026" }],
// JSON outputs: structured response format
OutputConfig = new OutputConfig
{
Format = new JsonOutputFormat
{
Schema = new Dictionary
{
["type"] = JsonSerializer.SerializeToElement("object"),
["properties"] = JsonSerializer.SerializeToElement(new
{
summary = new { type = "string" },
next_steps = new { type = "array", items = new { type = "string" } },
}),
["required"] = JsonSerializer.SerializeToElement(new[] { "summary", "next_steps" }),
["additionalProperties"] = JsonSerializer.SerializeToElement(false),
},
},
},
// Strict tool use: guaranteed tool parameters
Tools =
[
new Tool
{
Name = "search_flights",
Strict = true,
InputSchema = new InputSchema(new Dictionary
{
["properties"] = JsonSerializer.SerializeToElement(new Dictionary
{
["destination"] = new { type = "string" },
["date"] = new { type = "string", format = "date" },
}),
["required"] = JsonSerializer.SerializeToElement(new[] { "destination", "date" }),
["additionalProperties"] = JsonSerializer.SerializeToElement(false),
}),
}
],
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Help me plan a trip to Paris departing May 15, 2026")),
},
// JSON outputs: structured response format
OutputConfig: anthropic.OutputConfigParam{
Format: anthropic.JSONOutputFormatParam{
Schema: map[string]any{
"type": "object",
"additionalProperties": false,
"properties": map[string]any{
"summary": map[string]any{"type": "string"},
"next_steps": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
},
"required": []string{"summary", "next_steps"},
},
},
},
// Strict tool use: guaranteed tool parameters
Tools: []anthropic.ToolUnionParam{
{OfTool: &anthropic.ToolParam{
Name: "search_flights",
Strict: anthropic.Bool(true),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"destination": map[string]any{"type": "string"},
"date": map[string]any{"type": "string", "format": "date"},
},
Required: []string{"destination", "date"},
ExtraFields: map[string]any{
"additionalProperties": false,
},
}}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Content)
}
```
```java Java hidelines={1..12,53}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.JsonOutputFormat;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.OutputConfig;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.Tool.InputSchema;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// JSON outputs: structured response format
JsonOutputFormat.Schema outputSchema = JsonOutputFormat.Schema.builder()
.putAdditionalProperty("type", JsonValue.from("object"))
.putAdditionalProperty("properties", JsonValue.from(Map.of(
"summary", Map.of("type", "string"),
"next_steps", Map.of("type", "array", "items", Map.of("type", "string"))
)))
.putAdditionalProperty("required", JsonValue.from(List.of("summary", "next_steps")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build();
// Strict tool use: guaranteed tool parameters
InputSchema toolSchema = InputSchema.builder()
.properties(JsonValue.from(Map.of(
"destination", Map.of("type", "string"),
"date", Map.of("type", "string", "format", "date")
)))
.putAdditionalProperty("required", JsonValue.from(List.of("destination", "date")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessage("Help me plan a trip to Paris departing May 15, 2026")
.outputConfig(OutputConfig.builder()
.format(JsonOutputFormat.builder().schema(outputSchema).build())
.build())
.addTool(Tool.builder()
.name("search_flights")
.description("Search for available flights to a destination on a specific date")
.strict(true)
.inputSchema(toolSchema)
.build())
.build();
Message response = client.messages().create(params);
IO.println(response);
}
```
```php PHP hidelines={1..3}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Help me plan a trip to Paris departing May 15, 2026']
],
model: 'claude-opus-4-7',
// JSON outputs: structured response format
outputConfig: ['format' => TripPlan::class],
// Strict tool use: guaranteed tool parameters
tools: [
[
'name' => 'search_flights',
'strict' => true,
'input_schema' => [
'type' => 'object',
'properties' => [
'destination' => ['type' => 'string'],
'date' => ['type' => 'string', 'format' => 'date']
],
'required' => ['destination', 'date'],
'additionalProperties' => false
]
]
],
);
// Claude may call the tool first (tool_use) or respond with JSON (text)
$plan = $message->parsedOutput();
if ($plan instanceof TripPlan) {
echo $plan->summary, "\n";
} elseif ($toolUse = array_find($message->content, fn($block) => $block instanceof ToolUseBlock)) {
echo "Tool call: {$toolUse->name}(", json_encode($toolUse->input), ")\n";
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{role: "user", content: "Help me plan a trip to Paris departing May 15, 2026"}
],
# JSON outputs: structured response format
output_config: {
format: {
type: :json_schema,
schema: {
type: "object",
properties: {
summary: {type: "string"},
next_steps: {type: "array", items: {type: "string"}}
},
required: ["summary", "next_steps"],
additionalProperties: false
}
}
},
# Strict tool use: guaranteed tool parameters
tools: [
{
name: "search_flights",
strict: true,
input_schema: {
type: "object",
properties: {
destination: {type: "string"},
date: {type: "string", format: "date"}
},
required: ["destination", "date"],
additionalProperties: false
}
}
]
)
puts message
```
## Important considerations
### Grammar compilation and caching
Structured outputs use constrained sampling with compiled grammar artifacts. This introduces some performance characteristics to be aware of:
- **First request latency:** The first time you use a specific schema, there is additional latency while the grammar compiles
- **Automatic caching:** Compiled grammars are cached for 24 hours from last use, making subsequent requests much faster
- **Cache invalidation:** The cache is invalidated if you change:
- The JSON schema structure
- The set of tools in your request (when using both structured outputs and tool use)
- Changing only `name` or `description` fields does not invalidate the cache
### Prompt modification and token costs
When using structured outputs, Claude automatically receives an additional system prompt explaining the expected output format. This means:
- Your input token count is slightly higher
- The injected prompt costs you tokens like any other system prompt
- Changing the `output_config.format` parameter will invalidate any [prompt cache](/docs/en/build-with-claude/prompt-caching) for that conversation thread
### JSON Schema limitations
Structured outputs support standard JSON Schema with some limitations. Both JSON outputs and strict tool use share these limitations.
- All basic types: object, array, string, integer, number, boolean, null
- `enum` (strings, numbers, bools, or nulls only - no complex types)
- `const`
- `anyOf` and `allOf` (with limitations - `allOf` with `$ref` not supported)
- `$ref`, `$def`, and `definitions` (external `$ref` not supported)
- `default` property for all supported types
- `required` and `additionalProperties` (must be set to `false` for objects)
- String formats: `date-time`, `time`, `date`, `duration`, `email`, `hostname`, `uri`, `ipv4`, `ipv6`, `uuid`
- Array `minItems` (only values 0 and 1 supported)
- Recursive schemas
- Complex types within enums
- External `$ref` (for example, `'$ref': 'http://...'`)
- Numerical constraints (`minimum`, `maximum`, `multipleOf`, etc.)
- String constraints (`minLength`, `maxLength`)
- Array constraints beyond `minItems` of 0 or 1
- `additionalProperties` set to anything other than `false`
If you use an unsupported feature, you'll receive a 400 error with details.
**Supported regex features:**
- Full matching (`^...$`) and partial matching
- Quantifiers: `*`, `+`, `?`, simple `{n,m}` cases
- Character classes: `[]`, `.`, `\d`, `\w`, `\s`
- Groups: `(...)`
**NOT supported:**
- Backreferences to groups (for example, `\1`, `\2`)
- Lookahead/lookbehind assertions (for example, `(?=...)`, `(?!...)`)
- Word boundaries: `\b`, `\B`
- Complex `{n,m}` quantifiers with large ranges
Simple regex patterns work well. Complex patterns may result in 400 errors.
The Python, TypeScript, Ruby, and PHP SDKs can automatically transform schemas with unsupported features by removing them and adding constraints to field descriptions. See [SDK-specific methods](#sdk-specific-methods) for details.
### Property ordering
When using structured outputs, properties in objects maintain their defined ordering from your schema, with one important caveat: **required properties appear first, followed by optional properties**.
For example, given this schema:
```json
{
"type": "object",
"properties": {
"notes": { "type": "string" },
"name": { "type": "string" },
"email": { "type": "string" },
"age": { "type": "integer" }
},
"required": ["name", "email"],
"additionalProperties": false
}
```
The output will order properties as:
1. `name` (required, in schema order)
2. `email` (required, in schema order)
3. `notes` (optional, in schema order)
4. `age` (optional, in schema order)
This means the output might look like:
```json
{
"name": "John Smith",
"email": "john@example.com",
"notes": "Interested in enterprise plan",
"age": 35
}
```
If property order in the output is important to your application, mark all properties as required, or account for this reordering in your parsing logic.
### Invalid outputs
While structured outputs guarantee schema compliance in most cases, there are scenarios where the output may not match your schema:
**Refusals** (`stop_reason: "refusal"`)
Claude maintains its safety and helpfulness properties even when using structured outputs. If Claude refuses a request for safety reasons:
- The response has `stop_reason: "refusal"`
- You'll receive a 200 status code
- You'll be billed for the tokens generated
- The output may not match your schema because the refusal message takes precedence over schema constraints
**Token limit reached** (`stop_reason: "max_tokens"`)
If the response is cut off due to reaching the `max_tokens` limit:
- The response has `stop_reason: "max_tokens"`
- The output may be incomplete and not match your schema
- Retry with a higher `max_tokens` value to get the complete structured output
### Schema complexity limits
Structured outputs work by compiling your JSON schemas into a grammar that constrains Claude's output. More complex schemas produce larger grammars that take longer to compile. To protect against excessive compilation times, the API enforces several complexity limits.
#### Explicit limits
The following limits apply to all requests with `output_config.format` or `strict: true`:
| Limit | Value | Description |
|-------|-------|-------------|
| Strict tools per request | 20 | Maximum number of tools with `strict: true`. Non-strict tools don't count toward this limit. |
| Optional parameters | 24 | Total optional parameters across all strict tool schemas and JSON output schemas. Each parameter not listed in `required` counts toward this limit. |
| Parameters with union types | 16 | Total parameters that use `anyOf` or type arrays (for example, `"type": ["string", "null"]`) across all strict schemas. These are especially expensive because they create exponential compilation cost. |
These limits apply to the combined total across all strict schemas in a single request. For example, if you have 4 strict tools with 6 optional parameters each, you'll reach the 24-parameter limit even though no single tool seems complex.
#### Additional internal limits
Beyond the explicit limits in the preceding table, there are additional internal limits on the compiled grammar size. These limits exist because schema complexity doesn't reduce to a single dimension: features like optional parameters, union types, nested objects, and number of tools interact with each other in ways that can make the compiled grammar disproportionately large.
When these limits are exceeded, you'll receive a 400 error with the message "Schema is too complex for compilation." These errors mean the combined complexity of your schemas exceeds what can be efficiently compiled, even if each individual limit in the preceding table is satisfied. As a final stop-gap, the API also enforces a **compilation timeout of 180 seconds**. Schemas that pass all explicit checks but produce very large compiled grammars may hit this timeout.
#### Tips for reducing schema complexity
If you're hitting complexity limits, try these strategies in order:
1. **Mark only critical tools as strict.** If you have many tools, reserve it for tools where schema violations cause real problems, and rely on Claude's natural adherence for simpler tools.
2. **Reduce optional parameters.** Make parameters `required` where possible. Each optional parameter roughly doubles a portion of the grammar's state space. If a parameter always has a reasonable default, consider making it required and having Claude provide that default explicitly.
3. **Simplify nested structures.** Deeply nested objects with optional fields compound the complexity. Flatten structures where possible.
4. **Split into multiple requests.** If you have many strict tools, consider splitting them across separate requests or sub-agents.
For persistent issues with valid schemas, [contact support](https://support.claude.com/en/articles/9015913-how-to-get-support) with your schema definition.
## Data retention
Prompts and responses are processed with ZDR when using structured outputs. However, the JSON schema itself is temporarily cached for up to 24 hours since last use for optimization purposes. No prompt or response data is retained beyond the API response.
Structured outputs are HIPAA eligible, but **PHI must not be included in JSON schema definitions**. The API compiles JSON schemas into grammars that are cached separately from message content, and these cached schemas do not receive the same PHI protections as prompts and responses. Do not include PHI in schema property names, `enum` values, `const` values, or `pattern` regular expressions. PHI should only appear in message content (prompts and responses), where it is protected under HIPAA safeguards.
For ZDR and HIPAA eligibility across all features, see [API and data retention](/docs/en/manage-claude/api-and-data-retention).
## Feature compatibility
**Works with:**
- **[Batch processing](/docs/en/build-with-claude/batch-processing):** Process structured outputs at scale with 50% discount
- **[Token counting](/docs/en/build-with-claude/token-counting):** Count tokens without compilation
- **[Streaming](/docs/en/build-with-claude/streaming):** Stream structured outputs like normal responses
- **Combined usage:** Use JSON outputs (`output_config.format`) and strict tool use (`strict: true`) together in the same request
**Incompatible with:**
- **[Citations](/docs/en/build-with-claude/citations):** Citations require interleaving citation blocks with text, which conflicts with strict JSON schema constraints. Returns 400 error if citations enabled with `output_config.format`.
- **Message Prefilling:** Incompatible with JSON outputs
**Grammar scope:** Grammars apply only to Claude's direct output, not to tool use calls, tool results, or thinking tags (when using [Extended Thinking](/docs/en/build-with-claude/extended-thinking)). Grammar state resets between sections, allowing Claude to think freely while still producing structured output in the final response.
---
# Task budgets
URL: https://platform.claude.com/docs/en/build-with-claude/task-budgets
# Task budgets
Give Claude an advisory token budget for the full agentic loop to help the model self-regulate on long agentic tasks with task budgets.
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
Task budgets let you tell Claude how many tokens it has for a full agentic loop, including thinking, tool calls, tool results, and output. The model sees a running countdown and uses it to prioritize work and finish gracefully as the budget is consumed.
Task budgets are in public beta on [Claude Opus 4.7](/docs/en/about-claude/models/overview). Set the `task-budgets-2026-03-13` beta header to opt in.
## When to use task budgets
Task budgets work best for agentic workflows where Claude makes multiple tool calls and decisions before finalizing its output to await the next human response. Use them when:
- You want Claude to self-regulate token spend on long-horizon tasks.
- You have a predictable per-task cost or latency ceiling to enforce.
- You want the model to finish gracefully (summarize findings, report progress) as it approaches the budget rather than cutting off mid-action.
Task budgets complement the [effort parameter](/docs/en/build-with-claude/effort): effort controls how thoroughly Claude reasons about each step, while task budgets cap the total work Claude can do across an agentic loop.
## Setting a task budget
Add `task_budget` to `output_config` and include the beta header:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "anthropic-beta: task-budgets-2026-03-13" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 128000,
"messages": [{
"role": "user",
"content": "Review the codebase and propose a refactor plan."
}],
"output_config": {
"effort": "high",
"task_budget": {"type": "tokens", "total": 64000}
}
}'
```
```bash CLI
ant beta:messages create --beta task-budgets-2026-03-13 <<'YAML'
model: claude-opus-4-7
max_tokens: 128000
messages:
- role: user
content: Review the codebase and propose a refactor plan.
output_config:
effort: high
task_budget:
type: tokens
total: 64000
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=128000,
output_config={
"effort": "high",
"task_budget": {"type": "tokens", "total": 64000},
},
messages=[
{"role": "user", "content": "Review the codebase and propose a refactor plan."}
],
betas=["task-budgets-2026-03-13"],
)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 128000,
output_config: {
effort: "high",
task_budget: { type: "tokens", total: 64000 }
},
messages: [{ role: "user", content: "Review the codebase and propose a refactor plan." }],
betas: ["task-budgets-2026-03-13"]
});
```
```go Go hidelines={1..10,-2..}
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, _ := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 128000,
Betas: []anthropic.AnthropicBeta{"task-budgets-2026-03-13"},
Messages: []anthropic.BetaMessageParam{{
Role: anthropic.BetaMessageParamRoleUser,
Content: []anthropic.BetaContentBlockParamUnion{{
OfText: &anthropic.BetaTextBlockParam{Text: "Review the codebase and propose a refactor plan."},
}},
}},
OutputConfig: anthropic.BetaOutputConfigParam{
Effort: anthropic.BetaOutputConfigEffortHigh,
TaskBudget: anthropic.BetaTokenTaskBudgetParam{
Total: 64000,
},
},
})
fmt.Println(response)
}
```
```java Java hidelines={1..7,9..11,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaOutputConfig;
import com.anthropic.models.beta.messages.BetaTokenTaskBudget;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
public class Main {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(128000L)
.addUserMessage("Review the codebase and propose a refactor plan.")
.outputConfig(BetaOutputConfig.builder()
.effort(BetaOutputConfig.Effort.HIGH)
.taskBudget(BetaTokenTaskBudget.builder().total(64000L).build())
.build())
.addBeta("task-budgets-2026-03-13")
.build();
BetaMessage response = client.beta().messages().create(params);
}
}
```
```csharp C# hidelines={1..3}
using Anthropic;
using Anthropic.Models.Beta.Messages;
using Anthropic.Models.Messages;
var client = new AnthropicClient();
var response = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 128000,
Messages = [new() { Role = Role.User, Content = "Review the codebase and propose a refactor plan." }],
OutputConfig = new BetaOutputConfig
{
Effort = Effort.High,
TaskBudget = new BetaTokenTaskBudget { Total = 64000 },
},
Betas = ["task-budgets-2026-03-13"],
});
```
```php PHP hidelines={1..4}
beta->messages->create(
model: 'claude-opus-4-7',
maxTokens: 128000,
messages: [
['role' => 'user', 'content' => 'Review the codebase and propose a refactor plan.'],
],
outputConfig: [
'effort' => 'high',
'taskBudget' => ['type' => 'tokens', 'total' => 64000],
],
betas: ['task-budgets-2026-03-13'],
);
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 128_000,
messages: [
{ role: "user", content: "Review the codebase and propose a refactor plan." }
],
output_config: {
effort: :high,
task_budget: { type: :tokens, total: 64_000 }
},
betas: ["task-budgets-2026-03-13"]
)
puts response
```
The `task_budget` object has three fields:
- `type`: always `"tokens"`.
- `total`: the number of tokens Claude can spend across the agentic loop, including thinking, tool calls, tool results, and output.
- `remaining` (optional): the budget remainder carried over from a prior request. Defaults to `total` when omitted.
## How the budget countdown works
Claude sees a budget-countdown marker injected server-side throughout the conversation. The marker shows how many tokens remain in the current agentic loop and updates as the model generates thinking, tool calls, and output, and as it processes tool results. Claude uses this signal to pace itself and finish gracefully as the budget is consumed.
**The countdown reflects tokens Claude has processed in the current agentic loop, not tokens you resend between turns.** If your client sends the full conversation history on every follow-up request, your client-side token count may differ from the budget Claude is tracking. If you also decrement `remaining` while resending full history, the model sees an under-reported budget and the countdown drops faster than it should, causing Claude to wrap up earlier than the budget actually allows. Set a generous budget and let the model self-regulate against the countdown rather than trying to mirror it client-side.
### Worked example: budget counting across turns
The task budget counts what Claude **sees** (thinking, tool calls and results, and text), not what's in your request payload. In an agentic loop your client resends the full conversation on every request, so the payload grows turn over turn, but the budget only decrements by the tokens Claude sees this turn.
Consider a loop with `task_budget: {type: "tokens", total: 100000}` and a single `bash` tool.
**Turn 1.** You send the initial request:
```json
{
"messages": [
{ "role": "user", "content": "Audit this repo for security issues and report findings." }
]
}
```
Claude thinks, then emits a tool call and stops with `stop_reason: "tool_use"`:
```json
{
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": "I'll start by listing dependencies to look for known-vulnerable packages..."
},
{
"type": "tool_use",
"id": "toolu_01",
"name": "bash",
"input": { "command": "cat package.json && npm audit --json" }
}
]
}
```
Suppose this assistant turn (thinking plus the tool call) totals 5,000 generated tokens. The countdown Claude saw during generation ended near `remaining` ≈ 95,000.
**Turn 2.** Your client executes the tool, then resends the full history with the tool result appended:
```json
{
"messages": [
{ "role": "user", "content": "Audit this repo for security issues and report findings." },
{
"role": "assistant",
"content": [
{ "type": "thinking", "thinking": "I'll start by listing dependencies..." },
{
"type": "tool_use",
"id": "toolu_01",
"name": "bash",
"input": { "command": "cat package.json && npm audit --json" }
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01",
"content": "<2,800 tokens of npm audit output>"
}
]
}
]
}
```
The resent turn-1 user and assistant messages are not counted again, but the 2,800-token tool result is new content Claude sees this turn and counts against the budget. Claude spends another 4,000 tokens on thinking and a second tool call (`grep -rn "eval(" src/`). The countdown ends near `remaining` ≈ 88,200.
**Turn 3.** Full history resent again with the second tool result (1,200 tokens of grep output) appended. Claude writes a 6,000-token final findings report and stops with `stop_reason: "end_turn"`. `remaining` ≈ 81,000.
Putting the three turns side by side makes the distinction between payload size and budget spend explicit:
| Turn | Request payload (approx. input tokens you sent) | Tokens counted against budget this turn | Budget `remaining` after |
|---|---|---|---|
| 1 | ~20 | 5,000 (thinking + `tool_use`) | ~95,000 |
| 2 | ~7,800 (turn 1 history + tool result) | 6,800 (2,800 tool result + 4,000 thinking and `tool_use`) | ~88,200 |
| 3 | ~13,000 (full history + second tool result) | 7,200 (1,200 tool result + 6,000 `text`) | ~81,000 |
| **Total** | **~20,820 sent across requests** | **19,000 counted against budget** | — |
Your client sent the turn-1 user message three times and the turn-1 assistant message twice, but each was counted once. The budget spent 19,000 of 100,000 tokens, even though the cumulative payload your client transmitted was larger and the prompt-cached input on turns 2 and 3 was larger still.
### Carrying a budget across compaction with `remaining`
If your agentic loop compacts or rewrites context between requests (for example, by summarizing earlier turns), the server has no memory of how much budget was spent before compaction. Pass `remaining` on the next request so the countdown continues from where you left off rather than resetting to `total`:
```python Python
output_config = {
"effort": "high",
"task_budget": {
"type": "tokens",
"total": 128000,
"remaining": 128000 - tokens_spent_so_far,
},
}
```
```typescript TypeScript
const output_config = {
effort: "high",
task_budget: {
type: "tokens",
total: 128000,
remaining: 128000 - tokensSpentSoFar
}
};
```
```go Go
outputConfig := anthropic.BetaOutputConfigParam{
Effort: anthropic.BetaOutputConfigEffortHigh,
TaskBudget: anthropic.BetaTokenTaskBudgetParam{
Total: 128000,
Remaining: anthropic.Int(128000 - tokensSpentSoFar),
},
}
```
```java Java
BetaOutputConfig outputConfig = BetaOutputConfig.builder()
.effort(BetaOutputConfig.Effort.HIGH)
.taskBudget(BetaTokenTaskBudget.builder()
.total(128000L)
.remaining(128000L - tokensSpentSoFar)
.build())
.build();
```
```csharp C#
var outputConfig = new BetaOutputConfig
{
Effort = Effort.High,
TaskBudget = new BetaTokenTaskBudget
{
Total = 128000,
Remaining = 128000 - tokensSpentSoFar,
},
};
```
```php PHP
$outputConfig = [
'effort' => 'high',
'taskBudget' => [
'type' => 'tokens',
'total' => 128000,
'remaining' => 128000 - $tokensSpentSoFar,
],
];
```
```ruby Ruby
output_config = {
effort: :high,
task_budget: {
type: :tokens,
total: 128_000,
remaining: 128_000 - tokens_spent_so_far
}
}
```
For loops that resend the full uncompacted history on every turn, omit `remaining` and let the server track the countdown.
## Task budgets are advisory, not enforced
Task budgets are a **soft hint, not a hard cap**. Claude may occasionally exceed the budget if it is in the middle of an action that would be more disruptive to interrupt than to finish. The enforced limit on total output tokens is still `max_tokens`, which truncates the response with `stop_reason: "max_tokens"` when reached.
For a hard cap on cost or latency, combine task budgets with a reasonable `max_tokens` value:
- Use `task_budget` to give Claude a target to pace against.
- Use `max_tokens` as the absolute ceiling that prevents runaway generation.
Because `task_budget` spans the full agentic loop (potentially many requests) while `max_tokens` caps each individual request, the two values are independent; one is not required to be at or below the other.
**A budget that is too small for the task can cause refusal-like behavior.** When Claude sees a budget that is clearly insufficient for the work being asked (for example, a 20,000-token budget for a multi-hour agentic coding task), it may decline to attempt the task at all, scope it down aggressively, or stop early with a partial result rather than start work it cannot finish. If you observe unexpected refusals or premature stops after setting a budget, raise the budget before debugging other parameters. Size budgets against your actual task-length distribution rather than a fixed default; see [Choosing a budget](#choosing-a-budget).
## Choosing a budget
The right budget depends on how much work your agentic loop currently does. Rather than guessing, measure your existing token usage first and then tune from there.
### Measure your current usage
Run a representative sample of tasks **without** `task_budget` set and record the total tokens Claude spends per task. For an agentic loop, sum `usage.output_tokens` plus thinking and tool-result tokens across every request in the loop:
```python Python
def run_task_and_count_tokens(messages: list) -> int:
"""Runs an agentic loop to completion and returns total tokens spent."""
total_spend = 0
while True:
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=128000,
messages=messages,
tools=tools,
betas=["task-budgets-2026-03-13"],
)
# Count what Claude generated this turn (output covers text + thinking + tool calls).
# Tool-result tokens also count against the budget; add the token count of the
# tool_result blocks you append below if you want client-side tracking to match
# the server-side countdown.
total_spend += response.usage.output_tokens
if response.stop_reason == "end_turn":
return total_spend
# Append the assistant turn and your tool results, then continue the loop.
messages += [
{"role": "assistant", "content": response.content},
{"role": "user", "content": run_tools(response.content)},
]
```
```typescript TypeScript
async function runTaskAndCountTokens(
messages: Anthropic.Beta.BetaMessageParam[]
): Promise {
let totalSpend = 0;
while (true) {
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 128000,
messages,
tools,
betas: ["task-budgets-2026-03-13"]
});
// Count what Claude generated this turn (output covers text + tool calls;
// add cache creation and thinking via the same usage object if you opt in).
totalSpend += response.usage.output_tokens;
if (response.stop_reason === "end_turn") {
return totalSpend;
}
// Append the assistant turn and your tool results, then continue the loop.
messages = [
...messages,
{ role: "assistant", content: response.content },
{ role: "user", content: runTools(response.content) }
];
}
}
```
Run this across a representative set of tasks and record the distribution. Start with the p99 of your per-task token spend to understand how providing the model with a task budget may modify the model's behavior, then test up or down as needed.
The minimum accepted `task_budget.total` is **20,000 tokens**; values below the minimum return a 400 error.
## Interaction with other parameters
- **`max_tokens`:** Orthogonal to task budgets. `max_tokens` is a hard per-request cap on generated tokens, while `task_budget` is an advisory cap across the full agentic loop (potentially spanning many requests). At `xhigh` or `max` effort, set `max_tokens` to at least 64k to give Claude room to think and act on each request.
- **[Effort](/docs/en/build-with-claude/effort):** Effort controls how deeply Claude reasons per step. Task budgets control how much total work Claude does across an agentic loop. The two are complementary: effort tunes depth, task budgets tune breadth.
- **[Adaptive thinking](/docs/en/build-with-claude/adaptive-thinking):** Task budgets include thinking tokens in the count, so adaptive thinking naturally scales down as the budget depletes.
- **[Prompt caching](/docs/en/build-with-claude/prompt-caching):** The budget-countdown marker is injected server-side per turn, so it does not match across requests. If your client decrements `task_budget.remaining` on each follow-up request, the changed value invalidates any cache prefix that contains it. To preserve caching, set the budget once on the initial request and let the model self-regulate against the server-side countdown rather than mutating the budget client-side.
## Feature support
| Model | Support |
|-------|---------|
| Claude Opus 4.7 | Public beta (set `task-budgets-2026-03-13` header) |
| Claude Opus 4.6 | Not supported |
| Claude Sonnet 4.6 | Not supported |
| Claude Haiku 4.5 | Not supported |
Task budgets are not supported on [Claude Code](https://docs.claude.com/en/docs/claude-code) or Cowork surfaces at launch. Use task budgets directly via the Messages API on Claude Opus 4.7.
### Tools
---
# Tool use with Claude
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview
# Tool use with Claude
Connect Claude to external tools and APIs. Learn where tools execute and how the agentic loop works.
---
Tool use lets Claude call functions you define or that Anthropic provides. Claude decides when to call a tool based on the user's request and the tool's description, then returns a structured call that your application executes (client tools) or that Anthropic executes (server tools).
Here's the simplest example using a server tool, where Anthropic handles execution:
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"tools": [{"type": "web_search_20260209", "name": "web_search"}],
"messages": [{"role": "user", "content": "What'\''s the latest on the Mars rover?"}]
}'
```
```bash CLI
ant messages create --transform content --format yaml \
--model claude-opus-4-7 \
--max-tokens 1024 \
--tool '{type: web_search_20260209, name: web_search}' \
--message '{role: user, content: "What is the latest on the Mars rover?"}'
```
```python Python
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
tools=[{"type": "web_search_20260209", "name": "web_search"}],
messages=[{"role": "user", "content": "What's the latest on the Mars rover?"}],
)
print(response.content)
```
```typescript TypeScript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [{ type: "web_search_20260209", name: "web_search" }],
messages: [{ role: "user", content: "What's the latest on the Mars rover?" }]
});
console.log(response.content);
```
---
## How tool use works
Tools differ primarily by where the code executes. **Client tools** (including user-defined tools and Anthropic-schema tools like bash and text_editor) run in your application: Claude responds with `stop_reason: "tool_use"` and one or more `tool_use` blocks, your code executes the operation, and you send back a `tool_result`. **Server tools** (web_search, code_execution, web_fetch, tool_search) run on Anthropic's infrastructure: you see the results directly without handling execution.
For the full conceptual model including the agentic loop and when to choose each approach, see [How tool use works](/docs/en/agents-and-tools/tool-use/how-tool-use-works).
For connecting to MCP servers, see the [MCP connector](/docs/en/agents-and-tools/mcp-connector). For building your own MCP client, see [modelcontextprotocol.io](https://modelcontextprotocol.io/docs/develop/build-client).
**Guarantee schema conformance with strict tool use**
Add `strict: true` to your tool definitions to ensure Claude's tool calls always match your schema exactly. See [Strict tool use](/docs/en/agents-and-tools/tool-use/strict-tool-use).
Tool access is one of the highest-leverage primitives you can give an agent. On benchmarks like [LAB-Bench FigQA](https://lab-bench.org/) (scientific figure interpretation) and [SWE-bench](https://www.swebench.com/) (real-world software engineering), adding even basic tools produces outsized capability gains, often surpassing human expert baselines.
---
## Tool use examples
For a complete hands-on walkthrough, see the [tutorial](/docs/en/agents-and-tools/tool-use/build-a-tool-using-agent). For reference examples of individual concepts, see [Define tools](/docs/en/agents-and-tools/tool-use/define-tools) and [Handle tool calls](/docs/en/agents-and-tools/tool-use/handle-tool-calls).
If the user's prompt doesn't include enough information to fill all the required parameters for a tool, Claude Opus is much more likely to recognize that a parameter is missing and ask for it. Claude Sonnet may ask, especially when prompted to think before outputting a tool request. But it may also do its best to infer a reasonable value.
For example, given a `get_weather` tool that requires a `location` parameter, if you ask Claude "What's the weather?" without specifying a location, Claude (particularly Claude Sonnet) may make a guess about tool inputs:
```json JSON
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": { "location": "New York, NY", "unit": "fahrenheit" }
}
```
This behavior is not guaranteed, especially for more ambiguous prompts and for less intelligent models. If Claude Opus doesn't have enough context to fill in the required parameters, it is far more likely to respond with a clarifying question instead of making a tool call.
---
## Pricing
Tool use requests are priced based on:
1. The total number of input tokens sent to the model (including in the `tools` parameter)
2. The number of output tokens generated
3. For server-side tools, additional usage-based pricing (e.g., web search charges per search performed)
Client-side tools are priced the same as any other Claude API request, while server-side tools may incur additional charges based on their specific usage.
The additional tokens from tool use come from:
- The `tools` parameter in API requests (tool names, descriptions, and schemas)
- `tool_use` content blocks in API requests and responses
- `tool_result` content blocks in API requests
When you use `tools`, we also automatically include a special system prompt for the model which enables tool use. The number of tool use tokens required for each model are listed below (excluding the additional tokens listed above). Note that the table assumes at least 1 tool is provided. If no `tools` are provided, then a tool choice of `none` uses 0 additional system prompt tokens.
| Model | Tool choice | Tool use system prompt token count |
|--------------------------|------------------------------------------------------|---------------------------------------------|
| Claude Opus 4.7 | `auto`, `none``any`, `tool` | 346 tokens313 tokens |
| Claude Opus 4.6 | `auto`, `none``any`, `tool` | 346 tokens313 tokens |
| Claude Opus 4.5 | `auto`, `none``any`, `tool` | 346 tokens313 tokens |
| Claude Opus 4.1 | `auto`, `none``any`, `tool` | 346 tokens313 tokens |
| Claude Opus 4 ([deprecated](/docs/en/about-claude/model-deprecations)) | `auto`, `none``any`, `tool` | 346 tokens313 tokens |
| Claude Sonnet 4.6 | `auto`, `none``any`, `tool` | 346 tokens313 tokens |
| Claude Sonnet 4.5 | `auto`, `none``any`, `tool` | 346 tokens313 tokens |
| Claude Sonnet 4 ([deprecated](/docs/en/about-claude/model-deprecations)) | `auto`, `none``any`, `tool` | 346 tokens313 tokens |
| Claude Haiku 4.5 | `auto`, `none``any`, `tool` | 346 tokens313 tokens |
| Claude Haiku 3.5 ([retired, except on Bedrock and Vertex AI](/docs/en/about-claude/model-deprecations)) | `auto`, `none``any`, `tool` | 264 tokens340 tokens |
These token counts are added to your normal input and output tokens to calculate the total cost of a request.
Refer to the [models overview table](/docs/en/about-claude/models/overview#latest-models-comparison) for current per-model prices.
When you send a tool use prompt, just like any other API request, the response will output both input and output token counts as part of the reported `usage` metrics.
---
## Next steps
### Choose your path
Where tools run, how the loop works, and when to use tools.
The tutorial: from a single tool call to production.
Directory of Anthropic-provided tools and properties.
---
# Advisor tool
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/advisor-tool
# Advisor tool
Pair a faster executor model with a higher-intelligence advisor model that provides strategic guidance mid-generation.
---
The advisor tool lets a faster, lower-cost **executor model** consult a higher-intelligence **advisor model** mid-generation for strategic guidance. The advisor reads the full conversation, produces a plan or course correction (typically 400 to 700 text tokens, 1,400 to 1,800 tokens total including thinking), and the executor continues with the task.
This pattern fits long-horizon agentic workloads (coding agents, computer use, multi-step research pipelines) where most turns are mechanical but having an excellent plan is crucial. You get close to advisor-solo quality while the bulk of token generation happens at executor-model rates.
The advisor tool is in beta. Include the beta header `advisor-tool-2026-03-01`
in your requests. To request access or share feedback, contact your Anthropic
account team.
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
## When to use it
Early benchmarks show meaningful gains for these configurations:
- **You currently use Sonnet on complex tasks:** Add Opus as the advisor for a quality lift at similar or lower total cost.
- **You currently use Haiku and want a step up in intelligence:** Add Opus as the advisor. Expect higher cost than Haiku alone, but lower than switching the executor to a larger model.
Results are task-dependent. Evaluate on your own workload.
The advisor is a weaker fit for single-turn Q&A (nothing to plan), pure pass-through model pickers where your users already choose their own cost and quality tradeoff, or workloads where every turn genuinely requires the advisor model's full capability.
## Model compatibility
The executor model (the top-level `model` field) and the advisor model (the `model` field inside the tool definition) must form a valid pair. The advisor must be at least as capable as the executor.
| Executor models | Advisor models |
| ---------------------------------------------- | ----------------------------------- |
| Claude Haiku 4.5 (`claude-haiku-4-5-20251001`) | Claude Opus 4.7 (`claude-opus-4-7`) |
| Claude Sonnet 4.6 (`claude-sonnet-4-6`) | Claude Opus 4.7 (`claude-opus-4-7`) |
| Claude Opus 4.6 (`claude-opus-4-6`) | Claude Opus 4.7 (`claude-opus-4-7`) |
| Claude Opus 4.7 (`claude-opus-4-7`) | Claude Opus 4.7 (`claude-opus-4-7`) |
If you request an invalid pair, the API returns a `400 invalid_request_error` naming the unsupported combination.
## Platform availability
The advisor tool is available in beta on the Claude API and on [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws). It is not currently available on Amazon Bedrock, Vertex AI, or Microsoft Foundry.
## Quick start
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "anthropic-beta: advisor-tool-2026-03-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-sonnet-4-6",
"max_tokens": 4096,
"tools": [
{
"type": "advisor_20260301",
"name": "advisor",
"model": "claude-opus-4-7"
}
],
"messages": [{
"role": "user",
"content": "Build a concurrent worker pool in Go with graceful shutdown."
}]
}'
```
```bash CLI
ant beta:messages create --beta advisor-tool-2026-03-01 <<'YAML'
model: claude-sonnet-4-6
max_tokens: 4096
tools:
- type: advisor_20260301
name: advisor
model: claude-opus-4-7
messages:
- role: user
content: Build a concurrent worker pool in Go with graceful shutdown.
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
betas=["advisor-tool-2026-03-01"],
tools=[
{
"type": "advisor_20260301",
"name": "advisor",
"model": "claude-opus-4-7",
}
],
messages=[
{
"role": "user",
"content": "Build a concurrent worker pool in Go with graceful shutdown.",
}
],
)
print(response)
```
```typescript TypeScript hidelines={1..5,-3..-1}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function main() {
const response = await client.beta.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 4096,
betas: ["advisor-tool-2026-03-01"],
tools: [
{
type: "advisor_20260301",
name: "advisor",
model: "claude-opus-4-7"
}
],
messages: [
{
role: "user",
content: "Build a concurrent worker pool in Go with graceful shutdown."
}
]
});
console.log(response);
}
main().catch(console.error);
```
```csharp C# nocheck hidelines={1}
using Anthropic;
using Anthropic.Models.Beta.Messages;
using Messages = Anthropic.Models.Messages;
var client = new AnthropicClient();
var parameters = new MessageCreateParams
{
Model = Messages::Model.ClaudeSonnet4_6,
MaxTokens = 4096,
Tools = new BetaToolUnion[]
{
new BetaAdvisorTool20260301
{
Model = Messages::Model.ClaudeOpus4_7
}
},
Messages =
[
new BetaMessageParam
{
Role = Role.User,
Content = "Build a concurrent worker pool in Go with graceful shutdown."
}
],
Betas = ["advisor-tool-2026-03-01"]
};
var response = await client.Beta.Messages.Create(parameters);
Console.WriteLine(response);
```
```go Go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 4096,
Tools: []anthropic.BetaToolUnionParam{
{OfAdvisorTool20260301: &anthropic.BetaAdvisorTool20260301Param{
Model: anthropic.ModelClaudeOpus4_7,
}},
},
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Build a concurrent worker pool in Go with graceful shutdown.")),
},
Betas: []anthropic.AnthropicBeta{
anthropic.AnthropicBetaAdvisorTool2026_03_01,
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```php PHP hidelines={1..4}
beta->messages->create(
maxTokens: 4096,
messages: [
[
'role' => 'user',
'content' => 'Build a concurrent worker pool in Go with graceful shutdown.',
],
],
model: 'claude-sonnet-4-6',
tools: [
[
'type' => 'advisor_20260301',
'name' => 'advisor',
'model' => 'claude-opus-4-7',
],
],
betas: ['advisor-tool-2026-03-01'],
);
echo $response;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.beta.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 4096,
tools: [
{
type: "advisor_20260301",
name: "advisor",
model: "claude-opus-4-7"
}
],
messages: [
{
role: "user",
content: "Build a concurrent worker pool in Go with graceful shutdown."
}
],
betas: ["advisor-tool-2026-03-01"]
)
puts response
```
## How it works
When you add the advisor tool to your `tools` array, the executor model decides when to call it, just like any other tool. When the executor invokes the advisor:
1. The executor emits a `server_tool_use` block with `name: "advisor"` and an empty `input`. The executor signals timing; the server supplies context.
2. Anthropic runs a separate inference pass on the advisor model server-side, passing the executor's full transcript. The advisor sees the system prompt, all tool definitions, all prior turns, and all prior tool results.
3. The advisor's response returns to the executor as an `advisor_tool_result` block.
4. The executor continues generating, informed by the advice.
All of this happens inside a single `/v1/messages` request. No extra round trips on your side.
The advisor itself runs without tools and without context management. Its thinking blocks are dropped before the result returns; only the advice text reaches the executor.
## Tool parameters
| Parameter | Type | Default | Description |
| ----------------------- | -------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | _required_ | Must be `"advisor_20260301"`. |
| `name` | string | _required_ | Must be `"advisor"`. |
| `model` | string | _required_ | The advisor model ID, such as `"claude-opus-4-7"`. Billed at this model's rates for the sub-inference. |
| `max_uses` | integer | unlimited | Maximum number of advisor calls allowed in a single request. Once the executor reaches this cap, further advisor calls return an `advisor_tool_result_error` with `error_code: "max_uses_exceeded"` and the executor continues without further advice. This is a per-request cap, not a per-conversation cap; see [Cost control](#cost-control) for conversation-level limits. |
| `caching` | object \| null | `null` (off) | Enables prompt caching for the advisor's own transcript across calls within a conversation. See [Advisor prompt caching](#advisor-prompt-caching). |
The `caching` object has the shape `{"type": "ephemeral", "ttl": "5m" | "1h"}`. Unlike `cache_control` on content blocks, this is not a breakpoint marker; it is an on/off switch. The server decides where cache boundaries go.
## Response structure
### Successful advisor call
When the advisor is invoked, a `server_tool_use` block is followed by an `advisor_tool_result` block in the assistant's content:
```json
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "Let me consult the advisor on this."
},
{
"type": "server_tool_use",
"id": "srvtoolu_abc123",
"name": "advisor",
"input": {}
},
{
"type": "advisor_tool_result",
"tool_use_id": "srvtoolu_abc123",
"content": {
"type": "advisor_result",
"text": "Use a channel-based coordination pattern. The tricky part is draining in-flight work during shutdown: close the input channel first, then wait on a WaitGroup..."
}
},
{
"type": "text",
"text": "Here's the implementation. I'm using a channel-based coordination pattern to avoid writer starvation..."
}
]
}
```
The `server_tool_use.input` is always empty. The server constructs the advisor's view from the full transcript automatically; nothing the executor puts in `input` reaches the advisor.
### Result variants
The `advisor_tool_result.content` field is a discriminated union. For successful calls, the variant depends on the advisor model:
| Variant | Fields | Returned when |
| ------------------------- | ------------------- | ------------------------------------------------------------------- |
| `advisor_result` | `text` | The advisor model returns plaintext (for example, Claude Opus 4.7). |
| `advisor_redacted_result` | `encrypted_content` | The advisor model returns encrypted output. |
With `advisor_result`, the `text` field contains human-readable advice. With `advisor_redacted_result`, the `encrypted_content` field contains an opaque blob that you cannot read; on the next turn, the server decrypts it and renders the plaintext into the executor's prompt.
In both cases, round-trip the content verbatim on subsequent turns. If you switch advisor models mid-conversation, branch on `content.type` to handle both shapes.
### Error results
If the advisor call fails, the result carries an error:
```json
{
"type": "advisor_tool_result",
"tool_use_id": "srvtoolu_abc123",
"content": {
"type": "advisor_tool_result_error",
"error_code": "overloaded"
}
}
```
The executor sees the error and continues without further advice. The request itself does not fail.
| `error_code` | Meaning |
| ------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `max_uses_exceeded` | The request reached the `max_uses` cap set on the tool definition. Further advisor calls in the same request return this error. |
| `too_many_requests` | The advisor sub-inference was rate-limited. |
| `overloaded` | The advisor sub-inference hit capacity limits. |
| `prompt_too_long` | The transcript exceeded the advisor model's context window. |
| `execution_time_exceeded` | The advisor sub-inference timed out. |
| `unavailable` | Any other advisor failure. |
Advisor rate limits draw from the same per-model bucket as direct calls to the advisor model. A rate limit on the advisor appears as `too_many_requests` inside the tool result; a rate limit on the executor fails the whole request with HTTP 429.
## Multi-turn conversations
Pass the full assistant content, including `advisor_tool_result` blocks, back to the API on subsequent turns:
```python
import anthropic
client = anthropic.Anthropic()
tools = [
{
"type": "advisor_20260301",
"name": "advisor",
"model": "claude-opus-4-7",
}
]
messages = [
{
"role": "user",
"content": "Build a concurrent worker pool in Go with graceful shutdown.",
}
]
response = client.beta.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
betas=["advisor-tool-2026-03-01"],
tools=tools,
messages=messages,
)
# Append the full response content, including any advisor_tool_result blocks
messages.append({"role": "assistant", "content": response.content})
# Continue the conversation
messages.append({"role": "user", "content": "Now add a max-in-flight limit of 10."})
response = client.beta.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
betas=["advisor-tool-2026-03-01"],
tools=tools,
messages=messages,
)
```
If you omit the advisor tool from `tools` on a follow-up turn while the message history still contains `advisor_tool_result` blocks, the API returns a `400 invalid_request_error`.
The advisor tool has no built-in conversation-level cap. To limit advisor
calls across a conversation, count them client-side. When you reach your
ceiling, remove the advisor tool from your `tools` array **and** strip all
`advisor_tool_result` blocks from your message history to avoid a
`400 invalid_request_error`.
## Streaming
The advisor sub-inference does not stream. The executor's stream pauses while the advisor runs, then the full result arrives in a single event.
The `server_tool_use` block with `name: "advisor"` signals that an advisor call is starting. The pause begins when that block closes (`content_block_stop`). During the pause, the stream is quiet except for standard SSE `ping` keepalives emitted roughly every 30 seconds; short advisor calls may show no pings.
When the advisor finishes, the `advisor_tool_result` arrives fully formed in a single `content_block_start` event (no deltas). Executor output then resumes streaming.
A `message_delta` event follows with the updated `usage.iterations` array reflecting the advisor's token counts.
## Usage and billing
Advisor calls run as a separate sub-inference billed at the advisor model's rates. Usage is reported in the `usage.iterations[]` array:
```json
{
"usage": {
"input_tokens": 412,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0,
"output_tokens": 531,
"iterations": [
{
"type": "message",
"input_tokens": 412,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0,
"output_tokens": 89
},
{
"type": "advisor_message",
"model": "claude-opus-4-7",
"input_tokens": 823,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0,
"output_tokens": 1612
},
{
"type": "message",
"input_tokens": 1348,
"cache_read_input_tokens": 412,
"cache_creation_input_tokens": 0,
"output_tokens": 442
}
]
}
}
```
Top-level `usage` fields reflect executor tokens only. Advisor tokens are not rolled into the top-level totals because they are billed at a different rate. Iterations with `type: "advisor_message"` are billed at the advisor model's rates; iterations with `type: "message"` are billed at the executor model's rates.
The aggregation rules differ by field. Top-level `output_tokens` is the sum of all executor iterations. Top-level `input_tokens` and `cache_read_input_tokens` reflect the first executor iteration only; subsequent executor iterations' inputs are not re-summed because they include prior output tokens. Use `usage.iterations` for a full per-iteration breakdown when building cost-tracking logic.
Advisor output is typically 400 to 700 text tokens, or 1,400 to 1,800 tokens total including thinking. The cost savings come from the advisor not generating your full final output; the executor does that at its lower rate.
The top-level `max_tokens` applies to executor output only. It does not bound advisor sub-inference tokens. The advisor's tokens also do not draw from any task budget applied to the executor.
## Advisor prompt caching
There are two independent caching layers.
### Executor-side caching
The `advisor_tool_result` block is cacheable like any other content block. A `cache_control` breakpoint placed after it on a subsequent turn will hit. The executor's prompt always contains the plaintext advice regardless of whether your client received `text` or `encrypted_content`, so caching behavior is identical for both result variants.
### Advisor-side caching
Set `caching` on the tool definition to enable prompt caching for the advisor's own transcript across calls within the same conversation:
```python
tools = [
{
"type": "advisor_20260301",
"name": "advisor",
"model": "claude-opus-4-7",
"caching": {"type": "ephemeral", "ttl": "5m"},
}
]
```
The advisor's prompt on the Nth call is the (N-1)th call's prompt with one more segment appended, so the prefix is stable across calls. With `caching` enabled, each advisor call writes a cache entry; the next call reads up to that point and pays only for the delta. You'll see `cache_read_input_tokens` become nonzero on the second and later `advisor_message` iterations.
**When to enable it:** The cache write costs more than the reads save when the advisor is called two or fewer times per conversation. Caching breaks even at roughly three advisor calls and improves from there. Enable it for long agent loops; keep it off for short tasks.
**Keep it consistent:** Set `caching` once and leave it for the whole conversation. Toggling it off and on mid-conversation causes cache misses.
[`clear_thinking`](/docs/en/build-with-claude/context-editing) with a `keep`
value other than `"all"` shifts the advisor's quoted transcript each turn,
causing advisor-side cache misses. This is a cost degradation only; advice
quality is unaffected. When extended thinking is enabled without explicit
`clear_thinking` configuration, the API defaults to
`keep: {type: "thinking_turns", value: 1}`, which triggers this behavior
(the default on earlier Opus/Sonnet models and all Haiku models; on Opus
4.5+ and Sonnet 4.6+ the default is to keep all turns). Set `keep: "all"`
to preserve advisor cache stability.
## Combining with other tools
The advisor tool composes with other server-side and client-side tools. Add them all to the same `tools` array:
```python
tools = [
{
"type": "web_search_20250305",
"name": "web_search",
"max_uses": 5,
},
{
"type": "advisor_20260301",
"name": "advisor",
"model": "claude-opus-4-7",
},
{
"name": "run_bash",
"description": "Run a bash command",
"input_schema": {
"type": "object",
"properties": {"command": {"type": "string"}},
},
},
]
```
The executor can search the web, call the advisor, and use your custom tools in the same turn. The advisor's plan can inform which tools the executor reaches for next.
| Feature | Interaction |
| ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Batch processing](/docs/en/build-with-claude/batch-processing) | Supported. `usage.iterations` is reported per item. |
| [Token counting](/docs/en/build-with-claude/token-counting) | Returns the executor's first-iteration input tokens only. For a rough advisor estimate, call `count_tokens` with `model` set to the advisor model and the same messages. |
| [Context editing](/docs/en/build-with-claude/context-editing) | `clear_tool_uses` is not fully compatible with advisor tool blocks. With `clear_thinking`, see the earlier caching warning. |
| `pause_turn` | A dangling advisor call ends the response with `stop_reason: "pause_turn"` and the `server_tool_use` block as the last content block. The advisor executes on resumption. See [Server tools](/docs/en/agents-and-tools/tool-use/server-tools#the-server-side-loop-and-pause-turn). |
## Best practices
### Prompting for coding and agent tasks
The advisor tool ships with a built-in description that nudges the executor to call it near the start of complex tasks and when it hits difficulty. For research tasks, no additional prompting is typically needed.
On coding and agent tasks, the advisor produces higher intelligence at similar cost when it reduces total tool calls and conversation length. Two timings drive this improvement:
1. An early first advisor call, after a few exploratory reads are in the transcript.
2. For difficult tasks, a final advisor call after file writes and test outputs are in the transcript.
If your agent exposes other planner-like tools (for example, a todo list tool), prompt the model to call the advisor before those tools so the advisor's plan funnels into them. The suggested system prompt below reinforces the early-call pattern; add your own funnel-in sentence pointing at whichever planner tools your agent exposes.
#### Suggested system prompt for coding tasks
For coding tasks where you want consistent advisor timing and around two to three calls per task, prepend the following blocks to your executor system prompt before any other sentences that mention the advisor. On internal coding evaluations this pattern produced the highest intelligence at near-Sonnet cost.
Timing guidance:
```text
You have access to an `advisor` tool backed by a stronger reviewer model. It takes NO parameters — when you call advisor(), your entire conversation history is automatically forwarded. They see the task, every tool call you've made, every result you've seen.
Call advisor BEFORE substantive work — before writing, before committing to an interpretation, before building on an assumption. If the task requires orientation first (finding files, fetching a source, seeing what's there), do that, then call advisor. Orientation is not substantive work. Writing, editing, and declaring an answer are.
Also call advisor:
- When you believe the task is complete. BEFORE this call, make your deliverable durable: write the file, save the result, commit the change. The advisor call takes time; if the session ends during it, a durable result persists and an unwritten one doesn't.
- When stuck — errors recurring, approach not converging, results that don't fit.
- When considering a change of approach.
On tasks longer than a few steps, call advisor at least once before committing to an approach and once before declaring done. On short reactive tasks where the next action is dictated by tool output you just read, you don't need to keep calling — the advisor adds most of its value on the first call, before the approach crystallizes.
```
How the executor should treat the advice (place directly after the timing block):
```text
Give the advice serious weight. If you follow a step and it fails empirically, or you have primary-source evidence that contradicts a specific claim (the file says X, the paper states Y), adapt. A passing self-test is not evidence the advice is wrong — it's evidence your test doesn't check what the advice is checking.
If you've already retrieved data pointing one way and the advisor points another: don't silently switch. Surface the conflict in one more advisor call — "I found X, you suggest Y, which constraint breaks the tie?" The advisor saw your evidence but may have underweighted it; a reconcile call is cheaper than committing to the wrong branch.
```
#### Trimming advisor output length
Advisor output is the advisor's largest cost driver. To reduce that cost, prepend a single conciseness instruction to the system prompt before any other sentence that mentions the advisor. In internal testing, the following line cut total advisor output tokens by roughly 35 to 45 percent without changing call frequency:
```text
The advisor should respond in under 100 words and use enumerated steps, not explanations.
```
Pair this with the timing block above for the strongest cost-versus-quality tradeoff.
### Pairing with effort settings
For coding tasks, pairing a Sonnet executor at medium [effort](/docs/en/build-with-claude/effort) with an Opus advisor achieves intelligence comparable to Sonnet at default effort, at lower cost. For maximum intelligence, keep the executor at default effort.
### Cost control
- For conversation-level budgets, count advisor calls client-side. When you reach your cap, remove the advisor tool from `tools` **and** strip all `advisor_tool_result` blocks from your message history to avoid a `400 invalid_request_error`.
- Enable `caching` only for conversations where you expect three or more advisor calls.
## Limitations
- **Advisor output does not stream.** Expect a pause in the stream while the sub-inference runs.
- **No built-in conversation-level cap on advisor calls.** Track and cap them client-side.
- **`max_tokens` applies to executor output only.** It does not bound advisor tokens.
- **[Priority Tier](/docs/en/api/service-tiers)** is honored for each model. Priority Tier on the executor model does not extend to the advisor; you need Priority Tier on the advisor model specifically.
---
# Bash tool
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/bash-tool
# Bash tool
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
The bash tool enables Claude to execute shell commands in a persistent bash session, allowing system operations, script execution, and command-line automation. Shell access is a foundational agent capability. On [Terminal-Bench 2.0](https://github.com/terminal-bench/terminal-bench), a benchmark that evaluates real-world terminal tasks using shell-only validation, Claude shows strong performance gains with access to a persistent bash session.
## Overview
The bash tool provides Claude with:
- Persistent bash session that maintains state
- Ability to run any shell command
- Access to environment variables and working directory
- Command chaining and scripting capabilities
For model support, see the [Tool reference](/docs/en/agents-and-tools/tool-use/tool-reference).
## Use cases
- **Development workflows:** Run build commands, tests, and development tools
- **System automation:** Execute scripts, manage files, automate tasks
- **Data processing:** Process files, run analysis scripts, manage datasets
- **Environment setup:** Install packages, configure environments
## Quick start
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"tools": [
{
"type": "bash_20250124",
"name": "bash"
}
],
"messages": [
{
"role": "user",
"content": "List all Python files in the current directory."
}
]
}'
```
```bash CLI
ant messages create \
--model claude-opus-4-7 \
--max-tokens 1024 \
--tool '{type: bash_20250124, name: bash}' \
--message '{role: user, content: List all Python files in the current directory.}'
```
```python Python
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
tools=[{"type": "bash_20250124", "name": "bash"}],
messages=[
{"role": "user", "content": "List all Python files in the current directory."}
],
)
print(response)
```
## How it works
The bash tool maintains a persistent session:
1. Claude determines what command to run
2. You execute the command in a bash shell
3. Return the output (stdout and stderr) to Claude
4. Session state persists between commands (environment variables, working directory)
## Parameters
| Parameter | Required | Description |
|-----------|----------|-------------|
| `command` | Yes* | The bash command to run |
| `restart` | No | Set to `true` to restart the bash session |
*Required unless using `restart`
Run a command:
```json
{
"command": "ls -la *.py"
}
```
Restart the session:
```json
{
"restart": true
}
```
## Example: Multi-step automation
Claude can chain commands to complete complex tasks:
```text
User request:
"Install the requests library and create a simple Python script that
fetches a joke from an API, then run it."
Claude's tool uses:
1. Install package
{"command": "pip install requests"}
2. Create script
{"command": "cat > fetch_joke.py << 'EOF'\nimport requests\nresponse = requests.get('https://official-joke-api.appspot.com/random_joke')\njoke = response.json()\nprint(f\"Setup: {joke['setup']}\")\nprint(f\"Punchline: {joke['punchline']}\")\nEOF"}
3. Run script
{"command": "python fetch_joke.py"}
```
The session maintains state between commands, so files created in step 2 are available in step 3.
## Implement the bash tool
The bash tool is implemented as a schema-less tool. When using this tool, you don't need to provide an input schema as with other tools; the schema is built into Claude's model and can't be modified.
Create a persistent bash session that Claude can interact with:
```python hidelines={-2..-1}
import subprocess
import threading
import queue
class BashSession:
def __init__(self):
self.process = subprocess.Popen(
["/bin/bash"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=0,
)
self.output_queue = queue.Queue()
self.error_queue = queue.Queue()
self._start_readers()
def _start_readers(self): ...
```
Create a function to execute commands and capture output:
```python hidelines={1..2,-1}
class BashSession:
def _read_output(self, timeout): ...
def execute_command(self, command):
# Send command to bash
self.process.stdin.write(command + "\n")
self.process.stdin.flush()
# Capture output with timeout
output = self._read_output(timeout=10)
return output
process = None
```
Extract and execute commands from Claude's responses:
```python hidelines={1..6}
from types import SimpleNamespace as _SN
response = _SN(
content=[_SN(type="tool_use", name="bash", input={"command": "ls"}, id="toolu_01")]
)
bash_session = _SN(restart=lambda: None, execute_command=lambda c: "output")
for content in response.content:
if content.type == "tool_use" and content.name == "bash":
if content.input.get("restart"):
bash_session.restart()
result = "Bash session restarted"
else:
command = content.input.get("command")
result = bash_session.execute_command(command)
# Return result to Claude
tool_result = {
"type": "tool_result",
"tool_use_id": content.id,
"content": result,
}
```
Add validation and restrictions. Use an allowlist rather than a blocklist, since blocklists are easy to bypass. Reject shell operators so chained commands can't slip past the allowlist:
```python
import shlex
ALLOWED_COMMANDS = {"ls", "cat", "echo", "pwd", "grep", "find", "wc", "head", "tail"}
SHELL_OPERATORS = {"&&", "||", "|", ";", "&", ">", "<", ">>"}
def validate_command(command):
# Allow only commands from an explicit allowlist
try:
tokens = shlex.split(command)
except ValueError:
return False, "Could not parse command"
if not tokens:
return False, "Empty command"
executable = tokens[0]
if executable not in ALLOWED_COMMANDS:
return False, f"Command '{executable}' is not in the allowlist"
# Reject shell operators that would chain additional commands
for token in tokens[1:]:
if token in SHELL_OPERATORS or token.startswith(("$", "`")):
return False, f"Shell operator '{token}' is not allowed"
return True, None
```
This check is a first line of defense. For stronger isolation, run validated commands with `shell=False` and pass `shlex.split(command)` as the argument list, so the shell never interprets the string.
### Handle errors
When implementing the bash tool, handle various error scenarios:
If a command takes too long to execute:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: Command timed out after 30 seconds",
"is_error": true
}
]
}
```
If a command doesn't exist:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "bash: nonexistentcommand: command not found",
"is_error": true
}
]
}
```
If there are permission issues:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "bash: /root/sensitive-file: Permission denied",
"is_error": true
}
]
}
```
### Follow implementation best practices
Implement timeouts to prevent hanging commands:
```python hidelines={1..3}
import subprocess
def execute_with_timeout(command, timeout=30):
try:
result = subprocess.run(
command, shell=True, capture_output=True, text=True, timeout=timeout
)
return result.stdout + result.stderr
except subprocess.TimeoutExpired:
return f"Command timed out after {timeout} seconds"
```
Keep the bash session persistent to maintain environment variables and working directory:
```python
# Commands run in the same session maintain state
commands = [
"cd /tmp",
"echo 'Hello' > test.txt",
"cat test.txt", # This works because we're still in /tmp
]
```
Truncate very large outputs to prevent token limit issues:
```python
def truncate_output(output, max_lines=100):
lines = output.split("\n")
if len(lines) > max_lines:
truncated = "\n".join(lines[:max_lines])
return f"{truncated}\n\n... Output truncated ({len(lines)} total lines) ..."
return output
```
Keep an audit trail of executed commands:
```python
import logging
def log_command(command, output, user_id):
logging.info(f"User {user_id} executed: {command}")
logging.info(f"Output: {output[:200]}...") # Log first 200 chars
```
Remove sensitive information from command outputs:
```python
def sanitize_output(output):
# Remove potential secrets or credentials
import re
# Example: Remove AWS credentials
output = re.sub(r"aws_access_key_id\s*=\s*\S+", "aws_access_key_id=***", output)
output = re.sub(
r"aws_secret_access_key\s*=\s*\S+", "aws_secret_access_key=***", output
)
return output
```
## Security
The bash tool provides direct system access. Implement these essential safety measures:
- Running in isolated environments (Docker/VM)
- Implementing command filtering and allowlists
- Setting resource limits (CPU, memory, disk)
- Logging all executed commands
### Key recommendations
- Use `ulimit` to set resource constraints
- Filter dangerous commands (`sudo`, `rm -rf`, etc.)
- Run with minimal user permissions
- Monitor and log all command execution
## Pricing
The bash tool adds **245 input tokens** to your API calls.
Additional tokens are consumed by:
- Command outputs (stdout/stderr)
- Error messages
- Large file contents
See [tool use pricing](/docs/en/agents-and-tools/tool-use/overview#pricing) for complete pricing details.
## Common patterns
### Development workflows
- Running tests: `pytest && coverage report`
- Building projects: `npm install && npm run build`
- Git operations: `git status && git add . && git commit -m "message"`
#### Git-based checkpointing
Git serves as a structured recovery mechanism in long-running agent workflows, not just a way to save changes:
- **Capture a baseline:** Before any agent work begins, commit the current state. This is the known-good starting point.
- **Commit per feature:** Each completed feature gets its own commit. These serve as rollback points if something goes wrong later.
- **Reconstruct state at session start:** Read `git log` alongside a progress file to understand what has already been done and what comes next.
- **Revert on failure:** If work goes sideways, `git checkout` reverts to the last good commit instead of trying to debug a broken state.
### File operations
- Processing data: `wc -l *.csv && ls -lh *.csv`
- Searching files: `find . -name "*.py" | xargs grep "pattern"`
- Creating backups: `tar -czf backup.tar.gz ./data`
### System tasks
- Checking resources: `df -h && free -m`
- Process management: `ps aux | grep python`
- Environment setup: `export PATH=$PATH:/new/path && echo $PATH`
## Limitations
- **No interactive commands:** Cannot handle `vim`, `less`, or password prompts
- **No GUI applications:** Command-line only
- **Session scope:** Bash session state is client-side. The API is stateless. Your application is responsible for maintaining the shell session between turns.
- **Output limits:** Large outputs may be truncated
- **No streaming:** Results returned after completion
## Combining with other tools
The bash tool is most powerful when combined with the [text editor](/docs/en/agents-and-tools/tool-use/text-editor-tool) and other tools.
If you're also using the [code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool), Claude has access to two separate execution environments: your local bash session and Anthropic's sandboxed container. State is not shared between them. See [Using code execution with other execution tools](/docs/en/agents-and-tools/tool-use/code-execution-tool#using-code-execution-with-other-execution-tools) for guidance on prompting Claude to distinguish between environments.
## Next steps
Learn about tool use with Claude
View and edit text files with Claude
---
# Code execution tool
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/code-execution-tool
# Code execution tool
Run Python and bash code in a sandboxed container to analyze data, generate files, and iterate on solutions.
---
Claude can analyze data, create visualizations, perform complex calculations, run system commands, create and edit files, and process uploaded files directly within the API conversation. The code execution tool allows Claude to run Bash commands and manipulate files, including writing code, in a secure, sandboxed environment.
**Code execution is free when used with web search or web fetch.** When `web_search_20260209` or `web_fetch_20260209` is included in your request, there are no additional charges for code execution tool calls beyond the standard input and output token costs. Standard code execution charges apply when these tools are not included.
Code execution is a core primitive for building high-performance agents. It enables dynamic filtering in web search and web fetch tools, allowing Claude to process results before they reach the context window, improving accuracy while reducing token consumption.
Reach out through the [feedback form](https://forms.gle/LTAU6Xn2puCJMi1n6) to share your feedback on this feature.
This feature is **not** eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). Data is retained according to the feature's standard retention policy.
## Model compatibility
The code execution tool is available on the following models:
| Model | Tool versions |
|-------|--------------|
| Claude Opus 4.7 (`claude-opus-4-7`) | `code_execution_20250825`, `code_execution_20260120` |
| Claude Opus 4.6 (`claude-opus-4-6`) | `code_execution_20250825`, `code_execution_20260120` |
| Claude Sonnet 4.6 (`claude-sonnet-4-6`) | `code_execution_20250825`, `code_execution_20260120` |
| Claude Opus 4.5 (`claude-opus-4-5-20251101`) | `code_execution_20250825`, `code_execution_20260120` |
| Claude Sonnet 4.5 (`claude-sonnet-4-5-20250929`) | `code_execution_20250825`, `code_execution_20260120` |
| Claude Haiku 4.5 (`claude-haiku-4-5-20251001`) | `code_execution_20250825` |
| Claude Opus 4.1 (`claude-opus-4-1-20250805`) | `code_execution_20250825` |
| Claude Opus 4 (`claude-opus-4-20250514`) ([deprecated](/docs/en/about-claude/model-deprecations)) | `code_execution_20250825` |
| Claude Sonnet 4 (`claude-sonnet-4-20250514`) ([deprecated](/docs/en/about-claude/model-deprecations)) | `code_execution_20250825` |
`code_execution_20250825` supports Bash commands and file operations and is available on every model listed above. `code_execution_20260120` adds REPL state persistence and [programmatic tool calling](/docs/en/agents-and-tools/tool-use/programmatic-tool-calling) from within the sandbox, and is available on Opus 4.5+ and Sonnet 4.5+ only. If you're still using the legacy `code_execution_20250522` (Python only), see [Upgrade to latest tool version](#upgrade-to-latest-tool-version) to migrate from it.
Older tool versions are not guaranteed to be backwards-compatible with newer models. Always use the tool version that corresponds to your model version.
## Platform availability
Code execution is available on:
- **Claude API** (Anthropic)
- **[Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws)**
- **[Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry)**
Code execution is not currently available on Amazon Bedrock or Vertex AI.
For [Claude Mythos Preview](https://anthropic.com/glasswing), code execution is supported on the Claude API and Microsoft Foundry only. It is not available for Mythos Preview on Amazon Bedrock, Vertex AI, or Claude Platform on AWS.
## Quick start
Here's a simple example that asks Claude to perform a calculation:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": "Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
}
],
"tools": [{
"type": "code_execution_20250825",
"name": "code_execution"
}]
}'
```
```bash CLI
ant messages create \
--model claude-opus-4-7 \
--max-tokens 4096 \
--message '{role: user, content: "Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"}' \
--tool '{type: code_execution_20250825, name: code_execution}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
messages=[
{
"role": "user",
"content": "Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]",
}
],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
print(response)
```
```typescript TypeScript hidelines={1..5,-3..-1}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function main() {
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{
role: "user",
content: "Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
}
],
tools: [
{
type: "code_execution_20250825",
name: "code_execution"
}
]
});
console.log(response);
}
main().catch(console.error);
```
```csharp C# hidelines={1..11,-2..}
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Messages = [
new() {
Role = Role.User,
Content = "Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
}
],
Tools = [new ToolUnion(new CodeExecutionTool20250825())]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]")),
},
Tools: []anthropic.BetaToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.BetaCodeExecutionTool20250825Param{}},
},
Betas: []anthropic.AnthropicBeta{"code-execution-2025-08-25"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..5,7..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.CodeExecutionTool20250825;
public class CodeExecution {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(4096L)
.addUserMessage("Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]")
.addTool(CodeExecutionTool20250825.builder().build())
.build();
Message response = client.messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 4096,
messages: [
[
'role' => 'user',
'content' => 'Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]'
]
],
model: 'claude-opus-4-7',
tools: [
[
'type' => 'code_execution_20250825',
'name' => 'code_execution'
]
],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{
role: "user",
content: "Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
}
],
tools: [
{
type: "code_execution_20250825",
name: "code_execution"
}
]
)
puts message
```
## How code execution works
When you add the code execution tool to your API request:
1. Claude evaluates whether code execution would help answer your question
2. The tool automatically provides Claude with the following capabilities:
- **Bash commands**: Execute shell commands for system operations and package management
- **File operations**: Create, view, and edit files directly, including writing code
3. Claude can use any combination of these capabilities in a single request
4. All operations run in a secure sandbox environment
5. Claude provides results with any generated charts, calculations, or analysis
## Using code execution with other execution tools
When you provide code execution alongside client-provided tools that also run code (such as a [bash tool](/docs/en/agents-and-tools/tool-use/bash-tool) or custom REPL), Claude is operating in a multi-computer environment. The code execution tool runs in Anthropic's sandboxed container, while your client-provided tools run in a separate environment that you control. Claude can sometimes confuse these environments, attempting to use the wrong tool or assuming state is shared between them.
To avoid this, add instructions to your system prompt that clarify the distinction:
```text
When multiple code execution environments are available, be aware that:
- Variables, files, and state do NOT persist between different execution environments
- Use the code_execution tool for general-purpose computation in Anthropic's sandboxed environment
- Use client-provided execution tools (e.g., bash) when you need access to the user's local system, files, or data
- If you need to pass results between environments, explicitly include outputs in subsequent tool calls rather than assuming shared state
```
This is especially important when combining code execution with [web search](/docs/en/agents-and-tools/tool-use/web-search-tool) or [web fetch](/docs/en/agents-and-tools/tool-use/web-fetch-tool), which enable code execution automatically. If your application already provides a client-side shell tool, the automatic code execution creates a second execution environment that Claude needs to distinguish between.
## How to use the tool
### Upload and analyze your own files
To analyze your own data files (such as CSV, Excel, or images), upload them through the Files API and reference them in your request:
Using the Files API with Code Execution requires the Files API beta header: `"anthropic-beta": "files-api-2025-04-14"`
The Python environment can process various file types uploaded through the Files API, including:
- CSV
- Excel (.xlsx, .xls)
- JSON
- XML
- Images (JPEG, PNG, GIF, WebP)
- Text files (.txt, .md, .py, and others)
#### Upload and analyze files
1. **Upload your file** using the [Files API](/docs/en/build-with-claude/files)
2. **Reference the file** in your message using a `container_upload` content block
3. **Include the code execution tool** in your API request
```bash cURL hidelines={1..2}
cd "$(mktemp -d)"
printf 'name,value\nfoo,1\nbar,2\n' > data.csv
# First, upload a file
curl https://api.anthropic.com/v1/files \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "anthropic-beta: files-api-2025-04-14" \
--form 'file=@"data.csv"'
# Then use the file_id with code execution
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "anthropic-beta: files-api-2025-04-14" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "Analyze this CSV data"},
{"type": "container_upload", "file_id": "file_abc123"}
]
}],
"tools": [{
"type": "code_execution_20250825",
"name": "code_execution"
}]
}'
```
```bash CLI hidelines={1}
printf 'name,value\nfoo,1\nbar,2\n' > data.csv
# Upload a file
FILE_ID=$(ant beta:files upload \
--file ./data.csv \
--transform id --raw-output)
# Use the file_id with code execution
ant beta:messages create \
--beta files-api-2025-04-14 <beta->files->upload(
file: FileParam::fromResource(fopen('data.csv', 'r')),
);
// Use the file_id with code execution
$response = $client->beta->messages->create(
maxTokens: 4096,
messages: [
[
'role' => 'user',
'content' => [
['type' => 'text', 'text' => 'Analyze this CSV data'],
['type' => 'container_upload', 'file_id' => $fileObject->id],
],
],
],
model: 'claude-opus-4-7',
betas: ['files-api-2025-04-14'],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution'],
],
);
echo $response;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
# Upload a file
file_object = client.beta.files.upload(
file: File.open("data.csv", "rb")
)
# Use the file_id with code execution
response = client.beta.messages.create(
model: "claude-opus-4-7",
betas: ["files-api-2025-04-14"],
max_tokens: 4096,
messages: [
{
role: "user",
content: [
{ type: "text", text: "Analyze this CSV data" },
{ type: "container_upload", file_id: file_object.id }
]
}
],
tools: [
{ type: "code_execution_20250825", name: "code_execution" }
]
)
puts response
```
### Retrieve generated files
When Claude creates files during code execution, you can retrieve these files using the Files API:
```bash CLI nocheck
# Request code execution that creates files; extract file_ids from tool results
TOOL_RESULT='content.#(type=="bash_code_execution_tool_result")#'
FILE_IDS=$(ant beta:messages create \
--beta files-api-2025-04-14 \
--transform "${TOOL_RESULT}.content.content|@flatten|#.file_id" \
--format yaml \
--model claude-opus-4-7 \
--max-tokens 4096 \
--message '{role: user, content: Create a matplotlib visualization and save it as output.png}' \
--tool '{type: code_execution_20250825, name: code_execution}'
)
# Download each created file
while IFS= read -r LINE; do
[[ "$LINE" != "- "* ]] && continue
FILE_ID="${LINE#- }"
FILENAME=$(ant beta:files retrieve-metadata \
--file-id "$FILE_ID" \
--transform filename --raw-output)
ant beta:files download \
--file-id "$FILE_ID" \
--output "$FILENAME" > /dev/null
printf 'Downloaded: %s\n' "$FILENAME"
done <<< "$FILE_IDS"
```
```python Python nocheck hidelines={1..2}
from anthropic import Anthropic
# Initialize the client
client = Anthropic()
# Request code execution that creates files
response = client.beta.messages.create(
model="claude-opus-4-7",
betas=["files-api-2025-04-14"],
max_tokens=4096,
messages=[
{
"role": "user",
"content": "Create a matplotlib visualization and save it as output.png",
}
],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
# Extract file IDs from the response
def extract_file_ids(response):
file_ids = []
for item in response.content:
if item.type == "bash_code_execution_tool_result":
content_item = item.content
if content_item.type == "bash_code_execution_result":
# concrete-typed list: List[BashCodeExecutionOutputBlock]
for file in content_item.content:
file_ids.append(file.file_id)
return file_ids
# Download the created files
for file_id in extract_file_ids(response):
file_metadata = client.beta.files.retrieve_metadata(file_id)
file_content = client.beta.files.download(file_id)
file_content.write_to_file(file_metadata.filename)
print(f"Downloaded: {file_metadata.filename}")
```
```typescript TypeScript nocheck hidelines={5..6,-3..-1}
import Anthropic from "@anthropic-ai/sdk";
import { writeFile } from "fs/promises";
const client = new Anthropic();
async function main() {
// Request code execution that creates files
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
betas: ["files-api-2025-04-14"],
max_tokens: 4096,
messages: [
{
role: "user",
content: "Create a matplotlib visualization and save it as output.png"
}
],
tools: [
{
type: "code_execution_20250825",
name: "code_execution"
}
]
});
// Extract file IDs from the response
for (const item of response.content) {
if (item.type === "bash_code_execution_tool_result") {
const contentItem = item.content;
if (contentItem.type === "bash_code_execution_result" && contentItem.content) {
// concrete-typed list: BashCodeExecutionOutputBlock
for (const file of contentItem.content) {
const fileMetadata = await client.beta.files.retrieveMetadata(file.file_id);
const fileResponse = await client.beta.files.download(file.file_id);
const fileBytes = Buffer.from(await fileResponse.arrayBuffer());
await writeFile(fileMetadata.filename, fileBytes);
console.log(`Downloaded: ${fileMetadata.filename}`);
}
}
}
}
}
main().catch(console.error);
```
```csharp C# nocheck hidelines={1..14,-2..}
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using Anthropic;
using Anthropic.Models.Messages;
public class Program
{
static async Task Main(string[] args)
{
var client = new AnthropicClient();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Messages = [
new() {
Role = Role.User,
Content = "Create a matplotlib visualization and save it as output.png"
}
],
Tools = [new ToolUnion(new CodeExecutionTool20250825())]
};
var response = await client.Beta.Messages.Create(parameters, ["files-api-2025-04-14"]);
var fileIds = ExtractFileIds(response);
foreach (var fileId in fileIds)
{
var fileMetadata = await client.Beta.Files.RetrieveMetadata(fileId);
var fileContent = await client.Beta.Files.Download(fileId);
await File.WriteAllBytesAsync(fileMetadata.Filename, fileContent);
Console.WriteLine($"Downloaded: {fileMetadata.Filename}");
}
}
static List ExtractFileIds(dynamic response)
{
var fileIds = new List();
foreach (var item in response.Content)
{
if (item.Type == "bash_code_execution_tool_result")
{
var contentItem = item.Content;
if (contentItem.Type == "bash_code_execution_result")
{
// concrete-typed list: BetaBashCodeExecutionOutputBlock
foreach (var file in contentItem.Content)
{
if (file.FileId != null)
{
fileIds.Add(file.FileId);
}
}
}
}
}
return fileIds;
}
}
```
```go Go nocheck hidelines={1..13,61..62}
package main
import (
"context"
"fmt"
"io"
"log"
"os"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Create a matplotlib visualization and save it as output.png")),
},
Tools: []anthropic.BetaToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.BetaCodeExecutionTool20250825Param{}},
},
Betas: []anthropic.AnthropicBeta{
"code-execution-2025-08-25",
anthropic.AnthropicBetaFilesAPI2025_04_14,
},
})
if err != nil {
log.Fatal(err)
}
fileIDs := extractFileIDs(response)
for _, fileID := range fileIDs {
fileMetadata, err := client.Beta.Files.GetMetadata(context.TODO(), fileID, anthropic.BetaFileGetMetadataParams{})
if err != nil {
log.Fatal(err)
}
fileContent, err := client.Beta.Files.Download(context.TODO(), fileID, anthropic.BetaFileDownloadParams{})
if err != nil {
log.Fatal(err)
}
outFile, err := os.Create(fileMetadata.Filename)
if err != nil {
log.Fatal(err)
}
_, err = io.Copy(outFile, fileContent.Body)
if err != nil {
log.Fatal(err)
}
outFile.Close()
fileContent.Body.Close()
fmt.Printf("Downloaded: %s\n", fileMetadata.Filename)
}
}
func extractFileIDs(response *anthropic.BetaMessage) []string {
var fileIDs []string
for _, item := range response.Content {
switch variant := item.AsAny().(type) {
case anthropic.BetaBashCodeExecutionToolResultBlock:
// concrete-typed list: BashCodeExecutionOutputBlock
for _, file := range variant.Content.Content {
if file.FileID != "" {
fileIDs = append(fileIDs, file.FileID)
}
}
}
}
return fileIDs
}
```
```java Java nocheck hidelines={1..6,11..15,39..40}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.http.HttpResponse;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaContentBlock;
import com.anthropic.models.beta.messages.BetaCodeExecutionTool20250825;
import com.anthropic.models.beta.messages.BetaBashCodeExecutionResultBlock;
import com.anthropic.models.beta.messages.BetaBashCodeExecutionOutputBlock;
import com.anthropic.models.beta.files.FileMetadata;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
void main() throws Exception {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.addBeta("files-api-2025-04-14")
.maxTokens(4096L)
.addUserMessage("Create a matplotlib visualization and save it as output.png")
.addTool(BetaCodeExecutionTool20250825.builder().build())
.build();
BetaMessage response = client.beta().messages().create(params);
List fileIds = extractFileIds(response);
for (String fileId : fileIds) {
FileMetadata fileMetadata = client.beta().files().retrieveMetadata(fileId);
try (HttpResponse fileContent = client.beta().files().download(fileId)) {
try (FileOutputStream fos = new FileOutputStream(fileMetadata.filename())) {
fileContent.body().transferTo(fos);
}
}
IO.println("Downloaded: " + fileMetadata.filename());
}
}
List extractFileIds(BetaMessage response) {
List fileIds = new ArrayList<>();
// .ifPresent() is the discriminator guard (not concrete-typed; scanner can't see lambda guards)
for (BetaContentBlock item : response.content()) {
item.bashCodeExecutionToolResult().ifPresent(toolResult -> {
if (toolResult.content().isBetaBashCodeExecutionResultBlock()) {
BetaBashCodeExecutionResultBlock result =
toolResult.content().asBetaBashCodeExecutionResultBlock();
// concrete-typed list: BetaBashCodeExecutionOutputBlock
for (BetaBashCodeExecutionOutputBlock output : result.content()) {
fileIds.add(output.fileId());
}
}
});
}
return fileIds;
}
```
```php PHP hidelines={1..2,4..5} nocheck
beta->messages->create(
maxTokens: 4096,
messages: [
[
'role' => 'user',
'content' => 'Create a matplotlib visualization and save it as output.png',
],
],
model: 'claude-opus-4-7',
betas: ['files-api-2025-04-14'],
tools: [
[
'type' => 'code_execution_20250825',
'name' => 'code_execution',
],
],
);
function extractFileIds(BetaMessage $response): array
{
$fileIds = [];
foreach ($response->content as $item) {
if ($item->type !== 'bash_code_execution_tool_result') {
continue;
}
$contentItem = $item->content;
if ($contentItem->type !== 'bash_code_execution_result') {
continue;
}
// concrete-typed list: BashCodeExecutionOutputBlock
foreach ($contentItem->content as $file) {
$fileIds[] = $file->fileID;
}
}
return $fileIds;
}
foreach (extractFileIds($response) as $fileId) {
$fileMetadata = $client->beta->files->retrieveMetadata($fileId);
$fileContent = $client->beta->files->download($fileId);
file_put_contents($fileMetadata->filename, $fileContent);
echo "Downloaded: {$fileMetadata->filename}\n";
}
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.beta.messages.create(
model: "claude-opus-4-7",
betas: ["files-api-2025-04-14"],
max_tokens: 4096,
messages: [
{
role: "user",
content: "Create a matplotlib visualization and save it as output.png"
}
],
tools: [
{
type: "code_execution_20250825",
name: "code_execution"
}
]
)
def extract_file_ids(response)
file_ids = []
response.content.each do |item|
if item.type == :bash_code_execution_tool_result
content_item = item.content
if content_item.type == :bash_code_execution_result
# concrete-typed list: BashCodeExecutionOutputBlock
content_item.content.each do |file|
file_ids << file.file_id
end
end
end
end
file_ids
end
extract_file_ids(response).each do |file_id|
file_metadata = client.beta.files.retrieve_metadata(file_id)
file_content = client.beta.files.download(file_id)
File.open(file_metadata.filename, "wb") do |f|
f.write(file_content.read)
end
puts "Downloaded: #{file_metadata.filename}"
end
```
## Tool definition
The code execution tool requires no additional parameters:
```json JSON
{
"type": "code_execution_20250825",
"name": "code_execution"
}
```
When this tool is provided, Claude automatically gains access to two sub-tools:
- `bash_code_execution`: Run shell commands
- `text_editor_code_execution`: View, create, and edit files, including writing code
## Response format
The code execution tool can return two types of results depending on the operation:
### Bash command response
```json Output hidelines={1,-1}
[
{
"type": "server_tool_use",
"id": "srvtoolu_01B3C4D5E6F7G8H9I0J1K2L3",
"name": "bash_code_execution",
"input": {
"command": "ls -la | head -5"
}
},
{
"type": "bash_code_execution_tool_result",
"tool_use_id": "srvtoolu_01B3C4D5E6F7G8H9I0J1K2L3",
"content": {
"type": "bash_code_execution_result",
"stdout": "total 24\ndrwxr-xr-x 2 user user 4096 Jan 1 12:00 .\ndrwxr-xr-x 3 user user 4096 Jan 1 11:00 ..\n-rw-r--r-- 1 user user 220 Jan 1 12:00 data.csv\n-rw-r--r-- 1 user user 180 Jan 1 12:00 config.json",
"stderr": "",
"return_code": 0
}
}
]
```
### File operation responses
**View file:**
```json Output hidelines={1,-1}
[
{
"type": "server_tool_use",
"id": "srvtoolu_01C4D5E6F7G8H9I0J1K2L3M4",
"name": "text_editor_code_execution",
"input": {
"command": "view",
"path": "config.json"
}
},
{
"type": "text_editor_code_execution_tool_result",
"tool_use_id": "srvtoolu_01C4D5E6F7G8H9I0J1K2L3M4",
"content": {
"type": "text_editor_code_execution_result",
"file_type": "text",
"content": "{\n \"setting\": \"value\",\n \"debug\": true\n}",
"numLines": 4,
"startLine": 1,
"totalLines": 4
}
}
]
```
**Create file:**
```json Output hidelines={1,-1}
[
{
"type": "server_tool_use",
"id": "srvtoolu_01D5E6F7G8H9I0J1K2L3M4N5",
"name": "text_editor_code_execution",
"input": {
"command": "create",
"path": "new_file.txt",
"file_text": "Hello, World!"
}
},
{
"type": "text_editor_code_execution_tool_result",
"tool_use_id": "srvtoolu_01D5E6F7G8H9I0J1K2L3M4N5",
"content": {
"type": "text_editor_code_execution_result",
"is_file_update": false
}
}
]
```
**Edit file (str_replace):**
```json Output hidelines={1,-1}
[
{
"type": "server_tool_use",
"id": "srvtoolu_01E6F7G8H9I0J1K2L3M4N5O6",
"name": "text_editor_code_execution",
"input": {
"command": "str_replace",
"path": "config.json",
"old_str": "\"debug\": true",
"new_str": "\"debug\": false"
}
},
{
"type": "text_editor_code_execution_tool_result",
"tool_use_id": "srvtoolu_01E6F7G8H9I0J1K2L3M4N5O6",
"content": {
"type": "text_editor_code_execution_result",
"oldStart": 3,
"oldLines": 1,
"newStart": 3,
"newLines": 1,
"lines": ["- \"debug\": true", "+ \"debug\": false"]
}
}
]
```
### Results
All execution results include:
- `stdout`: Output from successful execution
- `stderr`: Error messages if execution fails
- `return_code`: 0 for success, non-zero for failure
Additional fields for file operations:
- **View**: `file_type`, `content`, `numLines`, `startLine`, `totalLines`
- **Create**: `is_file_update` (whether file already existed)
- **Edit**: `oldStart`, `oldLines`, `newStart`, `newLines`, `lines` (diff format)
### Errors
Each tool type can return specific errors:
**Common errors (all tools):**
```json Output
{
"type": "bash_code_execution_tool_result",
"tool_use_id": "srvtoolu_01VfmxgZ46TiHbmXgy928hQR",
"content": {
"type": "bash_code_execution_tool_result_error",
"error_code": "unavailable"
}
}
```
**Error codes by tool type:**
| Tool | Error Code | Description |
|------|-----------|-------------|
| All tools | `unavailable` | The tool is temporarily unavailable |
| All tools | `execution_time_exceeded` | Execution exceeded maximum time limit |
| All tools | `container_expired` | Container expired and is no longer available |
| All tools | `invalid_tool_input` | Invalid parameters provided to the tool |
| All tools | `too_many_requests` | Rate limit exceeded for tool usage |
| bash | `output_file_too_large` | Command output exceeded the maximum size |
| text_editor | `file_not_found` | File doesn't exist (for view/edit operations) |
| text_editor | `string_not_found` | The `old_str` not found in file (for str_replace) |
#### `pause_turn` stop reason
The response may include a `pause_turn` stop reason, which indicates that the API paused a long-running turn. You may
provide the response back as-is in a subsequent request to let Claude continue its turn, or modify the content if you
wish to interrupt the conversation.
## Containers
The code execution tool runs in a secure, containerized environment designed specifically for code execution, with a higher focus on Python.
### Runtime environment
- **Python version**: 3.11.12
- **Operating system**: Linux-based container
- **Architecture**: x86_64 (AMD64)
### Resource limits
- **Memory**: 5GiB RAM
- **Disk space**: 5GiB workspace storage
- **CPU**: 1 CPU
### Networking and security
- **Internet access**: Completely disabled for security
- **External connections**: No outbound network requests permitted
- **Sandbox isolation**: Full isolation from host system and other containers
- **File access**: Limited to workspace directory only
- **Workspace scoping**: Like [Files](/docs/en/build-with-claude/files), containers are scoped to the workspace of the API key
- **Expiration**: Containers expire 30 days after creation
### Pre-installed libraries
The sandboxed Python environment includes these commonly used libraries:
- **Data Science**: pandas, numpy, scipy, scikit-learn, statsmodels
- **Visualization**: matplotlib, seaborn
- **File Processing**: pyarrow, openpyxl, xlsxwriter, xlrd, pillow, python-pptx, python-docx, pypdf, pdfplumber, pypdfium2, pdf2image, pdfkit, tabula-py, reportlab[pycairo], Img2pdf
- **Math & Computing**: sympy, mpmath
- **Utilities**: tqdm, python-dateutil, pytz, joblib, unzip, unrar, 7zip, bc, rg (ripgrep), fd, sqlite
## Container reuse
You can reuse an existing container across multiple API requests by providing the container ID from a previous response.
This allows you to maintain created files between requests.
### Example
```bash cURL hidelines={1}
cd "$(mktemp -d)"
# First request: Create a file with a random number
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"messages": [{
"role": "user",
"content": "Write a file with a random number and save it to \"/tmp/number.txt\""
}],
"tools": [{
"type": "code_execution_20250825",
"name": "code_execution"
}]
}' > response1.json
# Extract container ID from the response (using jq)
CONTAINER_ID=$(jq -r '.container.id' response1.json)
# Second request: Reuse the container to read the file
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"container": "'$CONTAINER_ID'",
"model": "claude-opus-4-7",
"max_tokens": 4096,
"messages": [{
"role": "user",
"content": "Read the number from \"/tmp/number.txt\" and calculate its square"
}],
"tools": [{
"type": "code_execution_20250825",
"name": "code_execution"
}]
}'
```
```bash CLI
# First request: Create a file with a random number
CONTAINER_ID=$(ant messages create \
--transform container.id --raw-output \
--model claude-opus-4-7 \
--max-tokens 4096 \
--message '{role: user, content: Write a file with a random number and save it to "/tmp/number.txt"}' \
--tool '{type: code_execution_20250825, name: code_execution}'
)
# Second request: Reuse the container to read the file
ant messages create --container "$CONTAINER_ID" \
--model claude-opus-4-7 \
--max-tokens 4096 \
--message '{role: user, content: Read the number from "/tmp/number.txt" and calculate its square}' \
--tool '{type: code_execution_20250825, name: code_execution}'
```
```python Python hidelines={1..6}
import os
from anthropic import Anthropic
# Initialize the client
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
# First request: Create a file with a random number
response1 = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
messages=[
{
"role": "user",
"content": "Write a file with a random number and save it to '/tmp/number.txt'",
}
],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
# Extract the container ID from the first response
container_id = response1.container.id
# Second request: Reuse the container to read the file
response2 = client.messages.create(
container=container_id, # Reuse the same container
model="claude-opus-4-7",
max_tokens=4096,
messages=[
{
"role": "user",
"content": "Read the number from '/tmp/number.txt' and calculate its square",
}
],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
print(response2)
```
```typescript TypeScript hidelines={1..5,-3..-1}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function main() {
// First request: Create a file with a random number
const response1 = await client.beta.messages.create({
model: "claude-opus-4-7",
betas: ["code-execution-2025-08-25"],
max_tokens: 4096,
messages: [
{
role: "user",
content: "Write a file with a random number and save it to '/tmp/number.txt'"
}
],
tools: [
{
type: "code_execution_20250825",
name: "code_execution"
}
]
});
// Extract the container ID from the first response
const containerId = response1.container!.id;
// Second request: Reuse the container to read the file
const response2 = await client.beta.messages.create({
container: containerId,
model: "claude-opus-4-7",
betas: ["code-execution-2025-08-25"],
max_tokens: 4096,
messages: [
{
role: "user",
content: "Read the number from '/tmp/number.txt' and calculate its square"
}
],
tools: [
{
type: "code_execution_20250825",
name: "code_execution"
}
]
});
console.log(response2.content);
}
main().catch(console.error);
```
```csharp C# hidelines={1..11,-2..}
using Anthropic;
using Anthropic.Models.Messages;
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
AnthropicClient client = new();
var parameters1 = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Messages = [new() { Role = Role.User, Content = "Write a file with a random number and save it to '/tmp/number.txt'" }],
Tools = [new ToolUnion(new CodeExecutionTool20250825())]
};
var response1 = await client.Messages.Create(parameters1);
var containerId = response1.Container!.ID;
var parameters2 = new MessageCreateParams
{
Container = containerId,
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Messages = [new() { Role = Role.User, Content = "Read the number from '/tmp/number.txt' and calculate its square" }],
Tools = [new ToolUnion(new CodeExecutionTool20250825())]
};
var response2 = await client.Messages.Create(parameters2);
Console.WriteLine(response2);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response1, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Write a file with a random number and save it to '/tmp/number.txt'")),
},
Tools: []anthropic.BetaToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.BetaCodeExecutionTool20250825Param{}},
},
Betas: []anthropic.AnthropicBeta{"code-execution-2025-08-25"},
})
if err != nil {
log.Fatal(err)
}
containerID := response1.Container.ID
response2, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Container: anthropic.BetaMessageNewParamsContainerUnion{
OfString: anthropic.String(containerID),
},
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Read the number from '/tmp/number.txt' and calculate its square")),
},
Tools: []anthropic.BetaToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.BetaCodeExecutionTool20250825Param{}},
},
Betas: []anthropic.AnthropicBeta{"code-execution-2025-08-25"},
})
if err != nil {
log.Fatal(err)
}
for _, block := range response2.Content {
if block.Type == "text" {
fmt.Println(block.Text)
}
}
}
```
```java Java hidelines={1..5,7..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.CodeExecutionTool20250825;
public class ContainerReuse {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params1 = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(4096L)
.addUserMessage("Write a file with a random number and save it to '/tmp/number.txt'")
.addTool(CodeExecutionTool20250825.builder().build())
.build();
Message response1 = client.messages().create(params1);
String containerId = response1.container().get().id();
MessageCreateParams params2 = MessageCreateParams.builder()
.container(containerId)
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(4096L)
.addUserMessage("Read the number from '/tmp/number.txt' and calculate its square")
.addTool(CodeExecutionTool20250825.builder().build())
.build();
Message response2 = client.messages().create(params2);
System.out.println(response2);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => "Write a file with a random number and save it to '/tmp/number.txt'"]
],
model: 'claude-opus-4-7',
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
],
);
$containerId = $response1->container->id;
$response2 = $client->messages->create(
container: $containerId,
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => "Read the number from '/tmp/number.txt' and calculate its square"]
],
model: 'claude-opus-4-7',
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
],
);
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response1 = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{ role: "user", content: "Write a file with a random number and save it to '/tmp/number.txt'" }
],
tools: [
{ type: "code_execution_20250825", name: "code_execution" }
]
)
container_id = response1.container.id
response2 = client.messages.create(
container: container_id,
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{ role: "user", content: "Read the number from '/tmp/number.txt' and calculate its square" }
],
tools: [
{ type: "code_execution_20250825", name: "code_execution" }
]
)
puts response2.content
```
## Streaming
With streaming enabled, you'll receive code execution events as they occur:
```sse
event: content_block_start
data: {"type": "content_block_start", "index": 1, "content_block": {"type": "server_tool_use", "id": "srvtoolu_xyz789", "name": "code_execution"}}
// Code execution streamed
event: content_block_delta
data: {"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "{\"code\":\"import pandas as pd\\ndf = pd.read_csv('data.csv')\\nprint(df.head())\"}"}}
// Pause while code executes
// Execution results streamed
event: content_block_start
data: {"type": "content_block_start", "index": 2, "content_block": {"type": "code_execution_tool_result", "tool_use_id": "srvtoolu_xyz789", "content": {"stdout": " A B C\n0 1 2 3\n1 4 5 6", "stderr": ""}}}
```
## Batch requests
You can include the code execution tool in the [Messages Batches API](/docs/en/build-with-claude/batch-processing). Code execution tool calls through the Messages Batches API are priced the same as those in regular Messages API requests.
## Usage and pricing
**Code execution is free when used with web search or web fetch.** When `web_search_20260209` or `web_fetch_20260209` is included in your API request, there are no additional charges for code execution tool calls beyond the standard input and output token costs.
When used without these tools, code execution is billed by execution time, tracked separately from token usage:
- Execution time has a minimum of 5 minutes
- Each organization receives **1,550 free hours** of usage per month
- Additional usage beyond 1,550 hours is billed at **$0.05 per hour, per container**
- If files are included in the request, execution time is billed even if the tool is not invoked, due to files being preloaded onto the container
Code execution usage is tracked in the response:
```json
"usage": {
"input_tokens": 105,
"output_tokens": 239,
"server_tool_use": {
"code_execution_requests": 1
}
}
```
## Upgrade to latest tool version
By upgrading to `code-execution-2025-08-25`, you get access to file manipulation and Bash capabilities, including code in multiple languages. There is no price difference.
### What's changed
| Component | Legacy | Current |
|-----------|------------------|----------------------------|
| Beta header | `code-execution-2025-05-22` | `code-execution-2025-08-25` |
| Tool type | `code_execution_20250522` | `code_execution_20250825` |
| Capabilities | Python only | Bash commands, file operations |
| Response types | `code_execution_result` | `bash_code_execution_result`, `text_editor_code_execution_result` |
### Backward compatibility
- All existing Python code execution continues to work exactly as before
- No changes required to existing Python-only workflows
### Upgrade steps
To upgrade, update the tool type in your API requests:
```diff
- "type": "code_execution_20250522"
+ "type": "code_execution_20250825"
```
**Review response handling** (if parsing responses programmatically):
- The previous blocks for Python execution responses will no longer be sent
- Instead, new response types for Bash and file operations will be sent (see Response Format section)
## Programmatic tool calling
For running tools inside the code execution container, see [Programmatic tool calling](/docs/en/agents-and-tools/tool-use/programmatic-tool-calling).
## Data retention
Code execution runs in server-side sandbox containers. Container data, including execution artifacts, uploaded files, and outputs, is retained for up to 30 days. This retention applies to all data processed within the container environment. Files that code execution creates in the [Files API](/docs/en/build-with-claude/files) (retrievable via `client.beta.files.download()`) persist until explicitly deleted.
For ZDR eligibility across all features, see [API and data retention](/docs/en/manage-claude/api-and-data-retention).
## Using code execution with Agent Skills
The code execution tool enables Claude to use [Agent Skills](/docs/en/agents-and-tools/agent-skills/overview). Skills are modular capabilities consisting of instructions, scripts, and resources that extend Claude's functionality.
Learn more in [Agent Skills](/docs/en/agents-and-tools/agent-skills/overview) and [Using Agent Skills with the API](/docs/en/build-with-claude/skills-guide).
---
# Computer use tool
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/computer-use-tool
# Computer use tool
---
Claude can interact with computer environments through the computer use tool, which provides screenshot capabilities and mouse/keyboard control for autonomous desktop interaction. On [WebArena](https://webarena.dev/), a benchmark for autonomous web navigation across real websites, Claude achieves state-of-the-art results among single-agent systems, demonstrating strong ability to complete multi-step browser tasks end to end.
Computer use is in beta and requires a [beta header](/docs/en/api/beta-headers):
- `"computer-use-2025-11-24"` for Claude Opus 4.7, Claude Opus 4.6, Claude Sonnet 4.6, and Claude Opus 4.5
- `"computer-use-2025-01-24"` for Claude Sonnet 4.5, Claude Haiku 4.5, Claude Opus 4.1, Claude Sonnet 4 ([deprecated](/docs/en/about-claude/model-deprecations)), and Claude Opus 4 ([deprecated](/docs/en/about-claude/model-deprecations))
Reach out through the [feedback form](https://forms.gle/H6UFuXaaLywri9hz6) to share your feedback on this feature.
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
## Overview
Computer use is a beta feature that enables Claude to interact with desktop environments. This tool provides:
- **Screenshot capture**: See what's currently displayed on screen
- **Mouse control**: Click, drag, and move the cursor
- **Keyboard input**: Type text and use keyboard shortcuts
- **Desktop automation**: Interact with any application or interface
While computer use can be augmented with other tools like bash and text editor for more comprehensive automation workflows, computer use specifically refers to the computer use tool's capability to see and control desktop environments.
For model support, see the [Tool reference](/docs/en/agents-and-tools/tool-use/tool-reference).
## Security considerations
Computer use is a beta feature with unique risks distinct from standard API features. These risks are heightened when interacting with the internet.
To minimize risks, consider taking precautions such as:
1. Using a dedicated virtual machine or container with minimal privileges to prevent direct system attacks or accidents.
2. Avoiding giving the model access to sensitive data, such as account login information, to prevent information theft.
3. Limiting internet access to an allowlist of domains to reduce exposure to malicious content.
4. Asking a human to confirm decisions that may result in meaningful real-world consequences as well as any tasks requiring affirmative consent, such as accepting cookies, executing financial transactions, or agreeing to terms of service.
In some circumstances, Claude will follow commands found in content even if it conflicts with the user's instructions. For example, Claude instructions on webpages or contained in images may override instructions or cause Claude to make mistakes. Take precautions to isolate Claude from sensitive data and actions to avoid risks related to prompt injection.
The model has been trained to resist these prompt injections, and an extra layer of defense has been added. If you use the computer use tools, classifiers will automatically run on your prompts to flag potential instances of prompt injections. When these classifiers identify potential prompt injections in screenshots, they will automatically steer the model to ask for user confirmation before proceeding with the next action. This extra protection won't be ideal for every use case (for example, use cases without a human in the loop), so if you'd like to opt out and turn it off, please [contact support](https://support.claude.com/en/).
These precautions remain important even with the classifier defense layer in place.
Inform end users of relevant risks and obtain their consent prior to enabling computer use in your own products.
Get started quickly with the computer use reference implementation that includes a web interface, Docker container, example tool implementations, and an agent loop.
## Quick start
Here's how to get started with computer use:
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: computer-use-2025-11-24" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"tools": [
{
"type": "computer_20251124",
"name": "computer",
"display_width_px": 1024,
"display_height_px": 768,
"display_number": 1
},
{
"type": "text_editor_20250728",
"name": "str_replace_based_edit_tool"
},
{
"type": "bash_20250124",
"name": "bash"
}
],
"messages": [
{
"role": "user",
"content": "Save a picture of a cat to my desktop."
}
]
}'
```
```bash CLI
ant beta:messages create --beta computer-use-2025-11-24 <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
tools:
- type: computer_20251124
name: computer
display_width_px: 1024
display_height_px: 768
display_number: 1
- type: text_editor_20250728
name: str_replace_based_edit_tool
- type: bash_20250124
name: bash
messages:
- role: user
content: Save a picture of a cat to my desktop.
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-opus-4-7", # or another compatible model
max_tokens=1024,
tools=[
{
"type": "computer_20251124",
"name": "computer",
"display_width_px": 1024,
"display_height_px": 768,
"display_number": 1,
},
{"type": "text_editor_20250728", "name": "str_replace_based_edit_tool"},
{"type": "bash_20250124", "name": "bash"},
],
messages=[{"role": "user", "content": "Save a picture of a cat to my desktop."}],
betas=["computer-use-2025-11-24"],
)
print(response)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [
{
type: "computer_20251124",
name: "computer",
display_width_px: 1024,
display_height_px: 768,
display_number: 1
},
{
type: "text_editor_20250728",
name: "str_replace_based_edit_tool"
},
{
type: "bash_20250124",
name: "bash"
}
],
messages: [{ role: "user", content: "Save a picture of a cat to my desktop." }],
betas: ["computer-use-2025-11-24"]
});
console.log(response);
```
```csharp C#
using Anthropic;
using Anthropic.Models.Beta.Messages;
using Messages = Anthropic.Models.Messages;
var client = new AnthropicClient();
var parameters = new MessageCreateParams
{
Model = Messages::Model.ClaudeOpus4_7,
MaxTokens = 1024,
Tools = new BetaToolUnion[]
{
new BetaToolComputerUse20251124
{
DisplayWidthPx = 1024,
DisplayHeightPx = 768,
DisplayNumber = 1
},
new BetaToolTextEditor20250728(),
new BetaToolBash20250124()
},
Messages =
[
new BetaMessageParam
{
Role = Role.User,
Content = "Save a picture of a cat to my desktop."
}
],
Betas = ["computer-use-2025-11-24"]
};
var response = await client.Beta.Messages.Create(parameters);
Console.WriteLine(response);
```
```go Go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Tools: []anthropic.BetaToolUnionParam{
{OfComputerUseTool20251124: &anthropic.BetaToolComputerUse20251124Param{
DisplayWidthPx: 1024,
DisplayHeightPx: 768,
DisplayNumber: anthropic.Int(1),
}},
{OfTextEditor20250728: &anthropic.BetaToolTextEditor20250728Param{}},
{OfBashTool20250124: &anthropic.BetaToolBash20250124Param{}},
},
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Save a picture of a cat to my desktop.")),
},
Betas: []anthropic.AnthropicBeta{
anthropic.AnthropicBetaComputerUse2025_11_24,
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..3,7..10,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaToolBash20250124;
import com.anthropic.models.beta.messages.BetaToolComputerUse20251124;
import com.anthropic.models.beta.messages.BetaToolTextEditor20250728;
import com.anthropic.models.beta.messages.MessageCreateParams;
public class ComputerUseExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(1024L)
.addTool(BetaToolComputerUse20251124.builder()
.displayWidthPx(1024L)
.displayHeightPx(768L)
.displayNumber(1L)
.build())
.addTool(BetaToolTextEditor20250728.builder().build())
.addTool(BetaToolBash20250124.builder().build())
.addUserMessage("Save a picture of a cat to my desktop.")
.addBeta("computer-use-2025-11-24")
.build();
BetaMessage response = client.beta().messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4}
beta->messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Save a picture of a cat to my desktop.'],
],
model: 'claude-opus-4-7',
tools: [
[
'type' => 'computer_20251124',
'name' => 'computer',
'display_width_px' => 1024,
'display_height_px' => 768,
'display_number' => 1,
],
[
'type' => 'text_editor_20250728',
'name' => 'str_replace_based_edit_tool',
],
[
'type' => 'bash_20250124',
'name' => 'bash',
],
],
betas: ['computer-use-2025-11-24'],
);
echo $response;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [
{
type: "computer_20251124",
name: "computer",
display_width_px: 1024,
display_height_px: 768,
display_number: 1
},
{
type: "text_editor_20250728",
name: "str_replace_based_edit_tool"
},
{
type: "bash_20250124",
name: "bash"
}
],
messages: [
{ role: "user", content: "Save a picture of a cat to my desktop." }
],
betas: ["computer-use-2025-11-24"]
)
puts response
```
A beta header is only required for the computer use tool.
The example above shows all three tools being used together, which requires the beta header because it includes the computer use tool.
---
## How computer use works
- Add the computer use tool (and optionally other tools) to your API request.
- Include a user prompt that requires desktop interaction, for example, "Save a picture of a cat to my desktop."
- Claude assesses if the computer use tool can help with the user's query.
- If yes, Claude constructs a properly formatted tool use request.
- The API response has a `stop_reason` of `tool_use`, signaling Claude's intent.
- On your end, extract the tool name and input from Claude's request.
- Use the tool on a container or Virtual Machine.
- Continue the conversation with a new `user` message containing a `tool_result` content block.
- Claude analyzes the tool results to determine if more tool use is needed or the task has been completed.
- If Claude decides it needs another tool, it responds with another `tool_use` `stop_reason` and you should return to step 3.
- Otherwise, it crafts a text response to the user.
The repetition of steps 3 and 4 without user input is referred to as the "agent loop" (that is, Claude responding with a tool use request and your application responding to Claude with the results of evaluating that request).
### The computing environment
Computer use requires a sandboxed computing environment where Claude can safely interact with applications and the web. This environment includes:
1. **Virtual display**: A virtual X11 display server (using Xvfb) that renders the desktop interface Claude will see through screenshots and control with mouse/keyboard actions.
2. **Desktop environment**: A lightweight UI with window manager (Mutter) and panel (Tint2) running on Linux, which provides a consistent graphical interface for Claude to interact with.
3. **Applications**: Pre-installed Linux applications like Firefox, LibreOffice, text editors, and file managers that Claude can use to complete tasks.
4. **Tool implementations**: Integration code that translates Claude's abstract tool requests (like "move mouse" or "take screenshot") into actual operations in the virtual environment.
5. **Agent loop**: A program that handles communication between Claude and the environment, sending Claude's actions to the environment and returning the results (screenshots, command outputs) back to Claude.
When you use computer use, Claude doesn't directly connect to this environment. Instead, your application:
1. Receives Claude's tool use requests
2. Translates them into actions in your computing environment
3. Captures the results (screenshots, command outputs, etc.)
4. Returns these results to Claude
For security and isolation, the reference implementation runs all of this inside a Docker container with appropriate port mappings for viewing and interacting with the environment.
---
## How to implement computer use
### Start with the reference implementation
A [reference implementation](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo) is available that includes everything you need to get started quickly with computer use:
- A [containerized environment](https://github.com/anthropics/anthropic-quickstarts/blob/main/computer-use-demo/Dockerfile) suitable for computer use with Claude
- Implementations of [the computer use tools](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo/computer_use_demo/tools)
- An [agent loop](https://github.com/anthropics/anthropic-quickstarts/blob/main/computer-use-demo/computer_use_demo/loop.py) that interacts with the Claude API and executes the computer use tools
- A web interface to interact with the container, agent loop, and tools.
### Understanding the agentic loop
The core of computer use is the "agent loop" - a cycle where Claude requests tool actions, your application executes them, and returns results to Claude. Here's a simplified example:
```python hidelines={1}
from anthropic import Anthropic
async def sampling_loop(
*,
model: str,
messages: list[dict],
api_key: str,
max_tokens: int = 4096,
tool_version: str,
max_iterations: int = 10, # Add iteration limit to prevent infinite loops
):
"""
A simple agent loop for Claude computer use interactions.
This function handles the back-and-forth between:
1. Sending user messages to Claude
2. Claude requesting to use tools
3. Your app executing those tools
4. Sending tool results back to Claude
"""
# Set up tools and API parameters
client = Anthropic(api_key=api_key)
beta_flag = (
"computer-use-2025-11-24"
if "20251124" in tool_version
else "computer-use-2025-01-24"
)
# Configure tools - you should already have these initialized elsewhere
tools = [
{
"type": f"computer_{tool_version}",
"name": "computer",
"display_width_px": 1024,
"display_height_px": 768,
},
{"type": "text_editor_20250728", "name": "str_replace_based_edit_tool"},
{"type": "bash_20250124", "name": "bash"},
]
# Main agent loop (with iteration limit to prevent runaway API costs)
iterations = 0
while True and iterations < max_iterations:
iterations += 1
# Call the Claude API
response = client.beta.messages.create(
model=model,
max_tokens=max_tokens,
messages=messages,
tools=tools,
betas=[beta_flag],
)
# Add Claude's response to the conversation history
response_content = response.content
messages.append({"role": "assistant", "content": response_content})
# Check if Claude used any tools
tool_results = []
for block in response_content:
if block.type == "tool_use":
# In a real app, you would execute the tool here
# For example: result = run_tool(block.name, block.input)
result = {"result": "Tool executed successfully"}
# Format the result for Claude
tool_results.append(
{"type": "tool_result", "tool_use_id": block.id, "content": result}
)
# If no tools were used, Claude is done - return the final messages
if not tool_results:
return messages
# Add tool results to messages for the next iteration with Claude
messages.append({"role": "user", "content": tool_results})
```
The loop continues until either Claude responds without requesting any tools (task completion) or the maximum iteration limit is reached. This safeguard prevents potential infinite loops that could result in unexpected API costs.
Try the reference implementation out before reading the rest of this documentation.
### Optimize model performance with prompting
Here are some tips on how to get the best quality outputs:
1. Specify simple, well-defined tasks and provide explicit instructions for each step.
2. Claude sometimes assumes outcomes of its actions without explicitly checking their results. To prevent this you can prompt Claude with `After each step, take a screenshot and carefully evaluate if you have achieved the right outcome. Explicitly show your thinking: "I have evaluated step X..." If not correct, try again. Only when you confirm a step was executed correctly should you move on to the next one.`
3. Some UI elements (like dropdowns and scrollbars) might be tricky for Claude to manipulate using mouse movements. If you experience this, try prompting the model to use keyboard shortcuts.
4. For repeatable tasks or UI interactions, include example screenshots and tool calls of successful outcomes in your prompt.
5. If you need the model to log in, provide it with the username and password in your prompt inside xml tags like ``. Using computer use within applications that require login increases the risk of bad outcomes as a result of prompt injection. Review the [guide on mitigating prompt injections](/docs/en/test-and-evaluate/strengthen-guardrails/mitigate-jailbreaks) before providing the model with login credentials.
If you repeatedly encounter a clear set of issues or know in advance the tasks
Claude will need to complete, use the system prompt to provide Claude with
explicit tips or instructions on how to do the tasks successfully.
For agents that span multiple sessions, run end-to-end verification at the
start of each session, not only after implementation. Browser-based checks
catch regressions from prior sessions that code-level review alone misses. See
[Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents)
for details.
### System prompts
When one of the Anthropic-schema tools is requested via the Claude API, a computer use-specific system prompt is generated. It's similar to the [tool use system prompt](/docs/en/agents-and-tools/tool-use/define-tools#tool-use-system-prompt) but starts with:
> You have access to a set of functions you can use to answer the user's question. This includes access to a sandboxed computing environment. You do NOT currently have the ability to inspect files or interact with external resources, except by invoking the below functions.
As with regular tool use, the user-provided `system_prompt` field is still respected and used in the construction of the combined system prompt.
### Available actions
The computer use tool supports these actions:
**Basic actions (all versions)**
- **screenshot** - Capture the current display
- **left_click** - Click at coordinates `[x, y]`
- **type** - Type text string
- **key** - Press key or key combination (for example, "ctrl+s")
- **mouse_move** - Move cursor to coordinates
**Enhanced actions (`computer_20250124`)**
Available on all models that support computer use:
- **scroll** - Scroll in any direction with amount control
- **left_click_drag** - Click and drag between coordinates
- **right_click**, **middle_click** - Additional mouse buttons
- **double_click**, **triple_click** - Multiple clicks
- **left_mouse_down**, **left_mouse_up** - Fine-grained click control
- **hold_key** - Hold down a key for a specified duration (in seconds)
- **wait** - Pause between actions
**Enhanced actions (`computer_20251124`)**
Available in Claude Opus 4.7, Claude Opus 4.6, Claude Sonnet 4.6, and Claude Opus 4.5:
- All actions from `computer_20250124`
- **zoom** - View a specific region of the screen at full resolution. Requires `enable_zoom: true` in tool definition. Takes a `region` parameter with coordinates `[x1, y1, x2, y2]` defining top-left and bottom-right corners of the area to inspect.
Take a screenshot:
```json
{
"action": "screenshot"
}
```
Click at position:
```json
{
"action": "left_click",
"coordinate": [500, 300]
}
```
Type text:
```json
{
"action": "type",
"text": "Hello, world!"
}
```
Scroll down:
```json
{
"action": "scroll",
"coordinate": [500, 400],
"scroll_direction": "down",
"scroll_amount": 3
}
```
Zoom to view region in detail (Opus 4.7, Opus 4.6, Sonnet 4.6, Opus 4.5):
```json
{
"action": "zoom",
"region": [100, 200, 400, 350]
}
```
To hold modifier keys (like Shift, Ctrl, or Alt) while performing click or scroll actions, use the `text` parameter on those actions. This is different from `hold_key`, which simply holds a key for a duration without performing other actions.
Shift+click (e.g., for selecting a range of items):
```json
{
"action": "left_click",
"coordinate": [500, 300],
"text": "shift"
}
```
Ctrl+click (e.g., for multi-select on Windows/Linux):
```json
{
"action": "left_click",
"coordinate": [500, 300],
"text": "ctrl"
}
```
Cmd+click (e.g., for multi-select on macOS):
```json
{
"action": "left_click",
"coordinate": [500, 300],
"text": "super"
}
```
Shift+scroll (e.g., for horizontal scrolling):
```json
{
"action": "scroll",
"coordinate": [500, 400],
"scroll_direction": "down",
"scroll_amount": 3,
"text": "shift"
}
```
The `text` parameter in click/scroll actions accepts modifier keys like `shift`, `ctrl`, `alt`, and `super` (for the Command/Windows key).
### Tool parameters
| Parameter | Required | Description |
|-----------|----------|-------------|
| `type` | Yes | Tool version (`computer_20251124` or `computer_20250124`) |
| `name` | Yes | Must be "computer" |
| `display_width_px` | Yes | Display width in pixels |
| `display_height_px` | Yes | Display height in pixels |
| `display_number` | No | Display number for X11 environments |
| `enable_zoom` | No | Enable zoom action (`computer_20251124` only). Set to `true` to allow Claude to zoom into specific screen regions. Default: `false` |
**Important:** The computer use tool must be explicitly executed by your application - Claude cannot execute it directly. You are responsible for implementing the screenshot capture, mouse movements, keyboard inputs, and other actions based on Claude's requests.
### Combining with extended thinking
For combining computer use with extended thinking, see [Extended thinking](/docs/en/build-with-claude/extended-thinking).
### Augmenting computer use with other tools
To add other tools alongside computer use, include them in the same `tools` array. The quick start above shows this pattern with the [bash tool](/docs/en/agents-and-tools/tool-use/bash-tool) and [text editor tool](/docs/en/agents-and-tools/tool-use/text-editor-tool). You can add your own [custom tool definitions](/docs/en/agents-and-tools/tool-use/define-tools) the same way.
### Build a custom computer use environment
The [reference implementation](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo) is meant to help you get started with computer use. It includes all of the components needed to have Claude use a computer. However, you can build your own environment for computer use to suit your needs. You'll need:
- A virtualized or containerized environment suitable for computer use with Claude
- An implementation of at least one of the Anthropic-schema computer use tools
- An agent loop that interacts with the Claude API and executes the `tool_use` results using your tool implementations
- An API or UI that allows user input to start the agent loop
#### Implement the computer use tool
The computer use tool is implemented as a schema-less tool. When using this tool, you don't need to provide an input schema as with other tools; the schema is built into Claude's model and can't be modified.
Create a virtual display or connect to an existing display that Claude will interact with. This typically involves setting up Xvfb (X Virtual Framebuffer) or similar technology.
Create functions to handle each action type that Claude might request:
```python hidelines={1..3}
def capture_screenshot(): ...
def click_at(x, y): ...
def type_text(text): ...
def handle_computer_action(action_type, params):
if action_type == "screenshot":
return capture_screenshot()
elif action_type == "left_click":
x, y = params["coordinate"]
return click_at(x, y)
elif action_type == "type":
return type_text(params["text"])
# ... handle other actions
```
Extract and execute tool calls from Claude's responses:
```python hidelines={1..11}
from types import SimpleNamespace as _SN
response = _SN(
content=[_SN(type="tool_use", input={"action": "screenshot"}, id="toolu_01")]
)
def handle_computer_action(a, p):
return "ok"
for content in response.content:
if content.type == "tool_use":
action = content.input["action"]
result = handle_computer_action(action, content.input)
# Return result to Claude
tool_result = {
"type": "tool_result",
"tool_use_id": content.id,
"content": result,
}
```
Create a loop that continues until Claude completes the task:
```python hidelines={1..18}
import anthropic
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Take a screenshot"}]
tools = [
{
"type": "computer_20251124",
"name": "computer",
"display_width_px": 1024,
"display_height_px": 768,
}
]
def process_tool_calls(r):
return []
while True:
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
messages=messages,
tools=tools,
betas=["computer-use-2025-11-24"],
)
# Check if Claude used any tools
tool_results = process_tool_calls(response)
if not tool_results:
# No more tool use, task complete
break
# Continue conversation with tool results
messages.append({"role": "user", "content": tool_results})
```
#### Handle errors
When implementing the computer use tool, various errors may occur. Here's how to handle them:
If screenshot capture fails, return an appropriate error message:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: Failed to capture screenshot. Display may be locked or unavailable.",
"is_error": true
}
]
}
```
If Claude provides coordinates outside the display bounds:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: Coordinates (1200, 900) are outside display bounds (1024x768).",
"is_error": true
}
]
}
```
If an action fails to execute:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: Failed to perform click action. The application may be unresponsive.",
"is_error": true
}
]
}
```
#### Handle coordinate scaling for higher resolutions
Claude Opus 4.7 supports up to 2576 pixels on the long edge, and its coordinates are 1\:1 with image pixels (no scale-factor conversion required). The 1568-pixel guidance below applies to earlier models.
The API constrains images to a maximum of 1568 pixels on the longest edge and approximately 1.15 megapixels total (see [image resizing](/docs/en/build-with-claude/vision#evaluate-image-size) for details). For example, a 1512x982 screen gets downsampled to approximately 1330x864. Claude analyzes this smaller image and returns coordinates in that space, but your tool executes clicks in the original screen space.
This can cause Claude's click coordinates to miss their targets unless you handle the coordinate transformation.
To fix this, resize screenshots yourself and scale Claude's coordinates back up:
```python Python hidelines={1..7}
screen_width, screen_height = 1512, 982
def capture_and_resize(w, h): ...
def perform_click(x, y): ...
import math
def get_scale_factor(width, height):
"""Calculate scale factor to meet API constraints."""
long_edge = max(width, height)
total_pixels = width * height
long_edge_scale = 1568 / long_edge
total_pixels_scale = math.sqrt(1_150_000 / total_pixels)
return min(1.0, long_edge_scale, total_pixels_scale)
# When capturing screenshot
scale = get_scale_factor(screen_width, screen_height)
scaled_width = int(screen_width * scale)
scaled_height = int(screen_height * scale)
# Resize image to scaled dimensions before sending to Claude
screenshot = capture_and_resize(scaled_width, scaled_height)
# When handling Claude's coordinates, scale them back up
def execute_click(x, y):
screen_x = x / scale
screen_y = y / scale
perform_click(screen_x, screen_y)
```
```typescript TypeScript hidelines={1..6}
const screenWidth = 1512;
const screenHeight = 982;
function captureAndResize(w: number, h: number): string {
return "";
}
function performClick(x: number, y: number): void {}
const MAX_LONG_EDGE = 1568;
const MAX_PIXELS = 1_150_000;
function getScaleFactor(width: number, height: number): number {
const longEdge = Math.max(width, height);
const totalPixels = width * height;
const longEdgeScale = MAX_LONG_EDGE / longEdge;
const totalPixelsScale = Math.sqrt(MAX_PIXELS / totalPixels);
return Math.min(1.0, longEdgeScale, totalPixelsScale);
}
// When capturing screenshot
const scale = getScaleFactor(screenWidth, screenHeight);
const scaledWidth = Math.floor(screenWidth * scale);
const scaledHeight = Math.floor(screenHeight * scale);
// Resize image to scaled dimensions before sending to Claude
const screenshot = captureAndResize(scaledWidth, scaledHeight);
// When handling Claude's coordinates, scale them back up
function executeClick(x: number, y: number): void {
const screenX = x / scale;
const screenY = y / scale;
performClick(screenX, screenY);
}
```
#### Follow implementation best practices
Set display dimensions that match your use case while staying within recommended limits:
- For general desktop tasks: 1024x768 or 1280x720
- For web applications: 1280x800 or 1366x768
- Avoid resolutions above 1920x1080 to prevent performance issues
When returning screenshots to Claude:
- Encode screenshots as base64 PNG or JPEG
- Consider compressing large screenshots to improve performance
- Include relevant metadata like timestamp or display state
- If using higher resolutions, ensure coordinates are accurately scaled
Some applications need time to respond to actions:
```python hidelines={1..4}
import time
def click_at(x, y): ...
def click_and_wait(x, y, wait_time=0.5):
click_at(x, y)
time.sleep(wait_time) # Allow UI to update
```
Check that requested actions are safe and valid:
```python hidelines={1}
display_width, display_height = 1024, 768
def validate_action(action_type, params):
if action_type == "left_click":
x, y = params.get("coordinate", (0, 0))
if not (0 <= x < display_width and 0 <= y < display_height):
return False, "Coordinates out of bounds"
return True, None
```
Keep a log of all actions for troubleshooting:
```python
import logging
def log_action(action_type, params, result):
logging.info(f"Action: {action_type}, Params: {params}, Result: {result}")
```
---
## Understand computer use limitations
The computer use functionality is in beta. While Claude's capabilities are cutting edge, developers should be aware of its limitations:
1. **Latency**: the current computer use latency for human-AI interactions may be too slow compared to regular human-directed computer actions. Focus on use cases where speed isn't critical (for example, background information gathering, automated software testing) in trusted environments.
2. **Computer vision accuracy and reliability**: Claude may make mistakes or hallucinate when outputting specific coordinates while generating actions. Extended thinking can help you understand the model's reasoning and identify potential issues.
3. **Tool selection accuracy and reliability**: Claude may make mistakes or hallucinate when selecting tools while generating actions or take unexpected actions to solve problems. Additionally, reliability may be lower when interacting with niche applications or multiple applications at once. Prompt the model carefully when requesting complex tasks.
4. **Scrolling reliability**: The scroll action supports direction control (up, down, left, right) and a specified amount. In applications where scrolling doesn't take effect, keyboard alternatives such as Page Down can help.
5. **Spreadsheet interaction**: Use the fine-grained mouse control actions (`left_mouse_down`, `left_mouse_up`) and modifier-key combinations to select individual cells. Complex spreadsheet operations may still require multiple attempts.
6. **Account creation and content generation on social and communications platforms**: While Claude will visit websites, Claude's ability to create accounts or generate and share content or otherwise engage in human impersonation across social media websites and platforms is limited. This capability may be updated in the future.
7. **Vulnerabilities**: Vulnerabilities like jailbreaking or prompt injection may persist across frontier AI systems, including the beta computer use API. In some circumstances, Claude will follow commands found in content, sometimes even in conflict with the user's instructions. For example, Claude instructions on webpages or contained in images may override instructions or cause Claude to make mistakes. Consider the following:
a. Limiting computer use to trusted environments such as virtual machines or containers with minimal privileges
b. Avoiding giving computer use access to sensitive accounts or data without strict oversight
c. Informing end users of relevant risks and obtaining their consent before enabling or requesting permissions necessary for computer use features in your applications
8. **Inappropriate or illegal actions**: Per Anthropic's terms of service, you must not employ computer use to violate any laws or the Acceptable Use Policy.
Always carefully review and verify Claude's computer use actions and logs. Do not use Claude for tasks requiring perfect precision or sensitive user information without human oversight.
## Data retention
Computer use is a client-side tool. All screenshots, mouse actions, keyboard inputs, and any files involved in a session are captured and stored in your environment, not by Anthropic. Anthropic processes the screenshot images and action requests in real time as part of the API call but does not retain them after the response is returned.
Because your application controls where and how computer use data is stored, computer use is ZDR eligible. For ZDR eligibility across all features, see [API and data retention](/docs/en/manage-claude/api-and-data-retention).
## Pricing
Computer use follows the standard [tool use pricing](/docs/en/agents-and-tools/tool-use/overview#pricing). When using the computer use tool:
**System prompt overhead**: The computer use beta adds 466-499 tokens to the system prompt
**Computer use tool token usage**:
| Model | Input tokens per tool definition |
| ----- | -------------------------------- |
| Claude 4.x models | 735 tokens |
**Additional token consumption**:
- Screenshot images (see [Vision pricing](/docs/en/build-with-claude/vision))
- Tool execution results returned to Claude
If you're also using bash or text editor tools alongside computer use, those tools have their own token costs as documented in their respective pages.
## Next steps
Get started quickly with the complete Docker-based implementation
Learn more about tool use and creating custom tools
---
# Define tools
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/define-tools
# Define tools
Specify tool schemas, write effective descriptions, and control when Claude calls your tools.
---
## Choosing a model
Use the latest Claude Opus (4.7) model for complex tools and ambiguous queries; it handles multiple tools better and seeks clarification when needed.
Use Claude Haiku models for straightforward tools, but note they may infer missing parameters.
If using Claude with tool use and extended thinking, refer to the [extended thinking guide](/docs/en/build-with-claude/extended-thinking) for more information.
## Specifying client tools
Client tools (both Anthropic-schema and user-defined) are specified in the `tools` top-level parameter of the API request. Each tool definition includes:
| Parameter | Description |
| :------------- | :-------------------------------------------------------------------------------------------------- |
| `name` | The name of the tool. Must match the regex `^[a-zA-Z0-9_-]{1,64}$`. |
| `description` | A detailed plaintext description of what the tool does, when it should be used, and how it behaves. |
| `input_schema` | A [JSON Schema](https://json-schema.org/) object defining the expected parameters for the tool. |
| `input_examples` | (Optional) An array of example input objects to help Claude understand how to use the tool. See [Providing tool use examples](#providing-tool-use-examples). |
For the full set of optional properties available on any tool definition, including `cache_control`, `strict`, `defer_loading`, and `allowed_callers`, see the [Tool reference](/docs/en/agents-and-tools/tool-use/tool-reference#tool-definition-properties).
```json JSON
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The unit of temperature, either 'celsius' or 'fahrenheit'"
}
},
"required": ["location"]
}
}
```
This tool, named `get_weather`, expects an input object with a required `location` string and an optional `unit` string that must be either "celsius" or "fahrenheit".
### Tool use system prompt
When you call the Claude API with the `tools` parameter, the API constructs a special system prompt from the tool definitions, tool configuration, and any user-specified system prompt. The constructed prompt is designed to instruct the model to use the specified tool(s) and provide the necessary context for the tool to operate properly:
```text
In this environment you have access to a set of tools you can use to answer the user's question.
{{ FORMATTING INSTRUCTIONS }}
String and scalar parameters should be specified as is, while lists and objects should use JSON format. Note that spaces for string values are not stripped. The output is not expected to be valid XML and is parsed with regular expressions.
Here are the functions available in JSONSchema format:
{{ TOOL DEFINITIONS IN JSON SCHEMA }}
{{ USER SYSTEM PROMPT }}
{{ TOOL CONFIGURATION }}
```
### Best practices for tool definitions
To get the best performance out of Claude when using tools, follow these guidelines:
- **Provide extremely detailed descriptions.** This is by far the most important factor in tool performance. Your descriptions should explain every detail about the tool, including:
- What the tool does
- When it should be used (and when it shouldn't)
- What each parameter means and how it affects the tool's behavior
- Any important caveats or limitations, such as what information the tool does not return if the tool name is unclear. The more context you can give Claude about your tools, the better it will be at deciding when and how to use them. Aim for at least 3-4 sentences per tool description, more if the tool is complex.
- **Prioritize descriptions, but consider using `input_examples` for complex tools.** Clear descriptions are most important, but for tools with complex inputs, nested objects, or format-sensitive parameters, you can use the `input_examples` field to provide schema-validated examples. See [Providing tool use examples](#providing-tool-use-examples) for details.
- **Consolidate related operations into fewer tools.** Rather than creating a separate tool for every action (`create_pr`, `review_pr`, `merge_pr`), group them into a single tool with an `action` parameter. Fewer, more capable tools reduce selection ambiguity and make your tool surface easier for Claude to navigate.
- **Use meaningful namespacing in tool names.** When your tools span multiple services or resources, prefix names with the service (e.g., `github_list_prs`, `slack_send_message`). This makes tool selection unambiguous as your library grows, and is especially important when using [tool search](/docs/en/agents-and-tools/tool-use/tool-search-tool).
- **Design tool responses to return only high-signal information.** Return semantic, stable identifiers (e.g., slugs or UUIDs) rather than opaque internal references, and include only the fields Claude needs to reason about its next step. Bloated responses waste context and make it harder for Claude to extract what matters.
```json JSON
{
"name": "get_stock_price",
"description": "Retrieves the current stock price for a given ticker symbol. The ticker symbol must be a valid symbol for a publicly traded company on a major US stock exchange like NYSE or NASDAQ. The tool will return the latest trade price in USD. It should be used when the user asks about the current or most recent price of a specific stock. It will not provide any other information about the stock or company.",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "The stock ticker symbol, e.g. AAPL for Apple Inc."
}
},
"required": ["ticker"]
}
}
```
```json JSON
{
"name": "get_stock_price",
"description": "Gets the stock price for a ticker.",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string"
}
},
"required": ["ticker"]
}
}
```
The good description clearly explains what the tool does, when to use it, what data it returns, and what the `ticker` parameter means. The poor description is too brief and leaves Claude with many open questions about the tool's behavior and usage.
For deeper guidance on tool design (consolidation, naming, and response shaping), see [Writing tools for agents](https://www.anthropic.com/engineering/writing-tools-for-agents).
## Providing tool use examples
You can provide concrete examples of valid tool inputs to help Claude understand how to use your tools more effectively. This is particularly useful for complex tools with nested objects, optional parameters, or format-sensitive inputs.
### Basic usage
Add an optional `input_examples` field to your tool definition with an array of example input objects. Each example must be valid according to the tool's `input_schema`:
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
tools:
- name: get_weather
description: Get the current weather in a given location
input_schema:
type: object
properties:
location:
type: string
description: The city and state, e.g. San Francisco, CA
unit:
type: string
enum: [celsius, fahrenheit]
description: The unit of temperature
required: [location]
input_examples:
- location: San Francisco, CA
unit: fahrenheit
- location: Tokyo, Japan
unit: celsius
- location: New York, NY # 'unit' is optional
messages:
- role: user
content: What's the weather like in San Francisco?
YAML
```
```python Python
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
tools=[
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The unit of temperature",
},
},
"required": ["location"],
},
"input_examples": [
{"location": "San Francisco, CA", "unit": "fahrenheit"},
{"location": "Tokyo, Japan", "unit": "celsius"},
{
"location": "New York, NY" # 'unit' is optional
},
],
}
],
messages=[{"role": "user", "content": "What's the weather like in San Francisco?"}],
)
print(response)
```
```typescript TypeScript hidelines={1..4}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [
{
name: "get_weather",
description: "Get the current weather in a given location",
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "The unit of temperature"
}
},
required: ["location"]
},
input_examples: [
{
location: "San Francisco, CA",
unit: "fahrenheit"
},
{
location: "Tokyo, Japan",
unit: "celsius"
},
{
location: "New York, NY"
// Demonstrates that 'unit' is optional
}
]
}
],
messages: [{ role: "user", content: "What's the weather like in San Francisco?" }]
});
console.log(response);
```
```csharp C# hidelines={1..7}
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Tools = [
new ToolUnion(new Tool()
{
Name = "get_weather",
Description = "Get the current weather in a given location",
InputSchema = new InputSchema()
{
Properties = new Dictionary
{
["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "The city and state, e.g. San Francisco, CA" }),
["unit"] = JsonSerializer.SerializeToElement(new { type = "string", @enum = new[] { "celsius", "fahrenheit" }, description = "The unit of temperature" }),
},
Required = ["location"],
},
InputExamples =
[
new Dictionary()
{
{ "location", JsonSerializer.SerializeToElement("San Francisco, CA") },
{ "unit", JsonSerializer.SerializeToElement("fahrenheit") },
},
new Dictionary()
{
{ "location", JsonSerializer.SerializeToElement("Tokyo, Japan") },
{ "unit", JsonSerializer.SerializeToElement("celsius") },
},
new Dictionary()
{
{ "location", JsonSerializer.SerializeToElement("New York, NY") },
},
],
}),
],
Messages = [
new() { Role = Role.User, Content = "What's the weather like in San Francisco?" }
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Tools: []anthropic.ToolUnionParam{
{OfTool: &anthropic.ToolParam{
Name: "get_weather",
Description: anthropic.String("Get the current weather in a given location"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"location": map[string]any{
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
"unit": map[string]any{
"type": "string",
"enum": []string{"celsius", "fahrenheit"},
"description": "The unit of temperature",
},
},
Required: []string{"location"},
},
InputExamples: []map[string]any{
{
"location": "San Francisco, CA",
"unit": "fahrenheit",
},
{
"location": "Tokyo, Japan",
"unit": "celsius",
},
{
"location": "New York, NY",
// Demonstrates that 'unit' is optional
},
},
}},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather like in San Francisco?")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..6,9}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.Tool.InputSchema;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addTool(Tool.builder()
.name("get_weather")
.description("Get the current weather in a given location")
.inputSchema(InputSchema.builder()
.properties(JsonValue.from(Map.of(
"location", Map.of(
"type", "string",
"description", "The city and state, e.g. San Francisco, CA"
),
"unit", Map.of(
"type", "string",
"enum", List.of("celsius", "fahrenheit"),
"description", "The unit of temperature"
)
)))
.required(List.of("location"))
.build())
.putAdditionalProperty("input_examples", JsonValue.from(List.of(
Map.of(
"location", "San Francisco, CA",
"unit", "fahrenheit"
),
Map.of(
"location", "Tokyo, Japan",
"unit", "celsius"
),
Map.of(
"location", "New York, NY"
)
)))
.build())
.addUserMessage("What's the weather like in San Francisco?")
.build();
Message response = client.messages().create(params);
IO.println(response);
}
```
```php PHP
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => "What's the weather like in San Francisco?"]
],
model: 'claude-opus-4-7',
tools: [
[
'name' => 'get_weather',
'description' => 'Get the current weather in a given location',
'input_schema' => [
'type' => 'object',
'properties' => [
'location' => [
'type' => 'string',
'description' => 'The city and state, e.g. San Francisco, CA'
],
'unit' => [
'type' => 'string',
'enum' => ['celsius', 'fahrenheit'],
'description' => 'The unit of temperature'
]
],
'required' => ['location']
],
'input_examples' => [
[
'location' => 'San Francisco, CA',
'unit' => 'fahrenheit'
],
[
'location' => 'Tokyo, Japan',
'unit' => 'celsius'
],
[
'location' => 'New York, NY'
]
]
]
],
);
```
```ruby Ruby
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [
{
name: "get_weather",
description: "Get the current weather in a given location",
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "The unit of temperature"
}
},
required: ["location"]
},
input_examples: [
{
location: "San Francisco, CA",
unit: "fahrenheit"
},
{
location: "Tokyo, Japan",
unit: "celsius"
},
{
location: "New York, NY"
}
]
}
],
messages: [
{ role: "user", content: "What's the weather like in San Francisco?" }
]
)
puts message
```
Examples are included in the prompt alongside your tool schema, showing Claude concrete patterns for well-formed tool calls. This helps Claude understand when to include optional parameters, what formats to use, and how to structure complex inputs.
### Requirements and limitations
- **Schema validation** - Each example must be valid according to the tool's `input_schema`. Invalid examples return a 400 error
- **Not supported for server-side tools** - Input examples work on user-defined and Anthropic-schema client tools, but not on server tools like web search or code execution
- **Token cost** - Examples add to prompt tokens: ~20-50 tokens for simple examples, ~100-200 tokens for complex nested objects
## Controlling Claude's output
### Forcing tool use
In some cases, you may want Claude to use a specific tool to answer the user's question, even if Claude would otherwise answer directly without calling a tool. You can do this by specifying the tool in the `tool_choice` field like so:
```text
tool_choice = {"type": "tool", "name": "get_weather"}
```
When working with the tool_choice parameter, there are four possible options:
- `auto` allows Claude to decide whether to call any provided tools or not. This is the default value when `tools` are provided.
- `any` tells Claude that it must use one of the provided tools, but doesn't force a particular tool.
- `tool` forces Claude to always use a particular tool.
- `none` prevents Claude from using any tools. This is the default value when no `tools` are provided.
When using [prompt caching](/docs/en/build-with-claude/prompt-caching#what-invalidates-the-cache), changes to the `tool_choice` parameter will invalidate cached message blocks. Tool definitions and system prompts remain cached, but message content must be reprocessed.
This diagram illustrates how each option works:

Note that when you have `tool_choice` as `any` or `tool`, the API prefills the assistant message to force a tool to be used. This means that the models will not emit a natural language response or explanation before `tool_use` content blocks, even if explicitly asked to do so.
When using [extended thinking](/docs/en/build-with-claude/extended-thinking) with tool use, `tool_choice: {"type": "any"}` and `tool_choice: {"type": "tool", "name": "..."}` are not supported and will result in an error. Only `tool_choice: {"type": "auto"}` (the default) and `tool_choice: {"type": "none"}` are compatible with extended thinking.
[Claude Mythos Preview](https://anthropic.com/glasswing) does not support forced tool use. Requests with `tool_choice: {"type": "any"}` or `tool_choice: {"type": "tool", "name": "..."}` return a 400 error on this model. Use `tool_choice: {"type": "auto"}` (the default) or `tool_choice: {"type": "none"}` and rely on prompting to influence tool selection.
Testing has shown that this should not reduce performance. If you would like the model to provide natural language context or explanations while still requesting that the model use a specific tool, you can use `{"type": "auto"}` for `tool_choice` (the default) and add explicit instructions in a `user` message. For example: `What's the weather like in London? Use the get_weather tool in your response.`
**Guaranteed tool calls with strict tools**
Combine `tool_choice: {"type": "any"}` with [strict tool use](/docs/en/agents-and-tools/tool-use/strict-tool-use) to guarantee both that one of your tools will be called AND that the tool inputs strictly follow your schema. Set `strict: true` on your tool definitions to enable schema validation.
### Model responses with tools
When using tools, Claude will often comment on what it's doing or respond naturally to the user before invoking tools.
For example, given the prompt "What's the weather like in San Francisco right now, and what time is it there?", Claude might respond with:
```json JSON
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "I'll help you check the current weather and time in San Francisco."
},
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": { "location": "San Francisco, CA" }
}
]
}
```
This natural response style helps users understand what Claude is doing and creates a more conversational interaction. You can guide the style and content of these responses through your system prompts and by providing `` in your prompts.
It's important to note that Claude may use various phrasings and approaches when explaining its actions. Your code should treat these responses like any other assistant-generated text, and not rely on specific formatting conventions.
## Next steps
Parse tool_use blocks and format tool_result responses.
Let the SDK handle the agentic loop automatically.
Directory of Anthropic-provided tools and optional properties.
---
# Handle tool calls
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls
# Handle tool calls
Parse tool_use blocks, format tool_result responses, and handle errors with is_error.
---
This page covers the tool-call lifecycle: reading `tool_use` blocks from Claude's response, formatting `tool_result` blocks in your reply, and signaling errors. For the SDK abstraction that handles this automatically, see [Tool Runner](/docs/en/agents-and-tools/tool-use/tool-runner).
**Simpler with Tool Runner**: The manual tool handling described on this page is automatically managed by [Tool Runner](/docs/en/agents-and-tools/tool-use/tool-runner). Use this page when you need custom control over tool execution.
Claude's response differs based on whether it uses a [client or server tool](/docs/en/agents-and-tools/tool-use/overview#how-tool-use-works).
## Handling results from client tools
The response will have a `stop_reason` of `tool_use` and one or more `tool_use` content blocks that include:
- `id`: A unique identifier for this particular tool use block. This will be used to match up the tool results later.
- `name`: The name of the tool being used.
- `input`: An object containing the input being passed to the tool, conforming to the tool's `input_schema`.
```json JSON
{
"id": "msg_01Aq9w938a90dw8q",
"model": "claude-opus-4-7",
"stop_reason": "tool_use",
"role": "assistant",
"content": [
{
"type": "text",
"text": "I'll check the current weather in San Francisco for you."
},
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": { "location": "San Francisco, CA", "unit": "celsius" }
}
]
}
```
When you receive a tool use response for a client tool, you should:
1. Extract the `name`, `id`, and `input` from the `tool_use` block.
2. Run the actual tool in your codebase corresponding to that tool name, passing in the tool `input`.
3. Continue the conversation by sending a new message with the `role` of `user`, and a `content` block containing the `tool_result` type and the following information:
- `tool_use_id`: The `id` of the tool use request this is a result for.
- `content` (optional): The result of the tool, as a string (for example, `"content": "15 degrees"`), a list of nested content blocks (for example, `"content": [{"type": "text", "text": "15 degrees"}]`), or a list of document blocks (for example, `"content": [{"type": "document", "source": {"type": "text", "media_type": "text/plain", "data": "15 degrees"}}]`). These content blocks can use the `text`, `image`, or `document` types.
- `is_error` (optional): Set to `true` if the tool execution resulted in an error.
**Important formatting requirements**:
- Tool result blocks must immediately follow their corresponding tool use blocks in the message history. You cannot include any messages between the assistant's tool use message and the user's tool result message.
- In the user message containing tool results, the tool_result blocks must come FIRST in the content array. Any text must come AFTER all tool results.
For example, this will cause a 400 error:
```json
{
"role": "user",
"content": [
{ "type": "text", "text": "Here are the results:" }, // ❌ Text before tool_result
{ "type": "tool_result", "tool_use_id": "toolu_01" /* ... */ }
]
}
```
This is correct:
```json
{
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "toolu_01" /* ... */ },
{ "type": "text", "text": "What should I do next?" } // ✅ Text after tool_result
]
}
```
If you receive an error like "tool_use ids were found without tool_result blocks immediately after", check that your tool results are formatted correctly.
```json JSON
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "15 degrees"
}
]
}
```
```json JSON
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": [
{ "type": "text", "text": "15 degrees" },
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": "/9j/4AAQSkZJRg..."
}
}
]
}
]
}
```
```json JSON
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9"
}
]
}
```
```json JSON
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": [
{ "type": "text", "text": "The weather is" },
{
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": "15 degrees"
}
}
]
}
]
}
```
After receiving the tool result, Claude will use that information to continue generating a response to the original user prompt.
## Handling results from server tools
Claude executes the tool internally and incorporates the results directly into its response without requiring additional user interaction.
**Differences from other APIs**
Unlike APIs that separate tool use or use special roles like `tool` or `function`, the Claude API integrates tools directly into the `user` and `assistant` message structure.
Messages contain arrays of `text`, `image`, `tool_use`, and `tool_result` blocks. `user` messages include client content and `tool_result`, while `assistant` messages contain AI-generated content and `tool_use`.
## Handling errors with is_error
There are a few different types of errors that can occur when using tools with Claude:
If the tool itself throws an error during execution (for example, a network error when fetching weather data), you can return the error message in the `content` along with `"is_error": true`:
```json JSON
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "ConnectionError: the weather service API is not available (HTTP 500)",
"is_error": true
}
]
}
```
Claude will then incorporate this error into its response to the user. For example: "I'm sorry, I was unable to retrieve the current weather because the weather service API is not available. Please try again later."
Write instructive error messages. Instead of generic errors like `"failed"`, include what went wrong and what Claude should try next, e.g., `"Rate limit exceeded. Retry after 60 seconds."` This gives Claude the context it needs to recover or adapt without guessing.
If Claude's attempted use of a tool is invalid (for example, missing required parameters), it usually means that there wasn't enough information for Claude to use the tool correctly. Your best bet during development is to try the request again with more-detailed `description` values in your tool definitions.
However, you can also continue the conversation forward with a `tool_result` that indicates the error, and Claude will try to use the tool again with the missing information filled in:
```json JSON
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: Missing required 'location' parameter",
"is_error": true
}
]
}
```
If a tool request is invalid or missing parameters, Claude will retry 2-3 times with corrections before apologizing to the user.
To eliminate invalid tool calls entirely, use [strict tool use](/docs/en/agents-and-tools/tool-use/strict-tool-use) with `strict: true` on your tool definitions. This guarantees that tool inputs will always match your schema exactly, preventing missing parameters and type mismatches.
When server tools encounter errors (for example, network issues with Web Search), Claude will transparently handle these errors and attempt to provide an alternative response or explanation to the user. Unlike client tools, you do not need to handle `is_error` results for server tools.
For web search specifically, possible error codes include:
- `too_many_requests`: Rate limit exceeded
- `invalid_input`: Invalid search query parameter
- `max_uses_exceeded`: Maximum web search tool uses exceeded
- `query_too_long`: Query exceeds maximum length
- `unavailable`: An internal error occurred
## Next steps
- For running multiple tools in one turn, see [Parallel tool use](/docs/en/agents-and-tools/tool-use/parallel-tool-use).
- For the SDK abstraction that automates this loop, see [Tool Runner](/docs/en/agents-and-tools/tool-use/tool-runner).
- For the full tool-use workflow, see [Define tools](/docs/en/agents-and-tools/tool-use/define-tools).
---
# How tool use works
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/how-tool-use-works
# How tool use works
Understand the tool use loop, where tools execute, and when to use tools instead of prose.
---
This page explains the concepts behind tool use: where tools run, how the agentic loop works, and when tool use is the right approach. For hands-on guidance, start with the [tutorial](/docs/en/agents-and-tools/tool-use/build-a-tool-using-agent) or the [implementation guide](/docs/en/agents-and-tools/tool-use/define-tools).
## The tool-use contract
Tool use is a contract between your application and the model. You specify what operations are available and what shape their inputs and outputs take; Claude decides when and how to call them. The model never executes anything on its own. It emits a structured request, your code (or Anthropic's servers) runs the operation, and the result flows back into the conversation.
This contract makes the model behave less like a text generator and more like a function you call. Engineers with classical API experience can integrate tool use the same way they would any other typed interface: define the schema, handle the callback, return a result. The difference is that the caller on the other side is a language model choosing which function to invoke based on the conversation.
## Where tools run
The primary axis along which tools differ is where the code executes. Every tool falls into one of three buckets, and the bucket determines what your application is responsible for.
### User-defined tools (client-executed)
You write the schema, you execute the code, you return the results. This is the main event: the vast majority of tool-use traffic is user-defined tools calling into application-specific logic.
When Claude decides to use one of your tools, the API response contains a `tool_use` block with the tool name and a JSON object of arguments. Your application extracts those arguments, runs the operation (a database query, an HTTP call, a file write, whatever the tool does), and sends the output back in a `tool_result` block on the next request. Claude never sees your implementation; it only sees the schema you provided and the result you returned.
### Anthropic-schema tools (client-executed)
For a handful of common operations (running shell commands, editing files, controlling a browser, managing scratchpad memory), Anthropic publishes the tool schema and your application handles execution. The tools in this category are `bash`, `text_editor`, `computer`, and `memory`.
The execution model is identical to user-defined tools: the response contains a `tool_use` block, your code runs the operation, and you send back a `tool_result`. The reason to use an Anthropic-schema tool instead of defining your own equivalent is that these schemas are trained-in. Claude has been optimized on thousands of successful trajectories that use these exact tool signatures, so it calls them more reliably and recovers from errors more gracefully than it would with a custom tool that does the same thing. The schema is the interface the model already expects.
### Server-executed tools
For `web_search`, `web_fetch`, `code_execution`, and `tool_search`, Anthropic runs the code. You enable the tool in your request and the server handles everything else. You never construct a `tool_result` block for these tools because the server-side loop executes the operation and feeds the output back to the model before the response reaches you.
The response you receive contains `server_tool_use` blocks showing what ran and what came back, but by the time you see them, execution is already complete. Your application's job is to enable the tool and read the final answer, not to participate in the execution loop.
## The agentic loop (client tools)
Client-executed tools (both user-defined and Anthropic-schema) require your application to drive a loop. The model can't run your code, so every tool call is a round trip: the model asks, you execute, you report back, the model continues.
The canonical shape is a `while` loop keyed on `stop_reason`:
1. Send a request with your `tools` array and the user message.
2. Claude responds with `stop_reason: "tool_use"` and one or more `tool_use` blocks.
3. Execute each tool. Format the outputs as `tool_result` blocks.
4. Send a new request containing the original messages, the assistant's response, and a user message with the `tool_result` blocks.
5. Repeat from step 2 while `stop_reason` is `"tool_use"`.
In practice this reads as: while `stop_reason == "tool_use"`, execute the tools and continue the conversation. The loop exits on any other stop reason (`"end_turn"`, `"max_tokens"`, `"stop_sequence"`, or `"refusal"`), which means Claude has either produced a final answer or stopped for another reason that your application should handle.
For the mechanics of building requests, handling parallel tool calls, and formatting results, see [Handle tool calls](/docs/en/agents-and-tools/tool-use/handle-tool-calls).
## The server-side loop
Server-executed tools run their own loop inside Anthropic's infrastructure. A single request from your application might trigger several web searches or code executions before a response comes back. The model searches, reads results, decides to search again, and iterates until it has what it needs, all without your application participating.
This internal loop has an iteration limit. If the model is still iterating when it hits the cap, the response comes back with `stop_reason: "pause_turn"` instead of `"end_turn"`. A paused turn means the work isn't finished; re-send the conversation (including the paused response) to let the model continue where it left off. See [Server tools](/docs/en/agents-and-tools/tool-use/server-tools) for the continuation pattern.
## When to use tools (and when not to)
Tool use fits when the task requires something the model can't do from text alone:
- **Actions with side effects.** Sending an email, writing a file, updating a record. The model can describe these actions, but only a tool can perform them.
- **Fresh or external data.** Current prices, today's weather, the contents of a database. Anything outside the training data or specific to your system needs a tool to fetch it.
- **Structured, guaranteed-shape outputs.** When you need a JSON object with specific fields rather than prose that happens to contain the information, a tool schema enforces the shape.
- **Calling into existing systems.** Databases, internal APIs, file systems. Tool use is the bridge between natural-language requests and the systems that fulfill them.
The tell that you should be using tools: if you're writing a regex to extract a decision from model output, that decision should have been a tool call. Parsing free-form text to recover structured intent is a sign the structure belongs in the schema.
Tool use doesn't fit when:
- The model can answer from training alone. Summarization, translation, and general-knowledge questions don't need a tool round trip.
- The interaction is one-shot Q&A with no side effects. If there's nothing to execute, there's nothing for a tool to do.
- Tool-calling latency would dominate a trivial response. Every tool call is at least one extra round trip; for lightweight tasks the overhead can exceed the work.
## Choosing between approaches
| Approach | When to use it | What to expect | Learn more |
| --- | --- | --- | --- |
| User-defined client tools | Custom business logic, internal APIs, proprietary data | You handle execution and the agentic loop | [Define tools](/docs/en/agents-and-tools/tool-use/define-tools) |
| Anthropic-schema client tools | Standard dev operations (bash, file editing, browser control) | You handle execution; Claude calls the tool reliably because the schema is trained-in | [Tool reference](/docs/en/agents-and-tools/tool-use/tool-reference) |
| Server-executed tools | Web search, code sandbox, web fetch | Anthropic handles execution; you get results directly | [Server tools](/docs/en/agents-and-tools/tool-use/server-tools) |
## Next steps
Build an agent step by step from a single tool call to production.
Schema specification, descriptions, and tool_choice.
Directory of Anthropic-provided tools.
---
# Memory tool
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/memory-tool
# Memory tool
---
The memory tool enables Claude to store and retrieve information across conversations through a memory file directory. Claude can create, read, update, and delete files that persist between sessions, allowing it to build knowledge over time without keeping everything in the context window.
This is the key primitive for just-in-time context retrieval: rather than loading all relevant information upfront, agents store what they learn in memory and pull it back on demand. This keeps the active context focused on what's currently relevant, critical for long-running workflows where loading everything at once would overwhelm the context window. See [Effective context engineering](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents) for the broader pattern.
The memory tool operates client-side: you control where and how the data is stored through your own infrastructure.
Reach out through the [feedback form](https://forms.gle/YXC2EKGMhjN1c4L88) to share your feedback on this feature.
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
## Use cases
- Maintain project context across multiple agent executions
- Learn from past interactions, decisions, and feedback
- Build knowledge bases over time
- Enable cross-conversation learning where Claude improves at recurring workflows
## How it works
When enabled, Claude automatically checks its memory directory before starting tasks. Claude can create, read, update, and delete files in the `/memories` directory to store what it learns while working, then reference those memories in future conversations to handle similar tasks more effectively or pick up where it left off.
Since this is a client-side tool, Claude makes tool calls to perform memory operations, and your application executes those operations locally. This gives you complete control over where and how the memory is stored. For security, you should restrict all memory operations to the `/memories` directory.
### Example: How memory tool calls work
When you ask Claude to help with a task, Claude automatically checks its memory directory first. Here's what a typical interaction looks like:
**1. User request:**
```text
"Help me respond to this customer service ticket."
```
**2. Claude checks the memory directory:**
```text
"I'll help you respond to the customer service ticket. Let me check my memory for any previous context."
```
Claude calls the memory tool:
```json
{
"type": "tool_use",
"id": "toolu_01C4D5E6F7G8H9I0J1K2L3M4",
"name": "memory",
"input": {
"command": "view",
"path": "/memories"
}
}
```
**3. Your application returns the directory contents:**
```json
{
"type": "tool_result",
"tool_use_id": "toolu_01C4D5E6F7G8H9I0J1K2L3M4",
"content": "Here're the files and directories up to 2 levels deep in /memories, excluding hidden items and node_modules:\n4.0K\t/memories\n1.5K\t/memories/customer_service_guidelines.xml\n2.0K\t/memories/refund_policies.xml"
}
```
**4. Claude reads relevant files:**
```json
{
"type": "tool_use",
"id": "toolu_01D5E6F7G8H9I0J1K2L3M4N5",
"name": "memory",
"input": {
"command": "view",
"path": "/memories/customer_service_guidelines.xml"
}
}
```
**5. Your application returns the file contents:**
```json
{
"type": "tool_result",
"tool_use_id": "toolu_01D5E6F7G8H9I0J1K2L3M4N5",
"content": "Here's the content of /memories/customer_service_guidelines.xml with line numbers:\n 1\t\n 2\t\n 3\t- Always address customers by their first name\n 4\t- Use empathetic language\n..."
}
```
**6. Claude uses the memory to help:**
```text
"Based on your customer service guidelines, I can help you craft a response. Please share the ticket details..."
```
For model support, see the [Tool reference](/docs/en/agents-and-tools/tool-use/tool-reference).
## Getting started
To use the memory tool:
1. Add the memory tool to your request
2. Implement client-side handlers for memory operations
To handle memory tool operations in your application, you need to implement handlers for each memory command. The SDKs provide memory tool helpers that handle the tool interface. You can subclass `BetaAbstractMemoryTool` (Python) or use `betaMemoryTool` (TypeScript) to implement your own memory backend (file-based, database, cloud storage, encrypted files, etc.).
For working examples, see:
- Python: [examples/memory/basic.py](https://github.com/anthropics/anthropic-sdk-python/blob/main/examples/memory/basic.py)
- TypeScript: [examples/tools-helpers-memory.ts](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/examples/tools-helpers-memory.ts)
## Basic usage
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 2048,
"messages": [
{
"role": "user",
"content": "I'\''m working on a Python web scraper that keeps crashing with a timeout error. Here'\''s the problematic function:\n\n```python\ndef fetch_page(url, retries=3):\n for i in range(retries):\n try:\n response = requests.get(url, timeout=5)\n return response.text\n except requests.exceptions.Timeout:\n if i == retries - 1:\n raise\n time.sleep(1)\n```\n\nPlease help me debug this."
}
],
"tools": [{
"type": "memory_20250818",
"name": "memory"
}]
}'
```
````bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 2048
tools:
- type: memory_20250818
name: memory
messages:
- role: user
content: |
I'm working on a Python web scraper that keeps crashing with a
timeout error. Here's the problematic function:
```python
def fetch_page(url, retries=3):
for i in range(retries):
try:
response = requests.get(url, timeout=5)
return response.text
except requests.exceptions.Timeout:
if i == retries - 1:
raise
time.sleep(1)
```
Please help me debug this.
YAML
````
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=2048,
messages=[
{
"role": "user",
"content": "I'm working on a Python web scraper that keeps crashing with a timeout error. Here's the problematic function:\n\n```python\ndef fetch_page(url, retries=3):\n for i in range(retries):\n try:\n response = requests.get(url, timeout=5)\n return response.text\n except requests.exceptions.Timeout:\n if i == retries - 1:\n raise\n time.sleep(1)\n```\n\nPlease help me debug this.",
}
],
tools=[{"type": "memory_20250818", "name": "memory"}],
)
print(message)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
const message = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 2048,
messages: [
{
role: "user",
content:
"I'm working on a Python web scraper that keeps crashing with a timeout error. Here's the problematic function:\n\n```python\ndef fetch_page(url, retries=3):\n for i in range(retries):\n try:\n response = requests.get(url, timeout=5)\n return response.text\n except requests.exceptions.Timeout:\n if i == retries - 1:\n raise\n time.sleep(1)\n```\n\nPlease help me debug this."
}
],
tools: [{ type: "memory_20250818", name: "memory" }]
});
console.log(message);
```
```csharp C#
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
public class Program
{
public static async Task Main(string[] args)
{
AnthropicClient client = new()
{
ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")
};
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 2048,
Messages = [
new()
{
Role = Role.User,
Content = "I'm working on a Python web scraper that keeps crashing with a timeout error. Here's the problematic function:\n\n```python\ndef fetch_page(url, retries=3):\n for i in range(retries):\n try:\n response = requests.get(url, timeout=5)\n return response.text\n except requests.exceptions.Timeout:\n if i == retries - 1:\n raise\n time.sleep(1)\n```\n\nPlease help me debug this."
}
],
Tools = [new ToolUnion(new MemoryTool20250818())]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 2048,
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("I'm working on a Python web scraper that keeps crashing with a timeout error. Here's the problematic function:\n\n```python\ndef fetch_page(url, retries=3):\n for i in range(retries):\n try:\n response = requests.get(url, timeout=5)\n return response.text\n except requests.exceptions.Timeout:\n if i == retries - 1:\n raise\n time.sleep(1)\n```\n\nPlease help me debug this.")),
},
Tools: []anthropic.BetaToolUnionParam{
{OfMemoryTool20250818: &anthropic.BetaMemoryTool20250818Param{}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..2,4..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MemoryTool20250818;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
public class MemoryToolExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(2048L)
.addUserMessage("I'm working on a Python web scraper that keeps crashing with a timeout error. Here's the problematic function:\n\n```python\ndef fetch_page(url, retries=3):\n for i in range(retries):\n try:\n response = requests.get(url, timeout=5)\n return response.text\n except requests.exceptions.Timeout:\n if i == retries - 1:\n raise\n time.sleep(1)\n```\n\nPlease help me debug this.")
.addTool(MemoryTool20250818.builder().build())
.build();
Message response = client.messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 2048,
messages: [
[
'role' => 'user',
'content' => "I'm working on a Python web scraper that keeps crashing with a timeout error. Here's the problematic function:\n\n```python\ndef fetch_page(url, retries=3):\n for i in range(retries):\n try:\n response = requests.get(url, timeout=5)\n return response.text\n except requests.exceptions.Timeout:\n if i == retries - 1:\n raise\n time.sleep(1)\n```\n\nPlease help me debug this.",
],
],
model: 'claude-opus-4-7',
tools: [
[
'type' => 'memory_20250818',
'name' => 'memory',
],
],
);
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 2048,
messages: [
{
role: "user",
content: "I'm working on a Python web scraper that keeps crashing with a timeout error. Here's the problematic function:\n\n```python\ndef fetch_page(url, retries=3):\n for i in range(retries):\n try:\n response = requests.get(url, timeout=5)\n return response.text\n except requests.exceptions.Timeout:\n if i == retries - 1:\n raise\n time.sleep(1)\n```\n\nPlease help me debug this."
}
],
tools: [
{
type: "memory_20250818",
name: "memory"
}
]
)
puts message
```
## Tool commands
Your client-side implementation needs to handle these memory tool commands. While these specifications describe the recommended behaviors that Claude is most familiar with, you can modify your implementation and return strings as needed for your use case.
### view
Shows directory contents or file contents with optional line ranges:
```json
{
"command": "view",
"path": "/memories",
"view_range": [1, 10] // Optional: view specific lines
}
```
#### Return values
**For directories:** Return a listing that shows files and directories with their sizes:
```text
Here're the files and directories up to 2 levels deep in {path}, excluding hidden items and node_modules:
{size} {path}
{size} {path}/{filename1}
{size} {path}/{filename2}
```
- Lists files up to 2 levels deep
- Shows human-readable sizes (for example, `5.5K`, `1.2M`)
- Excludes hidden items (files starting with `.`) and `node_modules`
- Uses tab character between size and path
**For files:** Return file contents with a header and line numbers:
```text
Here's the content of {path} with line numbers:
{line_numbers}{tab}{content}
```
Line number formatting:
- **Width**: 6 characters, right-aligned with space padding
- **Separator**: Tab character between line number and content
- **Indexing**: 1-indexed (first line is line 1)
- **Line limit**: Files with more than 999,999 lines should return an error: `"File {path} exceeds maximum line limit of 999,999 lines."`
**Example output:**
```text
Here's the content of /memories/notes.txt with line numbers:
1 Hello World
2 This is line two
10 Line ten
100 Line one hundred
```
#### Error handling
- **File/directory does not exist**: `"The path {path} does not exist. Please provide a valid path."`
### create
Create a new file:
```json
{
"command": "create",
"path": "/memories/notes.txt",
"file_text": "Meeting notes:\n- Discussed project timeline\n- Next steps defined\n"
}
```
#### Return values
- **Success**: `"File created successfully at: {path}"`
#### Error handling
- **File already exists**: `"Error: File {path} already exists"`
### str_replace
Replace text in a file:
```json
{
"command": "str_replace",
"path": "/memories/preferences.txt",
"old_str": "Favorite color: blue",
"new_str": "Favorite color: green"
}
```
#### Return values
- **Success**: `"The memory file has been edited."` followed by a snippet of the edited file with line numbers
#### Error handling
- **File does not exist**: `"Error: The path {path} does not exist. Please provide a valid path."`
- **Text not found**: ``"No replacement was performed, old_str `{old_str}` did not appear verbatim in {path}."``
- **Duplicate text**: When `old_str` appears multiple times, return: ``"No replacement was performed. Multiple occurrences of old_str `{old_str}` in lines: {line_numbers}. Please ensure it is unique"``
#### Directory handling
If the path is a directory, return a "file does not exist" error.
### insert
Insert text at a specific line:
```json
{
"command": "insert",
"path": "/memories/todo.txt",
"insert_line": 2,
"insert_text": "- Review memory tool documentation\n"
}
```
#### Return values
- **Success**: `"The file {path} has been edited."`
#### Error handling
- **File does not exist**: `"Error: The path {path} does not exist"`
- **Invalid line number**: ``"Error: Invalid `insert_line` parameter: {insert_line}. It should be within the range of lines of the file: [0, {n_lines}]"``
#### Directory handling
If the path is a directory, return a "file does not exist" error.
### delete
Delete a file or directory:
```json
{
"command": "delete",
"path": "/memories/old_file.txt"
}
```
#### Return values
- **Success**: `"Successfully deleted {path}"`
#### Error handling
- **File/directory does not exist**: `"Error: The path {path} does not exist"`
#### Directory handling
Deletes the directory and all its contents recursively.
### rename
Rename or move a file/directory:
```json
{
"command": "rename",
"old_path": "/memories/draft.txt",
"new_path": "/memories/final.txt"
}
```
#### Return values
- **Success**: `"Successfully renamed {old_path} to {new_path}"`
#### Error handling
- **Source does not exist**: `"Error: The path {old_path} does not exist"`
- **Destination already exists**: Return an error (do not overwrite): `"Error: The destination {new_path} already exists"`
#### Directory handling
Renames the directory.
## Prompting guidance
This instruction is automatically included in the system prompt when the memory tool is enabled:
```text
IMPORTANT: ALWAYS VIEW YOUR MEMORY DIRECTORY BEFORE DOING ANYTHING ELSE.
MEMORY PROTOCOL:
1. Use the `view` command of your `memory` tool to check for earlier progress.
2. ... (work on the task) ...
- As you make progress, record status / progress / thoughts etc in your memory.
ASSUME INTERRUPTION: Your context window might be reset at any moment, so you risk losing any progress that is not recorded in your memory directory.
```
If you observe Claude creating cluttered memory files, you can include this instruction:
> Note: when editing your memory folder, always try to keep its content up-to-date, coherent and organized. You can rename or delete files that are no longer relevant. Do not create new files unless necessary.
You can also guide what Claude writes to memory. For example: "Only write down information relevant to \ in your memory system."
## Security considerations
Here are important security concerns when implementing your memory store:
### Sensitive information
Claude will usually refuse to write down sensitive information in memory files. However, you may want to implement stricter validation that strips out potentially sensitive information.
### File storage size
Consider tracking memory file sizes and preventing files from growing too large. Consider adding a maximum number of characters the memory read command can return, and let Claude paginate through contents.
### Memory expiration
Consider clearing out memory files periodically that haven't been accessed in an extended time.
### Path traversal protection
Malicious path inputs could attempt to access files outside the `/memories` directory. Your implementation **MUST** validate all paths to prevent directory traversal attacks.
Consider these safeguards:
- Validate that all paths start with `/memories`
- Resolve paths to their canonical form and verify they remain within the memory directory
- Reject paths containing sequences like `../`, `..\\`, or other traversal patterns
- Watch for URL-encoded traversal sequences (`%2e%2e%2f`)
- Use your language's built-in path security utilities (for example, Python's `pathlib.Path.resolve()` and `relative_to()`)
## Error handling
The memory tool uses similar error handling patterns to the [text editor tool](/docs/en/agents-and-tools/tool-use/text-editor-tool#handle-errors). See the individual tool command sections above for detailed error messages and behaviors. Common errors include file not found, permission errors, invalid paths, and duplicate text matches.
## Context editing integration
The memory tool pairs with context editing to manage long-running conversations. For details, see [Context editing](/docs/en/build-with-claude/context-editing).
## Using with Compaction
The memory tool can also be paired with [compaction](/docs/en/build-with-claude/compaction), which provides server-side summarization of older conversation context. While context editing clears specific tool results on the client side, compaction automatically summarizes the entire conversation on the server side when it approaches the context window limit.
For long-running agentic workflows, consider using both: compaction keeps the active context manageable without client-side bookkeeping, and memory persists important information across compaction boundaries so that nothing critical is lost in the summary.
## Multi-session software development pattern
For long-running software projects that span multiple agent sessions, memory files need to be bootstrapped deliberately, not just written ad hoc as work progresses. The pattern below turns memory into a structured recovery mechanism, so each new session can pick up exactly where the last one left off.
### How it works
1. **Initializer session:** The first session sets up the memory artifacts before any substantive work begins. This includes a progress log (tracking what has been done and what comes next), a feature checklist (defining the scope of work), and a reference to any startup or initialization script the project needs.
2. **Subsequent sessions:** Each new session opens by reading those memory artifacts. This recovers the full state of the project in seconds, without needing to re-explore the codebase or retrace earlier decisions.
3. **End-of-session update:** Before a session ends, it updates the progress log with what was completed and what remains. This ensures the next session has an accurate starting point.
### Key principle
Work on one feature at a time. Only mark a feature complete after end-to-end verification confirms it works, not just after the code is written. This keeps the progress log trustworthy and prevents scope creep from compounding across sessions.
For a detailed case study of this pattern in practice, including the initializer script, progress file structure, and git-based recovery, see [Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents).
## Next steps
Directory of Anthropic-provided tools and their properties.
Manage conversation length alongside memory.
---
# Parallel tool use
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/parallel-tool-use
# Parallel tool use
Enable and format parallel tool calls, with message-history guidance and troubleshooting.
---
This page covers parallel tool calls: when Claude calls multiple tools in one turn, how to format the message history so parallelism keeps working, and how to disable it. For the single-call flow, see [Handle tool calls](/docs/en/agents-and-tools/tool-use/handle-tool-calls).
By default, Claude may use multiple tools to answer a user query. You can disable this behavior by:
- Setting `disable_parallel_tool_use=true` when tool_choice type is `auto`, which ensures that Claude uses **at most one** tool
- Setting `disable_parallel_tool_use=true` when tool_choice type is `any` or `tool`, which ensures that Claude uses **exactly one** tool
## Worked example
**Simpler with Tool Runner**: The example below shows manual parallel tool handling. For most use cases, [Tool Runner](/docs/en/agents-and-tools/tool-use/tool-runner) automatically handles parallel tool execution with much less code.
Here's a complete, runnable script to test and verify parallel tool calls are working correctly:
```python Python hidelines={1..9}
#!/usr/bin/env python3
"""Test script to verify parallel tool calls with the Claude API"""
import os
from anthropic import Anthropic
# Initialize client
client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
# Define tools
tools = [
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
}
},
"required": ["location"],
},
},
{
"name": "get_time",
"description": "Get the current time in a given timezone",
"input_schema": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "The timezone, e.g. America/New_York",
}
},
"required": ["timezone"],
},
},
]
# Test conversation with parallel tool calls
messages = [
{
"role": "user",
"content": "What's the weather in SF and NYC, and what time is it there?",
}
]
# Make initial request
print("Requesting parallel tool calls...")
response = client.messages.create(
model="claude-opus-4-7", max_tokens=1024, messages=messages, tools=tools
)
# Check for parallel tool calls
tool_uses = [block for block in response.content if block.type == "tool_use"]
print(f"\n✓ Claude made {len(tool_uses)} tool calls")
if len(tool_uses) > 1:
print("✓ Parallel tool calls detected!")
for tool in tool_uses:
print(f" - {tool.name}: {tool.input}")
else:
print("✗ No parallel tool calls detected")
# Simulate tool execution and format results correctly
tool_results = []
for tool_use in tool_uses:
if tool_use.name == "get_weather":
if "San Francisco" in str(tool_use.input):
result = "San Francisco: 68°F, partly cloudy"
else:
result = "New York: 45°F, clear skies"
else: # get_time
if "Los_Angeles" in str(tool_use.input):
result = "2:30 PM PST"
else:
result = "5:30 PM EST"
tool_results.append(
{"type": "tool_result", "tool_use_id": tool_use.id, "content": result}
)
# Continue conversation with tool results
messages.extend(
[
{"role": "assistant", "content": response.content},
{"role": "user", "content": tool_results}, # All results in one message!
]
)
# Get final response
print("\nGetting final response...")
final_response = client.messages.create(
model="claude-opus-4-7", max_tokens=1024, messages=messages, tools=tools
)
print(f"\nClaude's response:\n{final_response.content[0].text}")
# Verify formatting
print("\n--- Verification ---")
print(f"✓ Tool results sent in single user message: {len(tool_results)} results")
print("✓ No text before tool results in content array")
print("✓ Conversation formatted correctly for future parallel tool use")
```
```typescript TypeScript hidelines={1..4}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// Define tools
const tools: Anthropic.Tool[] = [
{
name: "get_weather",
description: "Get the current weather in a given location",
input_schema: {
type: "object" as const,
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
}
},
required: ["location"]
}
},
{
name: "get_time",
description: "Get the current time in a given timezone",
input_schema: {
type: "object" as const,
properties: {
timezone: {
type: "string",
description: "The timezone, e.g. America/New_York"
}
},
required: ["timezone"]
}
}
];
async function testParallelTools() {
// Make initial request
console.log("Requesting parallel tool calls...");
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: "What's the weather in SF and NYC, and what time is it there?"
}
],
tools: tools
});
// Check for parallel tool calls
const toolUses = response.content.filter((block) => block.type === "tool_use");
console.log(`\n✓ Claude made ${toolUses.length} tool calls`);
if (toolUses.length > 1) {
console.log("✓ Parallel tool calls detected!");
toolUses.forEach((tool) => {
if (tool.type === "tool_use") {
console.log(` - ${tool.name}: ${JSON.stringify(tool.input)}`);
}
});
} else {
console.log("✗ No parallel tool calls detected");
}
// Simulate tool execution and format results correctly
const toolResults: Anthropic.ToolResultBlockParam[] = toolUses
.filter((block): block is Anthropic.ToolUseBlock => block.type === "tool_use")
.map((toolUse) => {
const input = toolUse.input as Record;
let result: string;
if (toolUse.name === "get_weather") {
result = input.location?.includes("San Francisco")
? "San Francisco: 68F, partly cloudy"
: "New York: 45F, clear skies";
} else {
result = input.timezone?.includes("Los_Angeles") ? "2:30 PM PST" : "5:30 PM EST";
}
return {
type: "tool_result" as const,
tool_use_id: toolUse.id,
content: result
};
});
// Get final response with correct formatting
console.log("\nGetting final response...");
const finalResponse = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: "What's the weather in SF and NYC, and what time is it there?"
},
{ role: "assistant", content: response.content },
{ role: "user", content: toolResults }
],
tools: tools
});
for (const block of finalResponse.content) {
if (block.type === "text") {
console.log(`\nClaude's response:\n${block.text}`);
}
}
// Verify formatting
console.log("\n--- Verification ---");
console.log(`✓ Tool results sent in single user message: ${toolResults.length} results`);
console.log("✓ No text before tool results in content array");
console.log("✓ Conversation formatted correctly for future parallel tool use");
}
testParallelTools().catch(console.error);
```
```csharp C# hidelines={1..12,-2..-1}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
public class Program
{
public static async Task Main()
{
AnthropicClient client = new();
var tools = new List
{
new ToolUnion(new Tool()
{
Name = "get_weather",
Description = "Get the current weather in a given location",
InputSchema = new InputSchema()
{
Properties = new Dictionary
{
["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "The city and state, e.g. San Francisco, CA" }),
},
Required = ["location"],
},
}),
new ToolUnion(new Tool()
{
Name = "get_time",
Description = "Get the current time in a given timezone",
InputSchema = new InputSchema()
{
Properties = new Dictionary
{
["timezone"] = JsonSerializer.SerializeToElement(new { type = "string", description = "The timezone, e.g. America/New_York" }),
},
Required = ["timezone"],
},
}),
};
Console.WriteLine("Requesting parallel tool calls...");
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "What's the weather in SF and NYC, and what time is it there?" }],
Tools = tools
};
var response = await client.Messages.Create(parameters);
var toolUses = new List();
foreach (var block in response.Content)
{
if (block.TryPickToolUse(out var toolUse))
{
toolUses.Add(toolUse);
}
}
Console.WriteLine($"\n\u2713 Claude made {toolUses.Count} tool calls");
if (toolUses.Count > 1)
{
Console.WriteLine("\u2713 Parallel tool calls detected!");
foreach (var tool in toolUses)
{
Console.WriteLine($" - {tool.Name}: {tool.Input}");
}
}
else
{
Console.WriteLine("\u2717 No parallel tool calls detected");
}
var toolResults = new List();
foreach (var toolUse in toolUses)
{
string result;
if (toolUse.Name == "get_weather")
{
result = toolUse.Input.ToString()!.Contains("San Francisco")
? "San Francisco: 68\u00b0F, partly cloudy"
: "New York: 45\u00b0F, clear skies";
}
else
{
result = toolUse.Input.ToString()!.Contains("Los_Angeles")
? "2:30 PM PST"
: "5:30 PM EST";
}
toolResults.Add(new ContentBlockParam(new ToolResultBlockParam()
{
ToolUseID = toolUse.ID,
Content = result,
}));
}
Console.WriteLine("\nGetting final response...");
var finalParameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [
new() { Role = Role.User, Content = "What's the weather in SF and NYC, and what time is it there?" },
new() { Role = Role.Assistant, Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList() },
new() { Role = Role.User, Content = new MessageParamContent(toolResults) }
],
Tools = tools
};
var finalResponse = await client.Messages.Create(finalParameters);
finalResponse.Content[0].TryPickText(out var text);
Console.WriteLine($"\nClaude's response:\n{text?.Text}");
Console.WriteLine("\n--- Verification ---");
Console.WriteLine($"\u2713 Tool results sent in single user message: {toolResults.Count} results");
Console.WriteLine("\u2713 No text before tool results in content array");
Console.WriteLine("\u2713 Conversation formatted correctly for future parallel tool use");
}
}
```
```go Go hidelines={1..15,-1}
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
tools := []anthropic.ToolUnionParam{
{OfTool: &anthropic.ToolParam{
Name: "get_weather",
Description: anthropic.String("Get the current weather in a given location"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"location": map[string]any{
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
},
Required: []string{"location"},
},
}},
{OfTool: &anthropic.ToolParam{
Name: "get_time",
Description: anthropic.String("Get the current time in a given timezone"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"timezone": map[string]any{
"type": "string",
"description": "The timezone, e.g. America/New_York",
},
},
Required: []string{"timezone"},
},
}},
}
fmt.Println("Requesting parallel tool calls...")
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather in SF and NYC, and what time is it there?")),
},
Tools: tools,
})
if err != nil {
log.Fatal(err)
}
// Find tool use blocks using type switch
type toolUseInfo struct {
ID string
Name string
Input json.RawMessage
}
var toolUses []toolUseInfo
for _, block := range response.Content {
switch variant := block.AsAny().(type) {
case anthropic.ToolUseBlock:
toolUses = append(toolUses, toolUseInfo{
ID: variant.ID,
Name: variant.Name,
Input: variant.Input,
})
}
}
fmt.Printf("\n✓ Claude made %d tool calls\n", len(toolUses))
if len(toolUses) > 1 {
fmt.Println("✓ Parallel tool calls detected!")
for _, tool := range toolUses {
fmt.Printf(" - %s: %s\n", tool.Name, string(tool.Input))
}
} else {
fmt.Println("✗ No parallel tool calls detected")
}
// Build tool results
var toolResults []anthropic.ContentBlockParamUnion
for _, toolUse := range toolUses {
var result string
inputStr := string(toolUse.Input)
if toolUse.Name == "get_weather" {
if strings.Contains(inputStr, "San Francisco") {
result = "San Francisco: 68°F, partly cloudy"
} else {
result = "New York: 45°F, clear skies"
}
} else {
if strings.Contains(inputStr, "Los_Angeles") {
result = "2:30 PM PST"
} else {
result = "5:30 PM EST"
}
}
toolResults = append(toolResults, anthropic.NewToolResultBlock(toolUse.ID, result, false))
}
// Convert response content to param types for the assistant message
var contentParams []anthropic.ContentBlockParamUnion
for _, block := range response.Content {
contentParams = append(contentParams, block.ToParam())
}
fmt.Println("\nGetting final response...")
finalResponse, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather in SF and NYC, and what time is it there?")),
anthropic.NewAssistantMessage(contentParams...),
anthropic.NewUserMessage(toolResults...),
},
Tools: tools,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("\nClaude's response:\n%s\n", finalResponse.Content[0].Text)
fmt.Println("\n--- Verification ---")
fmt.Printf("✓ Tool results sent in single user message: %d results\n", len(toolResults))
fmt.Println("✓ No text before tool results in content array")
fmt.Println("✓ Conversation formatted correctly for future parallel tool use")
}
```
```java Java hidelines={1..17,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.ContentBlock;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.Tool.InputSchema;
import com.anthropic.models.messages.ToolResultBlockParam;
import com.anthropic.models.messages.ToolUseBlock;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
Tool weatherTool = Tool.builder()
.name("get_weather")
.description("Get the current weather in a given location")
.inputSchema(InputSchema.builder()
.properties(JsonValue.from(Map.of(
"location", Map.of(
"type", "string",
"description", "The city and state, e.g. San Francisco, CA"
)
)))
.putAdditionalProperty("required", JsonValue.from(List.of("location")))
.build())
.build();
Tool timeTool = Tool.builder()
.name("get_time")
.description("Get the current time in a given timezone")
.inputSchema(InputSchema.builder()
.properties(JsonValue.from(Map.of(
"timezone", Map.of(
"type", "string",
"description", "The timezone, e.g. America/New_York"
)
)))
.putAdditionalProperty("required", JsonValue.from(List.of("timezone")))
.build())
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addTool(weatherTool)
.addTool(timeTool)
.addUserMessage("What's the weather in SF and NYC, and what time is it there?")
.build();
IO.println("Requesting parallel tool calls...");
Message response = client.messages().create(params);
List toolUses = new ArrayList<>();
for (ContentBlock block : response.content()) {
if (block.toolUse().isPresent()) {
toolUses.add(block.toolUse().get());
}
}
IO.println("\n✓ Claude made " + toolUses.size() + " tool calls");
if (toolUses.size() > 1) {
IO.println("✓ Parallel tool calls detected!");
for (ToolUseBlock tool : toolUses) {
IO.println(" - " + tool.name() + ": " + tool._input());
}
} else {
IO.println("✗ No parallel tool calls detected");
}
List toolResults = new ArrayList<>();
for (ToolUseBlock toolUse : toolUses) {
String result;
if (toolUse.name().equals("get_weather")) {
String location = toolUse._input().toString();
result = location.contains("San Francisco")
? "San Francisco: 68°F, partly cloudy"
: "New York: 45°F, clear skies";
} else {
String timezone = toolUse._input().toString();
result = timezone.contains("Los_Angeles")
? "2:30 PM PST"
: "5:30 PM EST";
}
toolResults.add(ContentBlockParam.ofToolResult(
ToolResultBlockParam.builder()
.toolUseId(toolUse.id())
.content(result)
.build()
));
}
IO.println("\nGetting final response...");
MessageCreateParams finalParams = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addTool(weatherTool)
.addTool(timeTool)
.addUserMessage("What's the weather in SF and NYC, and what time is it there?")
.addMessage(response)
.addUserMessageOfBlockParams(toolResults)
.build();
Message finalResponse = client.messages().create(finalParams);
finalResponse.content().stream()
.flatMap(block -> block.text().stream())
.forEach(textBlock -> IO.println("\nClaude's response:\n" + textBlock.text()));
IO.println("\n--- Verification ---");
IO.println("✓ Tool results sent in single user message: " + toolResults.size() + " results");
IO.println("✓ No text before tool results in content array");
IO.println("✓ Conversation formatted correctly for future parallel tool use");
}
```
```php PHP hidelines={1..6}
'get_weather',
'description' => 'Get the current weather in a given location',
'input_schema' => [
'type' => 'object',
'properties' => [
'location' => [
'type' => 'string',
'description' => 'The city and state, e.g. San Francisco, CA'
]
],
'required' => ['location']
]
],
[
'name' => 'get_time',
'description' => 'Get the current time in a given timezone',
'input_schema' => [
'type' => 'object',
'properties' => [
'timezone' => [
'type' => 'string',
'description' => 'The timezone, e.g. America/New_York'
]
],
'required' => ['timezone']
]
]
];
echo "Requesting parallel tool calls...\n";
$response = $client->messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => "What's the weather in SF and NYC, and what time is it there?"]
],
model: 'claude-opus-4-7',
tools: $tools,
);
$toolUses = array_filter($response->content, fn($block) => $block->type === 'tool_use');
echo "\n✓ Claude made " . count($toolUses) . " tool calls\n";
if (count($toolUses) > 1) {
echo "✓ Parallel tool calls detected!\n";
foreach ($toolUses as $tool) {
echo " - {$tool->name}: " . json_encode($tool->input) . "\n";
}
} else {
echo "✗ No parallel tool calls detected\n";
}
$toolResults = [];
foreach ($toolUses as $toolUse) {
if ($toolUse->name === 'get_weather') {
$result = str_contains(json_encode($toolUse->input), 'San Francisco')
? 'San Francisco: 68°F, partly cloudy'
: 'New York: 45°F, clear skies';
} else {
$result = str_contains(json_encode($toolUse->input), 'Los_Angeles')
? '2:30 PM PST'
: '5:30 PM EST';
}
$toolResults[] = [
'type' => 'tool_result',
'tool_use_id' => $toolUse->id,
'content' => $result
];
}
echo "\nGetting final response...\n";
$finalResponse = $client->messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => "What's the weather in SF and NYC, and what time is it there?"],
['role' => 'assistant', 'content' => $response->content],
['role' => 'user', 'content' => $toolResults]
],
model: 'claude-opus-4-7',
tools: $tools,
);
echo "\nClaude's response:\n{$finalResponse->content[0]->text}\n";
echo "\n--- Verification ---\n";
echo "✓ Tool results sent in single user message: " . count($toolResults) . " results\n";
echo "✓ No text before tool results in content array\n";
echo "✓ Conversation formatted correctly for future parallel tool use\n";
```
```ruby Ruby
require "anthropic"
client = Anthropic::Client.new
tools = [
{
name: "get_weather",
description: "Get the current weather in a given location",
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
}
},
required: ["location"]
}
},
{
name: "get_time",
description: "Get the current time in a given timezone",
input_schema: {
type: "object",
properties: {
timezone: {
type: "string",
description: "The timezone, e.g. America/New_York"
}
},
required: ["timezone"]
}
}
]
puts "Requesting parallel tool calls..."
response = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "What's the weather in SF and NYC, and what time is it there?" }
],
tools: tools
)
tool_uses = response.content.select { |block| block.type == :tool_use }
puts "\n✓ Claude made #{tool_uses.length} tool calls"
if tool_uses.length > 1
puts "✓ Parallel tool calls detected!"
tool_uses.each do |tool|
puts " - #{tool.name}: #{tool.input}"
end
else
puts "✗ No parallel tool calls detected"
end
tool_results = tool_uses.map do |tool_use|
result = if tool_use.name == "get_weather"
location = tool_use.input["location"].to_s
location.include?("San Francisco") ? "San Francisco: 68°F, partly cloudy" : "New York: 45°F, clear skies"
else
timezone = tool_use.input["timezone"].to_s
timezone.include?("Los_Angeles") ? "2:30 PM PST" : "5:30 PM EST"
end
{
type: "tool_result",
tool_use_id: tool_use.id,
content: result
}
end
puts "\nGetting final response..."
final_response = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "What's the weather in SF and NYC, and what time is it there?" },
{ role: "assistant", content: response.content },
{ role: "user", content: tool_results }
],
tools: tools
)
puts "\nClaude's response:\n#{final_response.content.first.text}"
puts "\n--- Verification ---"
puts "✓ Tool results sent in single user message: #{tool_results.length} results"
puts "✓ No text before tool results in content array"
puts "✓ Conversation formatted correctly for future parallel tool use"
```
This script demonstrates:
- How to properly format parallel tool calls and results
- How to verify that parallel calls are being made
- The correct message structure that encourages future parallel tool use
- Common mistakes to avoid (like text before tool results)
Run this script to test your implementation and ensure Claude is making parallel tool calls effectively.
## Maximizing parallel tool use
While Claude 4 models have excellent parallel tool use capabilities by default, you can increase the likelihood of parallel tool execution across all models with targeted prompting:
For Claude 4 models, add this to your system prompt:
```text
For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.
```
For even stronger parallel tool use (recommended if the default isn't sufficient), use:
```text
For maximum efficiency, whenever you perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially. Prioritize calling tools in parallel whenever possible. For example, when reading 3 files, run 3 tool calls in parallel to read all 3 files into context at the same time. When running multiple read-only commands like `ls` or `list_dir`, always run all of the commands in parallel. Err on the side of maximizing parallel tool calls rather than running too many tools sequentially.
```
You can also encourage parallel tool use within specific user messages:
```text
Instead of:
"What's the weather in Paris? Also check London."
Use:
"Check the weather in Paris and London simultaneously."
Or be explicit:
"Please use parallel tool calls to get the weather for Paris, London, and Tokyo at the same time."
```
## Troubleshooting
If Claude isn't making parallel tool calls when expected, check these common issues:
**1. Incorrect tool result formatting**
The most common issue is formatting tool results incorrectly in the conversation history. This "teaches" Claude to avoid parallel calls.
Specifically for parallel tool use:
- ❌ **Wrong**: Sending separate user messages for each tool result
- ✅ **Correct**: All tool results must be in a single user message
```json
// ❌ This reduces parallel tool use
[
{"role": "assistant", "content": [tool_use_1, tool_use_2]},
{"role": "user", "content": [tool_result_1]},
{"role": "user", "content": [tool_result_2]} // Separate message
]
// ✅ This maintains parallel tool use
[
{"role": "assistant", "content": [tool_use_1, tool_use_2]},
{"role": "user", "content": [tool_result_1, tool_result_2]} // Single message
]
```
See [Handle tool calls](/docs/en/agents-and-tools/tool-use/handle-tool-calls) for other formatting rules.
**2. Weak prompting**
Default prompting may not be sufficient. Use the stronger system prompt from the [Maximizing parallel tool use](#maximizing-parallel-tool-use) section above.
**3. Measuring parallel tool usage**
To verify parallel tool calls are working:
```python hidelines={1}
messages = [] # Your conversation history of assistant Message objects
# Calculate average tools per tool-calling message
tool_call_messages = [
msg for msg in messages if any(block.type == "tool_use" for block in msg.content)
]
total_tool_calls = sum(
len([b for b in msg.content if b.type == "tool_use"]) for msg in tool_call_messages
)
avg_tools_per_message = (
total_tool_calls / len(tool_call_messages) if tool_call_messages else 0.0
)
print(f"Average tools per message: {avg_tools_per_message}")
# Should be > 1.0 if parallel calls are working
```
## Next steps
- For the single-tool-call flow and `tool_result` formatting rules, see [Handle tool calls](/docs/en/agents-and-tools/tool-use/handle-tool-calls).
- For the SDK abstraction that handles parallel execution automatically, see [Tool Runner](/docs/en/agents-and-tools/tool-use/tool-runner).
- For the full tool-use workflow, see [Define tools](/docs/en/agents-and-tools/tool-use/define-tools).
---
# Server tools
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/server-tools
# Server tools
Work with Anthropic-executed tools: server_tool_use blocks, pause_turn continuation, and domain filtering.
---
This page covers the shared mechanics of server-executed tools: the `server_tool_use` block, `pause_turn` continuation, ZDR considerations, and domain filtering. For individual tools, see the [tool reference](/docs/en/agents-and-tools/tool-use/tool-reference).
## The server_tool_use block
The `server_tool_use` block appears in Claude's response when a server-executed tool runs. Its `id` field uses the `srvtoolu_` prefix to distinguish it from client tool calls:
```json
{
"type": "server_tool_use",
"id": "srvtoolu_01A2B3C4D5E6F7G8H9",
"name": "web_search",
"input": { "query": "latest quantum computing breakthroughs" }
}
```
The API executes the tool internally. You see the call and its result in the response, but you don't handle execution. Unlike client `tool_use` blocks, you don't need to respond with a `tool_result`. The result block appears immediately after the `server_tool_use` block in the same assistant turn.
## The server-side loop and pause_turn
When using server tools like web search, the API may return a `pause_turn` stop reason, indicating that the API has paused a long-running turn.
Here's how to handle the `pause_turn` stop reason:
```python Python hidelines={1..4}
import anthropic
client = anthropic.Anthropic()
# Initial request with web search
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Search for comprehensive information about quantum computing breakthroughs in 2025",
}
],
tools=[{"type": "web_search_20250305", "name": "web_search", "max_uses": 10}],
)
# Check if the response has pause_turn stop reason
if response.stop_reason == "pause_turn":
# Continue the conversation with the paused content
messages = [
{
"role": "user",
"content": "Search for comprehensive information about quantum computing breakthroughs in 2025",
},
{"role": "assistant", "content": response.content},
]
# Send the continuation request
continuation = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=messages,
tools=[{"type": "web_search_20250305", "name": "web_search", "max_uses": 10}],
)
print(continuation)
else:
print(response)
```
```typescript TypeScript hidelines={1..4}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function main() {
// Initial request with web search
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content:
"Search for comprehensive information about quantum computing breakthroughs in 2025"
}
],
tools: [
{
type: "web_search_20250305",
name: "web_search",
max_uses: 10
}
]
});
// Check if the response has pause_turn stop reason
if (response.stop_reason === "pause_turn") {
// Continue the conversation with the paused content
const messages: Anthropic.MessageParam[] = [
{
role: "user",
content:
"Search for comprehensive information about quantum computing breakthroughs in 2025"
},
{ role: "assistant", content: response.content }
];
// Send the continuation request
const continuation = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: messages,
tools: [
{
type: "web_search_20250305",
name: "web_search",
max_uses: 10
}
]
});
console.log(continuation);
} else {
console.log(response);
}
}
main().catch(console.error);
```
```csharp C#
using Anthropic;
using Anthropic.Models.Messages;
using System;
using System.Linq;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 1024,
Messages = [
new() {
Role = Role.User,
Content = "Search for comprehensive information about quantum computing breakthroughs in 2025"
}
],
Tools = [new ToolUnion(new WebSearchTool20250305() { MaxUses = 10 })]
};
var response = await client.Messages.Create(parameters);
if (response.StopReason == "pause_turn")
{
var continuationParams = new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 1024,
Messages = [
new() {
Role = Role.User,
Content = "Search for comprehensive information about quantum computing breakthroughs in 2025"
},
new() {
Role = Role.Assistant,
Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList()
}
],
Tools = [new ToolUnion(new WebSearchTool20250305() { MaxUses = 10 })]
};
var continuation = await client.Messages.Create(continuationParams);
Console.WriteLine(continuation);
}
else
{
Console.WriteLine(response);
}
}
}
```
```go Go hidelines={1..13,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
webSearchTool := []anthropic.ToolUnionParam{
{OfWebSearchTool20250305: &anthropic.WebSearchTool20250305Param{
MaxUses: anthropic.Int(10),
}},
}
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Search for comprehensive information about quantum computing breakthroughs in 2025")),
},
Tools: webSearchTool,
})
if err != nil {
log.Fatal(err)
}
if response.StopReason == "pause_turn" {
// Convert response content to param types for the assistant message
var contentParams []anthropic.ContentBlockParamUnion
for _, block := range response.Content {
contentParams = append(contentParams, block.ToParam())
}
continuation, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Search for comprehensive information about quantum computing breakthroughs in 2025")),
anthropic.NewAssistantMessage(contentParams...),
},
Tools: webSearchTool,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(continuation)
} else {
fmt.Println(response)
}
}
```
```java Java hidelines={1..4,7..8,-1..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.StopReason;
import com.anthropic.models.messages.WebSearchTool20250305;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(1024L)
.addUserMessage("Search for comprehensive information about quantum computing breakthroughs in 2025")
.addTool(WebSearchTool20250305.builder()
.maxUses(10L)
.build())
.build();
Message response = client.messages().create(params);
if (response.stopReason().isPresent()
&& response.stopReason().get().equals(StopReason.PAUSE_TURN)) {
MessageCreateParams continuationParams = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(1024L)
.addUserMessage("Search for comprehensive information about quantum computing breakthroughs in 2025")
.addMessage(response)
.addTool(WebSearchTool20250305.builder()
.maxUses(10L)
.build())
.build();
Message continuation = client.messages().create(continuationParams);
IO.println(continuation);
} else {
IO.println(response);
}
}
```
```php PHP hidelines={1..6}
messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => 'Search for comprehensive information about quantum computing breakthroughs in 2025'
]
],
model: 'claude-opus-4-7',
tools: [
[
'type' => 'web_search_20250305',
'name' => 'web_search',
'max_uses' => 10
]
],
);
if ($response->stopReason === 'pause_turn') {
$messages = [
[
'role' => 'user',
'content' => 'Search for comprehensive information about quantum computing breakthroughs in 2025'
],
[
'role' => 'assistant',
'content' => $response->content
]
];
$continuation = $client->messages->create(
maxTokens: 1024,
messages: $messages,
model: 'claude-opus-4-7',
tools: [
[
'type' => 'web_search_20250305',
'name' => 'web_search',
'max_uses' => 10
]
],
);
echo $continuation;
} else {
echo $response;
}
```
```ruby Ruby
require "anthropic"
client = Anthropic::Client.new
response = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content:
"Search for comprehensive information about quantum computing breakthroughs in 2025"
}
],
tools: [
{
type: "web_search_20250305",
name: "web_search",
max_uses: 10
}
]
)
if response.stop_reason == :pause_turn
messages = [
{
role: "user",
content: "Search for comprehensive information about quantum computing breakthroughs in 2025"
},
{
role: "assistant",
content: response.content
}
]
continuation = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: messages,
tools: [
{
type: "web_search_20250305",
name: "web_search",
max_uses: 10
}
]
)
puts continuation
else
puts response
end
```
When handling `pause_turn`:
- **Continue the conversation:** Pass the paused response back as-is in a subsequent request to let Claude continue its turn
- **Modify if needed:** You can optionally modify the content before continuing if you want to interrupt or redirect the conversation
- **Preserve tool state:** Include the same tools in the continuation request to maintain functionality
## ZDR and allowed_callers
The basic versions of web search (`web_search_20250305`) and web fetch (`web_fetch_20250910`) are eligible for [Zero Data Retention (ZDR)](/docs/en/manage-claude/api-and-data-retention).
The `_20260209` versions with dynamic filtering are **not** ZDR-eligible by default because dynamic filtering relies on code execution internally.
To use a `_20260209` server tool with ZDR, disable dynamic filtering by setting `"allowed_callers": ["direct"]` on the tool:
```json
{
"type": "web_search_20260209",
"name": "web_search",
"allowed_callers": ["direct"]
}
```
This restricts the tool to direct invocation only, bypassing the internal code execution step.
Even when web fetch is used in a ZDR-eligible configuration, website publishers may retain any parameters passed to the URL if Claude fetches content from their site.
## Domain filtering
Server tools that access the web accept `allowed_domains` and `blocked_domains` parameters to control which domains Claude can reach.
When using domain filters:
- Domains should not include the HTTP/HTTPS scheme (use `example.com` instead of `https://example.com`)
- Subdomains are automatically included (`example.com` covers `docs.example.com`)
- Specific subdomains restrict results to only that subdomain (`docs.example.com` returns only results from that subdomain, not from `example.com` or `api.example.com`)
- Subpaths are supported and match anything after the path (`example.com/blog` matches `example.com/blog/post-1`)
- You can use either `allowed_domains` or `blocked_domains`, but not both in the same request
**Wildcard support:**
- Only one wildcard (`*`) is allowed per domain entry, and it must appear after the domain part (in the path)
- Valid: `example.com/*`, `example.com/*/articles`
- Invalid: `*.example.com`, `ex*.com`, `example.com/*/news/*`
Invalid domain formats return an `invalid_tool_input` tool error.
Request-level domain restrictions must be compatible with organization-level domain restrictions configured in Claude Console. Request-level domains can only further restrict domains, not override or expand beyond the organization-level list. If your request includes domains that conflict with organization settings, the API returns a validation error.
Be aware that Unicode characters in domain names can create security vulnerabilities through homograph attacks, where visually similar characters from different scripts can bypass domain filters. For example, `аmazon.com` (using Cyrillic 'а') may appear identical to `amazon.com` but represents a different domain.
When configuring domain allow/block lists:
- Use ASCII-only domain names when possible
- Consider that URL parsers may handle Unicode normalization differently
- Test your domain filters with potential homograph variations
- Regularly audit your domain configurations for suspicious Unicode characters
## Dynamic filtering with code execution
The `_20260209` versions of web search and web fetch use code execution internally to apply dynamic filters against search results.
Including a standalone `code_execution` tool alongside `_20260209` versions of web tools creates two execution environments, which can confuse the model. Use one or the other, or pin both to the same version.
## Streaming server-tool events
Server-tool events stream as part of the normal SSE flow. The `server_tool_use` block and its result arrive as `content_block_start` and `content_block_delta` events, the same way text and client tool calls stream.
See [Streaming](/docs/en/build-with-claude/streaming) for the full event reference. Individual tool pages document tool-specific event names where they differ.
## Batch requests
All server tools support batch processing. See [Batch processing](/docs/en/build-with-claude/batch-processing).
## Next steps
Search the web and cite results.
Retrieve content from specific URLs.
Run Python in a sandboxed container.
Discover and load tools on demand.
---
# Strict tool use
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use
# Strict tool use
Enforce JSON Schema compliance on Claude's tool inputs with grammar-constrained sampling.
---
Setting `strict: true` on a tool definition guarantees Claude's tool inputs match your JSON Schema by constraining the model's token sampling to schema-valid outputs (a technique called grammar-constrained sampling). This page covers why strict mode matters for agents, how to enable it, and common use cases. For the supported JSON Schema subset, see [JSON Schema limitations](/docs/en/build-with-claude/structured-outputs#json-schema-limitations). For non-strict schema guidance, see [Define tools](/docs/en/agents-and-tools/tool-use/define-tools).
Strict tool use validates tool parameters, ensuring Claude calls your functions with correctly-typed arguments. Use strict tool use when you need to:
- Validate tool parameters
- Build agentic workflows
- Ensure type-safe function calls
- Handle complex tools with nested properties
## Why strict tool use matters for agents
Building reliable agentic systems requires guaranteed schema conformance. Without strict mode, Claude might return incompatible types (`"2"` instead of `2`) or missing required fields, breaking your functions and causing runtime errors.
Strict tool use guarantees type-safe parameters:
- Functions receive correctly-typed arguments every time
- No need to validate and retry tool calls
- Production-ready agents that work consistently at scale
For example, suppose a booking system needs `passengers: int`. Without strict mode, Claude might provide `passengers: "two"` or `passengers: "2"`. With `strict: true`, the response always contains `passengers: 2`.
## Quick start
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "What is the weather in San Francisco?"}
],
"tools": [{
"name": "get_weather",
"description": "Get the current weather in a given location",
"strict": true,
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"],
"additionalProperties": false
}
}]
}'
```
```bash CLI
ant messages create --transform content <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content: What is the weather in San Francisco?
tools:
- name: get_weather
description: Get the current weather in a given location
strict: true
input_schema:
type: object
properties:
location:
type: string
description: The city and state, e.g. San Francisco, CA
unit:
type: string
enum: [celsius, fahrenheit]
required: [location]
additionalProperties: false
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{"role": "user", "content": "What's the weather like in San Francisco?"}],
tools=[
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"strict": True, # Enable strict mode
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The unit of temperature, either 'celsius' or 'fahrenheit'",
},
},
"required": ["location"],
"additionalProperties": False,
},
}
],
)
print(response.content)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: "What's the weather like in San Francisco?"
}
],
tools: [
{
name: "get_weather",
description: "Get the current weather in a given location",
strict: true, // Enable strict mode
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"]
}
},
required: ["location"],
additionalProperties: false
}
}
]
});
console.log(response.content);
```
```csharp C#
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "What's the weather like in San Francisco?" }],
Tools = [
new ToolUnion(new Tool()
{
Name = "get_weather",
Description = "Get the current weather in a given location",
Strict = true,
InputSchema = new InputSchema(new Dictionary
{
["properties"] = JsonSerializer.SerializeToElement(new Dictionary
{
["location"] = new { type = "string", description = "The city and state, e.g. San Francisco, CA" },
["unit"] = new { type = "string", @enum = new[] { "celsius", "fahrenheit" } },
}),
["required"] = JsonSerializer.SerializeToElement(new[] { "location" }),
["additionalProperties"] = JsonSerializer.SerializeToElement(false),
}),
}),
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather like in San Francisco?")),
},
Tools: []anthropic.ToolUnionParam{
{OfTool: &anthropic.ToolParam{
Name: "get_weather",
Description: anthropic.String("Get the current weather in a given location"),
Strict: anthropic.Bool(true),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"location": map[string]any{
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
"unit": map[string]any{
"type": "string",
"enum": []string{"celsius", "fahrenheit"},
},
},
Required: []string{"location"},
ExtraFields: map[string]any{
"additionalProperties": false,
},
}}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Content)
}
```
```java Java hidelines={1..12,-1..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.Tool.InputSchema;
import java.util.List;
import java.util.Map;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
InputSchema schema = InputSchema.builder()
.properties(
JsonValue.from(
Map.of(
"location", Map.of(
"type", "string",
"description", "The city and state, e.g. San Francisco, CA"
),
"unit", Map.of(
"type", "string",
"enum", List.of("celsius", "fahrenheit")
)
)
)
)
.putAdditionalProperty("required", JsonValue.from(List.of("location")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessage("What's the weather like in San Francisco?")
.addTool(
Tool.builder()
.name("get_weather")
.description("Get the current weather in a given location")
.strict(true)
.inputSchema(schema)
.build()
)
.build();
Message response = client.messages().create(params);
IO.println(response.content());
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => "What's the weather like in San Francisco?"]
],
model: 'claude-opus-4-7',
tools: [
[
'name' => 'get_weather',
'description' => 'Get the current weather in a given location',
'strict' => true,
'input_schema' => [
'type' => 'object',
'properties' => [
'location' => [
'type' => 'string',
'description' => 'The city and state, e.g. San Francisco, CA'
],
'unit' => [
'type' => 'string',
'enum' => ['celsius', 'fahrenheit']
]
],
'required' => ['location'],
'additionalProperties' => false
]
]
],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "What's the weather like in San Francisco?" }
],
tools: [
{
name: "get_weather",
description: "Get the current weather in a given location",
strict: true,
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"]
}
},
required: ["location"],
additionalProperties: false
}
}
]
)
puts message.content
```
**Response format:** Tool use blocks with validated inputs in `response.content[x].input`
```json Output
{
"type": "tool_use",
"name": "get_weather",
"input": {
"location": "San Francisco, CA"
}
}
```
**Guarantees:**
- Tool `input` strictly follows the `input_schema`
- Tool `name` is always valid (from provided tools or server tools)
## How it works
Create a JSON schema for your tool's `input_schema`. The schema uses standard JSON Schema format with some limitations (see [JSON Schema limitations](/docs/en/build-with-claude/structured-outputs#json-schema-limitations)).
Set `"strict": true` as a top-level property in your tool definition, alongside `name`, `description`, and `input_schema`.
When Claude uses the tool, the `input` field in the tool_use block strictly follows your `input_schema`, and the `name` is always valid.
## Common use cases
Ensure tool parameters exactly match your schema:
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content: Search for flights to Tokyo departing June 1, 2026
tools:
- name: search_flights
strict: true
input_schema:
type: object
properties:
destination:
type: string
departure_date:
type: string
format: date
passengers:
type: integer
enum: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
required: [destination, departure_date]
additionalProperties: false
YAML
```
```python Python hidelines={1..2}
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Search for flights to Tokyo departing June 1, 2026",
}
],
tools=[
{
"name": "search_flights",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"destination": {"type": "string"},
"departure_date": {"type": "string", "format": "date"},
"passengers": {
"type": "integer",
"enum": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
},
},
"required": ["destination", "departure_date"],
"additionalProperties": False,
},
}
],
)
print(response)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const searchFlightsTool: Anthropic.Tool = {
name: "search_flights",
strict: true,
input_schema: {
type: "object",
properties: {
destination: { type: "string" },
departure_date: { type: "string", format: "date" },
passengers: { type: "integer", enum: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }
},
required: ["destination", "departure_date"],
additionalProperties: false
}
};
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Search for flights to Tokyo departing June 1, 2026" }],
tools: [searchFlightsTool]
});
console.log(response);
```
```csharp C#
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Search for flights to Tokyo departing June 1, 2026" }],
Tools = [
new ToolUnion(new Tool()
{
Name = "search_flights",
Strict = true,
InputSchema = new InputSchema(new Dictionary
{
["properties"] = JsonSerializer.SerializeToElement(new Dictionary
{
["destination"] = new { type = "string" },
["departure_date"] = new { type = "string", format = "date" },
["passengers"] = new { type = "integer", @enum = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } },
}),
["required"] = JsonSerializer.SerializeToElement(new[] { "destination", "departure_date" }),
["additionalProperties"] = JsonSerializer.SerializeToElement(false),
}),
}),
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Search for flights to Tokyo departing June 1, 2026")),
},
Tools: []anthropic.ToolUnionParam{
{OfTool: &anthropic.ToolParam{
Name: "search_flights",
Strict: anthropic.Bool(true),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"destination": map[string]any{
"type": "string",
},
"departure_date": map[string]any{
"type": "string",
"format": "date",
},
"passengers": map[string]any{
"type": "integer",
"enum": []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
},
},
Required: []string{"destination", "departure_date"},
ExtraFields: map[string]any{
"additionalProperties": false,
},
}}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..12,-1..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.Tool.InputSchema;
import java.util.List;
import java.util.Map;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
InputSchema schema = InputSchema.builder()
.properties(
JsonValue.from(
Map.of(
"destination", Map.of("type", "string"),
"departure_date", Map.of("type", "string", "format", "date"),
"passengers", Map.of(
"type", "integer",
"enum", List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
)
)
)
)
.putAdditionalProperty("required", JsonValue.from(List.of("destination", "departure_date")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessage("Search for flights to Tokyo departing June 1, 2026")
.addTool(
Tool.builder()
.name("search_flights")
.strict(true)
.inputSchema(schema)
.build()
)
.build();
Message response = client.messages().create(params);
IO.println(response);
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Search for flights to Tokyo departing June 1, 2026']
],
model: 'claude-opus-4-7',
tools: [
[
'name' => 'search_flights',
'strict' => true,
'input_schema' => [
'type' => 'object',
'properties' => [
'destination' => ['type' => 'string'],
'departure_date' => ['type' => 'string', 'format' => 'date'],
'passengers' => [
'type' => 'integer',
'enum' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
]
],
'required' => ['destination', 'departure_date'],
'additionalProperties' => false
]
]
],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "Search for flights to Tokyo departing June 1, 2026" }
],
tools: [
{
name: "search_flights",
strict: true,
input_schema: {
type: "object",
properties: {
destination: { type: "string" },
departure_date: { type: "string", format: "date" },
passengers: {
type: "integer",
enum: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
},
required: ["destination", "departure_date"],
additionalProperties: false
}
}
]
)
puts message
```
Build reliable multi-step agents with guaranteed tool parameters:
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content: >-
Help me plan a trip from New York to Paris for 2 people,
departing June 1, 2026
tools:
- name: search_flights
strict: true
input_schema:
type: object
properties:
origin: {type: string}
destination: {type: string}
departure_date: {type: string, format: date}
travelers: {type: integer, enum: [1, 2, 3, 4, 5, 6]}
required: [origin, destination, departure_date]
additionalProperties: false
- name: search_hotels
strict: true
input_schema:
type: object
properties:
city: {type: string}
check_in: {type: string, format: date}
guests: {type: integer, enum: [1, 2, 3, 4]}
required: [city, check_in]
additionalProperties: false
YAML
```
```python Python hidelines={1..2}
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Help me plan a trip from New York to Paris for 2 people, departing June 1, 2026",
}
],
tools=[
{
"name": "search_flights",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"origin": {"type": "string"},
"destination": {"type": "string"},
"departure_date": {"type": "string", "format": "date"},
"travelers": {"type": "integer", "enum": [1, 2, 3, 4, 5, 6]},
},
"required": ["origin", "destination", "departure_date"],
"additionalProperties": False,
},
},
{
"name": "search_hotels",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"},
"check_in": {"type": "string", "format": "date"},
"guests": {"type": "integer", "enum": [1, 2, 3, 4]},
},
"required": ["city", "check_in"],
"additionalProperties": False,
},
},
],
)
print(response)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "search_flights",
strict: true,
input_schema: {
type: "object",
properties: {
origin: { type: "string" },
destination: { type: "string" },
departure_date: { type: "string", format: "date" },
travelers: { type: "integer", enum: [1, 2, 3, 4, 5, 6] }
},
required: ["origin", "destination", "departure_date"],
additionalProperties: false
}
},
{
name: "search_hotels",
strict: true,
input_schema: {
type: "object",
properties: {
city: { type: "string" },
check_in: { type: "string", format: "date" },
guests: { type: "integer", enum: [1, 2, 3, 4] }
},
required: ["city", "check_in"],
additionalProperties: false
}
}
];
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content:
"Help me plan a trip from New York to Paris for 2 people, departing June 1, 2026"
}
],
tools: tools
});
console.log(response);
```
```csharp C#
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Help me plan a trip from New York to Paris for 2 people, departing June 1, 2026" }],
Tools = [
new ToolUnion(new Tool()
{
Name = "search_flights",
Strict = true,
InputSchema = new InputSchema(new Dictionary
{
["properties"] = JsonSerializer.SerializeToElement(new Dictionary
{
["origin"] = new { type = "string" },
["destination"] = new { type = "string" },
["departure_date"] = new { type = "string", format = "date" },
["travelers"] = new { type = "integer", @enum = new[] { 1, 2, 3, 4, 5, 6 } },
}),
["required"] = JsonSerializer.SerializeToElement(new[] { "origin", "destination", "departure_date" }),
["additionalProperties"] = JsonSerializer.SerializeToElement(false),
}),
}),
new ToolUnion(new Tool()
{
Name = "search_hotels",
Strict = true,
InputSchema = new InputSchema(new Dictionary
{
["properties"] = JsonSerializer.SerializeToElement(new Dictionary
{
["city"] = new { type = "string" },
["check_in"] = new { type = "string", format = "date" },
["guests"] = new { type = "integer", @enum = new[] { 1, 2, 3, 4 } },
}),
["required"] = JsonSerializer.SerializeToElement(new[] { "city", "check_in" }),
["additionalProperties"] = JsonSerializer.SerializeToElement(false),
}),
}),
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Help me plan a trip from New York to Paris for 2 people, departing June 1, 2026")),
},
Tools: []anthropic.ToolUnionParam{
{OfTool: &anthropic.ToolParam{
Name: "search_flights",
Strict: anthropic.Bool(true),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"origin": map[string]any{"type": "string"},
"destination": map[string]any{"type": "string"},
"departure_date": map[string]any{"type": "string", "format": "date"},
"travelers": map[string]any{"type": "integer", "enum": []int{1, 2, 3, 4, 5, 6}},
},
Required: []string{"origin", "destination", "departure_date"},
ExtraFields: map[string]any{
"additionalProperties": false,
},
}}},
{OfTool: &anthropic.ToolParam{
Name: "search_hotels",
Strict: anthropic.Bool(true),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"city": map[string]any{"type": "string"},
"check_in": map[string]any{"type": "string", "format": "date"},
"guests": map[string]any{"type": "integer", "enum": []int{1, 2, 3, 4}},
},
Required: []string{"city", "check_in"},
ExtraFields: map[string]any{
"additionalProperties": false,
},
}}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..12,-1..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.Tool.InputSchema;
import java.util.List;
import java.util.Map;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
InputSchema flightsSchema = InputSchema.builder()
.properties(
JsonValue.from(
Map.of(
"origin", Map.of("type", "string"),
"destination", Map.of("type", "string"),
"departure_date", Map.of("type", "string", "format", "date"),
"travelers", Map.of("type", "integer", "enum", List.of(1, 2, 3, 4, 5, 6))
)
)
)
.putAdditionalProperty("required", JsonValue.from(List.of("origin", "destination", "departure_date")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build();
InputSchema hotelsSchema = InputSchema.builder()
.properties(
JsonValue.from(
Map.of(
"city", Map.of("type", "string"),
"check_in", Map.of("type", "string", "format", "date"),
"guests", Map.of("type", "integer", "enum", List.of(1, 2, 3, 4))
)
)
)
.putAdditionalProperty("required", JsonValue.from(List.of("city", "check_in")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessage("Help me plan a trip from New York to Paris for 2 people, departing June 1, 2026")
.addTool(
Tool.builder()
.name("search_flights")
.strict(true)
.inputSchema(flightsSchema)
.build()
)
.addTool(
Tool.builder()
.name("search_hotels")
.strict(true)
.inputSchema(hotelsSchema)
.build()
)
.build();
Message response = client.messages().create(params);
IO.println(response);
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Help me plan a trip from New York to Paris for 2 people, departing June 1, 2026']
],
model: 'claude-opus-4-7',
tools: [
[
'name' => 'search_flights',
'strict' => true,
'input_schema' => [
'type' => 'object',
'properties' => [
'origin' => ['type' => 'string'],
'destination' => ['type' => 'string'],
'departure_date' => ['type' => 'string', 'format' => 'date'],
'travelers' => ['type' => 'integer', 'enum' => [1, 2, 3, 4, 5, 6]]
],
'required' => ['origin', 'destination', 'departure_date'],
'additionalProperties' => false
]
],
[
'name' => 'search_hotels',
'strict' => true,
'input_schema' => [
'type' => 'object',
'properties' => [
'city' => ['type' => 'string'],
'check_in' => ['type' => 'string', 'format' => 'date'],
'guests' => ['type' => 'integer', 'enum' => [1, 2, 3, 4]]
],
'required' => ['city', 'check_in'],
'additionalProperties' => false
]
]
],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "Help me plan a trip from New York to Paris for 2 people, departing June 1, 2026" }
],
tools: [
{
name: "search_flights",
strict: true,
input_schema: {
type: "object",
properties: {
origin: { type: "string" },
destination: { type: "string" },
departure_date: { type: "string", format: "date" },
travelers: { type: "integer", enum: [1, 2, 3, 4, 5, 6] }
},
required: ["origin", "destination", "departure_date"],
additionalProperties: false
}
},
{
name: "search_hotels",
strict: true,
input_schema: {
type: "object",
properties: {
city: { type: "string" },
check_in: { type: "string", format: "date" },
guests: { type: "integer", enum: [1, 2, 3, 4] }
},
required: ["city", "check_in"],
additionalProperties: false
}
}
]
)
puts message
```
## Data retention
Strict tool use compiles tool `input_schema` definitions into grammars using the same pipeline as [structured outputs](/docs/en/build-with-claude/structured-outputs). Tool schemas are temporarily cached for up to 24 hours since last use. Prompts and responses are not retained beyond the API response.
Strict tool use is HIPAA eligible, but **PHI must not be included in tool schema definitions**. The API caches compiled schemas separately from message content, and these cached schemas do not receive the same PHI protections as prompts and responses. Do not include PHI in `input_schema` property names, `enum` values, `const` values, or `pattern` regular expressions. PHI should only appear in message content (prompts and responses), where it is protected under HIPAA safeguards.
For ZDR and HIPAA eligibility across all features, see [API and data retention](/docs/en/manage-claude/api-and-data-retention).
---
# Text editor tool
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/text-editor-tool
# Text editor tool
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
Claude can use an Anthropic-schema text editor tool to view and modify text files, helping you debug, fix, and improve your code or other text documents. This allows Claude to directly interact with your files, providing hands-on assistance rather than just suggesting changes.
For model support, see the [Tool reference](/docs/en/agents-and-tools/tool-use/tool-reference).
## When to use the text editor tool
Some examples of when to use the text editor tool are:
- **Code debugging:** Have Claude identify and fix bugs in your code, from syntax errors to logic issues.
- **Code refactoring:** Let Claude improve your code structure, readability, and performance through targeted edits.
- **Documentation generation:** Ask Claude to add docstrings, comments, or README files to your codebase.
- **Test creation:** Have Claude create unit tests for your code based on its understanding of the implementation.
## Use the text editor tool
Provide the text editor tool (named `str_replace_based_edit_tool`) to Claude using the Messages API.
You can optionally specify a `max_characters` parameter to control truncation when viewing large files.
`max_characters` is only compatible with `text_editor_20250728` and later versions of the text editor tool.
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"tools": [
{
"type": "text_editor_20250728",
"name": "str_replace_based_edit_tool",
"max_characters": 10000
}
],
"messages": [
{
"role": "user",
"content": "There'\''s a syntax error in my primes.py file. Can you help me fix it?"
}
]
}'
```
```bash CLI
ant messages create \
--model claude-opus-4-7 \
--max-tokens 1024 \
--tool '{type: text_editor_20250728, name: str_replace_based_edit_tool, max_characters: 10000}' \
--message '{role: user, content: There is a syntax error in my primes.py file. Can you help me fix it?}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
tools=[
{
"type": "text_editor_20250728",
"name": "str_replace_based_edit_tool",
"max_characters": 10000,
}
],
messages=[
{
"role": "user",
"content": "There's a syntax error in my primes.py file. Can you help me fix it?",
}
],
)
print(response)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const response = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [
{
type: "text_editor_20250728",
name: "str_replace_based_edit_tool",
max_characters: 10000
}
],
messages: [
{
role: "user",
content: "There's a syntax error in my primes.py file. Can you help me fix it?"
}
]
});
console.log(response);
```
```java Java hidelines={1..5,7..8,-1..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.ToolTextEditor20250728;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
ToolTextEditor20250728 editorTool =
ToolTextEditor20250728.builder()
.maxCharacters(10000L)
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addTool(editorTool)
.addUserMessage("There's a syntax error in my primes.py file. Can you help me fix it?")
.build();
Message message = client.messages().create(params);
IO.println(message);
}
```
The text editor tool can be used in the following way:
- Include the text editor tool in your API request
- Provide a user prompt that may require examining or modifying files, such as "Can you fix the syntax error in my code?"
- Claude assesses what it needs to look at and uses the `view` command to examine file contents or list directory contents
- The API response will contain a `tool_use` content block with the `view` command
- Extract the file or directory path from Claude's tool use request
- Read the file's contents or list the directory contents
- If a `max_characters` parameter was specified in the tool configuration, truncate the file contents to that length
- Return the results to Claude by continuing the conversation with a new `user` message containing a `tool_result` content block
- After examining the file or directory, Claude may use a command such as `str_replace` to make changes or `insert` to add text at a specific line number.
- If Claude uses the `str_replace` command, Claude constructs a properly formatted tool use request with the old text and new text to replace it with
- Extract the file path, old text, and new text from Claude's tool use request
- Perform the text replacement in the file
- Return the results to Claude
- After examining and possibly editing the files, Claude provides a complete explanation of what it found and what changes it made
### Text editor tool commands
The text editor tool supports several commands for viewing and modifying files:
#### view
The `view` command allows Claude to examine the contents of a file or list the contents of a directory. It can read the entire file or a specific range of lines.
Parameters:
- `command`: Must be "view"
- `path`: The path to the file or directory to view
- `view_range` (optional): An array of two integers specifying the start and end line numbers to view. Line numbers are 1-indexed, and -1 for the end line means read to the end of the file. This parameter only applies when viewing files, not directories.
Example for viewing a file:
```json
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "str_replace_based_edit_tool",
"input": {
"command": "view",
"path": "primes.py"
}
}
```
Example for viewing a directory:
```json
{
"type": "tool_use",
"id": "toolu_02B19r91rw91mr917835mr9",
"name": "str_replace_based_edit_tool",
"input": {
"command": "view",
"path": "src/"
}
}
```
#### str_replace
The `str_replace` command allows Claude to replace a specific string in a file with a new string. This is used for making precise edits.
Parameters:
- `command`: Must be "str_replace"
- `path`: The path to the file to modify
- `old_str`: The text to replace (must match exactly, including whitespace and indentation)
- `new_str`: The new text to insert in place of the old text
```json
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "str_replace_based_edit_tool",
"input": {
"command": "str_replace",
"path": "primes.py",
"old_str": "for num in range(2, limit + 1)",
"new_str": "for num in range(2, limit + 1):"
}
}
```
#### create
The `create` command allows Claude to create a new file with specified content.
Parameters:
- `command`: Must be "create"
- `path`: The path where the new file should be created
- `file_text`: The content to write to the new file
```json
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "str_replace_based_edit_tool",
"input": {
"command": "create",
"path": "test_primes.py",
"file_text": "import unittest\nimport primes\n\nclass TestPrimes(unittest.TestCase):\n def test_is_prime(self):\n self.assertTrue(primes.is_prime(2))\n self.assertTrue(primes.is_prime(3))\n self.assertFalse(primes.is_prime(4))\n\nif __name__ == '__main__':\n unittest.main()"
}
}
```
#### insert
The `insert` command allows Claude to insert text at a specific location in a file.
Parameters:
- `command`: Must be "insert"
- `path`: The path to the file to modify
- `insert_line`: The line number after which to insert the text (0 for beginning of file)
- `insert_text`: The text to insert
```json
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "str_replace_based_edit_tool",
"input": {
"command": "insert",
"path": "primes.py",
"insert_line": 0,
"insert_text": "\"\"\"Module for working with prime numbers.\n\nThis module provides functions to check if a number is prime\nand to generate a list of prime numbers up to a given limit.\n\"\"\"\n"
}
}
```
### Example: Fixing a syntax error with the text editor tool
This example demonstrates how Claude uses the text editor tool to fix a syntax error in a Python file.
First, your application provides Claude with the text editor tool and a prompt to fix a syntax error:
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"tools": [
{
"type": "text_editor_20250728",
"name": "str_replace_based_edit_tool"
}
],
"messages": [
{
"role": "user",
"content": "There'\''s a syntax error in my primes.py file. Can you help me fix it?"
}
]
}'
```
```bash CLI
ant messages create \
--model claude-opus-4-7 \
--max-tokens 1024 \
--tool '{type: text_editor_20250728, name: str_replace_based_edit_tool}' \
--message '{role: user, content: There is a syntax error in my primes.py file. Can you help me fix it?}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
tools=[{"type": "text_editor_20250728", "name": "str_replace_based_edit_tool"}],
messages=[
{
"role": "user",
"content": "There's a syntax error in my primes.py file. Can you help me fix it?",
}
],
)
print(response)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const response = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [
{
type: "text_editor_20250728",
name: "str_replace_based_edit_tool"
}
],
messages: [
{
role: "user",
content: "There's a syntax error in my primes.py file. Can you help me fix it?"
}
]
});
console.log(response);
```
```java Java hidelines={1..5,7..8,-1..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.ToolTextEditor20250728;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
ToolTextEditor20250728 editorTool =
ToolTextEditor20250728.builder().build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addTool(editorTool)
.addUserMessage("There's a syntax error in my primes.py file. Can you help me fix it?")
.build();
Message message = client.messages().create(params);
IO.println(message);
}
```
Claude uses the text editor tool first to view the file:
```json Output
{
"id": "msg_01XAbCDeFgHiJkLmNoPQrStU",
"model": "claude-opus-4-7",
"stop_reason": "tool_use",
"role": "assistant",
"content": [
{
"type": "text",
"text": "I'll help you fix the syntax error in your primes.py file. First, let me take a look at the file to identify the issue."
},
{
"type": "tool_use",
"id": "toolu_01AbCdEfGhIjKlMnOpQrStU",
"name": "str_replace_based_edit_tool",
"input": {
"command": "view",
"path": "primes.py"
}
}
]
}
```
Your application should then read the file and return its contents to Claude:
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"tools": [
{
"type": "text_editor_20250728",
"name": "str_replace_based_edit_tool"
}
],
"messages": [
{
"role": "user",
"content": "There'\''s a syntax error in my primes.py file. Can you help me fix it?"
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "I'\''ll help you fix the syntax error in your primes.py file. First, let me take a look at the file to identify the issue."
},
{
"type": "tool_use",
"id": "toolu_01AbCdEfGhIjKlMnOpQrStU",
"name": "str_replace_based_edit_tool",
"input": {
"command": "view",
"path": "primes.py"
}
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01AbCdEfGhIjKlMnOpQrStU",
"content": "1: def is_prime(n):\n2: \"\"\"Check if a number is prime.\"\"\"\n3: if n <= 1:\n4: return False\n5: if n <= 3:\n6: return True\n7: if n % 2 == 0 or n % 3 == 0:\n8: return False\n9: i = 5\n10: while i * i <= n:\n11: if n % i == 0 or n % (i + 2) == 0:\n12: return False\n13: i += 6\n14: return True\n15: \n16: def get_primes(limit):\n17: \"\"\"Generate a list of prime numbers up to the given limit.\"\"\"\n18: primes = []\n19: for num in range(2, limit + 1)\n20: if is_prime(num):\n21: primes.append(num)\n22: return primes\n23: \n24: def main():\n25: \"\"\"Main function to demonstrate prime number generation.\"\"\"\n26: limit = 100\n27: prime_list = get_primes(limit)\n28: print(f\"Prime numbers up to {limit}:\")\n29: print(prime_list)\n30: print(f\"Found {len(prime_list)} prime numbers.\")\n31: \n32: if __name__ == \"__main__\":\n33: main()"
}
]
}
]
}'
```
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
tools:
- type: text_editor_20250728
name: str_replace_based_edit_tool
messages:
- role: user
content: There's a syntax error in my primes.py file. Can you help me fix it?
- role: assistant
content:
- type: text
text: >-
I'll help you fix the syntax error in your primes.py file. First,
let me take a look at the file to identify the issue.
- type: tool_use
id: toolu_01AbCdEfGhIjKlMnOpQrStU
name: str_replace_based_edit_tool
input:
command: view
path: primes.py
- role: user
content:
- type: tool_result
tool_use_id: toolu_01AbCdEfGhIjKlMnOpQrStU
content: |-
1: def is_prime(n):
2: """Check if a number is prime."""
3: if n <= 1:
4: return False
5: if n <= 3:
6: return True
7: if n % 2 == 0 or n % 3 == 0:
8: return False
9: i = 5
10: while i * i <= n:
11: if n % i == 0 or n % (i + 2) == 0:
12: return False
13: i += 6
14: return True
15:
16: def get_primes(limit):
17: """Generate a list of prime numbers up to the given limit."""
18: primes = []
19: for num in range(2, limit + 1)
20: if is_prime(num):
21: primes.append(num)
22: return primes
23:
24: def main():
25: """Main function to demonstrate prime number generation."""
26: limit = 100
27: prime_list = get_primes(limit)
28: print(f"Prime numbers up to {limit}:")
29: print(prime_list)
30: print(f"Found {len(prime_list)} prime numbers.")
31:
32: if __name__ == "__main__":
33: main()
YAML
```
```python Python
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
tools=[{"type": "text_editor_20250728", "name": "str_replace_based_edit_tool"}],
messages=[
{
"role": "user",
"content": "There's a syntax error in my primes.py file. Can you help me fix it?",
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "I'll help you fix the syntax error in your primes.py file. First, let me take a look at the file to identify the issue.",
},
{
"type": "tool_use",
"id": "toolu_01AbCdEfGhIjKlMnOpQrStU",
"name": "str_replace_based_edit_tool",
"input": {"command": "view", "path": "primes.py"},
},
],
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01AbCdEfGhIjKlMnOpQrStU",
"content": '1: def is_prime(n):\n2: """Check if a number is prime."""\n3: if n <= 1:\n4: return False\n5: if n <= 3:\n6: return True\n7: if n % 2 == 0 or n % 3 == 0:\n8: return False\n9: i = 5\n10: while i * i <= n:\n11: if n % i == 0 or n % (i + 2) == 0:\n12: return False\n13: i += 6\n14: return True\n15: \n16: def get_primes(limit):\n17: """Generate a list of prime numbers up to the given limit."""\n18: primes = []\n19: for num in range(2, limit + 1)\n20: if is_prime(num):\n21: primes.append(num)\n22: return primes\n23: \n24: def main():\n25: """Main function to demonstrate prime number generation."""\n26: limit = 100\n27: prime_list = get_primes(limit)\n28: print(f"Prime numbers up to {limit}:")\n29: print(prime_list)\n30: print(f"Found {len(prime_list)} prime numbers.")\n31: \n32: if __name__ == "__main__":\n33: main()',
}
],
},
],
)
print(response)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const response = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [
{
type: "text_editor_20250728",
name: "str_replace_based_edit_tool"
}
],
messages: [
{
role: "user",
content: "There's a syntax error in my primes.py file. Can you help me fix it?"
},
{
role: "assistant",
content: [
{
type: "text",
text: "I'll help you fix the syntax error in your primes.py file. First, let me take a look at the file to identify the issue."
},
{
type: "tool_use",
id: "toolu_01AbCdEfGhIjKlMnOpQrStU",
name: "str_replace_based_edit_tool",
input: {
command: "view",
path: "primes.py"
}
}
]
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "toolu_01AbCdEfGhIjKlMnOpQrStU",
content:
'1: def is_prime(n):\n2: """Check if a number is prime."""\n3: if n <= 1:\n4: return False\n5: if n <= 3:\n6: return True\n7: if n % 2 == 0 or n % 3 == 0:\n8: return False\n9: i = 5\n10: while i * i <= n:\n11: if n % i == 0 or n % (i + 2) == 0:\n12: return False\n13: i += 6\n14: return True\n15: \n16: def get_primes(limit):\n17: """Generate a list of prime numbers up to the given limit."""\n18: primes = []\n19: for num in range(2, limit + 1)\n20: if is_prime(num):\n21: primes.append(num)\n22: return primes\n23: \n24: def main():\n25: """Main function to demonstrate prime number generation."""\n26: limit = 100\n27: prime_list = get_primes(limit)\n28: print(f"Prime numbers up to {limit}:")\n29: print(prime_list)\n30: print(f"Found {len(prime_list)} prime numbers.")\n31: \n32: if __name__ == "__main__":\n33: main()'
}
]
}
]
});
console.log(response);
```
```java Java hidelines={1..9,11..16,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import com.anthropic.models.messages.ToolResultBlockParam;
import com.anthropic.models.messages.ToolTextEditor20250728;
import com.anthropic.models.messages.ToolUseBlockParam;
import java.util.List;
public class TextEditorToolResultExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addTool(ToolTextEditor20250728.builder().build())
.addUserMessage("There's a syntax error in my primes.py file. Can you help me fix it?")
.addAssistantMessageOfBlockParams(
List.of(
ContentBlockParam.ofText(
TextBlockParam.builder()
.text("I'll help you fix the syntax error in your primes.py file. First, let me take a look at the file to identify the issue.")
.build()
),
ContentBlockParam.ofToolUse(
ToolUseBlockParam.builder()
.id("toolu_01AbCdEfGhIjKlMnOpQrStU")
.name("str_replace_based_edit_tool")
.input(
ToolUseBlockParam.Input.builder()
.putAdditionalProperty("command", JsonValue.from("view"))
.putAdditionalProperty("path", JsonValue.from("primes.py"))
.build()
)
.build()
)
)
)
.addUserMessageOfBlockParams(
List.of(
ContentBlockParam.ofToolResult(
ToolResultBlockParam.builder()
.toolUseId("toolu_01AbCdEfGhIjKlMnOpQrStU")
.content("1: def is_prime(n):\n2: \"\"\"Check if a number is prime.\"\"\"\n3: if n <= 1:\n4: return False\n5: if n <= 3:\n6: return True\n7: if n % 2 == 0 or n % 3 == 0:\n8: return False\n9: i = 5\n10: while i * i <= n:\n11: if n % i == 0 or n % (i + 2) == 0:\n12: return False\n13: i += 6\n14: return True\n15: \n16: def get_primes(limit):\n17: \"\"\"Generate a list of prime numbers up to the given limit.\"\"\"\n18: primes = []\n19: for num in range(2, limit + 1)\n20: if is_prime(num):\n21: primes.append(num)\n22: return primes\n23: \n24: def main():\n25: \"\"\"Main function to demonstrate prime number generation.\"\"\"\n26: limit = 100\n27: prime_list = get_primes(limit)\n28: print(f\"Prime numbers up to {limit}:\")\n29: print(prime_list)\n30: print(f\"Found {len(prime_list)} prime numbers.\")\n31: \n32: if __name__ == \"__main__\":\n33: main()")
.build()
)
)
)
.build();
Message message = client.messages().create(params);
System.out.println(message);
}
}
```
**Line numbers**
In the example above, the `view` tool result includes file contents with line numbers prepended to each line (e.g., "1: def is_prime(n):"). Line numbers are not required, but they are essential for successfully using the `view_range` parameter to examine specific sections of files and the `insert_line` parameter to add content at precise locations.
Claude identifies the syntax error and uses the `str_replace` command to fix it:
```json Output
{
"id": "msg_01VwXyZAbCdEfGhIjKlMnO",
"model": "claude-opus-4-7",
"stop_reason": "tool_use",
"role": "assistant",
"content": [
{
"type": "text",
"text": "I found the syntax error in your primes.py file. In the `get_primes` function, there is a missing colon (:) at the end of the for loop line. Let me fix that for you."
},
{
"type": "tool_use",
"id": "toolu_01PqRsTuVwXyZAbCdEfGh",
"name": "str_replace_based_edit_tool",
"input": {
"command": "str_replace",
"path": "primes.py",
"old_str": " for num in range(2, limit + 1)",
"new_str": " for num in range(2, limit + 1):"
}
}
]
}
```
Your application should then make the edit and return the result:
```bash CLI nocheck
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
tools:
- type: text_editor_20250728
name: str_replace_based_edit_tool
messages:
# Previous messages...
- role: assistant
content:
- type: text
text: >-
I found the syntax error in your primes.py file. In the `get_primes`
function, there is a missing colon (:) at the end of the for loop
line. Let me fix that for you.
- type: tool_use
id: toolu_01PqRsTuVwXyZAbCdEfGh
name: str_replace_based_edit_tool
input:
command: str_replace
path: primes.py
old_str: " for num in range(2, limit + 1)"
new_str: " for num in range(2, limit + 1):"
- role: user
content:
- type: tool_result
tool_use_id: toolu_01PqRsTuVwXyZAbCdEfGh
content: Successfully replaced text at exactly one location.
YAML
```
```python Python
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
tools=[{"type": "text_editor_20250728", "name": "str_replace_based_edit_tool"}],
messages=[
# Previous messages...
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "I found the syntax error in your primes.py file. In the `get_primes` function, there is a missing colon (:) at the end of the for loop line. Let me fix that for you.",
},
{
"type": "tool_use",
"id": "toolu_01PqRsTuVwXyZAbCdEfGh",
"name": "str_replace_based_edit_tool",
"input": {
"command": "str_replace",
"path": "primes.py",
"old_str": " for num in range(2, limit + 1)",
"new_str": " for num in range(2, limit + 1):",
},
},
],
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01PqRsTuVwXyZAbCdEfGh",
"content": "Successfully replaced text at exactly one location.",
}
],
},
],
)
print(response)
```
```typescript TypeScript
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [
{
type: "text_editor_20250728",
name: "str_replace_based_edit_tool"
}
],
messages: [
// Previous messages...
{
role: "assistant",
content: [
{
type: "text",
text: "I found the syntax error in your primes.py file. In the `get_primes` function, there is a missing colon (:) at the end of the for loop line. Let me fix that for you."
},
{
type: "tool_use",
id: "toolu_01PqRsTuVwXyZAbCdEfGh",
name: "str_replace_based_edit_tool",
input: {
command: "str_replace",
path: "primes.py",
old_str: " for num in range(2, limit + 1)",
new_str: " for num in range(2, limit + 1):"
}
}
]
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "toolu_01PqRsTuVwXyZAbCdEfGh",
content: "Successfully replaced text at exactly one location."
}
]
}
]
});
console.log(response);
```
```java Java hidelines={1..9,11..16,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import com.anthropic.models.messages.ToolResultBlockParam;
import com.anthropic.models.messages.ToolTextEditor20250728;
import com.anthropic.models.messages.ToolUseBlockParam;
import java.util.List;
public class TextEditorConversationExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addTool(ToolTextEditor20250728.builder().build())
// Previous messages would go here
.addAssistantMessageOfBlockParams(
List.of(
ContentBlockParam.ofText(
TextBlockParam.builder()
.text(
"I found the syntax error in your primes.py file. In the `get_primes` function, there is a missing colon (:) at the end of the for loop line. Let me fix that for you."
)
.build()
),
ContentBlockParam.ofToolUse(
ToolUseBlockParam.builder()
.id("toolu_01PqRsTuVwXyZAbCdEfGh")
.name("str_replace_based_edit_tool")
.input(
ToolUseBlockParam.Input.builder()
.putAdditionalProperty("command", JsonValue.from("str_replace"))
.putAdditionalProperty("path", JsonValue.from("primes.py"))
.putAdditionalProperty(
"old_str",
JsonValue.from(" for num in range(2, limit + 1)")
)
.putAdditionalProperty(
"new_str",
JsonValue.from(" for num in range(2, limit + 1):")
)
.build()
)
.build()
)
)
)
.addUserMessageOfBlockParams(
List.of(
ContentBlockParam.ofToolResult(
ToolResultBlockParam.builder()
.toolUseId("toolu_01PqRsTuVwXyZAbCdEfGh")
.content("Successfully replaced text at exactly one location.")
.build()
)
)
)
.build();
Message message = client.messages().create(params);
System.out.println(message);
}
}
```
Finally, Claude provides a complete explanation of the fix:
```json Output
{
"id": "msg_01IjKlMnOpQrStUvWxYzAb",
"model": "claude-opus-4-7",
"stop_reason": "end_turn",
"role": "assistant",
"content": [
{
"type": "text",
"text": "I've fixed the syntax error in your primes.py file. The issue was in the `get_primes` function at line 19. There was a missing colon (:) at the end of the for loop line.\n\nHere's what I changed:\n\nFrom:\n```python\nfor num in range(2, limit + 1)\n```\n\nTo:\n```python\nfor num in range(2, limit + 1):\n```\n\nIn Python, a colon is required at the end of statements that start a new code block, such as for loops, if statements, function definitions, etc. Without the colon, Python raises a syntax error because it doesn't know where the code block begins.\n\nYour code should now run correctly. The function will properly generate a list of prime numbers up to the specified limit."
}
]
}
```
## Implement the text editor tool
The text editor tool is implemented as a schema-less tool. When using this tool, you don't need to provide an input schema as with other tools; the schema is built into Claude's model and can't be modified.
The tool type is `type: "text_editor_20250728"` for Claude 4 models.
Create helper functions to handle file operations like reading, writing, and modifying files. Consider implementing backup functionality to recover from mistakes.
Create a function that processes tool calls from Claude based on the command type:
```python
def handle_editor_tool(tool_call):
input_params = tool_call.input
command = input_params.get("command", "")
file_path = input_params.get("path", "")
if command == "view":
# Read and return file contents
pass
elif command == "str_replace":
# Replace text in file
pass
elif command == "create":
# Create new file
pass
elif command == "insert":
# Insert text at location
pass
```
Add validation and security checks:
- Validate file paths to prevent directory traversal
- Create backups before making changes
- Handle errors gracefully
- Implement permissions checks
Extract and handle tool calls from Claude's responses:
```python hidelines={1..15}
from types import SimpleNamespace as _SN
response = _SN(
content=[
_SN(
type="tool_use", name="str_replace_based_edit_tool", input={}, id="toolu_01"
)
]
)
def handle_editor_tool(tc):
return "ok"
# Process tool use in Claude's response
for content in response.content:
if content.type == "tool_use":
# Execute the tool based on command
result = handle_editor_tool(content)
# Return result to Claude
tool_result = {
"type": "tool_result",
"tool_use_id": content.id,
"content": result,
}
```
When implementing the text editor tool, keep in mind:
1. **Security:** The tool has access to your local filesystem, so implement proper security measures.
2. **Backup:** Always create backups before allowing edits to important files.
3. **Validation:** Validate all inputs to prevent unintended changes.
4. **Unique matching:** Make sure replacements match exactly one location to avoid unintended edits.
### Handle errors
When using the text editor tool, various errors may occur. Here is guidance on how to handle them:
If Claude tries to view or modify a file that doesn't exist, return an appropriate error message in the `tool_result`:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: File not found",
"is_error": true
}
]
}
```
If Claude's `str_replace` command matches multiple locations in the file, return an appropriate error message:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: Found 3 matches for replacement text. Please provide more context to make a unique match.",
"is_error": true
}
]
}
```
If Claude's `str_replace` command doesn't match any text in the file, return an appropriate error message:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: No match found for replacement. Please check your text and try again.",
"is_error": true
}
]
}
```
If there are permission issues with creating, reading, or modifying files, return an appropriate error message:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: Permission denied. Cannot write to file.",
"is_error": true
}
]
}
```
### Follow implementation best practices
When asking Claude to fix or modify code, be specific about what files need to be examined or what issues need to be addressed. Clear context helps Claude identify the right files and make appropriate changes.
**Less helpful prompt**: "Can you fix my code?"
**Better prompt**: "There's a syntax error in my primes.py file that prevents it from running. Can you fix it?"
Specify file paths clearly when needed, especially if you're working with multiple files or files in different directories.
**Less helpful prompt**: "Review my helper file"
**Better prompt**: "Can you check my utils/helpers.py file for any performance issues?"
Implement a backup system in your application that creates copies of files before allowing Claude to edit them, especially for important or production code.
```python hidelines={1}
import os
def backup_file(file_path):
"""Create a backup of a file before editing."""
backup_path = f"{file_path}.backup"
if os.path.exists(file_path):
with open(file_path, "r") as src, open(backup_path, "w") as dst:
dst.write(src.read())
```
The `str_replace` command requires an exact match for the text to be replaced. Your application should ensure that there is exactly one match for the old text or provide appropriate error messages.
```python
def safe_replace(file_path, old_text, new_text):
"""Replace text only if there's exactly one match."""
with open(file_path, "r") as f:
content = f.read()
count = content.count(old_text)
if count == 0:
return "Error: No match found"
elif count > 1:
return f"Error: Found {count} matches"
else:
new_content = content.replace(old_text, new_text)
with open(file_path, "w") as f:
f.write(new_content)
return "Successfully replaced text"
```
After Claude makes changes to a file, verify the changes by running tests or checking that the code still works as expected.
```python
def verify_changes(file_path):
"""Run tests or checks after making changes."""
try:
# For Python files, check for syntax errors
if file_path.endswith(".py"):
import ast
with open(file_path, "r") as f:
ast.parse(f.read())
return "Syntax check passed"
except Exception as e:
return f"Verification failed: {str(e)}"
```
---
## Pricing and token usage
The text editor tool uses the same pricing structure as other tools used with Claude. It follows the standard input and output token pricing based on the Claude model you're using.
In addition to the base tokens, the following additional input tokens are needed for the text editor tool:
| Tool | Additional input tokens |
| ----------------------------------------- | --------------------------------------- |
| `text_editor_20250429` (Claude 4.x) | 700 tokens |
For more detailed information about tool pricing, see [Tool use pricing](/docs/en/agents-and-tools/tool-use/overview#pricing).
## Integrate the text editor tool with other tools
The text editor tool can be used alongside other Claude tools. When combining tools, ensure you:
- Match the tool version with the model you're using
- Account for the additional token usage for all tools included in your request
## Change log
| Date | Version | Changes |
| ---- | ------- | ------- |
| July 28, 2025 | `text_editor_20250728` | Release of an updated text editor Tool that fixes some issues and adds an optional `max_characters` parameter. It is otherwise identical to `text_editor_20250429`. |
| April 29, 2025 | `text_editor_20250429` | Release of the text editor Tool for Claude 4. This version removes the `undo_edit` command but maintains all other capabilities. The tool name has been updated to reflect its str_replace-based architecture. |
| March 13, 2025 | `text_editor_20250124` | Introduction of standalone text editor Tool documentation. This version is optimized for Claude Sonnet 3.7 but has identical capabilities to the previous version. |
| October 22, 2024 | `text_editor_20241022` | Initial release of the text editor Tool with Claude Sonnet 3.5 ([retired](/docs/en/about-claude/model-deprecations)). Provides capabilities for viewing, creating, and editing files through the `view`, `create`, `str_replace`, `insert`, and `undo_edit` commands. |
## Next steps
Here are some ideas for how to use the text editor tool in more convenient and powerful ways:
- **Integrate with your development workflow**: Build the text editor tool into your development tools or IDE
- **Create a code review system**: Have Claude review your code and make improvements
- **Build a debugging assistant**: Create a system where Claude can help you diagnose and fix issues in your code
- **Implement file format conversion**: Let Claude help you convert files from one format to another
- **Automate documentation**: Set up workflows for Claude to automatically document your code
The text editor tool enables Claude to work directly with your codebase, supporting workflows from debugging to automated documentation.
Learn how to implement tool workflows for use with Claude.
Execute shell commands with Claude.
---
# Tool Runner (SDK)
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-runner
# Tool Runner (SDK)
Use the SDK's Tool Runner abstraction to handle the agentic loop, error wrapping, and type safety automatically.
---
Tool Runner handles the agentic loop, error wrapping, and type safety so you don't have to. Use the [manual loop](/docs/en/agents-and-tools/tool-use/handle-tool-calls) only when you need human-in-the-loop approval, custom logging, or conditional execution. Available in Python, TypeScript, and Ruby SDKs.
The tool runner provides an out-of-the-box solution for executing tools with Claude. Instead of manually handling tool calls, tool results, and conversation management, the tool runner automatically:
- Executes tools when Claude calls them
- Handles the request/response cycle
- Manages conversation state
- Provides type safety and validation
Use the tool runner for most tool use implementations.
The tool runner is currently in beta and available in the [Python](https://github.com/anthropics/anthropic-sdk-python/blob/main/tools.md), [TypeScript](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/helpers.md#tool-helpers), and [Ruby](https://github.com/anthropics/anthropic-sdk-ruby/blob/main/helpers.md#3-auto-looping-tool-runner-beta) SDKs.
**Automatic context management with compaction**
The tool runner supports automatic [compaction](/docs/en/build-with-claude/context-editing#client-side-compaction-sdk), which generates summaries when token usage exceeds a threshold. This allows long-running agentic tasks to continue beyond context window limits.
## Basic usage
Define tools using the SDK helpers, then use the tool runner to execute them.
Use the `@beta_tool` decorator to define tools with type hints and docstrings.
If you're using the async client, replace `@beta_tool` with `@beta_async_tool` and define the function with `async def`.
```python
import json
from anthropic import Anthropic, beta_tool
client = Anthropic()
@beta_tool
def get_weather(location: str, unit: str = "fahrenheit") -> str:
"""Get the current weather in a given location.
Args:
location: The city and state, e.g. San Francisco, CA
unit: Temperature unit, either 'celsius' or 'fahrenheit'
"""
return json.dumps({"temperature": "20°C", "condition": "Sunny"})
@beta_tool
def calculate_sum(a: int, b: int) -> str:
"""Add two numbers together.
Args:
a: First number
b: Second number
"""
return str(a + b)
runner = client.beta.messages.tool_runner(
model="claude-opus-4-7",
max_tokens=1024,
tools=[get_weather, calculate_sum],
messages=[
{
"role": "user",
"content": "What's the weather like in Paris? Also, what's 15 + 27?",
}
],
)
for message in runner:
print(message)
```
The `@beta_tool` decorator inspects the function arguments and docstring to extract a JSON schema representation. For example, `calculate_sum` becomes:
```json
{
"name": "calculate_sum",
"description": "Add two numbers together.",
"input_schema": {
"additionalProperties": false,
"properties": {
"a": {
"description": "First number",
"title": "A",
"type": "integer"
},
"b": {
"description": "Second number",
"title": "B",
"type": "integer"
}
},
"required": ["a", "b"],
"type": "object"
}
}
```
Use `betaZodTool()` for type-safe tool definitions with Zod validation, or `betaTool()` for JSON Schema-based definitions.
TypeScript offers two approaches for defining tools:
**Using Zod (recommended)** - Use `betaZodTool()` for type-safe tool definitions with Zod validation (requires Zod 3.25.0 or higher):
```typescript hidelines={1}
import Anthropic from "@anthropic-ai/sdk";
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";
const client = new Anthropic();
const getWeatherTool = betaZodTool({
name: "get_weather",
description: "Get the current weather in a given location",
inputSchema: z.object({
location: z.string().describe("The city and state, e.g. San Francisco, CA"),
unit: z.enum(["celsius", "fahrenheit"]).default("fahrenheit").describe("Temperature unit")
}),
run: async (input) => {
return JSON.stringify({ temperature: "20°C", condition: "Sunny" });
}
});
const finalMessage = await client.beta.messages.toolRunner({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [getWeatherTool],
messages: [{ role: "user", content: "What's the weather like in Paris?" }]
});
for (const block of finalMessage.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
**Using JSON Schema** - Use `betaTool()` for type-safe tool definitions without Zod:
The input generated by Claude is not validated at runtime. Perform validation inside the `run` function if needed.
```typescript hidelines={1}
import Anthropic from "@anthropic-ai/sdk";
import { betaTool } from "@anthropic-ai/sdk/helpers/beta/json-schema";
const client = new Anthropic();
const calculateSumTool = betaTool({
name: "calculate_sum",
description: "Add two numbers together",
inputSchema: {
type: "object",
properties: {
a: { type: "number", description: "First number" },
b: { type: "number", description: "Second number" }
},
required: ["a", "b"]
},
run: async (input) => {
return String(input.a + input.b);
}
});
const finalMessage = await client.beta.messages.toolRunner({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [calculateSumTool],
messages: [{ role: "user", content: "What's 15 + 27?" }]
});
for (const block of finalMessage.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
Use the `Anthropic::BaseTool` class to define tools with typed input schemas.
```ruby
require "anthropic"
# Initialize client
client = Anthropic::Client.new
# Define input schema
class GetWeatherInput < Anthropic::BaseModel
required :location, String, doc: "The city and state, e.g. San Francisco, CA"
optional :unit, Anthropic::InputSchema::EnumOf["celsius", "fahrenheit"],
doc: "Temperature unit"
end
# Define tool
class GetWeather < Anthropic::BaseTool
doc "Get the current weather in a given location"
input_schema GetWeatherInput
def call(input)
# In a full implementation, you'd call a weather API here
JSON.generate({temperature: "20°C", condition: "Sunny"})
end
end
class CalculateSumInput < Anthropic::BaseModel
required :a, Integer, doc: "First number"
required :b, Integer, doc: "Second number"
end
class CalculateSum < Anthropic::BaseTool
doc "Add two numbers together"
input_schema CalculateSumInput
def call(input)
(input.a + input.b).to_s
end
end
# Use the tool runner
runner = client.beta.messages.tool_runner(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [GetWeather.new, CalculateSum.new],
messages: [
{role: "user", content: "What's the weather like in Paris? Also, what's 15 + 27?"}
]
)
runner.each_message do |message|
message.content.each do |block|
puts block.text if block.type == :text
end
end
```
The `Anthropic::BaseTool` class uses the `doc` method for the tool description and `input_schema` to define the expected parameters. The SDK automatically converts this to the appropriate JSON schema format.
The tool function must return a content block or content block array, including text, images, or document blocks. This allows tools to return rich, multimodal responses. Returned strings are converted to a text content block. If you want to return a structured JSON object to Claude, encode it to a JSON string before returning it. Numbers, booleans, or other non-string primitives must also be converted to strings.
## Iterating over the tool runner
The tool runner is an iterable that yields messages from Claude. This is often referred to as a "tool call loop". Each iteration, the runner checks if Claude requested a tool use. If so, it calls the tool and sends the result back to Claude automatically, then yields the next message from Claude to continue your loop.
You can end the loop at any iteration with a `break` statement. The runner loops until Claude returns a message without a tool use.
If you don't need intermediate messages, you can get the final message directly:
Use `runner.until_done()` to get the final message.
```python hidelines={1..16}
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def get_weather(location: str) -> str:
"""Get the current weather in a given location."""
return "20°C, Sunny"
@beta_tool
def calculate_sum(a: int, b: int) -> str:
"""Add two numbers together."""
return str(a + b)
runner = client.beta.messages.tool_runner(
model="claude-opus-4-7",
max_tokens=1024,
tools=[get_weather, calculate_sum],
messages=[
{
"role": "user",
"content": "What's the weather like in Paris? Also, what's 15 + 27?",
}
],
)
final_message = runner.until_done()
for block in final_message.content:
if block.type == "text":
print(block.text)
```
Simply `await` the runner to get the final message.
```typescript hidelines={1..13}
import Anthropic from "@anthropic-ai/sdk";
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";
const client = new Anthropic();
const getWeatherTool = betaZodTool({
name: "get_weather",
description: "Get the current weather in a given location",
inputSchema: z.object({ location: z.string() }),
run: async () => JSON.stringify({ temperature: "20°C", condition: "Sunny" })
});
const runner = client.beta.messages.toolRunner({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [getWeatherTool],
messages: [{ role: "user", content: "What's the weather like in Paris?" }]
});
const finalMessage = await runner;
for (const block of finalMessage.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
Use `runner.run_until_finished` to get all messages.
```ruby hidelines={1..29}
require "anthropic"
client = Anthropic::Client.new
class GetWeatherInput < Anthropic::BaseModel
required :location, String
end
class GetWeather < Anthropic::BaseTool
doc "Get the current weather in a given location"
input_schema GetWeatherInput
def call(input)
"Weather in #{input.location}: 20°C, Sunny"
end
end
class CalculateSumInput < Anthropic::BaseModel
required :a, Integer
required :b, Integer
end
class CalculateSum < Anthropic::BaseTool
doc "Add two numbers together"
input_schema CalculateSumInput
def call(input)
(input.a + input.b).to_s
end
end
runner = client.beta.messages.tool_runner(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [GetWeather.new, CalculateSum.new],
messages: [
{role: "user", content: "What's the weather like in Paris? Also, what's 15 + 27?"}
]
)
all_messages = runner.run_until_finished
all_messages.each { |msg| puts msg.content }
```
## Advanced usage
Within the loop, you can fully customize the tool runner's next request to the Messages API. The runner automatically appends tool results to the message history, so you don't need to manually manage them. You can optionally inspect the tool result for logging or debugging, and modify the request parameters before the next API call.
Use `generate_tool_call_response()` to optionally inspect the tool result (the runner appends it automatically). Use `set_messages_params()` and `append_messages()` to modify the request.
```python nocheck
runner = client.beta.messages.tool_runner(
model="claude-opus-4-7",
max_tokens=1024,
tools=[get_weather],
messages=[{"role": "user", "content": "What's the weather in San Francisco?"}],
)
for message in runner:
# Optional: inspect the tool response (automatically appended by the runner)
tool_response = runner.generate_tool_call_response()
if tool_response:
print(f"Tool result: {tool_response}")
# Customize the next request
runner.set_messages_params(
lambda params: {
**params,
"max_tokens": 2048, # Increase tokens for next request
}
)
# Or add additional messages
runner.append_messages(
{"role": "user", "content": "Please be concise in your response."}
)
```
Use `generateToolResponse()` to optionally inspect the tool result (the runner appends it automatically). Use `setMessagesParams()` and `pushMessages()` to modify the request.
```typescript nocheck
const runner = client.beta.messages.toolRunner({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [getWeatherTool],
messages: [{ role: "user", content: "What's the weather in San Francisco?" }]
});
for await (const message of runner) {
// Optional: inspect the tool result message (automatically appended by the runner)
const toolResultMessage = await runner.generateToolResponse();
if (toolResultMessage) {
console.log("Tool result:", toolResultMessage);
}
// Customize the next request
runner.setMessagesParams((params) => ({
...params,
max_tokens: 2048 // Increase tokens for next request
}));
// Or add additional messages
runner.pushMessages({ role: "user", content: "Please be concise in your response." });
}
```
Use `next_message` for step-by-step control. Use `feed_messages` to inject messages and `params` to access parameters.
```ruby hidelines={1..16}
require "anthropic"
client = Anthropic::Client.new
class GetWeatherInput < Anthropic::BaseModel
required :location, String
end
class GetWeather < Anthropic::BaseTool
doc "Get the current weather in a given location"
input_schema GetWeatherInput
def call(input)
"Weather in #{input.location}: 20°C, Sunny"
end
end
runner = client.beta.messages.tool_runner(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [GetWeather.new],
messages: [{role: "user", content: "What's the weather in San Francisco?"}]
)
# Manual step-by-step control
message = runner.next_message
puts message.content
# Inject follow-up messages
runner.feed_messages([
{role: "user", content: "Also check Boston"}
])
# Access current parameters
puts runner.params
```
### Debugging tool execution
When a tool throws an exception, the tool runner catches it and returns the error to Claude as a tool result with `is_error: true`. By default, only the exception message is included, not the full stack trace.
To view full stack traces and debug information, set the `ANTHROPIC_LOG` environment variable:
```bash
# View info-level logs including tool errors
export ANTHROPIC_LOG=info
# View debug-level logs for more verbose output
export ANTHROPIC_LOG=debug
```
When enabled, the SDK logs full exception details (using Python's `logging` module, the console in TypeScript, or Ruby's logger), including the complete stack trace when a tool fails.
### Intercepting tool errors
By default, tool errors are passed back to Claude, which can then respond appropriately. However, you may want to detect errors and handle them differently, for example, to stop execution early or implement custom error handling.
Use the tool response method to intercept tool results and check for errors before they're sent to Claude:
```python hidelines={1..11}
import anthropic
import json
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def my_tool(query: str) -> str:
"""A sample tool."""
return f"Result for: {query}"
runner = client.beta.messages.tool_runner(
model="claude-opus-4-7",
max_tokens=1024,
tools=[my_tool],
messages=[{"role": "user", "content": "Run the tool"}],
)
for message in runner:
tool_response = runner.generate_tool_call_response()
if tool_response is not None:
# tool_response is a dict: {"role": "user", "content": [...]}
# Check if any tool result has an error
for block in tool_response["content"]:
if block.get("is_error"):
# Option 1: Raise an exception to stop the loop
raise RuntimeError(f"Tool failed: {json.dumps(block['content'])}")
# Option 2: Log and continue (let Claude handle it)
# logger.error(f"Tool error: {json.dumps(block['content'])}")
# Process the message normally
print(message.content)
```
```typescript hidelines={1..13}
import Anthropic from "@anthropic-ai/sdk";
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";
const client = new Anthropic();
const myTool = betaZodTool({
name: "my_tool",
description: "A sample tool",
inputSchema: z.object({ query: z.string() }),
run: async (input) => `Result for: ${input.query}`
});
const runner = client.beta.messages.toolRunner({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [myTool],
messages: [{ role: "user", content: "Run the tool" }]
});
for await (const message of runner) {
const toolResultMessage = await runner.generateToolResponse();
if (toolResultMessage) {
// Check if any tool result has an error
for (const block of toolResultMessage.content) {
if (block.type === "tool_result" && block.is_error) {
// Option 1: Throw to stop the loop
throw new Error(`Tool failed: ${JSON.stringify(block.content)}`);
// Option 2: Log and continue (let Claude handle it)
// console.error(`Tool error: ${JSON.stringify(block.content)}`);
}
}
}
// Process the message normally
console.log(message.content);
}
```
```ruby hidelines={1..16}
require "anthropic"
client = Anthropic::Client.new
class MyToolInput < Anthropic::BaseModel
required :query, String
end
class MyTool < Anthropic::BaseTool
doc "A sample tool"
input_schema MyToolInput
def call(input)
"Result for: #{input.query}"
end
end
runner = client.beta.messages.tool_runner(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [MyTool.new],
messages: [{role: "user", content: "Run the tool"}]
)
runner.each_message do |message|
# Get the tool response to check for errors
# Note: The runner automatically handles tool execution and appends results
# This is just for error checking/logging purposes
tool_results = runner.params[:messages].last
if tool_results && tool_results[:role] == :user && tool_results[:content].is_a?(Array)
tool_results[:content].each do |block|
if block[:type] == :tool_result && block[:is_error]
# Option 1: Raise an exception to stop the loop
raise "Tool failed: #{block[:content]}"
# Option 2: Log and continue (let Claude handle it)
# logger.error("Tool error: #{block[:content]}")
end
end
end
puts message.content
end
```
### Modifying tool results
You can modify tool results before they're sent back to Claude. This is useful for adding metadata such as `cache_control` to enable [prompt caching](/docs/en/build-with-claude/prompt-caching) on tool results, or for transforming the tool output.
Use the tool response method to get the tool result, then modify it before the runner proceeds. Whether you explicitly append the modified result or mutate it in place depends on the SDK; see the code comments in each tab.
```python hidelines={1..10}
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def search_documents(query: str) -> str:
"""Search documents for relevant information."""
return f"Found 3 documents matching: {query}"
runner = client.beta.messages.tool_runner(
model="claude-opus-4-7",
max_tokens=1024,
tools=[search_documents],
messages=[
{
"role": "user",
"content": "Search for information about the climate of San Francisco",
}
],
)
for message in runner:
tool_response = runner.generate_tool_call_response()
if tool_response is not None:
# tool_response is a dict: {"role": "user", "content": [...]}
# Modify the tool result to add cache control
for block in tool_response["content"]:
if block["type"] == "tool_result":
# Add cache_control to cache this tool result
block["cache_control"] = {"type": "ephemeral"}
# Append the modified response (this prevents auto-append of the original)
runner.append_messages(message, tool_response)
print(message.content)
```
```typescript hidelines={1..13}
import Anthropic from "@anthropic-ai/sdk";
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";
const client = new Anthropic();
const searchDocuments = betaZodTool({
name: "search_documents",
description: "Search documents for relevant information",
inputSchema: z.object({ query: z.string() }),
run: async (input) => `Found 3 documents matching: ${input.query}`
});
const runner = client.beta.messages.toolRunner({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [searchDocuments],
messages: [
{ role: "user", content: "Search for information about the climate of San Francisco" }
]
});
for await (const message of runner) {
const toolResultMessage = await runner.generateToolResponse();
if (toolResultMessage && typeof toolResultMessage.content !== "string") {
// Modify the tool result to add cache control
for (const block of toolResultMessage.content) {
if (block.type === "tool_result") {
// Add cache_control to cache this tool result
block.cache_control = { type: "ephemeral" };
}
}
// No pushMessages call needed: the runner auto-appends both the assistant
// message and the (now-mutated) cached tool response.
}
console.log(message.content);
}
```
```ruby hidelines={1..16}
require "anthropic"
client = Anthropic::Client.new
class SearchDocumentsInput < Anthropic::BaseModel
required :query, String
end
class SearchDocuments < Anthropic::BaseTool
doc "Search documents for relevant information"
input_schema SearchDocumentsInput
def call(input)
"Found 3 documents matching: #{input.query}"
end
end
runner = client.beta.messages.tool_runner(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [SearchDocuments.new],
messages: [{role: "user", content: "Search for information about the climate of San Francisco"}]
)
loop do
message = runner.next_message
break unless message
# Access the most recent tool results from the messages array
# The runner automatically adds tool results, but you can modify them
tool_results_message = runner.params[:messages].last
if tool_results_message && tool_results_message[:role] == :user && tool_results_message[:content].is_a?(Array)
tool_results_message[:content].each do |block|
if block[:type] == :tool_result
# Modify the tool result to add cache control
block[:cache_control] = {type: "ephemeral"}
end
end
end
puts message.content
break if message.stop_reason != :tool_use
end
```
Adding `cache_control` to tool results is particularly useful when tools return large amounts of data (such as document search results) that you want to cache for subsequent API calls. See [Prompt caching](/docs/en/build-with-claude/prompt-caching) for more details on caching strategies.
## Streaming
Enable streaming to receive events as they arrive. Each iteration yields a stream object that you can iterate for events.
Set `stream=True` and use `get_final_message()` to get the accumulated message.
```python hidelines={1..10}
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def calculate_sum(a: int, b: int) -> str:
"""Add two numbers together."""
return str(a + b)
runner = client.beta.messages.tool_runner(
model="claude-opus-4-7",
max_tokens=1024,
tools=[calculate_sum],
messages=[{"role": "user", "content": "What is 15 + 27?"}],
stream=True,
)
# When streaming, the runner returns BetaMessageStream
for message_stream in runner:
for event in message_stream:
print("event:", event)
print("message:", message_stream.get_final_message())
print(runner.until_done())
```
Set `stream: true` and use `finalMessage()` to get the accumulated message.
```typescript hidelines={1..13}
import Anthropic from "@anthropic-ai/sdk";
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";
const client = new Anthropic();
const getWeatherTool = betaZodTool({
name: "get_weather",
description: "Get the current weather in a given location",
inputSchema: z.object({ location: z.string() }),
run: async () => JSON.stringify({ temperature: "20°C", condition: "Sunny" })
});
const runner = client.beta.messages.toolRunner({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "What is the weather in San Francisco?" }],
tools: [getWeatherTool],
stream: true
});
// When streaming, the runner returns BetaMessageStream
for await (const messageStream of runner) {
for await (const event of messageStream) {
console.log("event:", event);
}
console.log("message:", await messageStream.finalMessage());
}
console.log(await runner);
```
Use `each_streaming` to iterate over streaming events.
```ruby hidelines={1..17}
require "anthropic"
client = Anthropic::Client.new
class CalculateSumInput < Anthropic::BaseModel
required :a, Integer
required :b, Integer
end
class CalculateSum < Anthropic::BaseTool
doc "Add two numbers together"
input_schema CalculateSumInput
def call(input)
(input.a + input.b).to_s
end
end
runner = client.beta.messages.tool_runner(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [CalculateSum.new],
messages: [{role: "user", content: "What is 15 + 27?"}]
)
runner.each_streaming do |event|
case event
when Anthropic::Streaming::TextEvent
print event.text
when Anthropic::Streaming::InputJsonEvent
puts "\nTool input: #{event.partial_json}"
end
end
```
## Next steps
- For manual control over the tool-call loop, see [Handle tool calls](/docs/en/agents-and-tools/tool-use/handle-tool-calls).
- For running multiple tools concurrently, see [Parallel tool use](/docs/en/agents-and-tools/tool-use/parallel-tool-use).
- For the full tool-use workflow, see [Define tools](/docs/en/agents-and-tools/tool-use/define-tools).
---
# Tool use with prompt caching
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-use-with-prompt-caching
# Tool use with prompt caching
Cache tool definitions across turns and understand what invalidates your cache.
---
This page covers prompt caching for tool definitions: where to place `cache_control` breakpoints, how `defer_loading` preserves your cache, and what invalidates it. For general prompt caching, see [Prompt caching](/docs/en/build-with-claude/prompt-caching).
## cache_control on tool definitions
Place `cache_control: {"type": "ephemeral"}` on the last tool in your `tools` array. This caches the entire tool-definitions prefix, from the first tool through the marked breakpoint:
```json
{
"tools": [
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": { "type": "string" }
},
"required": ["location"]
}
},
{
"name": "get_time",
"description": "Get the current time in a given time zone",
"input_schema": {
"type": "object",
"properties": {
"timezone": { "type": "string" }
},
"required": ["timezone"]
},
"cache_control": { "type": "ephemeral" }
}
]
}
```
For `mcp_toolset`, the `cache_control` breakpoint lands on the last tool in the set. You don't control tool order within an MCP toolset, so place the breakpoint on the `mcp_toolset` entry itself and the API applies it to the final expanded tool.
## defer_loading and cache preservation
Deferred tools are not included in the system-prompt prefix. When the model discovers a deferred tool through [tool search](/docs/en/agents-and-tools/tool-use/tool-search-tool), the definition is appended inline as a `tool_reference` block in the conversation history. The prefix is untouched, so prompt caching is preserved.
This means adding tools dynamically through tool search does not break your cache. You can start a conversation with a small set of always-loaded tools (cached), let the model discover additional tools as needed, and keep the same cache hit across every turn.
`defer_loading` also acts independently of grammar construction for [strict mode](/docs/en/agents-and-tools/tool-use/strict-tool-use). The grammar builds from the full toolset regardless of which tools are deferred, so prompt caching and grammar caching are both preserved when tools load dynamically.
## What invalidates your cache
The cache follows a prefix hierarchy (`tools` → `system` → `messages`), so a change at one level invalidates that level and everything after it:
| Change | Invalidates |
|---|---|
| Modifying tool definitions | Entire cache (tools, system, messages) |
| Toggling web search or citations | System and messages caches |
| Changing `tool_choice` | Messages cache |
| Changing `disable_parallel_tool_use` | Messages cache |
| Toggling images present/absent | Messages cache |
| Changing thinking parameters | Messages cache |
If you need to vary `tool_choice` mid-conversation, consider placing cache breakpoints before the variation point.
## Per-tool interaction table
| Tool | Caching considerations |
|---|---|
| [Web search](/docs/en/agents-and-tools/tool-use/web-search-tool) | Enabling or disabling invalidates the system and messages caches |
| [Web fetch](/docs/en/agents-and-tools/tool-use/web-fetch-tool) | Enabling or disabling invalidates the system and messages caches |
| [Code execution](/docs/en/agents-and-tools/tool-use/code-execution-tool) | Container state is independent of prompt cache |
| [Tool search](/docs/en/agents-and-tools/tool-use/tool-search-tool) | Discovered tools load as `tool_reference` blocks, preserving prefix cache |
| [Computer use](/docs/en/agents-and-tools/tool-use/computer-use-tool) | Screenshot presence affects messages cache |
| [Text editor](/docs/en/agents-and-tools/tool-use/text-editor-tool) | Standard client tool, no special caching interaction |
| [Bash](/docs/en/agents-and-tools/tool-use/bash-tool) | Standard client tool, no special caching interaction |
| [Memory](/docs/en/agents-and-tools/tool-use/memory-tool) | Standard client tool, no special caching interaction |
## Next steps
Learn the full prompt caching model, including TTLs and pricing.
Load tools on demand without breaking your cache.
Browse all available tools and their parameters.
---
# Troubleshooting tool use
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/troubleshooting-tool-use
# Troubleshooting tool use
Fix the most common tool-use errors with symptom-to-fix diagnostic tables.
---
Symptom-to-fix tables for the most common tool-use errors. Each fix cross-references the page that owns the feature.
## Claude calls the wrong tool
| Symptom | Likely cause | Fix |
|---|---|---|
| Claude calls tool A when you wanted tool B | Description ambiguity | Sharpen descriptions. Differentiate tools by WHEN to use them, not only WHAT they do. See [Define tools](/docs/en/agents-and-tools/tool-use/define-tools). |
| Claude never calls your tool | Tool name collision or overly-generic schema | Check for duplicate names across your tool list. Add `input_examples` to make the intended use concrete. |
| Claude calls with wrong parameter types | Model guessing at ambiguous schema | Add `strict: true` (if your schema is in the supported subset) or add `input_examples`. |
## Claude invents tool parameters
| Symptom | Likely cause | Fix |
|---|---|---|
| Parameter that doesn't exist in your schema | Model over-generation without strict mode | Add `strict: true` if your schema is in the [supported subset](/docs/en/agents-and-tools/tool-use/strict-tool-use). |
| Parameter values outside your enum | Missing strict mode or too-large enum | Shrink the enum or add `input_examples` showing valid choices. |
## Parallel tool calls don't work
| Symptom | Likely cause | Fix |
|---|---|---|
| Claude calls tools sequentially when parallel would be better | Message history formatting | Send multiple `tool_result` blocks in ONE user message, not one per turn. See [Parallel tool use](/docs/en/agents-and-tools/tool-use/parallel-tool-use). |
| `disable_parallel_tool_use` seems ignored | Set too late in the conversation | Must be set on the request that returns `tool_use`. Setting it on a later request has no effect on earlier tool calls. |
## Cache keeps invalidating
| Symptom | Likely cause | Fix |
|---|---|---|
| Every request is a cache miss | `tool_choice` varying between requests | Keep `tool_choice` stable or place the `cache_control` breakpoint before the variation point. See [Tool use with prompt caching](/docs/en/agents-and-tools/tool-use/tool-use-with-prompt-caching). |
| Adding a tool mid-conversation breaks cache | Tool prepended to the tools array | Use `defer_loading: true` with tool search to append the tool inline instead of modifying the array head. |
## Errors at request time
| Error | Cause | Fix |
|---|---|---|
| `tool_use ids were found without tool_result blocks immediately after` | Missing `tool_result` for some `tool_use` ids, or `tool_result` is not the first content block in the user message | Return one `tool_result` for every `tool_use` block in the assistant response. Put `tool_result` blocks before any text. See [Handle tool calls](/docs/en/agents-and-tools/tool-use/handle-tool-calls) and [Parallel tool use](/docs/en/agents-and-tools/tool-use/parallel-tool-use). |
| `Input schema is not compatible with strict mode: string patterns are not supported` | Using `pattern` with `strict: true` | Remove the pattern or drop `strict: true`. The `pattern` keyword is not in the supported JSON Schema subset yet. |
| `All tools have defer_loading: true` | No tools visible to the model | At least one tool must be immediately loaded. The tool search tool itself must never have `defer_loading: true`. |
## JSON escaping differences (Opus 4.6+)
| Symptom | Cause | Fix |
|---|---|---|
| String comparison on tool inputs fails with newer models | Unicode and forward-slash escaping differs between model versions | Parse with `json.loads()` or `JSON.parse()`. Never do raw string matching on serialized input. |
## Next steps
Write schemas and descriptions that steer Claude toward the right tool.
Execute tools and return results in the required message format.
Full directory of Anthropic-schema tools and their version strings.
---
# Tutorial: Build a tool-using agent
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/build-a-tool-using-agent
# Tutorial: Build a tool-using agent
A guided walkthrough from a single tool call to a production-ready agentic loop.
---
This tutorial builds a calendar-management agent in five concentric rings. Each ring is a complete, runnable program that adds exactly one concept to the ring before it. By the end you will have written the agentic loop by hand and then replaced it with the Tool Runner SDK abstraction.
The example tool is `create_calendar_event`. Its schema uses nested objects, arrays, and optional fields, so you will see how Claude handles realistic input shapes rather than a single flat string.
Every ring runs standalone. Copy any ring into a fresh file and it will execute without the code from earlier rings.
## Ring 1: Single tool, single turn
The smallest possible tool-using program: one tool, one user message, one tool call, one result. The code is heavily commented so you can map each line to the [tool use lifecycle](/docs/en/agents-and-tools/tool-use/how-tool-use-works).
The request sends a `tools` array alongside the user message. When Claude decides to call a tool, the response comes back with `stop_reason: "tool_use"` and a `tool_use` content block containing the tool name, a unique `id`, and the structured `input`. Your code executes the tool, then sends the result back in a `tool_result` block whose `tool_use_id` matches the `id` from the call.
````bash
#!/bin/bash
# Ring 1: Single tool, single turn.
# Source for in build-a-tool-using-agent.mdx.
# Define one tool as a JSON fragment. The input_schema is a JSON Schema
# object describing the arguments Claude should pass when it calls this
# tool. This schema includes nested objects (recurrence), arrays
# (attendees), and optional fields, which is closer to real-world tools
# than a flat string argument.
TOOLS='[
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"}
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1}
}
}
},
"required": ["title", "start", "end"]
}
}
]'
USER_MSG="Schedule a 30-minute sync with alice@example.com and bob@example.com next Monday at 10am."
# Send the user's request along with the tool definition. Claude decides
# whether to call the tool based on the request and the tool description.
RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d "$(jq -n \
--argjson tools "$TOOLS" \
--arg msg "$USER_MSG" \
'{
model: "claude-opus-4-6",
max_tokens: 1024,
tools: $tools,
tool_choice: {type: "auto", disable_parallel_tool_use: true},
messages: [{role: "user", content: $msg}]
}')")
# When Claude calls a tool, the response has stop_reason "tool_use"
# and the content array contains a tool_use block alongside any text.
echo "stop_reason: $(echo "$RESPONSE" | jq -r '.stop_reason')"
# Find the tool_use block. A response may contain text blocks before the
# tool_use block, so filter by type rather than assuming position.
TOOL_USE=$(echo "$RESPONSE" | jq '.content[] | select(.type == "tool_use")')
TOOL_USE_ID=$(echo "$TOOL_USE" | jq -r '.id')
echo "Tool: $(echo "$TOOL_USE" | jq -r '.name')"
echo "Input: $(echo "$TOOL_USE" | jq -c '.input')"
# Execute the tool. In a real system this would call your calendar API.
# Here the result is hardcoded to keep the example self-contained.
RESULT='{"event_id": "evt_123", "status": "created"}'
# Send the result back. The tool_result block goes in a user message and
# its tool_use_id must match the id from the tool_use block above. The
# assistant's previous response is included so Claude has the full history.
ASSISTANT_CONTENT=$(echo "$RESPONSE" | jq '.content')
FOLLOWUP=$(curl -s https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d "$(jq -n \
--argjson tools "$TOOLS" \
--arg msg "$USER_MSG" \
--argjson assistant "$ASSISTANT_CONTENT" \
--arg tool_use_id "$TOOL_USE_ID" \
--arg result "$RESULT" \
'{
model: "claude-opus-4-6",
max_tokens: 1024,
tools: $tools,
tool_choice: {type: "auto", disable_parallel_tool_use: true},
messages: [
{role: "user", content: $msg},
{role: "assistant", content: $assistant},
{role: "user", content: [
{type: "tool_result", tool_use_id: $tool_use_id, content: $result}
]}
]
}')")
# With the tool result in hand, Claude produces a final natural-language
# answer and stop_reason becomes "end_turn".
echo "stop_reason: $(echo "$FOLLOWUP" | jq -r '.stop_reason')"
echo "$FOLLOWUP" | jq -r '.content[] | select(.type == "text") | .text'
````
````bash
#!/usr/bin/env bash
# Ring 1: Single tool, single turn.
# Uses jq for cross-turn message-array state — building an agentic loop in shell
# requires JSON manipulation beyond ant's single-call --transform scope.
# Source for in build-a-tool-using-agent.mdx.
set -euo pipefail
USER_MSG="Schedule a 30-minute sync with alice@example.com and bob@example.com next Monday at 10am."
MESSAGES=$(jq -n --arg msg "$USER_MSG" '[{role: "user", content: $msg}]')
# Define one tool. The input_schema is a JSON Schema object describing
# the arguments Claude should pass when it calls this tool. This schema
# includes nested objects (recurrence), arrays (attendees), and optional
# fields, which is closer to real-world tools than a flat string argument.
call_api() {
# ant reads the request body as YAML on stdin: no auth headers, no
# hand-built JSON envelope. The static keys (model, tools, tool_choice)
# live in a quoted heredoc; the growing messages array is appended as
# JSON, which YAML accepts as flow syntax.
{
cat <<'YAML'
model: claude-opus-4-6
max_tokens: 1024
tool_choice: {type: auto, disable_parallel_tool_use: true}
tools:
- name: create_calendar_event
description: Create a calendar event with attendees and optional recurrence.
input_schema:
type: object
properties:
title: {type: string}
start: {type: string, format: date-time}
end: {type: string, format: date-time}
attendees:
type: array
items: {type: string, format: email}
recurrence:
type: object
properties:
frequency: {enum: [daily, weekly, monthly]}
count: {type: integer, minimum: 1}
required: [title, start, end]
YAML
printf 'messages: %s\n' "$MESSAGES"
} | ant messages create --format json
}
# Send the user's request along with the tool definition. Claude decides
# whether to call the tool based on the request and the tool description.
RESPONSE=$(call_api)
# When Claude calls a tool, the response has stop_reason "tool_use"
# and the content array contains a tool_use block alongside any text.
echo "stop_reason: $(jq -r '.stop_reason' <<<"$RESPONSE")"
# Find the tool_use block. A response may contain text blocks before the
# tool_use block, so filter by type rather than assuming position.
TOOL_USE=$(jq '.content[] | select(.type == "tool_use")' <<<"$RESPONSE")
TOOL_USE_ID=$(jq -r '.id' <<<"$TOOL_USE")
echo "Tool: $(jq -r '.name' <<<"$TOOL_USE")"
echo "Input: $(jq -c '.input' <<<"$TOOL_USE")"
# Execute the tool. In a real system this would call your calendar API.
# Here the result is hardcoded to keep the example self-contained.
RESULT='{"event_id": "evt_123", "status": "created"}'
# Send the result back. The tool_result block goes in a user message and
# its tool_use_id must match the id from the tool_use block above. The
# assistant's previous response is included so Claude has the full history.
MESSAGES=$(jq \
--argjson assistant "$(jq '.content' <<<"$RESPONSE")" \
--arg tool_use_id "$TOOL_USE_ID" \
--arg result "$RESULT" \
'. + [
{role: "assistant", content: $assistant},
{role: "user", content: [
{type: "tool_result", tool_use_id: $tool_use_id, content: $result}
]}
]' <<<"$MESSAGES")
FOLLOWUP=$(call_api)
# With the tool result in hand, Claude produces a final natural-language
# answer and stop_reason becomes "end_turn".
echo "stop_reason: $(jq -r '.stop_reason' <<<"$FOLLOWUP")"
jq -r '.content[] | select(.type == "text") | .text' <<<"$FOLLOWUP"
````
````python
# Ring 1: Single tool, single turn.
# Source for in build-a-tool-using-agent.mdx.
import json
import anthropic
# Create a client. It reads ANTHROPIC_API_KEY from the environment.
client = anthropic.Anthropic()
# Define one tool. The input_schema is a JSON Schema object describing
# the arguments Claude should pass when it calls this tool. This schema
# includes nested objects (recurrence), arrays (attendees), and optional
# fields, which is closer to real-world tools than a flat string argument.
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
}
]
# Send the user's request along with the tool definition. Claude decides
# whether to call the tool based on the request and the tool description.
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=[
{
"role": "user",
"content": "Schedule a 30-minute sync with alice@example.com and bob@example.com next Monday at 10am.",
}
],
)
# When Claude calls a tool, the response has stop_reason "tool_use"
# and the content array contains a tool_use block alongside any text.
print(f"stop_reason: {response.stop_reason}")
# Find the tool_use block. A response may contain text blocks before the
# tool_use block, so scan the content array rather than assuming position.
tool_use = next(block for block in response.content if block.type == "tool_use")
print(f"Tool: {tool_use.name}")
print(f"Input: {tool_use.input}")
# Execute the tool. In a real system this would call your calendar API.
# Here the result is hardcoded to keep the example self-contained.
result = {"event_id": "evt_123", "status": "created"}
# Send the result back. The tool_result block goes in a user message and
# its tool_use_id must match the id from the tool_use block above. The
# assistant's previous response is included so Claude has the full history.
followup = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=[
{
"role": "user",
"content": "Schedule a 30-minute sync with alice@example.com and bob@example.com next Monday at 10am.",
},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": json.dumps(result),
}
],
},
],
)
# With the tool result in hand, Claude produces a final natural-language
# answer and stop_reason becomes "end_turn".
print(f"stop_reason: {followup.stop_reason}")
final_text = next(block for block in followup.content if block.type == "text")
print(final_text.text)
````
````typescript
// Ring 1: Single tool, single turn.
// Source for in build-a-tool-using-agent.mdx.
import Anthropic from "@anthropic-ai/sdk";
// Create a client. It reads ANTHROPIC_API_KEY from the environment.
const client = new Anthropic();
// Define one tool. The input_schema is a JSON Schema object describing
// the arguments Claude should pass when it calls this tool. This schema
// includes nested objects (recurrence), arrays (attendees), and optional
// fields, which is closer to real-world tools than a flat string argument.
const tools: Anthropic.Tool[] = [
{
name: "create_calendar_event",
description:
"Create a calendar event with attendees and optional recurrence.",
input_schema: {
type: "object",
properties: {
title: { type: "string" },
start: { type: "string", format: "date-time" },
end: { type: "string", format: "date-time" },
attendees: {
type: "array",
items: { type: "string", format: "email" },
},
recurrence: {
type: "object",
properties: {
frequency: { enum: ["daily", "weekly", "monthly"] },
count: { type: "integer", minimum: 1 },
},
},
},
required: ["title", "start", "end"],
},
},
];
// Send the user's request along with the tool definition. Claude decides
// whether to call the tool based on the request and the tool description.
const response = await client.messages.create({
model: "claude-opus-4-6",
max_tokens: 1024,
tools,
tool_choice: { type: "auto", disable_parallel_tool_use: true },
messages: [
{
role: "user",
content:
"Schedule a 30-minute sync with alice@example.com and bob@example.com next Monday at 10am.",
},
],
});
// When Claude calls a tool, the response has stop_reason "tool_use"
// and the content array contains a tool_use block alongside any text.
console.log(`stop_reason: ${response.stop_reason}`);
// Find the tool_use block. A response may contain text blocks before the
// tool_use block, so scan the content array rather than assuming position.
const toolUse = response.content.find(
(block): block is Anthropic.ToolUseBlock => block.type === "tool_use",
)!;
console.log(`Tool: ${toolUse.name}`);
console.log(`Input: ${JSON.stringify(toolUse.input)}`);
// Execute the tool. In a real system this would call your calendar API.
// Here the result is hardcoded to keep the example self-contained.
const result = { event_id: "evt_123", status: "created" };
// Send the result back. The tool_result block goes in a user message and
// its tool_use_id must match the id from the tool_use block above. The
// assistant's previous response is included so Claude has the full history.
const followup = await client.messages.create({
model: "claude-opus-4-6",
max_tokens: 1024,
tools,
tool_choice: { type: "auto", disable_parallel_tool_use: true },
messages: [
{
role: "user",
content:
"Schedule a 30-minute sync with alice@example.com and bob@example.com next Monday at 10am.",
},
{ role: "assistant", content: response.content },
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: toolUse.id,
content: JSON.stringify(result),
},
],
},
],
});
// With the tool result in hand, Claude produces a final natural-language
// answer and stop_reason becomes "end_turn".
console.log(`stop_reason: ${followup.stop_reason}`);
for (const block of followup.content) {
if (block.type === "text") {
console.log(block.text);
}
}
````
**What to expect**
```text Output
stop_reason: tool_use
Tool: create_calendar_event
Input: {'title': 'Sync', 'start': '2026-03-30T10:00:00', 'end': '2026-03-30T10:30:00', 'attendees': ['alice@example.com', 'bob@example.com']}
stop_reason: end_turn
I've scheduled your 30-minute sync with Alice and Bob for next Monday at 10am.
```
The first `stop_reason` is `tool_use` because Claude is waiting for the calendar result. After you send the result, the second `stop_reason` is `end_turn` and the content is natural language for the user.
## Ring 2: The agentic loop
Ring 1 assumed Claude would call the tool exactly once. Real tasks often need several calls: Claude might create an event, read the confirmation, then create another. The fix is a `while` loop that keeps running tools and feeding results back until `stop_reason` is no longer `"tool_use"`.
The other change is conversation history. Instead of rebuilding the `messages` array from scratch on each request, keep a running list and append to it. Every turn sees the complete prior context.
````bash
#!/bin/bash
# Ring 2: The agentic loop.
# Source for in build-a-tool-using-agent.mdx.
TOOLS='[
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {"type": "array", "items": {"type": "string", "format": "email"}},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1}
}
}
},
"required": ["title", "start", "end"]
}
}
]'
run_tool() {
local name="$1"
local input="$2"
if [ "$name" = "create_calendar_event" ]; then
local title=$(echo "$input" | jq -r '.title')
jq -n --arg title "$title" '{event_id: "evt_123", status: "created", title: $title}'
else
echo "{\"error\": \"Unknown tool: $name\"}"
fi
}
# Keep the full conversation history in a JSON array so each turn sees prior context.
MESSAGES='[{"role": "user", "content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com."}]'
call_api() {
curl -s https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d "$(jq -n --argjson tools "$TOOLS" --argjson messages "$MESSAGES" \
'{model: "claude-opus-4-6", max_tokens: 1024, tools: $tools, tool_choice: {type: "auto", disable_parallel_tool_use: true}, messages: $messages}')"
}
RESPONSE=$(call_api)
# Loop until Claude stops asking for tools. Each iteration runs the requested
# tool, appends the result to history, and asks Claude to continue.
while [ "$(echo "$RESPONSE" | jq -r '.stop_reason')" = "tool_use" ]; do
TOOL_USE=$(echo "$RESPONSE" | jq '.content[] | select(.type == "tool_use")')
TOOL_NAME=$(echo "$TOOL_USE" | jq -r '.name')
TOOL_INPUT=$(echo "$TOOL_USE" | jq -c '.input')
TOOL_USE_ID=$(echo "$TOOL_USE" | jq -r '.id')
RESULT=$(run_tool "$TOOL_NAME" "$TOOL_INPUT")
ASSISTANT_CONTENT=$(echo "$RESPONSE" | jq '.content')
MESSAGES=$(echo "$MESSAGES" | jq \
--argjson assistant "$ASSISTANT_CONTENT" \
--arg tool_use_id "$TOOL_USE_ID" \
--arg result "$RESULT" \
'. + [
{role: "assistant", content: $assistant},
{role: "user", content: [{type: "tool_result", tool_use_id: $tool_use_id, content: $result}]}
]')
RESPONSE=$(call_api)
done
echo "$RESPONSE" | jq -r '.content[] | select(.type == "text") | .text'
````
````bash
#!/usr/bin/env bash
# Ring 2: The agentic loop.
# Uses jq for cross-turn message-array state — building an agentic loop in shell
# requires JSON manipulation beyond ant's single-call --transform scope.
# Source for in build-a-tool-using-agent.mdx.
set -euo pipefail
run_tool() {
local name="$1" input="$2"
if [ "$name" = "create_calendar_event" ]; then
jq -n --arg title "$(jq -r '.title' <<<"$input")" \
'{event_id: "evt_123", status: "created", title: $title}'
else
printf '{"error": "Unknown tool: %s"}' "$name"
fi
}
# Keep the full conversation history in a JSON array so each turn sees
# prior context.
MESSAGES='[{"role": "user", "content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com."}]'
call_api() {
# ant reads the request body as YAML on stdin: no auth headers, no
# hand-built JSON envelope. The static keys (model, tools, tool_choice)
# live in a quoted heredoc; the growing messages array is appended as
# JSON, which YAML accepts as flow syntax.
{
cat <<'YAML'
model: claude-opus-4-6
max_tokens: 1024
tool_choice: {type: auto, disable_parallel_tool_use: true}
tools:
- name: create_calendar_event
description: Create a calendar event with attendees and optional recurrence.
input_schema:
type: object
properties:
title: {type: string}
start: {type: string, format: date-time}
end: {type: string, format: date-time}
attendees:
type: array
items: {type: string, format: email}
recurrence:
type: object
properties:
frequency: {enum: [daily, weekly, monthly]}
count: {type: integer, minimum: 1}
required: [title, start, end]
YAML
printf 'messages: %s\n' "$MESSAGES"
} | ant messages create --format json
}
RESPONSE=$(call_api)
# Loop until Claude stops asking for tools. Each iteration runs the
# requested tool, appends the result to history, and asks Claude to
# continue.
while [ "$(jq -r '.stop_reason' <<<"$RESPONSE")" = "tool_use" ]; do
TOOL_USE=$(jq '.content[] | select(.type == "tool_use")' <<<"$RESPONSE")
TOOL_NAME=$(jq -r '.name' <<<"$TOOL_USE")
TOOL_INPUT=$(jq -c '.input' <<<"$TOOL_USE")
TOOL_USE_ID=$(jq -r '.id' <<<"$TOOL_USE")
RESULT=$(run_tool "$TOOL_NAME" "$TOOL_INPUT")
MESSAGES=$(jq \
--argjson assistant "$(jq '.content' <<<"$RESPONSE")" \
--arg tool_use_id "$TOOL_USE_ID" \
--arg result "$RESULT" \
'. + [
{role: "assistant", content: $assistant},
{role: "user", content: [
{type: "tool_result", tool_use_id: $tool_use_id, content: $result}
]}
]' <<<"$MESSAGES")
RESPONSE=$(call_api)
done
jq -r '.content[] | select(.type == "text") | .text' <<<"$RESPONSE"
````
````python
# Ring 2: The agentic loop.
# Source for in build-a-tool-using-agent.mdx.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
}
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
return {"error": f"Unknown tool: {name}"}
# Keep the full conversation history in a list so each turn sees prior context.
messages = [
{
"role": "user",
"content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.",
}
]
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=messages,
)
# Loop until Claude stops asking for tools. Each iteration runs the requested
# tool, appends the result to history, and asks Claude to continue.
while response.stop_reason == "tool_use":
tool_use = next(block for block in response.content if block.type == "tool_use")
result = run_tool(tool_use.name, tool_use.input)
messages.append({"role": "assistant", "content": response.content})
messages.append(
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": json.dumps(result),
}
],
}
)
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)
````
````typescript
// Ring 2: The agentic loop.
// Source for in build-a-tool-using-agent.mdx.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "create_calendar_event",
description:
"Create a calendar event with attendees and optional recurrence.",
input_schema: {
type: "object",
properties: {
title: { type: "string" },
start: { type: "string", format: "date-time" },
end: { type: "string", format: "date-time" },
attendees: {
type: "array",
items: { type: "string", format: "email" },
},
recurrence: {
type: "object",
properties: {
frequency: { enum: ["daily", "weekly", "monthly"] },
count: { type: "integer", minimum: 1 },
},
},
},
required: ["title", "start", "end"],
},
},
];
function runTool(name: string, input: Record) {
if (name === "create_calendar_event") {
return { event_id: "evt_123", status: "created", title: input.title };
}
return { error: `Unknown tool: ${name}` };
}
// Keep the full conversation history so each turn sees prior context.
const messages: Anthropic.MessageParam[] = [
{
role: "user",
content:
"Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.",
},
];
let response = await client.messages.create({
model: "claude-opus-4-6",
max_tokens: 1024,
tools,
tool_choice: { type: "auto", disable_parallel_tool_use: true },
messages,
});
// Loop until Claude stops asking for tools. Each iteration runs the requested
// tool, appends the result to history, and asks Claude to continue.
while (response.stop_reason === "tool_use") {
const toolUse = response.content.find(
(block): block is Anthropic.ToolUseBlock => block.type === "tool_use",
)!;
const result = runTool(toolUse.name, toolUse.input as Record);
messages.push({ role: "assistant", content: response.content });
messages.push({
role: "user",
content: [
{
type: "tool_result",
tool_use_id: toolUse.id,
content: JSON.stringify(result),
},
],
});
response = await client.messages.create({
model: "claude-opus-4-6",
max_tokens: 1024,
tools,
tool_choice: { type: "auto", disable_parallel_tool_use: true },
messages,
});
}
for (const block of response.content) {
if (block.type === "text") {
console.log(block.text);
}
}
````
**What to expect**
```text Output
I've set up your weekly team standup for the next 4 Mondays at 9am with Alice, Bob, and Carol invited.
```
The loop may run once or several times depending on how Claude breaks down the task. Your code no longer needs to know in advance.
## Ring 3: Multiple tools, parallel calls
Agents rarely have just one capability. Add a second tool, `list_calendar_events`, so Claude can check the existing schedule before creating something new.
When Claude has multiple independent tool calls to make, it may return several `tool_use` blocks in a single response. Your loop needs to process all of them and send back all results together in one user message. Iterate over every `tool_use` block in `response.content`, not just the first.
````bash
#!/bin/bash
# Ring 3: Multiple tools, parallel calls.
# Source for in build-a-tool-using-agent.mdx.
TOOLS='[
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {"type": "array", "items": {"type": "string", "format": "email"}},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1}
}
}
},
"required": ["title", "start", "end"]
}
},
{
"name": "list_calendar_events",
"description": "List all calendar events on a given date.",
"input_schema": {
"type": "object",
"properties": {"date": {"type": "string", "format": "date"}},
"required": ["date"]
}
}
]'
run_tool() {
case "$1" in
create_calendar_event)
jq -n --arg title "$(echo "$2" | jq -r '.title')" '{event_id: "evt_123", status: "created", title: $title}' ;;
list_calendar_events)
echo '{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}' ;;
*)
echo "{\"error\": \"Unknown tool: $1\"}" ;;
esac
}
MESSAGES='[{"role": "user", "content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts."}]'
call_api() {
curl -s https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d "$(jq -n --argjson tools "$TOOLS" --argjson messages "$MESSAGES" \
'{model: "claude-opus-4-6", max_tokens: 1024, tools: $tools, messages: $messages}')"
}
RESPONSE=$(call_api)
while [ "$(echo "$RESPONSE" | jq -r '.stop_reason')" = "tool_use" ]; do
# A single response can contain multiple tool_use blocks. Process all of
# them and return all results together in one user message.
TOOL_RESULTS='[]'
while read -r block; do
NAME=$(echo "$block" | jq -r '.name')
INPUT=$(echo "$block" | jq -c '.input')
ID=$(echo "$block" | jq -r '.id')
RESULT=$(run_tool "$NAME" "$INPUT")
TOOL_RESULTS=$(echo "$TOOL_RESULTS" | jq --arg id "$ID" --arg result "$RESULT" \
'. + [{type: "tool_result", tool_use_id: $id, content: $result}]')
done < <(echo "$RESPONSE" | jq -c '.content[] | select(.type == "tool_use")')
MESSAGES=$(echo "$MESSAGES" | jq \
--argjson assistant "$(echo "$RESPONSE" | jq '.content')" \
--argjson results "$TOOL_RESULTS" \
'. + [{role: "assistant", content: $assistant}, {role: "user", content: $results}]')
RESPONSE=$(call_api)
done
echo "$RESPONSE" | jq -r '.content[] | select(.type == "text") | .text'
````
````bash
#!/usr/bin/env bash
# Ring 3: Multiple tools, parallel calls.
# Uses jq for cross-turn message-array state — building an agentic loop in shell
# requires JSON manipulation beyond ant's single-call --transform scope.
# Source for in build-a-tool-using-agent.mdx.
set -euo pipefail
run_tool() {
case "$1" in
create_calendar_event)
jq -n --arg title "$(jq -r '.title' <<<"$2")" \
'{event_id: "evt_123", status: "created", title: $title}' ;;
list_calendar_events)
echo '{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}' ;;
*)
printf '{"error": "Unknown tool: %s"}' "$1" ;;
esac
}
MESSAGES='[{"role": "user", "content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts."}]'
call_api() {
# ant reads the request body as YAML on stdin: no auth headers, no
# hand-built JSON envelope. The static keys (model, tools) live in a
# quoted heredoc; the growing messages array is appended as JSON,
# which YAML accepts as flow syntax.
{
cat <<'YAML'
model: claude-opus-4-6
max_tokens: 1024
tools:
- name: create_calendar_event
description: Create a calendar event with attendees and optional recurrence.
input_schema:
type: object
properties:
title: {type: string}
start: {type: string, format: date-time}
end: {type: string, format: date-time}
attendees:
type: array
items: {type: string, format: email}
recurrence:
type: object
properties:
frequency: {enum: [daily, weekly, monthly]}
count: {type: integer, minimum: 1}
required: [title, start, end]
- name: list_calendar_events
description: List all calendar events on a given date.
input_schema:
type: object
properties:
date: {type: string, format: date}
required: [date]
YAML
printf 'messages: %s\n' "$MESSAGES"
} | ant messages create --format json
}
RESPONSE=$(call_api)
while [ "$(jq -r '.stop_reason' <<<"$RESPONSE")" = "tool_use" ]; do
# A single response can contain multiple tool_use blocks. Process all
# of them and return all results together in one user message.
TOOL_RESULTS='[]'
while read -r block; do
NAME=$(jq -r '.name' <<<"$block")
INPUT=$(jq -c '.input' <<<"$block")
ID=$(jq -r '.id' <<<"$block")
RESULT=$(run_tool "$NAME" "$INPUT")
TOOL_RESULTS=$(jq --arg id "$ID" --arg result "$RESULT" \
'. + [{type: "tool_result", tool_use_id: $id, content: $result}]' \
<<<"$TOOL_RESULTS")
done < <(jq -c '.content[] | select(.type == "tool_use")' <<<"$RESPONSE")
MESSAGES=$(jq \
--argjson assistant "$(jq '.content' <<<"$RESPONSE")" \
--argjson results "$TOOL_RESULTS" \
'. + [
{role: "assistant", content: $assistant},
{role: "user", content: $results}
]' <<<"$MESSAGES")
RESPONSE=$(call_api)
done
jq -r '.content[] | select(.type == "text") | .text' <<<"$RESPONSE"
````
````python
# Ring 3: Multiple tools, parallel calls.
# Source for in build-a-tool-using-agent.mdx.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
},
{
"name": "list_calendar_events",
"description": "List all calendar events on a given date.",
"input_schema": {
"type": "object",
"properties": {
"date": {"type": "string", "format": "date"},
},
"required": ["date"],
},
},
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
if name == "list_calendar_events":
return {"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}
return {"error": f"Unknown tool: {name}"}
messages = [
{
"role": "user",
"content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",
}
]
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
)
while response.stop_reason == "tool_use":
# A single response can contain multiple tool_use blocks. Process all of
# them and return all results together in one user message.
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = run_tool(block.name, block.input)
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result),
}
)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)
````
````typescript
// Ring 3: Multiple tools, parallel calls.
// Source for in build-a-tool-using-agent.mdx.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "create_calendar_event",
description:
"Create a calendar event with attendees and optional recurrence.",
input_schema: {
type: "object",
properties: {
title: { type: "string" },
start: { type: "string", format: "date-time" },
end: { type: "string", format: "date-time" },
attendees: {
type: "array",
items: { type: "string", format: "email" },
},
recurrence: {
type: "object",
properties: {
frequency: { enum: ["daily", "weekly", "monthly"] },
count: { type: "integer", minimum: 1 },
},
},
},
required: ["title", "start", "end"],
},
},
{
name: "list_calendar_events",
description: "List all calendar events on a given date.",
input_schema: {
type: "object",
properties: {
date: { type: "string", format: "date" },
},
required: ["date"],
},
},
];
function runTool(name: string, input: Record) {
if (name === "create_calendar_event") {
return { event_id: "evt_123", status: "created", title: input.title };
}
if (name === "list_calendar_events") {
return {
events: [{ title: "Existing meeting", start: "14:00", end: "15:00" }],
};
}
return { error: `Unknown tool: ${name}` };
}
const messages: Anthropic.MessageParam[] = [
{
role: "user",
content:
"Check what I have next Monday, then schedule a planning session that avoids any conflicts.",
},
];
let response = await client.messages.create({
model: "claude-opus-4-6",
max_tokens: 1024,
tools,
messages,
});
while (response.stop_reason === "tool_use") {
// A single response can contain multiple tool_use blocks. Process all of
// them and return all results together in one user message.
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === "tool_use") {
const result = runTool(block.name, block.input as Record);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: JSON.stringify(result),
});
}
}
messages.push({ role: "assistant", content: response.content });
messages.push({ role: "user", content: toolResults });
response = await client.messages.create({
model: "claude-opus-4-6",
max_tokens: 1024,
tools,
messages,
});
}
for (const block of response.content) {
if (block.type === "text") {
console.log(block.text);
}
}
````
**What to expect**
```text Output
I checked your calendar for next Monday and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.
```
For more on concurrent execution and ordering guarantees, see [Parallel tool use](/docs/en/agents-and-tools/tool-use/parallel-tool-use).
## Ring 4: Error handling
Tools fail. A calendar API might reject an event with too many attendees, or a date might be malformed. When a tool raises an error, send the error message back with `is_error: true` instead of crashing. Claude reads the error and can retry with corrected input, ask the user for clarification, or explain the limitation.
````bash
#!/bin/bash
# Ring 4: Error handling.
# Source for in build-a-tool-using-agent.mdx.
TOOLS='[
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {"type": "array", "items": {"type": "string", "format": "email"}},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1}
}
}
},
"required": ["title", "start", "end"]
}
},
{
"name": "list_calendar_events",
"description": "List all calendar events on a given date.",
"input_schema": {
"type": "object",
"properties": {"date": {"type": "string", "format": "date"}},
"required": ["date"]
}
}
]'
run_tool() {
case "$1" in
create_calendar_event)
local count=$(echo "$2" | jq '.attendees | length // 0')
if [ "$count" -gt 10 ]; then
echo "ERROR: Too many attendees (max 10)"
return 1
fi
jq -n --arg title "$(echo "$2" | jq -r '.title')" '{event_id: "evt_123", status: "created", title: $title}' ;;
list_calendar_events)
echo '{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}' ;;
*)
echo "ERROR: Unknown tool: $1"
return 1 ;;
esac
}
EMAILS=$(seq 0 14 | sed 's/.*/user&@example.com/' | paste -sd, -)
MESSAGES="[{\"role\": \"user\", \"content\": \"Schedule an all-hands with everyone: $EMAILS\"}]"
call_api() {
curl -s https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d "$(jq -n --argjson tools "$TOOLS" --argjson messages "$MESSAGES" \
'{model: "claude-opus-4-6", max_tokens: 1024, tools: $tools, messages: $messages}')"
}
RESPONSE=$(call_api)
while [ "$(echo "$RESPONSE" | jq -r '.stop_reason')" = "tool_use" ]; do
TOOL_RESULTS='[]'
while read -r block; do
NAME=$(echo "$block" | jq -r '.name')
INPUT=$(echo "$block" | jq -c '.input')
ID=$(echo "$block" | jq -r '.id')
if OUTPUT=$(run_tool "$NAME" "$INPUT"); then
TOOL_RESULTS=$(echo "$TOOL_RESULTS" | jq --arg id "$ID" --arg result "$OUTPUT" \
'. + [{type: "tool_result", tool_use_id: $id, content: $result}]')
else
# Signal failure so Claude can retry or ask for clarification.
TOOL_RESULTS=$(echo "$TOOL_RESULTS" | jq --arg id "$ID" --arg result "$OUTPUT" \
'. + [{type: "tool_result", tool_use_id: $id, content: $result, is_error: true}]')
fi
done < <(echo "$RESPONSE" | jq -c '.content[] | select(.type == "tool_use")')
MESSAGES=$(echo "$MESSAGES" | jq \
--argjson assistant "$(echo "$RESPONSE" | jq '.content')" \
--argjson results "$TOOL_RESULTS" \
'. + [{role: "assistant", content: $assistant}, {role: "user", content: $results}]')
RESPONSE=$(call_api)
done
echo "$RESPONSE" | jq -r '.content[] | select(.type == "text") | .text'
````
````bash
#!/usr/bin/env bash
# Ring 4: Error handling.
# Uses jq for cross-turn message-array state — building an agentic loop in shell
# requires JSON manipulation beyond ant's single-call --transform scope.
# Source for in build-a-tool-using-agent.mdx.
set -euo pipefail
run_tool() {
case "$1" in
create_calendar_event)
local count
count=$(jq '.attendees | length // 0' <<<"$2")
if [ "$count" -gt 10 ]; then
echo "ERROR: Too many attendees (max 10)"
return 1
fi
jq -n --arg title "$(jq -r '.title' <<<"$2")" \
'{event_id: "evt_123", status: "created", title: $title}' ;;
list_calendar_events)
echo '{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}' ;;
*)
echo "ERROR: Unknown tool: $1"
return 1 ;;
esac
}
EMAILS=$(seq 0 14 | sed 's/.*/user&@example.com/' | paste -sd, -)
MESSAGES=$(jq -n --arg msg "Schedule an all-hands with everyone: $EMAILS" \
'[{role: "user", content: $msg}]')
call_api() {
# ant reads the request body as YAML on stdin: no auth headers, no
# hand-built JSON envelope. The static keys (model, tools) live in a
# quoted heredoc; the growing messages array is appended as JSON,
# which YAML accepts as flow syntax.
{
cat <<'YAML'
model: claude-opus-4-6
max_tokens: 1024
tools:
- name: create_calendar_event
description: Create a calendar event with attendees and optional recurrence.
input_schema:
type: object
properties:
title: {type: string}
start: {type: string, format: date-time}
end: {type: string, format: date-time}
attendees:
type: array
items: {type: string, format: email}
recurrence:
type: object
properties:
frequency: {enum: [daily, weekly, monthly]}
count: {type: integer, minimum: 1}
required: [title, start, end]
- name: list_calendar_events
description: List all calendar events on a given date.
input_schema:
type: object
properties:
date: {type: string, format: date}
required: [date]
YAML
printf 'messages: %s\n' "$MESSAGES"
} | ant messages create --format json
}
RESPONSE=$(call_api)
while [ "$(jq -r '.stop_reason' <<<"$RESPONSE")" = "tool_use" ]; do
TOOL_RESULTS='[]'
while read -r block; do
NAME=$(jq -r '.name' <<<"$block")
INPUT=$(jq -c '.input' <<<"$block")
ID=$(jq -r '.id' <<<"$block")
if OUTPUT=$(run_tool "$NAME" "$INPUT"); then
TOOL_RESULTS=$(jq --arg id "$ID" --arg result "$OUTPUT" \
'. + [{type: "tool_result", tool_use_id: $id, content: $result}]' \
<<<"$TOOL_RESULTS")
else
# Signal failure so Claude can retry or ask for clarification.
TOOL_RESULTS=$(jq --arg id "$ID" --arg result "$OUTPUT" \
'. + [{type: "tool_result", tool_use_id: $id, content: $result, is_error: true}]' \
<<<"$TOOL_RESULTS")
fi
done < <(jq -c '.content[] | select(.type == "tool_use")' <<<"$RESPONSE")
MESSAGES=$(jq \
--argjson assistant "$(jq '.content' <<<"$RESPONSE")" \
--argjson results "$TOOL_RESULTS" \
'. + [
{role: "assistant", content: $assistant},
{role: "user", content: $results}
]' <<<"$MESSAGES")
RESPONSE=$(call_api)
done
jq -r '.content[] | select(.type == "text") | .text' <<<"$RESPONSE"
````
````python
# Ring 4: Error handling.
# Source for in build-a-tool-using-agent.mdx.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
},
{
"name": "list_calendar_events",
"description": "List all calendar events on a given date.",
"input_schema": {
"type": "object",
"properties": {
"date": {"type": "string", "format": "date"},
},
"required": ["date"],
},
},
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
if "attendees" in tool_input and len(tool_input["attendees"]) > 10:
raise ValueError("Too many attendees (max 10)")
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
if name == "list_calendar_events":
return {"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}
raise ValueError(f"Unknown tool: {name}")
messages = [
{
"role": "user",
"content": "Schedule an all-hands with everyone: " + ", ".join(f"user{i}@example.com" for i in range(15)),
}
]
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
)
while response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
try:
result = run_tool(block.name, block.input)
tool_results.append(
{"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result)}
)
except Exception as exc:
# Signal failure so Claude can retry or ask for clarification.
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": str(exc),
"is_error": True,
}
)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)
````
````typescript
// Ring 4: Error handling.
// Source for in build-a-tool-using-agent.mdx.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "create_calendar_event",
description:
"Create a calendar event with attendees and optional recurrence.",
input_schema: {
type: "object",
properties: {
title: { type: "string" },
start: { type: "string", format: "date-time" },
end: { type: "string", format: "date-time" },
attendees: {
type: "array",
items: { type: "string", format: "email" },
},
recurrence: {
type: "object",
properties: {
frequency: { enum: ["daily", "weekly", "monthly"] },
count: { type: "integer", minimum: 1 },
},
},
},
required: ["title", "start", "end"],
},
},
{
name: "list_calendar_events",
description: "List all calendar events on a given date.",
input_schema: {
type: "object",
properties: {
date: { type: "string", format: "date" },
},
required: ["date"],
},
},
];
function runTool(name: string, input: Record) {
if (name === "create_calendar_event") {
const attendees = input.attendees as string[] | undefined;
if (attendees && attendees.length > 10) {
throw new Error("Too many attendees (max 10)");
}
return { event_id: "evt_123", status: "created", title: input.title };
}
if (name === "list_calendar_events") {
return {
events: [{ title: "Existing meeting", start: "14:00", end: "15:00" }],
};
}
throw new Error(`Unknown tool: ${name}`);
}
const emails = Array.from({ length: 15 }, (_, i) => `user${i}@example.com`);
const messages: Anthropic.MessageParam[] = [
{
role: "user",
content: `Schedule an all-hands with everyone: ${emails.join(", ")}`,
},
];
let response = await client.messages.create({
model: "claude-opus-4-6",
max_tokens: 1024,
tools,
messages,
});
while (response.stop_reason === "tool_use") {
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === "tool_use") {
try {
const result = runTool(block.name, block.input as Record);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: JSON.stringify(result),
});
} catch (err) {
// Signal failure so Claude can retry or ask for clarification.
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: String(err),
is_error: true,
});
}
}
}
messages.push({ role: "assistant", content: response.content });
messages.push({ role: "user", content: toolResults });
response = await client.messages.create({
model: "claude-opus-4-6",
max_tokens: 1024,
tools,
messages,
});
}
for (const block of response.content) {
if (block.type === "text") {
console.log(block.text);
}
}
````
**What to expect**
```text Output
I tried to schedule the all-hands but the calendar only allows 10 attendees per event. I can split this into two sessions, or you can let me know which 10 people to prioritize.
```
The `is_error` flag is the only difference from a successful result. Claude sees the flag and the error text, and responds accordingly. See [Handle tool calls](/docs/en/agents-and-tools/tool-use/handle-tool-calls) for the full error-handling reference.
## Ring 5: The Tool Runner SDK abstraction
Rings 2 through 4 wrote the same loop by hand: call the API, check `stop_reason`, run tools, append results, repeat. The Tool Runner does this for you. Define each tool as a function, pass the list to `tool_runner`, and retrieve the final message once the loop completes. Error wrapping, result formatting, and conversation management are handled internally.
The Python SDK uses the `@beta_tool` decorator to infer the schema from type hints and the docstring. The TypeScript SDK uses `betaZodTool` with a Zod schema.
Tool Runner is available in the Python, TypeScript, and Ruby SDKs. The cURL and CLI tabs show a note instead of code; keep the Ring 4 loop for curl- or CLI-based scripts.
````bash
#!/bin/bash
# Ring 5: The Tool Runner SDK abstraction.
# Source for in build-a-tool-using-agent.mdx.
# The Tool Runner SDK abstraction is available in the Python, TypeScript,
# and Ruby SDKs. There is no equivalent for raw curl requests. Switch to
# the Python or TypeScript tab to see Ring 5, or keep the Ring 4 loop as
# your shell implementation.
````
````bash
#!/usr/bin/env bash
# Ring 5: The Tool Runner SDK abstraction.
# Source for in build-a-tool-using-agent.mdx.
set -euo pipefail
# The Tool Runner SDK abstraction is available in the Python, TypeScript,
# and Ruby SDKs. The ant CLI exposes the Messages API directly and has
# no equivalent helper. Switch to the Python or TypeScript tab to see
# Ring 5, or keep the Ring 4 loop as your CLI implementation.
````
````python
# Ring 5: The Tool Runner SDK abstraction.
# Source for in build-a-tool-using-agent.mdx.
import json
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def create_calendar_event(
title: str,
start: str,
end: str,
attendees: list[str] | None = None,
recurrence: dict | None = None,
) -> str:
"""Create a calendar event with attendees and optional recurrence.
Args:
title: Event title.
start: Start time in ISO 8601 format.
end: End time in ISO 8601 format.
attendees: Email addresses to invite.
recurrence: Dict with 'frequency' (daily, weekly, monthly) and 'count'.
"""
if attendees and len(attendees) > 10:
raise ValueError("Too many attendees (max 10)")
return json.dumps({"event_id": "evt_123", "status": "created", "title": title})
@beta_tool
def list_calendar_events(date: str) -> str:
"""List all calendar events on a given date.
Args:
date: Date in YYYY-MM-DD format.
"""
return json.dumps({"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]})
final_message = client.beta.messages.tool_runner(
model="claude-opus-4-6",
max_tokens=1024,
tools=[create_calendar_event, list_calendar_events],
messages=[
{
"role": "user",
"content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",
}
],
).until_done()
for block in final_message.content:
if block.type == "text":
print(block.text)
````
````typescript
// Ring 5: The Tool Runner SDK abstraction.
// Source for in build-a-tool-using-agent.mdx.
import Anthropic from "@anthropic-ai/sdk";
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";
const client = new Anthropic();
const createCalendarEvent = betaZodTool({
name: "create_calendar_event",
description:
"Create a calendar event with attendees and optional recurrence.",
inputSchema: z.object({
title: z.string(),
start: z.string().datetime(),
end: z.string().datetime(),
attendees: z.array(z.string().email()).optional(),
recurrence: z
.object({
frequency: z.enum(["daily", "weekly", "monthly"]),
count: z.number().int().min(1),
})
.optional(),
}),
run: async (input) => {
if (input.attendees && input.attendees.length > 10) {
throw new Error("Too many attendees (max 10)");
}
return JSON.stringify({
event_id: "evt_123",
status: "created",
title: input.title,
});
},
});
const listCalendarEvents = betaZodTool({
name: "list_calendar_events",
description: "List all calendar events on a given date.",
inputSchema: z.object({
date: z.string().date(),
}),
run: async () => {
return JSON.stringify({
events: [{ title: "Existing meeting", start: "14:00", end: "15:00" }],
});
},
});
const finalMessage = await client.beta.messages.toolRunner({
model: "claude-opus-4-6",
max_tokens: 1024,
tools: [createCalendarEvent, listCalendarEvents],
messages: [
{
role: "user",
content:
"Check what I have next Monday, then schedule a planning session that avoids any conflicts.",
},
],
});
for (const block of finalMessage.content) {
if (block.type === "text") {
console.log(block.text);
}
}
````
**What to expect**
```text Output
I checked your calendar for next Monday and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.
```
The output is identical to Ring 3. The difference is in the code: roughly half the lines, no manual loop, and the schema lives next to the implementation.
## What you built
You started with a single hardcoded tool call and ended with a production-shaped agent that handles multiple tools, parallel calls, and errors, then collapsed all of that into the Tool Runner. Along the way you saw every piece of the tool-use protocol: `tool_use` blocks, `tool_result` blocks, `tool_use_id` matching, `stop_reason` checking, and `is_error` signaling.
## Next steps
Schema specification and best practices.
The full SDK abstraction reference.
Fix common tool-use errors.
---
# Web fetch tool
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-fetch-tool
# Web fetch tool
Fetch and read content from specific URLs to augment Claude's context with live web content.
---
The web fetch tool allows Claude to retrieve full content from specified web pages and PDF documents.
The latest web fetch tool version (`web_fetch_20260209`) supports **dynamic filtering** with [Claude Mythos Preview](https://anthropic.com/glasswing), Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6. Claude can write and execute code to filter fetched content before it reaches the context window, keeping only relevant information and discarding the rest. This reduces token consumption while maintaining response quality. The previous tool version (`web_fetch_20250910`) remains available without dynamic filtering.
For [Claude Mythos Preview](https://anthropic.com/glasswing), web fetch is supported on the Claude API and Microsoft Foundry only. It is not available for Mythos Preview on Amazon Bedrock or Vertex AI.
Use the [feedback form](https://forms.gle/NhWcgmkcvPCMmPE86) to provide feedback on the quality of the model responses, the API itself, or the quality of the documentation.
For Zero Data Retention eligibility and the `allowed_callers` workaround, see [Server tools](/docs/en/agents-and-tools/tool-use/server-tools#zdr-and-allowed-callers).
Enabling the web fetch tool in environments where Claude processes untrusted input alongside sensitive data poses data exfiltration risks. Only use this tool in trusted environments or when handling non-sensitive data.
To minimize exfiltration risks, Claude is not allowed to dynamically construct URLs. Claude can only fetch URLs that have been explicitly provided by the user or that come from previous web search or web fetch results. However, there is still residual risk that should be carefully considered when using this tool.
If data exfiltration is a concern, consider:
- Disabling the web fetch tool entirely
- Using the `max_uses` parameter to limit the number of requests
- Using the `allowed_domains` parameter to restrict to known safe domains
For model support, see the [Tool reference](/docs/en/agents-and-tools/tool-use/tool-reference).
## How web fetch works
When you add the web fetch tool to your API request:
1. Claude decides when to fetch content based on the prompt and available URLs.
2. The API retrieves the full text content from the specified URL.
3. For PDFs, automatic text extraction is performed.
4. Claude analyzes the fetched content and provides a response with optional citations.
The web fetch tool currently does not support websites dynamically rendered with JavaScript.
### Dynamic filtering
Fetching full web pages and PDFs can quickly consume tokens, especially when only specific information is needed from large documents. With the `web_fetch_20260209` tool version, Claude can write and execute code to filter the fetched content before loading it into context.
This dynamic filtering is particularly useful for:
- Extracting specific sections from long documents
- Processing structured data from web pages
- Filtering relevant information from PDFs
- Reducing token costs when working with large documents
Dynamic filtering requires the [code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool) to be enabled. The web fetch tool (with and without dynamic filtering) is available on the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry). It is not available on Amazon Bedrock or Vertex AI.
To enable dynamic filtering, use the `web_fetch_20260209` tool version:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": "Fetch the content at https://example.com/research-paper and extract the key findings."
}
],
"tools": [{
"type": "web_fetch_20260209",
"name": "web_fetch"
}]
}'
```
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: >-
Fetch the content at https://example.com/research-paper
and extract the key findings.
tools:
- type: web_fetch_20260209
name: web_fetch
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
messages=[
{
"role": "user",
"content": "Fetch the content at https://example.com/research-paper and extract the key findings.",
}
],
tools=[{"type": "web_fetch_20260209", "name": "web_fetch"}],
)
print(response)
```
```typescript TypeScript hidelines={1..5,-3..-1}
import { Anthropic } from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
async function main() {
const response = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{
role: "user",
content:
"Fetch the content at https://example.com/research-paper and extract the key findings."
}
],
tools: [{ type: "web_fetch_20260209", name: "web_fetch" }]
});
console.log(response);
}
main().catch(console.error);
```
```csharp C# hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Messages = [new() { Role = Role.User, Content = "Fetch the content at https://example.com/research-paper and extract the key findings." }],
Tools = [new ToolUnion(new WebFetchTool20260209())]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Fetch the content at https://example.com/research-paper and extract the key findings.")),
},
Tools: []anthropic.ToolUnionParam{
{OfWebFetchTool20260209: &anthropic.WebFetchTool20260209Param{}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..5}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.WebFetchTool20260209;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(4096L)
.addUserMessage("Fetch the content at https://example.com/research-paper and extract the key findings.")
.addTool(WebFetchTool20260209.builder().build())
.build();
Message response = client.messages().create(params);
IO.println(response);
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Fetch the content at https://example.com/research-paper and extract the key findings.']
],
model: 'claude-opus-4-7',
tools: [[
'type' => 'web_fetch_20260209',
'name' => 'web_fetch',
]],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{ role: "user", content: "Fetch the content at https://example.com/research-paper and extract the key findings." }
],
tools: [{
type: "web_fetch_20260209",
name: "web_fetch"
}]
)
puts message
```
## How to use web fetch
Provide the web fetch tool in your API request:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "Please analyze the content at https://example.com/article"
}
],
"tools": [{
"type": "web_fetch_20250910",
"name": "web_fetch",
"max_uses": 5
}]
}'
```
```bash CLI
ant messages create \
--model claude-opus-4-7 \
--max-tokens 1024 \
--message '{role: user, content: "Please analyze the content at https://example.com/article"}' \
--tool '{type: web_fetch_20250910, name: web_fetch, max_uses: 5}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Please analyze the content at https://example.com/article",
}
],
tools=[{"type": "web_fetch_20250910", "name": "web_fetch", "max_uses": 5}],
)
print(response)
```
```typescript TypeScript hidelines={1..5,-3..-1}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function main() {
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: "Please analyze the content at https://example.com/article"
}
],
tools: [
{
type: "web_fetch_20250910",
name: "web_fetch",
max_uses: 5
}
]
});
console.log(response);
}
main().catch(console.error);
```
```csharp C# hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Please analyze the content at https://example.com/article" }],
Tools = [new ToolUnion(new WebFetchTool20250910() { MaxUses = 5 })]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Please analyze the content at https://example.com/article")),
},
Tools: []anthropic.ToolUnionParam{
{OfWebFetchTool20250910: &anthropic.WebFetchTool20250910Param{
MaxUses: anthropic.Int(5),
}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..5}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.WebFetchTool20250910;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessage("Please analyze the content at https://example.com/article")
.addTool(WebFetchTool20250910.builder()
.maxUses(5L)
.build())
.build();
Message response = client.messages().create(params);
IO.println(response);
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Please analyze the content at https://example.com/article']
],
model: 'claude-opus-4-7',
tools: [[
'type' => 'web_fetch_20250910',
'name' => 'web_fetch',
'max_uses' => 5,
]],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "Please analyze the content at https://example.com/article" }
],
tools: [{
type: "web_fetch_20250910",
name: "web_fetch",
max_uses: 5
}]
)
puts message
```
### Tool definition
The web fetch tool supports the following parameters:
```json JSON
{
"type": "web_fetch_20250910",
"name": "web_fetch",
// Optional: Limit the number of fetches per request
"max_uses": 10,
// Optional: Only fetch from these domains
"allowed_domains": ["example.com", "docs.example.com"],
// Optional: Never fetch from these domains
"blocked_domains": ["private.example.com"],
// Optional: Enable citations for fetched content
"citations": {
"enabled": true
},
// Optional: Maximum content length in tokens
"max_content_tokens": 100000
}
```
#### Max uses
The `max_uses` parameter limits the number of web fetches performed. If Claude attempts more fetches than allowed, the `web_fetch_tool_result` is an error with the `max_uses_exceeded` error code. There is currently no default limit.
#### Domain filtering
For domain filtering with `allowed_domains` and `blocked_domains`, see [Server tools](/docs/en/agents-and-tools/tool-use/server-tools#domain-filtering).
#### Content limits
The `max_content_tokens` parameter limits the amount of content included in the context. If the fetched content exceeds this limit, the tool truncates it. This helps control token usage when fetching large documents.
The `max_content_tokens` parameter limit is approximate. The actual number of input tokens used can vary by a small amount.
#### Citations
Unlike web search where citations are always enabled, citations are optional for web fetch. Set `"citations": {"enabled": true}` to enable Claude to cite specific passages from fetched documents.
When displaying API outputs directly to end users, citations must be included to the original source. If you are making modifications to API outputs, including by reprocessing and/or combining them with your own material before displaying them to end users, display citations as appropriate based on consultation with your legal team.
### Response
Here's an example response structure:
```json Output
{
"role": "assistant",
"content": [
// 1. Claude's decision to fetch
{
"type": "text",
"text": "I'll fetch the content from the article to analyze it."
},
// 2. The fetch request
{
"type": "server_tool_use",
"id": "srvtoolu_01234567890abcdef",
"name": "web_fetch",
"input": {
"url": "https://example.com/article"
}
},
// 3. Fetch results
{
"type": "web_fetch_tool_result",
"tool_use_id": "srvtoolu_01234567890abcdef",
"content": {
"type": "web_fetch_result",
"url": "https://example.com/article",
"content": {
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": "Full text content of the article..."
},
"title": "Article Title",
"citations": { "enabled": true }
},
"retrieved_at": "2025-08-25T10:30:00Z"
}
},
// 4. Claude's analysis with citations (if enabled)
{
"text": "Based on the article, ",
"type": "text"
},
{
"text": "the main argument presented is that artificial intelligence will transform healthcare",
"type": "text",
"citations": [
{
"type": "char_location",
"document_index": 0,
"document_title": "Article Title",
"start_char_index": 1234,
"end_char_index": 1456,
"cited_text": "Artificial intelligence is poised to revolutionize healthcare delivery..."
}
]
}
],
"id": "msg_a930390d3a",
"usage": {
"input_tokens": 25039,
"output_tokens": 931,
"server_tool_use": {
"web_fetch_requests": 1
}
},
"stop_reason": "end_turn"
}
```
#### Fetch results
Fetch results include:
- `url`: The URL that was fetched
- `content`: A document block containing the fetched content
- `retrieved_at`: Timestamp when the content was retrieved
The web fetch tool caches results to improve performance and reduce redundant requests. The content returned may not always reflect the latest version available at the URL. The cache behavior is managed automatically and may change over time to optimize for different content types and usage patterns.
For PDF documents, content is returned as base64-encoded data:
```json Output
{
"type": "web_fetch_tool_result",
"tool_use_id": "srvtoolu_02",
"content": {
"type": "web_fetch_result",
"url": "https://example.com/paper.pdf",
"content": {
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": "JVBERi0xLjQKJcOkw7zDtsOfCjIgMCBvYmo..."
},
"citations": { "enabled": true }
},
"retrieved_at": "2025-08-25T10:30:02Z"
}
}
```
#### Errors
When the web fetch tool encounters an error, the Claude API returns a 200 (success) response with the error represented in the response body:
```json Output
{
"type": "web_fetch_tool_result",
"tool_use_id": "srvtoolu_a93jad",
"content": {
"type": "web_fetch_tool_error",
"error_code": "url_not_accessible"
}
}
```
These are the possible error codes:
- `invalid_input`: Invalid URL format
- `url_too_long`: URL exceeds maximum length (250 characters)
- `url_not_allowed`: URL blocked by domain filtering rules and model restrictions
- `url_not_accessible`: Failed to fetch content (HTTP error)
- `too_many_requests`: Rate limit exceeded
- `unsupported_content_type`: Content type not supported (only text and PDF)
- `max_uses_exceeded`: Maximum web fetch tool uses exceeded
- `unavailable`: An internal error occurred
## URL validation
For security reasons, the web fetch tool can only fetch URLs that have previously appeared in the conversation context. This includes:
- URLs in user messages
- URLs in client-side tool results
- URLs from previous web search or web fetch results
The tool cannot fetch arbitrary URLs that Claude generates or URLs from container-based server tools (Code Execution, Bash, etc.).
## Combined search and fetch
Web fetch works seamlessly with web search for comprehensive information gathering:
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
messages=[
{
"role": "user",
"content": "Find recent articles about quantum computing and analyze the most relevant one in detail",
}
],
tools=[
{"type": "web_search_20250305", "name": "web_search", "max_uses": 3},
{
"type": "web_fetch_20250910",
"name": "web_fetch",
"max_uses": 5,
"citations": {"enabled": True},
},
],
)
print(response)
```
In this workflow, Claude will:
1. Use web search to find relevant articles
2. Select the most promising results
3. Use web fetch to retrieve full content
4. Provide detailed analysis with citations
## Prompt caching
For caching tool definitions across turns, see [Tool use with prompt caching](/docs/en/agents-and-tools/tool-use/tool-use-with-prompt-caching).
## Streaming
With streaming enabled, fetch events are part of the stream with a pause during content retrieval:
```sse Output
event: message_start
data: {"type": "message_start", "message": {"id": "msg_abc123", "type": "message"}}
event: content_block_start
data: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
// Claude's decision to fetch
event: content_block_start
data: {"type": "content_block_start", "index": 1, "content_block": {"type": "server_tool_use", "id": "srvtoolu_xyz789", "name": "web_fetch"}}
// Fetch URL streamed
event: content_block_delta
data: {"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "{\"url\":\"https://example.com/article\"}"}}
// Pause while fetch executes
// Fetch results streamed
event: content_block_start
data: {"type": "content_block_start", "index": 2, "content_block": {"type": "web_fetch_tool_result", "tool_use_id": "srvtoolu_xyz789", "content": {"type": "web_fetch_result", "url": "https://example.com/article", "content": {"type": "document", "source": {"type": "text", "media_type": "text/plain", "data": "Article content..."}}}}}
// Claude's response continues...
```
## Batch requests
You can include the web fetch tool in the [Messages Batches API](/docs/en/build-with-claude/batch-processing). Web fetch tool calls through the Messages Batches API are priced the same as those in regular Messages API requests.
## Usage and pricing
Web fetch usage has **no additional charges** beyond standard token costs:
```json
"usage": {
"input_tokens": 25039,
"output_tokens": 931,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0,
"server_tool_use": {
"web_fetch_requests": 1
}
}
```
The web fetch tool is available on the Claude API at **no additional cost**. You only pay standard token costs for the fetched content that becomes part of your conversation context.
To protect against inadvertently fetching large content that would consume excessive tokens, use the `max_content_tokens` parameter to set appropriate limits based on your use case and budget considerations.
Example token usage for typical content:
- Average web page (10 kB): ~2,500 tokens
- Large documentation page (100 kB): ~25,000 tokens
- Research paper PDF (500 kB): ~125,000 tokens
## Next steps
Shared mechanics for Anthropic-executed tools.
Directory of all Anthropic-provided tools.
---
# Web search tool
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool
# Web search tool
---
The web search tool gives Claude direct access to real-time web content, allowing it to answer questions with up-to-date information beyond its knowledge cutoff. The response includes citations for sources drawn from search results.
The latest web search tool version (`web_search_20260209`) supports **dynamic filtering** with [Claude Mythos Preview](https://anthropic.com/glasswing), Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6. Claude can write and execute code to filter search results before they reach the context window, keeping only relevant information and discarding the rest. This leads to more accurate responses while reducing token consumption. The previous tool version (`web_search_20250305`) remains available without dynamic filtering.
For [Claude Mythos Preview](https://anthropic.com/glasswing), web search is supported on the Claude API, Microsoft Foundry, and Vertex AI. Web search is not available for Mythos Preview on Amazon Bedrock or [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws).
For Zero Data Retention eligibility and the `allowed_callers` workaround, see [Server tools](/docs/en/agents-and-tools/tool-use/server-tools#zdr-and-allowed-callers).
For model support, see the [Tool reference](/docs/en/agents-and-tools/tool-use/tool-reference).
## How web search works
When you add the web search tool to your API request:
1. Claude decides when to search based on the prompt.
2. The API executes the searches and provides Claude with the results. This process may repeat multiple times throughout a single request.
3. At the end of its turn, Claude provides a final response with cited sources.
### Dynamic filtering
Web search is a token-intensive task. With basic web search, Claude needs to pull search results into context, fetch full HTML from multiple websites, and reason over all of it before arriving at an answer. Often, much of this content is irrelevant, which can degrade response quality.
With the `web_search_20260209` tool version, Claude can write and execute code to post-process query results. Instead of reasoning over full HTML files, Claude dynamically filters search results before loading them into context, keeping only what's relevant and discarding the rest.
Dynamic filtering is particularly effective for:
- Searching through technical documentation
- Literature review and citation verification
- Technical research
- Response grounding and verification
Dynamic filtering requires the [code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool) to be enabled. The web search tool (with and without dynamic filtering) is available on the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry). On Vertex AI, only the basic web search tool (without dynamic filtering) is available. Web search is not available on Amazon Bedrock.
To enable dynamic filtering, use the `web_search_20260209` tool version:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": "Search for the current prices of AAPL and GOOGL, then calculate which has a better P/E ratio."
}
],
"tools": [{
"type": "web_search_20260209",
"name": "web_search"
}]
}'
```
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: >-
Search for the current prices of AAPL and GOOGL, then calculate
which has a better P/E ratio.
tools:
- type: web_search_20260209
name: web_search
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
messages=[
{
"role": "user",
"content": "Search for the current prices of AAPL and GOOGL, then calculate which has a better P/E ratio.",
}
],
tools=[{"type": "web_search_20260209", "name": "web_search"}],
)
print(response)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const response = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{
role: "user",
content:
"Search for the current prices of AAPL and GOOGL, then calculate which has a better P/E ratio."
}
],
tools: [{ type: "web_search_20260209", name: "web_search" }]
});
console.log(response);
```
```csharp C# hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Messages = [new() { Role = Role.User, Content = "Search for the current prices of AAPL and GOOGL, then calculate which has a better P/E ratio." }],
Tools = [new ToolUnion(new WebSearchTool20260209())]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Search for the current prices of AAPL and GOOGL, then calculate which has a better P/E ratio.")),
},
Tools: []anthropic.ToolUnionParam{
{OfWebSearchTool20260209: &anthropic.WebSearchTool20260209Param{}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..5}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.WebSearchTool20260209;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(4096L)
.addUserMessage("Search for the current prices of AAPL and GOOGL, then calculate which has a better P/E ratio.")
.addTool(WebSearchTool20260209.builder().build())
.build();
Message response = client.messages().create(params);
IO.println(response);
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Search for the current prices of AAPL and GOOGL, then calculate which has a better P/E ratio.'],
],
model: 'claude-opus-4-7',
tools: [
[
'type' => 'web_search_20260209',
'name' => 'web_search',
],
],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{ role: "user", content: "Search for the current prices of AAPL and GOOGL, then calculate which has a better P/E ratio." }
],
tools: [{
type: "web_search_20260209",
name: "web_search"
}]
)
puts message
```
## How to use web search
Your organization's administrator must enable web search in the [Claude Console](/settings/privacy).
Provide the web search tool in your API request:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "What is the weather in NYC?"
}
],
"tools": [{
"type": "web_search_20250305",
"name": "web_search",
"max_uses": 5
}]
}'
```
```bash CLI
ant messages create \
--model claude-opus-4-7 \
--max-tokens 1024 \
--message '{role: user, content: What is the weather in NYC?}' \
--tool '{type: web_search_20250305, name: web_search, max_uses: 5}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{"role": "user", "content": "What's the weather in NYC?"}],
tools=[{"type": "web_search_20250305", "name": "web_search", "max_uses": 5}],
)
print(response)
```
```typescript TypeScript hidelines={1..5,-3..-1}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function main() {
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: "What's the weather in NYC?"
}
],
tools: [
{
type: "web_search_20250305",
name: "web_search",
max_uses: 5
}
]
});
console.log(response);
}
main().catch(console.error);
```
```csharp C# hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "What's the weather in NYC?" }],
Tools = [new ToolUnion(new WebSearchTool20250305() { MaxUses = 5 })]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather in NYC?")),
},
Tools: []anthropic.ToolUnionParam{
{OfWebSearchTool20250305: &anthropic.WebSearchTool20250305Param{
MaxUses: anthropic.Int(5),
}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..5}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.WebSearchTool20250305;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessage("What's the weather in NYC?")
.addTool(WebSearchTool20250305.builder()
.maxUses(5L)
.build())
.build();
Message response = client.messages().create(params);
IO.println(response);
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => "What's the weather in NYC?"],
],
model: 'claude-opus-4-7',
tools: [
[
'type' => 'web_search_20250305',
'name' => 'web_search',
'max_uses' => 5,
],
],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{ role: "user", content: "What's the weather in NYC?" }
],
tools: [{
type: "web_search_20250305",
name: "web_search",
max_uses: 5
}]
)
puts message
```
### Tool definition
The web search tool supports the following parameters:
```json JSON
{
"type": "web_search_20250305",
"name": "web_search",
// Optional: Limit the number of searches per request
"max_uses": 5,
// Optional: Only include results from these domains
"allowed_domains": ["example.com", "trusteddomain.org"],
// Optional: Never include results from these domains
"blocked_domains": ["untrustedsource.com"],
// Optional: Localize search results
"user_location": {
"type": "approximate",
"city": "San Francisco",
"region": "California",
"country": "US",
"timezone": "America/Los_Angeles"
}
}
```
#### Max uses
The `max_uses` parameter limits the number of searches performed. If Claude attempts more searches than allowed, the `web_search_tool_result` is an error with the `max_uses_exceeded` error code.
#### Domain filtering
For domain filtering with `allowed_domains` and `blocked_domains`, see [Server tools](/docs/en/agents-and-tools/tool-use/server-tools#domain-filtering).
#### Localization
The `user_location` parameter allows you to localize search results based on a user's location.
- `type`: The type of location (must be `approximate`)
- `city`: The city name
- `region`: The region or state
- `country`: The country
- `timezone`: The [IANA timezone ID](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
### Response
Here's an example response structure:
```json Output
{
"role": "assistant",
"content": [
// 1. Claude's decision to search
{
"type": "text",
"text": "I'll search for when Claude Shannon was born."
},
// 2. The search query used
{
"type": "server_tool_use",
"id": "srvtoolu_01WYG3ziw53XMcoyKL4XcZmE",
"name": "web_search",
"input": {
"query": "claude shannon birth date"
}
},
// 3. Search results
{
"type": "web_search_tool_result",
"tool_use_id": "srvtoolu_01WYG3ziw53XMcoyKL4XcZmE",
"content": [
{
"type": "web_search_result",
"url": "https://en.wikipedia.org/wiki/Claude_Shannon",
"title": "Claude Shannon - Wikipedia",
"encrypted_content": "EqgfCioIARgBIiQ3YTAwMjY1Mi1mZjM5LTQ1NGUtODgxNC1kNjNjNTk1ZWI3Y...",
"page_age": "April 30, 2025"
}
]
},
{
"text": "Based on the search results, ",
"type": "text"
},
// 4. Claude's response with citations
{
"text": "Claude Shannon was born on April 30, 1916, in Petoskey, Michigan",
"type": "text",
"citations": [
{
"type": "web_search_result_location",
"url": "https://en.wikipedia.org/wiki/Claude_Shannon",
"title": "Claude Shannon - Wikipedia",
"encrypted_index": "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm..",
"cited_text": "Claude Elwood Shannon (April 30, 1916 – February 24, 2001) was an American mathematician, electrical engineer, computer scientist, cryptographer and i..."
}
]
}
],
"id": "msg_a930390d3a",
"usage": {
"input_tokens": 6039,
"output_tokens": 931,
"server_tool_use": {
"web_search_requests": 1
}
},
"stop_reason": "end_turn"
}
```
#### Search results
Search results include:
- `url`: The URL of the source page
- `title`: The title of the source page
- `page_age`: When the site was last updated
- `encrypted_content`: Encrypted content that must be passed back in multi-turn conversations for citations
#### Citations
Citations are always enabled for web search, and each `web_search_result_location` includes:
- `url`: The URL of the cited source
- `title`: The title of the cited source
- `encrypted_index`: A reference that must be passed back for multi-turn conversations.
- `cited_text`: Up to 150 characters of the cited content
The web search citation fields `cited_text`, `title`, and `url` do not count towards input or output token usage.
When displaying API outputs directly to end users, citations must be included to the original source. If you are making modifications to API outputs, including by reprocessing and/or combining them with your own material before displaying them to end users, display citations as appropriate based on consultation with your legal team.
#### Errors
When the web search tool encounters an error (such as hitting rate limits), the Claude API still returns a 200 (success) response. The error is represented within the response body using the following structure:
```json Output
{
"type": "web_search_tool_result",
"tool_use_id": "srvtoolu_a93jad",
"content": {
"type": "web_search_tool_result_error",
"error_code": "max_uses_exceeded"
}
}
```
These are the possible error codes:
- `too_many_requests`: Rate limit exceeded
- `invalid_input`: Invalid search query parameter
- `max_uses_exceeded`: Maximum web search tool uses exceeded
- `query_too_long`: Query exceeds maximum length
- `unavailable`: An internal error occurred
#### `pause_turn` stop reason
For continuing after a `pause_turn` stop reason, see [Server tools](/docs/en/agents-and-tools/tool-use/server-tools#the-server-side-loop-and-pause-turn).
## Prompt caching
For caching tool definitions across turns, see [Tool use with prompt caching](/docs/en/agents-and-tools/tool-use/tool-use-with-prompt-caching).
## Streaming
With streaming enabled, you'll receive search events as part of the stream. There will be a pause while the search executes:
```sse Output
event: message_start
data: {"type": "message_start", "message": {"id": "msg_abc123", "type": "message"}}
event: content_block_start
data: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
// Claude's decision to search
event: content_block_start
data: {"type": "content_block_start", "index": 1, "content_block": {"type": "server_tool_use", "id": "srvtoolu_xyz789", "name": "web_search"}}
// Search query streamed
event: content_block_delta
data: {"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "{\"query\":\"latest quantum computing breakthroughs 2025\"}"}}
// Pause while search executes
// Search results streamed
event: content_block_start
data: {"type": "content_block_start", "index": 2, "content_block": {"type": "web_search_tool_result", "tool_use_id": "srvtoolu_xyz789", "content": [{"type": "web_search_result", "title": "Quantum Computing Breakthroughs in 2025", "url": "https://example.com"}]}}
// Claude's response with citations (omitted in this example)
```
## Batch requests
You can include the web search tool in the [Messages Batches API](/docs/en/build-with-claude/batch-processing). Web search tool calls through the Messages Batches API are priced the same as those in regular Messages API requests.
## Usage and pricing
Web search usage is charged in addition to token usage:
```json
"usage": {
"input_tokens": 105,
"output_tokens": 6039,
"cache_read_input_tokens": 7123,
"cache_creation_input_tokens": 7345,
"server_tool_use": {
"web_search_requests": 1
}
}
```
Web search is available on the Claude API for **$10 per 1,000 searches**, plus standard token costs for search-generated content. Web search results retrieved throughout a conversation are counted as input tokens, in search iterations executed during a single turn and in subsequent conversation turns.
Each web search counts as one use, regardless of the number of results returned. If an error occurs during web search, the web search will not be billed.
## Next steps
Shared mechanics for Anthropic-executed tools.
Directory of all Anthropic-provided tools.
### Tool infrastructure
---
# Fine-grained tool streaming
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/fine-grained-tool-streaming
# Fine-grained tool streaming
Stream tool inputs character-by-character for latency-sensitive applications.
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
Fine-grained tool streaming is available on all models and all platforms. It enables [streaming](/docs/en/build-with-claude/streaming) of tool use parameter values without buffering or JSON validation, reducing the latency to begin receiving large parameters.
When using fine-grained tool streaming, you may potentially receive invalid or partial JSON inputs. Make sure to account for these edge cases in your code.
## How to use fine-grained tool streaming
Fine-grained tool streaming is supported on the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), [Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock), [Vertex AI](/docs/en/build-with-claude/claude-on-vertex-ai), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry). To use it, set `eager_input_streaming` to `true` on any user-defined tool where you want fine-grained streaming enabled, and enable streaming on your request.
Here's an example of how to use fine-grained tool streaming with the API:
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 65536,
"tools": [
{
"name": "make_file",
"description": "Write text to a file",
"eager_input_streaming": true,
"input_schema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "The filename to write text to"
},
"lines_of_text": {
"type": "array",
"description": "An array of lines of text to write to the file"
}
},
"required": ["filename", "lines_of_text"]
}
}
],
"messages": [
{
"role": "user",
"content": "Can you write a long poem and make a file called poem.txt?"
}
],
"stream": true
}'
```
```bash CLI
ant messages create --stream \
--transform usage <<'YAML'
model: claude-opus-4-7
max_tokens: 65536
tools:
- name: make_file
description: Write text to a file
eager_input_streaming: true
input_schema:
type: object
properties:
filename:
type: string
description: The filename to write text to
lines_of_text:
type: array
description: An array of lines of text to write to the file
required:
- filename
- lines_of_text
messages:
- role: user
content: Can you write a long poem and make a file called poem.txt?
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
with client.messages.stream(
max_tokens=65536,
model="claude-opus-4-7",
tools=[
{
"name": "make_file",
"description": "Write text to a file",
"eager_input_streaming": True,
"input_schema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "The filename to write text to",
},
"lines_of_text": {
"type": "array",
"description": "An array of lines of text to write to the file",
},
},
"required": ["filename", "lines_of_text"],
},
}
],
messages=[
{
"role": "user",
"content": "Can you write a long poem and make a file called poem.txt?",
}
],
) as stream:
for event in stream:
pass
final_message = stream.get_final_message()
print(final_message.usage)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const stream = anthropic.messages.stream({
model: "claude-opus-4-7",
max_tokens: 65536,
tools: [
{
name: "make_file",
description: "Write text to a file",
eager_input_streaming: true,
input_schema: {
type: "object",
properties: {
filename: {
type: "string",
description: "The filename to write text to"
},
lines_of_text: {
type: "array",
description: "An array of lines of text to write to the file"
}
},
required: ["filename", "lines_of_text"]
}
}
],
messages: [
{
role: "user",
content: "Can you write a long poem and make a file called poem.txt?"
}
]
});
const message = await stream.finalMessage();
console.log(message.usage);
```
In this example, fine-grained tool streaming enables Claude to stream the lines of a long poem into the tool call `make_file` without buffering to validate if the `lines_of_text` parameter is valid JSON. This means you can see the parameter stream as it arrives, without having to wait for the entire parameter to buffer and validate.
With fine-grained tool streaming, tool use chunks start streaming faster, and are often longer and contain fewer word breaks. This is because of differences in chunking behavior.
Example:
Without fine-grained streaming (15s delay):
```text
Chunk 1: '{"'
Chunk 2: 'query": "Ty'
Chunk 3: 'peScri'
Chunk 4: 'pt 5.0 5.1 '
Chunk 5: '5.2 5'
Chunk 6: '.3'
Chunk 7: ' new f'
Chunk 8: 'eatur'
...
```
With fine-grained streaming (3s delay):
```text
Chunk 1: '{"query": "TypeScript 5.0 5.1 5.2 5.3'
Chunk 2: ' new features comparison'
```
Because fine-grained streaming sends parameters without buffering or JSON validation, there is no guarantee that the resulting stream will complete in a valid JSON string.
Particularly, if the [stop reason](/docs/en/build-with-claude/handling-stop-reasons) `max_tokens` is reached, the stream may end midway through a parameter and may be incomplete. You generally have to write specific support to handle when `max_tokens` is reached.
## Accumulating tool input deltas
When a `tool_use` content block streams, the initial `content_block_start` event contains `input: {}` (an empty object). This is a placeholder. The actual input arrives as a series of `input_json_delta` events, each carrying a `partial_json` string fragment. Your code must concatenate these fragments and parse the result once the block closes.
The accumulation contract:
1. On `content_block_start` with `type: "tool_use"`, initialize an empty string: `input_json = ""`
2. For each `content_block_delta` with `type: "input_json_delta"`, append: `input_json += event.delta.partial_json`
3. On `content_block_stop`, parse the accumulated string: `json.loads(input_json)`
The type mismatch between the initial `input: {}` (object) and `partial_json` (string) is by design. The empty object marks the slot in the content array; the delta strings build the real value.
```python Python
import json
import anthropic
client = anthropic.Anthropic()
tool_inputs = {} # index -> accumulated JSON string
with client.messages.stream(
model="claude-opus-4-7",
max_tokens=1024,
tools=[
{
"name": "get_weather",
"description": "Get current weather for a city",
"eager_input_streaming": True,
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}
],
messages=[{"role": "user", "content": "Weather in Paris?"}],
) as stream:
for event in stream:
if (
event.type == "content_block_start"
and event.content_block.type == "tool_use"
):
tool_inputs[event.index] = ""
elif (
event.type == "content_block_delta"
and event.delta.type == "input_json_delta"
):
tool_inputs[event.index] += event.delta.partial_json
elif event.type == "content_block_stop" and event.index in tool_inputs:
parsed = json.loads(tool_inputs[event.index])
print(f"Tool input: {parsed}")
```
```typescript TypeScript hidelines={1..4}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const toolInputs: Record = {};
const stream = anthropic.messages.stream({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [
{
name: "get_weather",
description: "Get current weather for a city",
eager_input_streaming: true,
input_schema: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"]
}
}
],
messages: [{ role: "user", content: "Weather in Paris?" }]
});
for await (const event of stream) {
if (event.type === "content_block_start" && event.content_block.type === "tool_use") {
toolInputs[event.index] = "";
} else if (event.type === "content_block_delta" && event.delta.type === "input_json_delta") {
toolInputs[event.index] += event.delta.partial_json;
} else if (event.type === "content_block_stop" && event.index in toolInputs) {
const parsed = JSON.parse(toolInputs[event.index]);
console.log("Tool input:", parsed);
}
}
```
The Python and TypeScript SDKs provide higher-level stream helpers (`stream.get_final_message()`, `stream.finalMessage()`) that perform this accumulation for you. Use the preceding manual pattern only when you need to react to partial input before the block closes, such as rendering a progress indicator or starting a downstream request early.
## Handling invalid JSON in tool responses
When using fine-grained tool streaming, you may receive invalid or incomplete JSON from the model. If you need to pass this invalid JSON back to the model in an error response block, you may wrap it in a JSON object to ensure proper handling (with a reasonable key). For example:
```json
{
"INVALID_JSON": ""
}
```
This approach helps the model understand that the content is invalid JSON while preserving the original malformed data for debugging purposes.
When wrapping invalid JSON, make sure to properly escape any quotes or special characters in the invalid JSON string to maintain valid JSON structure in the wrapper object.
## Next steps
Full reference for server-sent events and stream event types.
Execute tools and return results in the required message format.
Full directory of Anthropic-schema tools and their version strings.
---
# Manage tool context
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/manage-tool-context
# Manage tool context
Choose between tool search, programmatic tool calling, prompt caching, and context editing to manage context bloat.
---
Tool definitions and accumulated `tool_result` blocks consume your context window. Long-running agents with many tools or many turns can exhaust available context before the task is finished. Four approaches address this at different points in the pipeline.
## The four approaches
Each approach targets a different source of context pressure. Pick the one that matches where your tokens are going.
| Approach | What it reduces | When it fits | Learn more |
| --- | --- | --- | --- |
| Tool search | Tool definitions loaded upfront | Large toolsets (20+ tools) where most tools aren't needed every turn | [Tool search tool](/docs/en/agents-and-tools/tool-use/tool-search-tool) |
| Programmatic tool calling | `tool_result` roundtrips | Chains of tool calls that can execute as a single script | [Programmatic tool calling](/docs/en/agents-and-tools/tool-use/programmatic-tool-calling) |
| Prompt caching | Token cost of repeated tool definitions | Stable toolsets across many requests | [Tool use with prompt caching](/docs/en/agents-and-tools/tool-use/tool-use-with-prompt-caching) |
| Context editing | Old `tool_result` blocks in history | Long conversations where early results are no longer relevant | [Context editing](/docs/en/build-with-claude/context-editing) |
### Tool search
Tool search keeps tool definitions out of the context window until Claude asks for them. Instead of sending 50 tool schemas upfront, you send a single `tool_search` tool and let Claude discover the rest on demand. This trades a small amount of latency (one extra turn to look up a tool) for a large reduction in baseline context usage.
### Programmatic tool calling
Programmatic tool calling collapses a sequence of tool calls into a single code block that Claude writes and Anthropic's code execution sandbox runs. Rather than five roundtrips of `tool_use` and `tool_result`, Claude emits one script that calls all five functions from within the sandbox. The intermediate results never enter the conversation history.
### Prompt caching
Prompt caching doesn't reduce the number of tokens in context, but it reduces what you pay for them on subsequent requests. If your tool definitions are stable, cache them once and reuse the cached prefix across thousands of requests. This is the right choice when the toolset is large but fixed.
### Context editing
Context editing removes old `tool_result` blocks from the conversation history once they've served their purpose. A long agent loop might produce hundreds of intermediate results that were useful at the time but are now dead weight. Context editing lets you trim them without restarting the conversation.
## Combining approaches
These approaches compose. A long-running agent might use tool search to keep the toolset lean, prompt caching to amortize the cost of the remaining definitions, and context editing to trim stale results as the conversation grows. Each solves a different part of the problem, so there's no conflict in using them together.
A reasonable starting point for a high-volume agent:
1. Enable prompt caching on your tool definitions from day one. Cache writes carry a 25% markup over base input pricing, which pays back on the second request that hits the cache.
2. Add tool search once your toolset grows past roughly 20 tools or your baseline context usage becomes noticeable.
3. Add context editing once individual conversations start running long enough that early results become irrelevant.
4. Consider programmatic tool calling if you notice repetitive chains of small tool calls that could run as a single batch.
## Next steps
Load tool definitions on demand instead of upfront.
Collapse tool-call chains into a single executable script.
Cache tool definitions across requests to cut token costs.
Trim stale tool results from long-running conversations.
---
# Programmatic tool calling
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/programmatic-tool-calling
# Programmatic tool calling
---
Programmatic tool calling allows Claude to write code that calls your tools programmatically within a [code execution](/docs/en/agents-and-tools/tool-use/code-execution-tool) container, rather than requiring round trips through the model for each tool invocation. This reduces latency for multi-tool workflows and decreases token consumption by allowing Claude to filter or process data before it reaches the model's context window. On agentic search benchmarks like [BrowseComp](https://arxiv.org/abs/2504.12516) and [DeepSearchQA](https://github.com/google-deepmind/deepsearchqa), which test multi-step web research and complex information retrieval, adding programmatic tool calling on top of basic search tools was the key factor that fully unlocked agent performance.
The difference compounds fast in real workflows. Consider checking budget compliance across 20 employees: the traditional approach requires 20 separate model round-trips, pulling thousands of expense line items into the context along the way. With programmatic tool calling, a single script runs all 20 lookups, filters the results, and returns only the employees who exceeded their limits, shrinking what Claude needs to reason over from hundreds of kilobytes down to a handful of lines.
For a deeper look at the inference and context costs that programmatic tool calling addresses, see [Advanced tool use](https://www.anthropic.com/engineering/advanced-tool-use).
This feature requires the code execution tool to be enabled.
This feature is **not** eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). Data is retained according to the feature's standard retention policy.
## Model compatibility
Programmatic tool calling requires `code_execution_20260120`, which is supported on the following models:
| Model |
|-------|
| Claude Opus 4.7 (`claude-opus-4-7`) |
| Claude Opus 4.6 (`claude-opus-4-6`) |
| Claude Sonnet 4.6 (`claude-sonnet-4-6`) |
| Claude Opus 4.5 (`claude-opus-4-5-20251101`) |
| Claude Sonnet 4.5 (`claude-sonnet-4-5-20250929`) |
For the full code execution tool version matrix, see the [code execution tool model compatibility table](/docs/en/agents-and-tools/tool-use/code-execution-tool#model-compatibility). Programmatic tool calling is available on the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry). It is not currently available on Amazon Bedrock or Vertex AI.
## Quick start
Here's a simple example where Claude programmatically queries a database multiple times and aggregates results:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": "Query sales data for the West, East, and Central regions, then tell me which region had the highest revenue"
}
],
"tools": [
{
"type": "code_execution_20260120",
"name": "code_execution"
},
{
"name": "query_database",
"description": "Execute a SQL query against the sales database. Returns a list of rows as JSON objects.",
"input_schema": {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "SQL query to execute"
}
},
"required": ["sql"]
},
"allowed_callers": ["code_execution_20260120"]
}
]
}'
```
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: >-
Query sales data for the West, East, and Central regions, then
tell me which region had the highest revenue
tools:
- type: code_execution_20260120
name: code_execution
- name: query_database
description: >-
Execute a SQL query against the sales database. Returns a list
of rows as JSON objects.
input_schema:
type: object
properties:
sql:
type: string
description: SQL query to execute
required:
- sql
allowed_callers:
- code_execution_20260120
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
messages=[
{
"role": "user",
"content": "Query sales data for the West, East, and Central regions, then tell me which region had the highest revenue",
}
],
tools=[
{"type": "code_execution_20260120", "name": "code_execution"},
{
"name": "query_database",
"description": "Execute a SQL query against the sales database. Returns a list of rows as JSON objects.",
"input_schema": {
"type": "object",
"properties": {
"sql": {"type": "string", "description": "SQL query to execute"}
},
"required": ["sql"],
},
"allowed_callers": ["code_execution_20260120"],
},
],
)
print(response)
```
```typescript TypeScript hidelines={1..5,-3..-1}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function main() {
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{
role: "user",
content:
"Query sales data for the West, East, and Central regions, then tell me which region had the highest revenue"
}
],
tools: [
{
type: "code_execution_20260120",
name: "code_execution"
},
{
name: "query_database",
description:
"Execute a SQL query against the sales database. Returns a list of rows as JSON objects.",
input_schema: {
type: "object" as const,
properties: {
sql: {
type: "string",
description: "SQL query to execute"
}
},
required: ["sql"]
},
allowed_callers: ["code_execution_20260120"]
}
]
});
console.log(response);
}
main().catch(console.error);
```
```csharp C# hidelines={1..13,-2..}
using Anthropic;
using Anthropic.Models.Messages;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Messages = [
new() {
Role = Role.User,
Content = "Query sales data for the West, East, and Central regions, then tell me which region had the highest revenue"
}
],
Tools = [
new CodeExecutionTool20260120(),
new ToolUnion(new Tool()
{
Name = "query_database",
Description = "Execute a SQL query against the sales database. Returns a list of rows as JSON objects.",
InputSchema = new InputSchema()
{
Properties = new Dictionary
{
["sql"] = JsonSerializer.SerializeToElement(new { type = "string", description = "SQL query to execute" }),
},
Required = ["sql"],
},
AllowedCallers = ["code_execution_20260120"]
}),
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go hidelines={1..13,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Query sales data for the West, East, and Central regions, then tell me which region had the highest revenue")),
},
Tools: []anthropic.ToolUnionParam{
{OfCodeExecutionTool20260120: &anthropic.CodeExecutionTool20260120Param{}},
{OfTool: &anthropic.ToolParam{
Name: "query_database",
Description: anthropic.String("Execute a SQL query against the sales database. Returns a list of rows as JSON objects."),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"sql": map[string]any{
"type": "string",
"description": "SQL query to execute",
},
},
Required: []string{"sql"},
},
AllowedCallers: []string{"code_execution_20260120"},
}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..7,9..13,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.Tool.InputSchema;
import com.anthropic.models.messages.CodeExecutionTool20260120;
import java.util.List;
import java.util.Map;
public class Main {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addUserMessage("Query sales data for the West, East, and Central regions, then tell me which region had the highest revenue")
.addTool(CodeExecutionTool20260120.builder().build())
.addTool(Tool.builder()
.name("query_database")
.description("Execute a SQL query against the sales database. Returns a list of rows as JSON objects.")
.inputSchema(InputSchema.builder()
.properties(JsonValue.from(Map.of(
"sql", Map.of(
"type", "string",
"description", "SQL query to execute"
)
)))
.putAdditionalProperty("required", JsonValue.from(List.of("sql")))
.build())
.allowedCallers(List.of(Tool.AllowedCaller.of("code_execution_20260120")))
.build())
.build();
Message response = client.messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Query sales data for the West, East, and Central regions, then tell me which region had the highest revenue'],
],
model: 'claude-opus-4-7',
tools: [
[
'type' => 'code_execution_20260120',
'name' => 'code_execution',
],
[
'name' => 'query_database',
'description' => 'Execute a SQL query against the sales database. Returns a list of rows as JSON objects.',
'input_schema' => [
'type' => 'object',
'properties' => [
'sql' => [
'type' => 'string',
'description' => 'SQL query to execute',
],
],
'required' => ['sql'],
],
'allowed_callers' => ['code_execution_20260120'],
],
],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{
role: "user",
content: "Query sales data for the West, East, and Central regions, then tell me which region had the highest revenue"
}
],
tools: [
{
type: "code_execution_20260120",
name: "code_execution"
},
{
name: "query_database",
description: "Execute a SQL query against the sales database. Returns a list of rows as JSON objects.",
input_schema: {
type: "object",
properties: {
sql: {
type: "string",
description: "SQL query to execute"
}
},
required: ["sql"]
},
allowed_callers: ["code_execution_20260120"]
}
]
)
puts message
```
## How programmatic tool calling works
When you configure a tool to be callable from code execution and Claude decides to use that tool:
1. Claude writes Python code that invokes the tool as a function, potentially including multiple tool calls and pre/post-processing logic
2. Claude runs this code in a sandboxed container through code execution
3. When a tool function is called, code execution pauses and the API returns a `tool_use` block
4. You provide the tool result, and code execution continues (intermediate results are not loaded into Claude's context window)
5. Once all code execution completes, Claude receives the final output and continues working on the task
This approach is particularly useful for:
- **Large data processing:** Filter or aggregate tool results before they reach Claude's context
- **Multi-step workflows:** Save tokens and latency by calling tools serially or in a loop without sampling Claude in-between tool calls
- **Conditional logic:** Make decisions based on intermediate tool results
Custom tools are converted to async Python functions to support parallel tool calling. When Claude writes code that calls your tools, it uses `await` (e.g., `result = await query_database("")`) and automatically includes the appropriate async wrapper function.
The async wrapper is omitted from code examples in this documentation for clarity.
## Core concepts
### The `allowed_callers` field
The `allowed_callers` field specifies which contexts can invoke a tool:
```json
{
"name": "query_database",
"description": "Execute a SQL query against the database",
"input_schema": {
// ...
},
"allowed_callers": ["code_execution_20260120"]
}
```
**Possible values:**
- `["direct"]` - Only Claude can call this tool directly (default if omitted)
- `["code_execution_20260120"]` - Only callable from within code execution
- `["direct", "code_execution_20260120"]` - Callable both directly and from code execution
Choose either `["direct"]` or `["code_execution_20260120"]` for each tool rather than enabling both, as this provides clearer guidance to Claude for how best to use the tool.
### The `caller` field in responses
Every tool use block includes a `caller` field indicating how it was invoked:
**Direct invocation (traditional tool use):**
```json
{
"type": "tool_use",
"id": "toolu_abc123",
"name": "query_database",
"input": { "sql": "" },
"caller": { "type": "direct" }
}
```
**Programmatic invocation:**
```json
{
"type": "tool_use",
"id": "toolu_xyz789",
"name": "query_database",
"input": { "sql": "" },
"caller": {
"type": "code_execution_20260120",
"tool_id": "srvtoolu_abc123"
}
}
```
The `tool_id` references the code execution tool that made the programmatic call.
### Container lifecycle
Programmatic tool calling uses the same containers as code execution:
- **Container creation:** A new container is created for each request unless you reuse an existing one
- **Expiration:** Containers have a 30-day maximum lifetime and are cleaned up after 4.5 minutes of idle time
- **Container ID:** Returned in responses in the `container` field
- **Reuse:** Pass the container ID to maintain state across requests
When a tool is called programmatically and the container is waiting for your tool result, you must respond before the container expires. Monitor the `expires_at` field. If the container expires, Claude may treat the tool call as timed out and retry it.
## Example workflow
Here's how a complete programmatic tool calling flow works:
### Step 1: Initial request
Send a request with code execution and a tool that allows programmatic calling. To enable programmatic calling, add the `allowed_callers` field to your tool definition.
Provide detailed descriptions of your tool's output format in the tool description. If you specify that the tool returns JSON, Claude attempts to deserialize and process the result in code. The more detail you provide about the output schema, the better Claude can handle the response programmatically.
The request shape is identical to the [Quick start](#quick-start) example: include `code_execution` in your tools list, add `allowed_callers: ["code_execution_20260120"]` to any tool you want Claude to invoke from code, and send your user message. The remaining steps in this workflow use the user message `"Query customer purchase history from the last quarter and identify our top 5 customers by revenue"`.
### Step 2: API response with tool call
Claude writes code that calls your tool. The API pauses and returns:
```json Output
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "I'll query the purchase history and analyze the results."
},
{
"type": "server_tool_use",
"id": "srvtoolu_abc123",
"name": "code_execution",
"input": {
"code": "results = await query_database('')\ntop_customers = sorted(results, key=lambda x: x['revenue'], reverse=True)[:5]\nprint(f'Top 5 customers: {top_customers}')"
}
},
{
"type": "tool_use",
"id": "toolu_def456",
"name": "query_database",
"input": { "sql": "" },
"caller": {
"type": "code_execution_20260120",
"tool_id": "srvtoolu_abc123"
}
}
],
"container": {
"id": "container_xyz789",
"expires_at": "2026-01-20T14:30:00Z"
},
"stop_reason": "tool_use"
}
```
### Step 3: Provide tool result
Include the full conversation history plus your tool result:
```bash CLI nocheck
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container: container_xyz789
messages:
- role: user
content: >-
Query customer purchase history from the last quarter and identify our
top 5 customers by revenue
- role: assistant
content:
- type: text
text: I'll query the purchase history and analyze the results.
- type: server_tool_use
id: srvtoolu_abc123
name: code_execution
input:
code: "..."
- type: tool_use
id: toolu_def456
name: query_database
input:
sql: ""
caller:
type: code_execution_20260120
tool_id: srvtoolu_abc123
- role: user
content:
- type: tool_result
tool_use_id: toolu_def456
content: >-
[{"customer_id": "C1", "revenue": 45000}, {"customer_id": "C2",
"revenue": 38000}, ...]
tools: [...]
YAML
```
```python Python nocheck
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
container="container_xyz789", # Reuse the container
messages=[
{
"role": "user",
"content": "Query customer purchase history from the last quarter and identify our top 5 customers by revenue",
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "I'll query the purchase history and analyze the results.",
},
{
"type": "server_tool_use",
"id": "srvtoolu_abc123",
"name": "code_execution",
"input": {"code": "..."},
},
{
"type": "tool_use",
"id": "toolu_def456",
"name": "query_database",
"input": {"sql": ""},
"caller": {
"type": "code_execution_20260120",
"tool_id": "srvtoolu_abc123",
},
},
],
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_def456",
"content": '[{"customer_id": "C1", "revenue": 45000}, {"customer_id": "C2", "revenue": 38000}, ...]',
}
],
},
],
tools=[...],
)
print(response)
```
```typescript TypeScript nocheck
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
container: "container_xyz789", // Reuse the container
messages: [
{
role: "user",
content:
"Query customer purchase history from the last quarter and identify our top 5 customers by revenue"
},
{
role: "assistant",
content: [
{ type: "text", text: "I'll query the purchase history and analyze the results." },
{
type: "server_tool_use",
id: "srvtoolu_abc123",
name: "code_execution",
input: { code: "..." }
},
{
type: "tool_use",
id: "toolu_def456",
name: "query_database",
input: { sql: "" },
caller: {
type: "code_execution_20260120",
tool_id: "srvtoolu_abc123"
}
}
]
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "toolu_def456",
content:
'[{"customer_id": "C1", "revenue": 45000}, {"customer_id": "C2", "revenue": 38000}, ...]'
}
]
}
],
tools: [
/* ... */
]
});
console.log(response);
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Container = "container_xyz789",
Messages =
[
new()
{
Role = Role.User,
Content = "Query customer purchase history from the last quarter and identify our top 5 customers by revenue"
},
new()
{
Role = Role.Assistant,
Content = new ContentBlock[]
{
new TextBlock { Text = "I'll query the purchase history and analyze the results." },
new ServerToolUseBlock
{
Id = "srvtoolu_abc123",
Name = "code_execution",
Input = new { code = "..." }
},
new ToolUseBlock
{
Id = "toolu_def456",
Name = "query_database",
Input = new { sql = "" },
Caller = new ToolCaller
{
Type = "code_execution_20260120",
ToolId = "srvtoolu_abc123"
}
}
}
},
new()
{
Role = Role.User,
Content = new ContentBlockParam[]
{
new ToolResultBlockParam
{
ToolUseID = "toolu_def456",
Content = "[{\"customer_id\": \"C1\", \"revenue\": 45000}, {\"customer_id\": \"C2\", \"revenue\": 38000}, ...]"
}
}
}
],
Tools = []
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go nocheck hidelines={1..13,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Container: anthropic.MessageNewParamsContainerUnion{
OfString: anthropic.String("container_xyz789"),
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Query customer purchase history from the last quarter and identify our top 5 customers by revenue")),
{
Role: anthropic.MessageParamRoleAssistant,
Content: []anthropic.ContentBlockParamUnion{
anthropic.NewTextBlock("I'll query the purchase history and analyze the results."),
{OfServerToolUse: &anthropic.ServerToolUseBlockParam{
ID: "srvtoolu_abc123",
Name: anthropic.ServerToolUseBlockParamNameCodeExecution,
Input: map[string]any{"code": "..."},
}},
{OfToolUse: &anthropic.ToolUseBlockParam{
ID: "toolu_def456",
Name: "query_database",
Input: map[string]any{"sql": ""},
Caller: anthropic.ServerToolUseBlockParamCallerUnion{
OfCodeExecution20260120: &anthropic.ServerToolCaller20260120Param{
ToolID: "srvtoolu_abc123",
},
},
}},
},
},
{
Role: anthropic.MessageParamRoleUser,
Content: []anthropic.ContentBlockParamUnion{
{OfToolResult: &anthropic.ToolResultBlockParam{
ToolUseID: "toolu_def456",
Content: []anthropic.ToolResultBlockParamContentUnion{
{OfText: &anthropic.TextBlockParam{
Text: `[{"customer_id": "C1", "revenue": 45000}, {"customer_id": "C2", "revenue": 38000}, ...]`,
}},
},
}},
},
},
},
Tools: []anthropic.ToolUnionParam{
{OfCodeExecutionTool20260120: &anthropic.CodeExecutionTool20260120Param{}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java nocheck hidelines={1..3,5..8,10..17,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.CodeExecutionTool20260120;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.ServerToolUseBlockParam;
import com.anthropic.models.messages.TextBlockParam;
import com.anthropic.models.messages.ToolResultBlockParam;
import com.anthropic.models.messages.ToolUseBlockParam;
import java.util.List;
import java.util.Map;
public class ContainerReuse {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(4096L)
.container("container_xyz789")
.addUserMessage("Query customer purchase history from the last quarter and identify our top 5 customers by revenue")
.addAssistantMessageOfBlockParams(List.of(
ContentBlockParam.ofText(
TextBlockParam.builder()
.text("I'll query the purchase history and analyze the results.")
.build()),
ContentBlockParam.ofServerToolUse(
ServerToolUseBlockParam.builder()
.id("srvtoolu_abc123")
.name("code_execution")
.input(JsonValue.from(Map.of("code", "...")))
.build()),
ContentBlockParam.ofToolUse(
ToolUseBlockParam.builder()
.id("toolu_def456")
.name("query_database")
.input(JsonValue.from(Map.of("sql", "")))
.codeExecution20260120Caller("srvtoolu_abc123")
.build())
))
.addUserMessageOfBlockParams(List.of(
ContentBlockParam.ofToolResult(
ToolResultBlockParam.builder()
.toolUseId("toolu_def456")
.content("[{\"customer_id\": \"C1\", \"revenue\": 45000}, {\"customer_id\": \"C2\", \"revenue\": 38000}, ...]")
.build())
))
.addTool(CodeExecutionTool20260120.builder().build())
.build();
Message response = client.messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..6} nocheck
messages->create(
maxTokens: 4096,
messages: [
[
'role' => 'user',
'content' => 'Query customer purchase history from the last quarter and identify our top 5 customers by revenue',
],
[
'role' => 'assistant',
'content' => [
[
'type' => 'text',
'text' => "I'll query the purchase history and analyze the results.",
],
[
'type' => 'server_tool_use',
'id' => 'srvtoolu_abc123',
'name' => 'code_execution',
'input' => ['code' => '...'],
],
[
'type' => 'tool_use',
'id' => 'toolu_def456',
'name' => 'query_database',
'input' => ['sql' => ''],
'caller' => [
'type' => 'code_execution_20260120',
'tool_id' => 'srvtoolu_abc123',
],
],
],
],
[
'role' => 'user',
'content' => [
[
'type' => 'tool_result',
'tool_use_id' => 'toolu_def456',
'content' => '[{"customer_id": "C1", "revenue": 45000}, {"customer_id": "C2", "revenue": 38000}, ...]',
],
],
],
],
model: 'claude-opus-4-7',
container: 'container_xyz789',
tools: [],
);
echo $message;
```
```ruby Ruby nocheck
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
container: "container_xyz789",
messages: [
{
role: "user",
content: "Query customer purchase history from the last quarter and identify our top 5 customers by revenue"
},
{
role: "assistant",
content: [
{
type: "text",
text: "I'll query the purchase history and analyze the results."
},
{
type: "server_tool_use",
id: "srvtoolu_abc123",
name: "code_execution",
input: { code: "..." }
},
{
type: "tool_use",
id: "toolu_def456",
name: "query_database",
input: { sql: "" },
caller: {
type: "code_execution_20260120",
tool_id: "srvtoolu_abc123"
}
}
]
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "toolu_def456",
content: '[{"customer_id": "C1", "revenue": 45000}, {"customer_id": "C2", "revenue": 38000}, ...]'
}
]
}
],
tools: [
{ type: "code_execution_20260120", name: "code_execution" }
]
)
puts message
```
### Step 4: Next tool call or completion
The code execution continues and processes the results. If additional tool calls are needed, repeat Step 3 until all tool calls are satisfied.
### Step 5: Final response
Once the code execution completes, Claude provides the final response:
```json Output
{
"content": [
{
"type": "code_execution_tool_result",
"tool_use_id": "srvtoolu_abc123",
"content": {
"type": "code_execution_result",
"stdout": "Top 5 customers: [{'customer_id': 'C1', 'revenue': 45000}, {'customer_id': 'C2', 'revenue': 38000}, {'customer_id': 'C5', 'revenue': 32000}, {'customer_id': 'C8', 'revenue': 28500}, {'customer_id': 'C3', 'revenue': 24000}]",
"stderr": "",
"return_code": 0,
"content": []
}
},
{
"type": "text",
"text": "I've analyzed the purchase history from last quarter. Your top 5 customers generated $167,500 in total revenue, with Customer C1 leading at $45,000."
}
],
"stop_reason": "end_turn"
}
```
## Advanced patterns
### Batch processing with loops
Claude can write code that processes multiple items efficiently:
```python hidelines={1..4,-1}
async def query_database(sql):
return [{"revenue": 100}]
async def _claude_code():
regions = ["West", "East", "Central", "North", "South"]
results = {}
for region in regions:
data = await query_database(f"")
results[region] = sum(row["revenue"] for row in data)
# Process results programmatically
top_region = max(results.items(), key=lambda x: x[1])
print(f"Top region: {top_region[0]} with ${top_region[1]:,} in revenue")
_ = _claude_code
```
This pattern:
- Reduces model round-trips from N (one per region) to 1
- Processes large result sets programmatically before returning to Claude
- Saves tokens by only returning aggregated conclusions instead of raw data
### Early termination
Claude can stop processing as soon as success criteria are met:
```python hidelines={1..4,-1}
async def check_health(ep):
return "healthy"
async def _claude_code():
endpoints = ["us-east", "eu-west", "apac"]
for endpoint in endpoints:
status = await check_health(endpoint)
if status == "healthy":
print(f"Found healthy endpoint: {endpoint}")
break # Stop early, don't check remaining
_ = _claude_code
```
### Conditional tool selection
```python hidelines={1..15,-1}
async def get_file_info(p):
return {"size": 500}
async def read_full_file(p):
return "content"
async def read_file_summary(p):
return "summary"
path = "/tmp/example.txt"
async def _claude_code():
file_info = await get_file_info(path)
if file_info["size"] < 10000:
content = await read_full_file(path)
else:
content = await read_file_summary(path)
print(content)
_ = _claude_code
```
### Data filtering
```python hidelines={1..7,-1}
async def fetch_logs(sid):
return ["INFO: ok", "ERROR: failed"]
server_id = "srv-01"
async def _claude_code():
logs = await fetch_logs(server_id)
errors = [log for log in logs if "ERROR" in log]
print(f"Found {len(errors)} errors")
for error in errors[-10:]: # Only return last 10 errors
print(error)
_ = _claude_code
```
## Response format
### Programmatic tool call
When code execution calls a tool:
```json
{
"type": "tool_use",
"id": "toolu_abc123",
"name": "query_database",
"input": { "sql": "" },
"caller": {
"type": "code_execution_20260120",
"tool_id": "srvtoolu_xyz789"
}
}
```
### Tool result handling
Your tool result is passed back to the running code:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_abc123",
"content": "[{\"customer_id\": \"C1\", \"revenue\": 45000, \"orders\": 23}, {\"customer_id\": \"C2\", \"revenue\": 38000, \"orders\": 18}, ...]"
}
]
}
```
### Code execution completion
When all tool calls are satisfied and code completes:
```json
{
"type": "code_execution_tool_result",
"tool_use_id": "srvtoolu_xyz789",
"content": {
"type": "code_execution_result",
"stdout": "Analysis complete. Top 5 customers identified from 847 total records.",
"stderr": "",
"return_code": 0,
"content": []
}
}
```
## Error handling
### Common errors
| Error | Description | Solution |
|-------|-------------|----------|
| `invalid_tool_input` | Tool input doesn't match schema | Validate your tool's input_schema |
| `tool_not_allowed` | Tool doesn't allow the requested caller type | Check `allowed_callers` includes the right contexts |
### Container expiration during tool call
If your tool takes too long to respond, the code execution receives a `TimeoutError`. Claude sees this in stderr and typically retries:
```json
{
"type": "code_execution_tool_result",
"tool_use_id": "srvtoolu_abc123",
"content": {
"type": "code_execution_result",
"stdout": "",
"stderr": "TimeoutError: Calling tool ['query_database'] timed out.",
"return_code": 0,
"content": []
}
}
```
To prevent timeouts:
- Monitor the `expires_at` field in responses
- Implement timeouts for your tool execution
- Consider breaking long operations into smaller chunks
### Tool execution errors
If your tool returns an error:
```json
{
"type": "tool_result",
"tool_use_id": "toolu_abc123",
"content": "Error: Query timeout - table lock exceeded 30 seconds"
}
```
Claude's code receives this error and can handle it appropriately.
## Constraints and limitations
### Feature incompatibilities
- **Structured outputs:** Tools with `strict: true` are not supported with programmatic calling
- **Tool choice:** You cannot force programmatic calling of a specific tool through `tool_choice`
- **Parallel tool use:** `disable_parallel_tool_use: true` is not supported with programmatic calling
### Tool restrictions
The following tools cannot be called programmatically:
- Tools provided by an [MCP connector](/docs/en/agents-and-tools/mcp-connector)
### Message formatting restrictions
When responding to programmatic tool calls, there are strict formatting requirements:
**Tool result only responses:** If there are pending programmatic tool calls waiting for results, your response message must contain **only** `tool_result` blocks. You cannot include any text content, even after the tool results.
Invalid - Cannot include text when responding to programmatic tool calls:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01",
"content": "[{\"customer_id\": \"C1\", \"revenue\": 45000}]"
},
{ "type": "text", "text": "What should I do next?" }
]
}
```
Valid - Only tool results when responding to programmatic tool calls:
```json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01",
"content": "[{\"customer_id\": \"C1\", \"revenue\": 45000}]"
}
]
}
```
This restriction only applies when responding to programmatic (code execution) tool calls. For regular client-side tool calls, you can include text content after tool results.
### Rate limits
Programmatic tool calls are subject to the same rate limits as regular tool calls. Each tool call from code execution counts as a separate invocation.
### Validate tool results before use
When implementing user-defined tools that will be called programmatically:
- **Tool results are returned as strings:** They can contain any content, including code snippets or executable commands that may be processed by the execution environment.
- **Validate external tool results:** If your tool returns data from external sources or accepts user input, be aware of code injection risks if the output will be interpreted or executed as code.
## Token efficiency
Programmatic tool calling can significantly reduce token consumption:
- **Tool results from programmatic calls are not added to Claude's context** - only the final code output is
- **Intermediate processing happens in code** - filtering, aggregation, etc. don't consume model tokens
- **Multiple tool calls in one code execution** - reduces overhead compared to separate model turns
For example, calling 10 tools directly uses ~10x the tokens of calling them programmatically and returning a summary.
## Usage and pricing
Programmatic tool calling uses the same pricing as code execution. See the [code execution pricing](/docs/en/agents-and-tools/tool-use/code-execution-tool#usage-and-pricing) for details.
Token counting for programmatic tool calls: Tool results from programmatic invocations do not count toward your input/output token usage. Only the final code execution result and Claude's response count.
## Best practices
### Tool design
- **Provide detailed output descriptions:** Since Claude deserializes tool results in code, clearly document the format (JSON structure, field types, etc.)
- **Return structured data:** JSON or other easily parseable formats work best for programmatic processing
- **Keep responses concise:** Return only necessary data to minimize processing overhead
### When to use programmatic calling
**Good use cases:**
- Processing large datasets where you only need aggregates or summaries
- Multi-step workflows with 3+ dependent tool calls
- Operations requiring filtering, sorting, or transformation of tool results
- Tasks where intermediate data shouldn't influence Claude's reasoning
- Parallel operations across many items (e.g., checking 50 endpoints)
**Less ideal use cases:**
- Single tool calls with simple responses
- Tools that need immediate user feedback
- Very fast operations where code execution overhead would outweigh the benefit
### Performance optimization
- **Reuse containers** when making multiple related requests to maintain state
- **Batch similar operations** in a single code execution when possible
## Troubleshooting
### Common issues
**"Tool not allowed" error**
- Verify your tool definition includes `"allowed_callers": ["code_execution_20260120"]`
**Container expiration**
- Ensure you respond to tool calls before the container idles out (4.5 minutes of inactivity; 30-day hard maximum)
- Monitor the `expires_at` field in responses
- Consider implementing faster tool execution
**Tool result not parsed correctly**
- Ensure your tool returns string data that Claude can deserialize
- Provide clear output format documentation in your tool description
### Debugging tips
1. **Log all tool calls and results** to track the flow
2. **Check the `caller` field** to confirm programmatic invocation
3. **Monitor container IDs** to ensure proper reuse
4. **Test tools independently** before enabling programmatic calling
## Why programmatic tool calling works
Claude's training includes extensive exposure to code, making it effective at reasoning through and chaining function calls. When tools are presented as callable functions within a code execution environment, Claude can leverage this strength to:
- **Reason naturally about tool composition:** Chain operations and handle dependencies as naturally as writing any Python code
- **Process large results efficiently:** Filter down large tool outputs, extract only relevant data, or write intermediate results to files before returning summaries to the context window
- **Reduce latency significantly:** Eliminate the overhead of re-sampling Claude between each tool call in multi-step workflows
This approach enables workflows that would be impractical with traditional tool use (such as processing files over 1M tokens) by allowing Claude to work with data programmatically rather than loading everything into the conversation context.
## Alternative implementations
Programmatic tool calling is a generalizable pattern that can also be implemented on your own infrastructure. Here's how the approaches compare:
### Client-side direct execution
Provide Claude with a code execution tool and describe what functions are available in that environment. When Claude invokes the tool with code, your application executes it locally where those functions are defined.
**Advantages:**
- Simple to implement with minimal re-architecting
- Full control over the environment and instructions
**Disadvantages:**
- Executes untrusted code outside of a sandbox
- Tool invocations can be vectors for code injection
**Use when:** Your application can safely execute arbitrary code, you want a simple solution, and Anthropic's managed offering doesn't fit your needs.
### Self-managed sandboxed execution
Same approach from Claude's perspective, but code runs in a sandboxed container with security restrictions (e.g., no network egress). If your tools require external resources, you'll need a protocol for executing tool calls outside the sandbox.
**Advantages:**
- Safe programmatic tool calling on your own infrastructure
- Full control over the execution environment
**Disadvantages:**
- Complex to build and maintain
- Requires managing both infrastructure and inter-process communication
**Use when:** Security is critical and Anthropic's managed solution doesn't fit your requirements.
### Anthropic-managed execution
Anthropic's programmatic tool calling is a managed version of sandboxed execution with an opinionated Python environment tuned for Claude. Anthropic handles container management, code execution, and secure tool invocation communication.
**Advantages:**
- Safe and secure by default
- Easy to enable with minimal configuration
- Environment and instructions optimized for Claude
Consider using Anthropic's managed solution if you're using the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), or [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry).
## Data retention
Programmatic tool calling is built on the code execution infrastructure and uses the same sandbox containers. Container data, including execution artifacts and outputs, is retained for up to 30 days.
For ZDR eligibility across all features, see [API and data retention](/docs/en/manage-claude/api-and-data-retention).
## Related features
Learn about the underlying code execution capability that powers programmatic tool calling.
Understand the fundamentals of tool use with Claude.
Step-by-step guide for defining tools.
---
# Tool combinations
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-combinations
# Tool combinations
Common Anthropic tool pairings for research agents, coding agents, and long-running agents.
---
Anthropic-provided tools are designed to work together. Common agent patterns pair tools that cover complementary stages of a workflow: one tool gathers or discovers, another processes or acts. The combinations below are starting points, not prescriptions. Mix them to fit your task.
Each snippet shows only the `tools` array. See [Handle tool calls](/docs/en/agents-and-tools/tool-use/handle-tool-calls) for the full request shape.
## Research agent: web_search + code_execution
Search finds sources; code execution analyzes and synthesizes. Claude searches for data, then writes Python to process, tabulate, or visualize it. This pairing is a good fit for questions that require both up-to-date information and nontrivial computation over that information, such as "compare this quarter's earnings across the top five cloud providers."
```json
{
"tools": [
{ "type": "web_search_20260209", "name": "web_search" },
{ "type": "code_execution_20250825", "name": "code_execution" }
]
}
```
The flow is typically search, then execute, then optionally search again if the first pass surfaced a gap. Code execution runs server-side, so there's no client-side sandbox to manage.
## Coding agent: text_editor + bash
The text editor reads and modifies files; bash runs tests and build commands. This is the canonical software-development loop: inspect the code, make an edit, run the tests, repeat. Both tools are client-executed, so your application controls which files and commands are accessible.
```json
{
"tools": [
{ "type": "text_editor_20250728", "name": "str_replace_based_edit_tool" },
{ "type": "bash_20250124", "name": "bash" }
]
}
```
Pair this with a constrained working directory and a command allowlist if the agent operates on untrusted code. See [Text editor tool](/docs/en/agents-and-tools/tool-use/text-editor-tool) and [Bash tool](/docs/en/agents-and-tools/tool-use/bash-tool) for the execution contracts.
## Cite-then-fetch: web_search + web_fetch
Search surfaces candidate URLs; fetch retrieves full page content for the relevant ones. This avoids fetching everything upfront. Claude runs a search, inspects the snippets, picks the two or three results that actually look relevant, and fetches only those.
```json
{
"tools": [
{ "type": "web_search_20260209", "name": "web_search" },
{ "type": "web_fetch_20260209", "name": "web_fetch" }
]
}
```
This pairing is useful when the answer lives in long-form content (documentation pages, articles, specifications) that a search snippet can't fully capture. Fetch pulls the complete page so Claude can cite specific passages.
## Long-running agent: memory + any toolset
Memory persists state across conversations; the other tools do the work. Add memory to any agent that needs to remember prior sessions, such as a support agent that recalls a customer's earlier issues or a project assistant that tracks decisions made last week.
```json
{
"tools": [{ "type": "memory_20250818", "name": "memory" }]
}
```
Add your other tools alongside `memory` in the same array.
Memory is orthogonal to the rest of your toolset. It doesn't change how other tools behave; it gives Claude a place to write down and later retrieve facts that would otherwise be lost when the context window resets. See [Memory tool](/docs/en/agents-and-tools/tool-use/memory-tool) for the storage model.
## All-in-one: computer_use
The computer use tool subsumes most others by operating a full desktop. Claude sees screenshots and issues mouse and keyboard actions, which means it can drive any application a human can. Use this when the task requires arbitrary GUI interaction that more specific tools can't reach: legacy software without an API, visual verification steps, or workflows that span multiple desktop apps.
```json
{
"tools": [
{
"type": "computer_20250124",
"name": "computer",
"display_width_px": 1280,
"display_height_px": 800
}
]
}
```
Computer use is the most general option and also the slowest, since every action requires a screenshot roundtrip. Prefer narrower tools when they cover your use case, and reach for computer use when nothing else fits. See [Computer use tool](/docs/en/agents-and-tools/tool-use/computer-use-tool) for the sandbox setup.
## Next steps
Full catalog of Anthropic-provided tools with type strings and parameters.
How tool use works and when to use Anthropic tools versus defining your own.
---
# Tool reference
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-reference
# Tool reference
Directory of Anthropic-provided tools and reference for optional tool definition properties.
---
This page is a reference for the tools Anthropic provides and the optional properties you can set on any tool definition. For a conceptual introduction to tool use, see [Tool use with Claude](/docs/en/agents-and-tools/tool-use/overview). For guidance on implementing tool use in your application, see [Define tools](/docs/en/agents-and-tools/tool-use/define-tools).
## Anthropic-provided tools
Anthropic provides two kinds of tools: **server tools** that execute on Anthropic's infrastructure, and **client tools** where Anthropic defines the schema but your application handles execution. Both kinds appear in your request's `tools` array alongside any user-defined tools.
| Tool | `type` | Execution | Status |
| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------- | ------------------------------------------------------------- |
| [Web search tool](/docs/en/agents-and-tools/tool-use/web-search-tool) | `web_search_20260209` `web_search_20250305` | Server | GA |
| [Web fetch tool](/docs/en/agents-and-tools/tool-use/web-fetch-tool) | `web_fetch_20260209` `web_fetch_20250910` | Server | GA |
| [Code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool) | `code_execution_20260120` `code_execution_20250825` | Server | GA |
| [Advisor tool](/docs/en/agents-and-tools/tool-use/advisor-tool) | `advisor_20260301` | Server | Beta: `advisor-tool-2026-03-01` |
| [Tool search tool](/docs/en/agents-and-tools/tool-use/tool-search-tool) | `tool_search_tool_regex_20251119` `tool_search_tool_bm25_20251119` | Server | GA |
| [MCP connector](/docs/en/agents-and-tools/mcp-connector) | `mcp_toolset` | Server | Beta: `mcp-client-2025-11-20` |
| [Memory tool](/docs/en/agents-and-tools/tool-use/memory-tool) | `memory_20250818` | Client | GA |
| [Bash tool](/docs/en/agents-and-tools/tool-use/bash-tool) | `bash_20250124` | Client | GA |
| [Text editor tool](/docs/en/agents-and-tools/tool-use/text-editor-tool) | `text_editor_20250728` `text_editor_20250124` | Client | GA |
| [Computer use tool](/docs/en/agents-and-tools/tool-use/computer-use-tool) | `computer_20251124` `computer_20250124` | Client | Beta: `computer-use-2025-11-24` `computer-use-2025-01-24` |
For model compatibility, see each tool's page. Supported models vary by tool and by tool version.
The tool search `type` values also accept undated aliases:
`tool_search_tool_regex` and `tool_search_tool_bm25`. These resolve to the
latest dated version.
### Tool versioning
Most Anthropic-provided tools carry a `_YYYYMMDD` suffix in the `type` string. A new version is released when the tool's behavior, schema, or model support changes. Older versions remain available so that existing integrations continue to work.
When a tool has multiple active versions, the relationship between them varies:
- **Capability-keyed:** `web_search_20260209` and `web_fetch_20260209` add dynamic content filtering over their predecessors. `code_execution_20260120` adds [programmatic tool calling](/docs/en/agents-and-tools/tool-use/programmatic-tool-calling) from within the sandbox. In each case, both the new and old versions are current; which one you use depends on whether you need the new capability.
- **Model-keyed:** `text_editor_20250728` is for Claude 4 models and `text_editor_20250124` is for earlier models. The version you use depends on the model you target.
- **Variant, not version:** `tool_search_tool_regex_20251119` and `tool_search_tool_bm25_20251119` are two search algorithms released together. Neither supersedes the other.
- **Legacy:** `code_execution_20250522` supports only Python. `code_execution_20250825` adds Bash and file operations.
The `mcp_toolset` type is not date-versioned; versioning is carried in the `anthropic-beta` header instead.
## Tool definition properties
Every tool in the `tools` array, including user-defined tools, accepts optional properties that control how the tool is loaded, who can call it, and how its inputs are validated. These properties compose: you can set `defer_loading` and `cache_control` and `strict` on the same tool.
| Property | Purpose | Available on | Detailed guide |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `cache_control` | Set a prompt-cache breakpoint at this tool definition | All tools | [Prompt caching](/docs/en/build-with-claude/prompt-caching) |
| `strict` | Guarantee schema validation on tool names and inputs | All tools except `mcp_toolset` | [Strict tool use](/docs/en/agents-and-tools/tool-use/strict-tool-use) |
| `defer_loading` | Exclude the tool from the initial system prompt; load it on demand when tool search returns a `tool_reference` for it | All tools (for `mcp_toolset`, see [tool configuration](/docs/en/agents-and-tools/mcp-connector#mcp-toolset-configuration)) | [Tool search tool](/docs/en/agents-and-tools/tool-use/tool-search-tool) |
| `allowed_callers` | Restrict which callers can call the tool | All tools except `mcp_toolset` | [Programmatic tool calling](/docs/en/agents-and-tools/tool-use/programmatic-tool-calling#the-allowed-callers-field) |
| `input_examples` | Provide example input objects to help Claude understand how to call the tool | User-defined and Anthropic-schema client tools. Not available on server tools. | [Define tools](/docs/en/agents-and-tools/tool-use/define-tools#providing-tool-use-examples) |
| `eager_input_streaming` | Enable fine-grained input streaming (`true`) or keep standard buffered streaming (`false`) for this tool | User-defined tools only | [Fine-grained tool streaming](/docs/en/agents-and-tools/tool-use/fine-grained-tool-streaming) |
### `allowed_callers` values
`allowed_callers` is an array that accepts any combination of:
| Value | Meaning |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `"direct"` | The model can call this tool directly in a `tool_use` block. This is the default if `allowed_callers` is omitted. |
| `"code_execution_20260120"` | Code running inside a `code_execution_20260120` sandbox can call this tool. |
Omitting `"direct"` from the array (for example, `"allowed_callers": ["code_execution_20260120"]`) means the tool is callable only from within code execution. The response's `tool_use` block includes a `caller` field that identifies which caller called the tool. See [Programmatic tool calling](/docs/en/agents-and-tools/tool-use/programmatic-tool-calling#the-allowed-callers-field) for the full treatment, including the `caller` response shape and error behavior.
### `defer_loading` and prompt caching
Tools with `defer_loading: true` are stripped from the rendered tools section before the cache key is computed. They don't appear in the system-prompt prefix at all. When tool search discovers a deferred tool and returns a `tool_reference` for it, the tool's full definition is expanded inline at that point in the conversation body, not in the prefix.
This means `defer_loading: true` preserves your prompt cache. You can add deferred tools to a request without invalidating an existing cache entry, and the cache remains valid across the turn where the tool is discovered and the turn where it's called.
For how to combine `defer_loading` with `cache_control` breakpoints, see the [Tool search tool prompt caching guidance](/docs/en/agents-and-tools/tool-use/tool-search-tool#prompt-caching).
---
# Tool search tool
URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool
# Tool search tool
---
The tool search tool enables Claude to work with hundreds or thousands of tools by dynamically discovering and loading them on-demand. Instead of loading all tool definitions into the context window upfront, Claude searches your tool catalog (including tool names, descriptions, argument names, and argument descriptions) and loads only the tools it needs.
This approach solves two problems that compound quickly as tool libraries scale:
- **Context bloat:** Tool definitions eat into your context budget fast. A typical multi-server setup (GitHub, Slack, Sentry, Grafana, Splunk) can consume ~55k tokens in definitions before Claude does any actual work. Tool search typically reduces this by over 85%, loading only the 3–5 tools Claude actually needs for a given request.
- **Tool selection accuracy:** Claude's ability to correctly pick the right tool degrades significantly once you exceed 30–50 available tools. By surfacing a focused set of relevant tools on demand, tool search keeps selection accuracy high even across thousands of tools.
For background on the scaling challenges that tool search solves, see [Advanced tool use](https://www.anthropic.com/engineering/advanced-tool-use). Tool search's on-demand loading is also an instance of the broader just-in-time retrieval principle described in [Effective context engineering](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents).
Although this is provided as a server-side tool, you can also implement your own client-side tool search functionality. See [Custom tool search implementation](#custom-tool-search-implementation) for details.
Share feedback on this feature through the [feedback form](https://forms.gle/MhcGFFwLxuwnWTkYA).
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
On Amazon Bedrock, server-side tool search is available only through the
[InvokeModel
API](https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-runtime_example_bedrock-runtime_InvokeModel_AnthropicClaude_section.html),
not the Converse API.
On [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), server-side tool search works identically to the Claude API. Claude Platform on AWS uses the Anthropic Messages API directly, so there is no InvokeModel or Converse distinction.
## How tool search works
There are two tool search variants:
- **Regex** (`tool_search_tool_regex_20251119`): Claude constructs regex patterns to search for tools
- **BM25** (`tool_search_tool_bm25_20251119`): Claude uses natural language queries to search for tools
When you enable the tool search tool:
1. You include a tool search tool (for example, `tool_search_tool_regex_20251119` or `tool_search_tool_bm25_20251119`) in your tools list.
2. You provide all tool definitions with `defer_loading: true` for tools that shouldn't be loaded immediately.
3. Claude sees only the tool search tool and any non-deferred tools initially.
4. When Claude needs additional tools, it searches using a tool search tool.
5. The API returns 3-5 most relevant `tool_reference` blocks.
6. These references are automatically expanded into full tool definitions.
7. Claude selects from the discovered tools and calls them.
This keeps your context window efficient while maintaining high tool selection accuracy.
## Quick start
Here's a simple example with deferred tools:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 2048,
"messages": [
{
"role": "user",
"content": "What is the weather in San Francisco?"
}
],
"tools": [
{
"type": "tool_search_tool_regex_20251119",
"name": "tool_search_tool_regex"
},
{
"name": "get_weather",
"description": "Get the weather at a specific location",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string"},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"]
},
"defer_loading": true
},
{
"name": "search_files",
"description": "Search through files in the workspace",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"file_types": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["query"]
},
"defer_loading": true
}
]
}'
```
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 2048
messages:
- role: user
content: What is the weather in San Francisco?
tools:
- type: tool_search_tool_regex_20251119
name: tool_search_tool_regex
- name: get_weather
description: Get the weather at a specific location
input_schema:
type: object
properties:
location:
type: string
unit:
type: string
enum: [celsius, fahrenheit]
required: [location]
defer_loading: true
- name: search_files
description: Search through files in the workspace
input_schema:
type: object
properties:
query:
type: string
file_types:
type: array
items:
type: string
required: [query]
defer_loading: true
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=2048,
messages=[{"role": "user", "content": "What is the weather in San Francisco?"}],
tools=[
{"type": "tool_search_tool_regex_20251119", "name": "tool_search_tool_regex"},
{
"name": "get_weather",
"description": "Get the weather at a specific location",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["location"],
},
"defer_loading": True,
},
{
"name": "search_files",
"description": "Search through files in the workspace",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"file_types": {"type": "array", "items": {"type": "string"}},
},
"required": ["query"],
},
"defer_loading": True,
},
],
)
print(response)
```
```typescript TypeScript hidelines={1..4}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 2048,
messages: [
{
role: "user",
content: "What is the weather in San Francisco?"
}
],
tools: [
{
type: "tool_search_tool_regex_20251119",
name: "tool_search_tool_regex"
},
{
name: "get_weather",
description: "Get the weather at a specific location",
input_schema: {
type: "object" as const,
properties: {
location: { type: "string" },
unit: {
type: "string",
enum: ["celsius", "fahrenheit"]
}
},
required: ["location"]
},
defer_loading: true
},
{
name: "search_files",
description: "Search through files in the workspace",
input_schema: {
type: "object" as const,
properties: {
query: { type: "string" },
file_types: {
type: "array",
items: { type: "string" }
}
},
required: ["query"]
},
defer_loading: true
}
]
});
console.log(response);
```
```csharp C# hidelines={1..5}
using System;
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 2048,
Messages = [
new() {
Role = Role.User,
Content = "What is the weather in San Francisco?"
}
],
Tools = [
new ToolUnion(new ToolSearchToolRegex20251119
{
Type = ToolSearchToolRegex20251119Type.ToolSearchToolRegex20251119
}),
new ToolUnion(new Tool()
{
Name = "get_weather",
Description = "Get the weather at a specific location",
InputSchema = new InputSchema()
{
Properties = new Dictionary
{
["location"] = JsonSerializer.SerializeToElement(new { type = "string" }),
["unit"] = JsonSerializer.SerializeToElement(new { type = "string", @enum = new[] { "celsius", "fahrenheit" } }),
},
Required = ["location"],
},
DeferLoading = true,
}),
new ToolUnion(new Tool()
{
Name = "search_files",
Description = "Search through files in the workspace",
InputSchema = new InputSchema()
{
Properties = new Dictionary
{
["query"] = JsonSerializer.SerializeToElement(new { type = "string" }),
["file_types"] = JsonSerializer.SerializeToElement(new { type = "array", items = new { type = "string" } }),
},
Required = ["query"],
},
DeferLoading = true,
}),
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 2048,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the weather in San Francisco?")),
},
Tools: []anthropic.ToolUnionParam{
{OfToolSearchToolRegex20251119: &anthropic.ToolSearchToolRegex20251119Param{
Type: anthropic.ToolSearchToolRegex20251119TypeToolSearchToolRegex20251119,
}},
{OfTool: &anthropic.ToolParam{
Name: "get_weather",
Description: anthropic.String("Get the weather at a specific location"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"location": map[string]any{"type": "string"},
"unit": map[string]any{
"type": "string",
"enum": []string{"celsius", "fahrenheit"},
},
},
Required: []string{"location"},
},
DeferLoading: anthropic.Bool(true),
}},
{OfTool: &anthropic.ToolParam{
Name: "search_files",
Description: anthropic.String("Search through files in the workspace"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"query": map[string]any{"type": "string"},
"file_types": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
},
Required: []string{"query"},
},
DeferLoading: anthropic.Bool(true),
}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..8}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.Tool.InputSchema;
import com.anthropic.models.messages.ToolSearchToolRegex20251119;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
InputSchema weatherSchema = InputSchema.builder()
.properties(JsonValue.from(Map.of(
"location", Map.of("type", "string"),
"unit", Map.of(
"type", "string",
"enum", List.of("celsius", "fahrenheit")
)
)))
.putAdditionalProperty("required", JsonValue.from(List.of("location")))
.build();
InputSchema searchSchema = InputSchema.builder()
.properties(JsonValue.from(Map.of(
"query", Map.of("type", "string"),
"file_types", Map.of(
"type", "array",
"items", Map.of("type", "string")
)
)))
.putAdditionalProperty("required", JsonValue.from(List.of("query")))
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(2048L)
.addUserMessage("What is the weather in San Francisco?")
.addTool(ToolSearchToolRegex20251119.builder()
.type(ToolSearchToolRegex20251119.Type.TOOL_SEARCH_TOOL_REGEX_20251119)
.build())
.addTool(Tool.builder()
.name("get_weather")
.description("Get the weather at a specific location")
.inputSchema(weatherSchema)
.deferLoading(true)
.build())
.addTool(Tool.builder()
.name("search_files")
.description("Search through files in the workspace")
.inputSchema(searchSchema)
.deferLoading(true)
.build())
.build();
Message response = client.messages().create(params);
IO.println(response);
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 2048,
messages: [
['role' => 'user', 'content' => 'What is the weather in San Francisco?'],
],
model: 'claude-opus-4-7',
tools: [
[
'type' => 'tool_search_tool_regex_20251119',
'name' => 'tool_search_tool_regex',
],
[
'name' => 'get_weather',
'description' => 'Get the weather at a specific location',
'input_schema' => [
'type' => 'object',
'properties' => [
'location' => ['type' => 'string'],
'unit' => [
'type' => 'string',
'enum' => ['celsius', 'fahrenheit'],
],
],
'required' => ['location'],
],
'defer_loading' => true,
],
[
'name' => 'search_files',
'description' => 'Search through files in the workspace',
'input_schema' => [
'type' => 'object',
'properties' => [
'query' => ['type' => 'string'],
'file_types' => [
'type' => 'array',
'items' => ['type' => 'string'],
],
],
'required' => ['query'],
],
'defer_loading' => true,
],
],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 2048,
messages: [
{ role: "user", content: "What is the weather in San Francisco?" }
],
tools: [
{
type: "tool_search_tool_regex_20251119",
name: "tool_search_tool_regex"
},
{
name: "get_weather",
description: "Get the weather at a specific location",
input_schema: {
type: "object",
properties: {
location: { type: "string" },
unit: {
type: "string",
enum: ["celsius", "fahrenheit"]
}
},
required: ["location"]
},
defer_loading: true
},
{
name: "search_files",
description: "Search through files in the workspace",
input_schema: {
type: "object",
properties: {
query: { type: "string" },
file_types: {
type: "array",
items: { type: "string" }
}
},
required: ["query"]
},
defer_loading: true
}
]
)
puts message
```
## Tool definition
The tool search tool has two variants:
```json JSON
{
"type": "tool_search_tool_regex_20251119",
"name": "tool_search_tool_regex"
}
```
```json JSON
{
"type": "tool_search_tool_bm25_20251119",
"name": "tool_search_tool_bm25"
}
```
**Regex variant query format: Python regex, NOT natural language**
When using `tool_search_tool_regex_20251119`, Claude constructs regex patterns using Python's `re.search()` syntax, not natural language queries. Common patterns:
- `"weather"` - matches tool names/descriptions containing "weather"
- `"get_.*_data"` - matches tools like `get_user_data`, `get_weather_data`
- `"database.*query|query.*database"` - OR patterns for flexibility
- `"(?i)slack"` - case-insensitive search
Maximum query length: 200 characters
**BM25 variant query format: Natural language**
When using `tool_search_tool_bm25_20251119`, Claude uses natural language queries to search for tools.
### Deferred tool loading
Mark tools for on-demand loading by adding `defer_loading: true`:
```json JSON
{
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": { "type": "string" },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
},
"required": ["location"]
},
"defer_loading": true
}
```
**Key points:**
- Tools without `defer_loading` are loaded into context immediately
- Tools with `defer_loading: true` are only loaded when Claude discovers them through search
- The tool search tool itself should **never** have `defer_loading: true`
- Keep your 3-5 most frequently used tools as non-deferred for optimal performance
Both tool search variants (`regex` and `bm25`) search tool names, descriptions, argument names, and argument descriptions.
**How deferral works internally:** Deferred tools are not included in the system-prompt prefix. When the model discovers a deferred tool through tool search, the API appends a `tool_reference` block inline in the conversation, then expands it into the full tool definition before passing it to Claude. The prefix is untouched, so prompt caching is preserved. The grammar for [strict mode](/docs/en/agents-and-tools/tool-use/strict-tool-use) (the rules that constrain tool-call output to match your schemas) builds from the full toolset, so `defer_loading` and strict mode compose without grammar recompilation.
## Response format
When Claude uses the tool search tool, the response includes new block types:
```json JSON
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "I'll search for tools to help with the weather information."
},
{
"type": "server_tool_use",
"id": "srvtoolu_01ABC123",
"name": "tool_search_tool_regex",
"input": {
"query": "weather"
}
},
{
"type": "tool_search_tool_result",
"tool_use_id": "srvtoolu_01ABC123",
"content": {
"type": "tool_search_tool_search_result",
"tool_references": [{ "type": "tool_reference", "tool_name": "get_weather" }]
}
},
{
"type": "text",
"text": "I found a weather tool. Let me get the weather for San Francisco."
},
{
"type": "tool_use",
"id": "toolu_01XYZ789",
"name": "get_weather",
"input": { "location": "San Francisco", "unit": "fahrenheit" }
}
],
"stop_reason": "tool_use"
}
```
### Understanding the response
- **`server_tool_use`:** Indicates Claude is calling the tool search tool
- **`tool_search_tool_result`:** Contains the search results with a nested `tool_search_tool_search_result` object
- **`tool_references`:** Array of `tool_reference` objects pointing to discovered tools
- **`tool_use`:** Claude calling the discovered tool
The `tool_reference` blocks are automatically expanded into full tool definitions before being shown to Claude. You don't need to handle this expansion yourself. It happens automatically in the API as long as you provide all matching tool definitions in the `tools` parameter.
## MCP integration
For configuring `mcp_toolset` with `defer_loading`, see [MCP connector](/docs/en/agents-and-tools/mcp-connector).
## Custom tool search implementation
You can implement your own tool search logic (for example, using embeddings or semantic search) by returning `tool_reference` blocks from a custom tool. When Claude calls your custom search tool, return a standard `tool_result` with `tool_reference` blocks in the content array:
```json JSON
{
"type": "tool_result",
"tool_use_id": "toolu_your_tool_id",
"content": [{ "type": "tool_reference", "tool_name": "discovered_tool_name" }]
}
```
Every tool referenced must have a corresponding tool definition in the top-level `tools` parameter with `defer_loading: true`. This approach lets you use more sophisticated search algorithms while maintaining compatibility with the tool search system.
The `tool_search_tool_result` format shown in the [Response format](#response-format) section is the server-side format used internally by Anthropic's built-in tool search. For custom client-side implementations, always use the standard `tool_result` format with `tool_reference` content blocks as shown in the preceding example.
For a complete example using embeddings, see the [tool search with embeddings cookbook](https://platform.claude.com/cookbooks/tool_use).
## Error handling
The tool search tool is not compatible with [tool use
examples](/docs/en/agents-and-tools/tool-use/define-tools#providing-tool-use-examples).
If you need to provide examples of tool usage, use standard tool calling
without tool search.
### HTTP errors (400 status)
These errors prevent the request from being processed:
**All tools deferred:**
```json
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "All tools have defer_loading set. At least one tool must be non-deferred."
}
}
```
**Missing tool definition:**
```json
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "Tool reference 'unknown_tool' has no corresponding tool definition"
}
}
```
### Tool result errors (200 status)
Errors during tool execution return a 200 response with error information in the body:
```json JSON
{
"type": "tool_search_tool_result",
"tool_use_id": "srvtoolu_01ABC123",
"content": {
"type": "tool_search_tool_result_error",
"error_code": "invalid_pattern"
}
}
```
**Error codes:**
- `too_many_requests`: Rate limit exceeded for tool search operations
- `invalid_pattern`: Malformed regex pattern
- `pattern_too_long`: Pattern exceeds 200 character limit
- `unavailable`: Tool search service temporarily unavailable
### Common mistakes
**Cause:** You set `defer_loading: true` on ALL tools including the search tool
**Fix:** Remove `defer_loading` from the tool search tool:
```json
{
"type": "tool_search_tool_regex_20251119",
"name": "tool_search_tool_regex"
}
```
**Cause:** A `tool_reference` points to a tool not in your `tools` array
**Fix:** Ensure every tool that could be discovered has a complete definition:
```json
{
"name": "my_tool",
"description": "Full description here",
"input_schema": {
"type": "object"
},
"defer_loading": true
}
```
**Cause:** Tool name, description, argument names, or argument descriptions don't match the regex pattern
**Debugging steps:**
1. Check tool name, description, argument names, and argument descriptions. Claude searches all of these fields.
2. Test your pattern: `import re; re.search(r"your_pattern", "tool_name")`.
3. Remember searches are case-sensitive by default (use `(?i)` for case-insensitive).
4. Claude uses broad patterns such as `".*weather.*"` not exact matches.
**Tip:** Add common keywords to tool descriptions to improve discoverability
## Prompt caching
For how `defer_loading` preserves prompt caching, see [Tool use with prompt caching](/docs/en/agents-and-tools/tool-use/tool-use-with-prompt-caching).
The system automatically expands `tool_reference` blocks throughout the entire conversation history, so Claude can reuse discovered tools in subsequent turns without re-searching.
## Streaming
With streaming enabled, you'll receive tool search events as part of the stream:
```sse
event: content_block_start
data: {"type": "content_block_start", "index": 1, "content_block": {"type": "server_tool_use", "id": "srvtoolu_xyz789", "name": "tool_search_tool_regex"}}
// Search query streamed
event: content_block_delta
data: {"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "{\"query\":\"weather\"}"}}
// Pause while search executes
// Search results streamed
event: content_block_start
data: {"type": "content_block_start", "index": 2, "content_block": {"type": "tool_search_tool_result", "tool_use_id": "srvtoolu_xyz789", "content": {"type": "tool_search_tool_search_result", "tool_references": [{"type": "tool_reference", "tool_name": "get_weather"}]}}}
// Claude continues with discovered tools
```
## Batch requests
You can include the tool search tool in the [Messages Batches API](/docs/en/build-with-claude/batch-processing). Tool search operations through the Messages Batches API are priced the same as those in regular Messages API requests.
## Limits and best practices
### Limits
- **Maximum tools:** 10,000 tools in your catalog
- **Search results:** Returns 3-5 most relevant tools per search
- **Pattern length:** Maximum 200 characters for regex patterns
- **Model support:** [Claude Mythos Preview](https://anthropic.com/glasswing), Sonnet 4.0+, Opus 4.0+, Haiku 4.5+
### When to use tool search
**Good use cases:**
- 10+ tools available in your system
- Tool definitions consuming >10k tokens
- Experiencing tool selection accuracy issues with large tool sets
- Building MCP-powered systems with multiple servers (200+ tools)
- Tool library growing over time
**When traditional tool calling might be better:**
- Less than 10 tools total
- All tools are frequently used in every request
- Very small tool definitions (\<100 tokens total)
### Optimization tips
- Keep 3-5 most frequently used tools as non-deferred
- Write clear, descriptive tool names and descriptions
- Use consistent namespacing in tool names: prefix by service or resource (for example, `github_`, `slack_`) so that search queries naturally surface the right tool group
- Use semantic keywords in descriptions that match how users describe tasks
- Add a system prompt section describing available tool categories: "You can search for tools to interact with Slack, GitHub, and Jira"
- Monitor which tools Claude discovers to refine descriptions
## Usage
Tool search tool usage is tracked in the response usage object:
```json JSON
{
"usage": {
"input_tokens": 1024,
"output_tokens": 256,
"server_tool_use": {
"tool_search_requests": 2
}
}
}
```
## Next steps
Full tool catalog with model compatibility and parameters.
Configure MCP toolsets with deferred loading.
Combine tool search with cached tool definitions.
Step-by-step guide for defining tools.
### Context management
---
# Compaction
URL: https://platform.claude.com/docs/en/build-with-claude/compaction
# Compaction
Server-side context compaction for managing long conversations that approach context window limits.
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
Server-side compaction is the recommended strategy for managing context in long-running conversations and agentic workflows. It handles context management automatically with minimal integration work.
Compaction extends the effective context length for long-running conversations and tasks by automatically summarizing older context when approaching the context window limit. This isn't just about staying under a token cap. As conversations get longer, models struggle to maintain focus across the full history. Compaction keeps the active context focused and performant by replacing stale content with concise summaries.
For a deeper look at why long contexts degrade and how compaction helps, see
[Effective context engineering](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents).
This is ideal for:
- Chat-based, multi-turn conversations where you want users to use one chat for a long period of time
- Task-oriented prompts that require a lot of follow-up work (often tool use) that may exceed the context window
Compaction is in beta. Include the [beta header](/docs/en/api/beta-headers) `compact-2026-01-12` in your API requests to use this feature.
## Supported models
Compaction is supported on the following models:
- [Claude Mythos Preview](https://anthropic.com/glasswing) (`claude-mythos-preview`)
- Claude Opus 4.7 (`claude-opus-4-7`)
- Claude Opus 4.6 (`claude-opus-4-6`)
- Claude Sonnet 4.6 (`claude-sonnet-4-6`)
## How compaction works
When compaction is enabled, Claude automatically summarizes your conversation when it approaches the configured token threshold. The API:
1. Detects when input tokens exceed your specified trigger threshold.
2. Generates a summary of the current conversation.
3. Creates a `compaction` block containing the summary.
4. Continues the response with the compacted context.
On subsequent requests, append the response to your messages. The API automatically drops all message blocks prior to the `compaction` block, continuing the conversation from the summary.

## Basic usage
Enable compaction by adding the `compact_20260112` strategy to `context_management.edits` in your Messages API request.
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "anthropic-beta: compact-2026-01-12" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": "Help me build a website"
}
],
"context_management": {
"edits": [
{
"type": "compact_20260112"
}
]
}
}'
```
```bash CLI
ant beta:messages create --beta compact-2026-01-12 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: Help me build a website
context_management:
edits:
- type: compact_20260112
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Help me build a website"}]
response = client.beta.messages.create(
betas=["compact-2026-01-12"],
model="claude-opus-4-7",
max_tokens=4096,
messages=messages,
context_management={"edits": [{"type": "compact_20260112"}]},
)
# Append the response (including any compaction block) to continue the conversation
messages.append({"role": "assistant", "content": response.content})
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const messages: Anthropic.Beta.Messages.BetaMessageParam[] = [
{ role: "user", content: "Help me build a website" }
];
const response = await client.beta.messages.create({
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages,
context_management: {
edits: [
{
type: "compact_20260112"
}
]
}
});
// Append the response (including any compaction block) to continue the conversation
messages.push({
role: "assistant",
content: response.content
});
```
```csharp C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var messages = new List
{
new() { Role = Role.User, Content = "Help me build a website" }
};
var parameters = new MessageCreateParams
{
Betas = ["compact-2026-01-12"],
Model = "claude-opus-4-7",
MaxTokens = 4096,
Messages = messages,
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit()]
}
};
var response = await client.Beta.Messages.Create(parameters);
// Append the response (including any compaction block) to continue the conversation
messages.Add(new BetaMessageParam
{
Role = Role.Assistant,
Content = response.Content.Select(b => new BetaContentBlockParam(b.Json)).ToList()
});
Console.WriteLine(response);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
messages := []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Help me build a website")),
}
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: messages,
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{}},
},
},
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
// Append the response (including any compaction block) to continue the conversation
messages = append(messages, response.ToParam())
fmt.Println(response)
}
```
```java Java hidelines={1..4,7..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaCompact20260112Edit;
public class CompactionExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.addBeta("compact-2026-01-12")
.model("claude-opus-4-7")
.maxTokens(4096L)
.addUserMessage("Help me build a website")
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder().build())
.build())
.build();
BetaMessage response = client.beta().messages().create(params);
// Append the response (including any compaction block) to continue the conversation
// by including it in the next request's messages
System.out.println(response);
}
}
```
```php PHP hidelines={1..4}
'user', 'content' => 'Help me build a website']
];
$response = $client->beta->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [
['type' => 'compact_20260112']
]
]
);
// Append the response (including any compaction block) to continue the conversation
$messages[] = ['role' => 'assistant', 'content' => $response->content];
echo $response->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
messages = [
{ role: "user", content: "Help me build a website" }
]
response = client.beta.messages.create(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages: messages,
context_management: {
edits: [{ type: "compact_20260112" }]
}
)
# Append the response (including any compaction block) to continue the conversation
messages << { role: "assistant", content: response.content }
puts response
```
## Parameters
| Parameter | Type | Default | Description |
|:----------|:-----|:--------|:------------|
| `type` | string | Required | Must be `"compact_20260112"` |
| `trigger` | object | 150,000 tokens | When to trigger compaction. Must be at least 50,000 tokens. |
| `pause_after_compaction` | boolean | `false` | Whether to pause after generating the compaction summary |
| `instructions` | string | `null` | Custom summarization prompt. Completely replaces the default prompt when provided. |
### Trigger configuration
Configure when compaction triggers using the `trigger` parameter:
```bash CLI
ant beta:messages create --beta compact-2026-01-12 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: Hello, Claude
context_management:
edits:
- type: compact_20260112
trigger:
type: input_tokens
value: 150000
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Hello, Claude"}]
response = client.beta.messages.create(
betas=["compact-2026-01-12"],
model="claude-opus-4-7",
max_tokens=4096,
messages=messages,
context_management={
"edits": [
{
"type": "compact_20260112",
"trigger": {"type": "input_tokens", "value": 150000},
}
]
},
)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const messages: Anthropic.Beta.Messages.BetaMessageParam[] = [];
const response = await client.beta.messages.create({
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages,
context_management: {
edits: [
{
type: "compact_20260112",
trigger: {
type: "input_tokens",
value: 150000
}
}
]
}
});
```
```csharp C# hidelines={1..13,-2..}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
List messages = [new() { Role = Role.User, Content = "Hello" }];
var parameters = new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 4096,
Betas = ["compact-2026-01-12"],
Messages = messages,
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit
{
Trigger = new BetaInputTokensTrigger(150000)
}]
}
};
var message = await client.Beta.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
messages := []anthropic.BetaMessageParam{anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Hello, Claude"))}
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: messages,
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{
Trigger: anthropic.BetaInputTokensTriggerParam{Value: 150000},
}},
},
},
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..4,8..10,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaCompact20260112Edit;
import com.anthropic.models.beta.messages.BetaInputTokensTrigger;
public class CompactionExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("compact-2026-01-12")
.addUserMessage("Hello, Claude")
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder()
.trigger(BetaInputTokensTrigger.builder()
.value(150000L)
.build())
.build())
.build())
.build();
BetaMessage response = client.beta().messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4}
'user', 'content' => 'Hello, Claude']];
$message = $client->beta->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [
[
'type' => 'compact_20260112',
'trigger' => [
'type' => 'input_tokens',
'value' => 150000
]
]
]
]
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
messages = [{ role: "user", content: "Hello, Claude" }]
response = client.beta.messages.create(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages: messages,
context_management: {
edits: [
{
type: "compact_20260112",
trigger: {
type: "input_tokens",
value: 150000
}
}
]
}
)
puts response
```
### Custom summarization instructions
By default, compaction uses the following summarization prompt:
```text
You have written a partial transcript for the initial task above. Please write a summary of the transcript. The purpose of this summary is to provide continuity so you can continue to make progress towards solving the task in a future context, where the raw history above may not be accessible and will be replaced with this summary. Write down anything that would be helpful, including the state, next steps, learnings etc. You must wrap your summary in a block.
```
You can provide custom instructions via the `instructions` parameter to replace this prompt entirely. Custom instructions don't supplement the default; they completely replace it:
```bash CLI
ant beta:messages create --beta compact-2026-01-12 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: Hello, Claude
context_management:
edits:
- type: compact_20260112
instructions: >-
Focus on preserving code snippets, variable names, and
technical decisions.
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Hello, Claude"}]
response = client.beta.messages.create(
betas=["compact-2026-01-12"],
model="claude-opus-4-7",
max_tokens=4096,
messages=messages,
context_management={
"edits": [
{
"type": "compact_20260112",
"instructions": "Focus on preserving code snippets, variable names, and technical decisions.",
}
]
},
)
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const messages: Anthropic.Beta.Messages.BetaMessageParam[] = [];
const response = await client.beta.messages.create({
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages,
context_management: {
edits: [
{
type: "compact_20260112",
instructions:
"Focus on preserving code snippets, variable names, and technical decisions."
}
]
}
});
```
```csharp C#
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Betas = ["compact-2026-01-12"],
Model = "claude-opus-4-7",
MaxTokens = 4096,
Messages =
[
new BetaMessageParam { Role = Role.User, Content = "Help me build a Python web scraper" },
new BetaMessageParam { Role = Role.Assistant, Content = "I'll help you build a web scraper..." },
new BetaMessageParam { Role = Role.User, Content = "Add support for JavaScript-rendered pages" }
],
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit
{
Instructions = "Focus on preserving code snippets, variable names, and technical decisions."
}]
}
};
var message = await client.Beta.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Help me build a Python web scraper")),
{Role: anthropic.BetaMessageParamRoleAssistant, Content: []anthropic.BetaContentBlockParamUnion{anthropic.NewBetaTextBlock("I'll help you build a web scraper...")}},
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Add support for JavaScript-rendered pages")),
},
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{
Instructions: anthropic.String("Focus on preserving code snippets, variable names, and technical decisions."),
}},
},
},
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..4,7..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaCompact20260112Edit;
public class CompactionExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.addBeta("compact-2026-01-12")
.model("claude-opus-4-7")
.maxTokens(4096L)
.addUserMessage("Help me build a Python web scraper")
.addAssistantMessage("I'll help you build a web scraper...")
.addUserMessage("Add support for JavaScript-rendered pages")
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder()
.instructions("Focus on preserving code snippets, variable names, and technical decisions.")
.build())
.build())
.build();
BetaMessage response = client.beta().messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..3}
beta->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Help me build a Python web scraper'],
['role' => 'assistant', 'content' => "I'll help you build a web scraper..."],
['role' => 'user', 'content' => 'Add support for JavaScript-rendered pages']
],
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [
[
'type' => 'compact_20260112',
'instructions' => 'Focus on preserving code snippets, variable names, and technical decisions.'
]
]
]
);
echo $response->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.beta.messages.create(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{ role: "user", content: "Help me build a Python web scraper" },
{ role: "assistant", content: "I'll help you build a web scraper..." },
{ role: "user", content: "Add support for JavaScript-rendered pages" }
],
context_management: {
edits: [
{
type: "compact_20260112",
instructions:
"Focus on preserving code snippets, variable names, and technical decisions."
}
]
}
)
puts response
```
### Pausing after compaction
Use `pause_after_compaction` to pause the API after generating the compaction summary. This allows you to add additional content blocks (such as preserving recent messages or specific instruction-oriented messages) before the API continues with the response.
When enabled, the API returns a message with the `compaction` stop reason after generating the compaction block:
```bash CLI
ant beta:messages create --beta compact-2026-01-12 \
--transform '{stop_reason,content}' --format jsonl <<'YAML' > resp.json
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: "Hello, Claude"
context_management:
edits:
- type: compact_20260112
pause_after_compaction: true
YAML
# Check if compaction triggered a pause
if grep -q '"stop_reason":"compaction"' resp.json; then
# Response contains only the compaction block
RESP=$(cat resp.json)
CONTENT="${RESP#*\"content\":}"
printf '%s' "${CONTENT%\}}" > content.json
# Continue the request
ant beta:messages create --beta compact-2026-01-12 < /dev/null
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: "Hello, Claude"
- role: assistant
content: $(cat content.json)
context_management:
edits:
- type: compact_20260112
YAML
fi
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Hello, Claude"}]
response = client.beta.messages.create(
betas=["compact-2026-01-12"],
model="claude-opus-4-7",
max_tokens=4096,
messages=messages,
context_management={
"edits": [{"type": "compact_20260112", "pause_after_compaction": True}]
},
)
# Check if compaction triggered a pause
if response.stop_reason == "compaction":
# Response contains only the compaction block
messages.append({"role": "assistant", "content": response.content})
# Continue the request
response = client.beta.messages.create(
betas=["compact-2026-01-12"],
model="claude-opus-4-7",
max_tokens=4096,
messages=messages,
context_management={"edits": [{"type": "compact_20260112"}]},
)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const messages: Anthropic.Beta.Messages.BetaMessageParam[] = [
{ role: "user", content: "Hello, Claude" }
];
let response = await client.beta.messages.create({
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages,
context_management: {
edits: [
{
type: "compact_20260112",
pause_after_compaction: true
}
]
}
});
// Check if compaction triggered a pause
if (response.stop_reason === "compaction") {
// Response contains only the compaction block
messages.push({
role: "assistant",
content: response.content
});
// Continue the request
response = await client.beta.messages.create({
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages,
context_management: {
edits: [{ type: "compact_20260112" }]
}
});
}
```
```csharp C#
using Anthropic;
using Anthropic.Models.Beta.Messages;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var client = new AnthropicClient();
var messages = new List
{
new() { Role = Role.User, Content = "Hello, Claude" }
};
var parameters = new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 4096,
Betas = ["compact-2026-01-12"],
Messages = messages,
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit
{
PauseAfterCompaction = true
}]
}
};
var response = await client.Beta.Messages.Create(parameters);
if (response.StopReason == BetaStopReason.Compaction)
{
messages.Add(new BetaMessageParam
{
Role = Role.Assistant,
Content = response.Content.Select(b => new BetaContentBlockParam(b.Json)).ToList()
});
parameters = new()
{
Model = "claude-opus-4-7",
MaxTokens = 4096,
Betas = ["compact-2026-01-12"],
Messages = messages,
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit()]
}
};
response = await client.Beta.Messages.Create(parameters);
}
Console.WriteLine(response);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
messages := []anthropic.BetaMessageParam{anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Hello, Claude"))}
compactEdit := anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{
PauseAfterCompaction: anthropic.Bool(true),
}},
},
}
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: messages,
ContextManagement: compactEdit,
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
if response.StopReason == "compaction" {
messages = append(messages, response.ToParam())
response, err = client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: messages,
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{}},
},
},
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
}
fmt.Println(response)
}
```
```java Java hidelines={1..4,8..10,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaCompact20260112Edit;
import com.anthropic.models.beta.messages.BetaStopReason;
public class CompactionPauseExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("compact-2026-01-12")
.addUserMessage("Help me build a website")
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder()
.pauseAfterCompaction(true)
.build())
.build())
.build();
BetaMessage response = client.beta().messages().create(params);
// Check if compaction triggered a pause
if (response.stopReason().isPresent()
&& response.stopReason().get().equals(BetaStopReason.COMPACTION)) {
// Append the compaction block and continue the request
// by building a new request with the compacted context
MessageCreateParams continueParams = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("compact-2026-01-12")
.addUserMessage("Help me build a website")
.addMessage(response)
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder().build())
.build())
.build();
response = client.beta().messages().create(continueParams);
}
System.out.println(response);
}
}
```
```php PHP nocheck hidelines={1..4}
'user', 'content' => 'Hello, Claude']];
$response = $client->beta->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [
[
'type' => 'compact_20260112',
'pause_after_compaction' => true
]
]
]
);
if ($response->stopReason === 'compaction') {
$messages[] = [
'role' => 'assistant',
'content' => $response->content
];
$response = $client->beta->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [
['type' => 'compact_20260112']
]
]
);
}
echo $response;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
messages = [{ role: "user", content: "Hello, Claude" }]
response = client.beta.messages.create(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages: messages,
context_management: {
edits: [
{
type: "compact_20260112",
pause_after_compaction: true
}
]
}
)
if response.stop_reason == :compaction
messages << { role: "assistant", content: response.content }
response = client.beta.messages.create(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages: messages,
context_management: {
edits: [{ type: "compact_20260112" }]
}
)
end
puts response
```
#### Enforcing a total token budget
When a model works on long tasks with many tool-use iterations, total token consumption can grow significantly. You can combine `pause_after_compaction` with a compaction counter to estimate cumulative usage and gracefully wrap up the task once a budget is reached:
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Hello, Claude"}]
TRIGGER_THRESHOLD = 100_000
TOTAL_TOKEN_BUDGET = 3_000_000
n_compactions = 0
response = client.beta.messages.create(
betas=["compact-2026-01-12"],
model="claude-opus-4-7",
max_tokens=4096,
messages=messages,
context_management={
"edits": [
{
"type": "compact_20260112",
"trigger": {"type": "input_tokens", "value": TRIGGER_THRESHOLD},
"pause_after_compaction": True,
}
]
},
)
if response.stop_reason == "compaction":
n_compactions += 1
messages.append({"role": "assistant", "content": response.content})
# Estimate total tokens consumed; prompt wrap-up if over budget
if n_compactions * TRIGGER_THRESHOLD >= TOTAL_TOKEN_BUDGET:
messages.append(
{
"role": "user",
"content": "Please wrap up your current work and summarize the final state.",
}
)
```
## Working with compaction blocks
When compaction is triggered, the API returns a `compaction` block at the start of the assistant response.
A long-running conversation may result in multiple compactions. The last compaction block reflects the final state of the prompt, replacing content prior to it with the generated summary.
```json Output
{
"content": [
{
"type": "compaction",
"content": "Summary of the conversation: The user requested help building a web scraper..."
},
{
"type": "text",
"text": "Based on our conversation so far..."
}
]
}
```
### Passing compaction blocks back
You must pass the `compaction` block back to the API on subsequent requests to continue the conversation with the shortened prompt. The simplest approach is to append the entire response content to your messages:
```bash CLI
ant beta:messages create --beta compact-2026-01-12 \
--transform content --format jsonl <<'YAML' > content.json
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: Hello, Claude
context_management:
edits:
- type: compact_20260112
YAML
# After receiving a response with a compaction block, append it as the
# assistant turn and continue the conversation
ant beta:messages create --beta compact-2026-01-12 <
{
new() { Role = Role.User, Content = "Help me build a web scraper" }
};
var response = await client.Beta.Messages.Create(new()
{
Betas = ["compact-2026-01-12"],
Model = "claude-opus-4-7",
MaxTokens = 4096,
Messages = messages,
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit()]
}
});
messages.Add(new BetaMessageParam
{
Role = Role.Assistant,
Content = response.Content.Select(b => new BetaContentBlockParam(b.Json)).ToList()
});
messages.Add(new BetaMessageParam { Role = Role.User, Content = "Now add error handling" });
var nextResponse = await client.Beta.Messages.Create(new()
{
Betas = ["compact-2026-01-12"],
Model = "claude-opus-4-7",
MaxTokens = 4096,
Messages = messages,
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit()]
}
});
Console.WriteLine(nextResponse);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
messages := []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Help me build a web scraper")),
}
compactEdit := anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{}},
},
}
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: messages,
ContextManagement: compactEdit,
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
messages = append(messages, response.ToParam())
messages = append(messages, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Now add error handling")))
nextResponse, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: messages,
ContextManagement: compactEdit,
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(nextResponse)
}
```
```java Java hidelines={1..4,7..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaCompact20260112Edit;
public class CompactionExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// First request
BetaMessage response = client.beta().messages().create(
MessageCreateParams.builder()
.addBeta("compact-2026-01-12")
.model("claude-opus-4-7")
.maxTokens(4096L)
.addUserMessage("Help me build a web scraper")
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder().build())
.build())
.build());
// After receiving a response with a compaction block, append the full
// content (including compaction blocks) and continue the conversation
BetaMessage nextResponse = client.beta().messages().create(
MessageCreateParams.builder()
.addBeta("compact-2026-01-12")
.model("claude-opus-4-7")
.maxTokens(4096L)
.addUserMessage("Help me build a web scraper")
.addMessage(response)
.addUserMessage("Now add error handling")
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder().build())
.build())
.build());
System.out.println(nextResponse);
}
}
```
```php PHP hidelines={1..4}
'user', 'content' => 'Help me build a web scraper']
];
$response = $client->beta->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [['type' => 'compact_20260112']]
]
);
$messages[] = ['role' => 'assistant', 'content' => $response->content];
$messages[] = ['role' => 'user', 'content' => 'Now add error handling'];
$nextResponse = $client->beta->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [['type' => 'compact_20260112']]
]
);
echo $nextResponse->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
messages = [
{ role: "user", content: "Help me build a web scraper" }
]
response = client.beta.messages.create(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages: messages,
context_management: {
edits: [{ type: "compact_20260112" }]
}
)
messages << { role: "assistant", content: response.content }
messages << { role: "user", content: "Now add error handling" }
next_response = client.beta.messages.create(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages: messages,
context_management: {
edits: [{ type: "compact_20260112" }]
}
)
puts next_response.content
```
When the API receives a `compaction` block, all content blocks before it are ignored. You can either:
- Keep the original messages in your list and let the API handle removing the compacted content
- Manually drop the compacted messages and only include the compaction block onwards
### Streaming
When streaming responses with compaction enabled, you'll receive a `content_block_start` event when compaction begins. The compaction block streams differently from text blocks. You'll receive a `content_block_start` event, followed by a single `content_block_delta` with the complete summary content (no intermediate streaming), and then a `content_block_stop` event.
```bash CLI nocheck
ant beta:messages create --stream --format jsonl \
--beta compact-2026-01-12 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: Hello, Claude
context_management:
edits:
- type: compact_20260112
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Hello, Claude"}]
with client.beta.messages.stream(
betas=["compact-2026-01-12"],
model="claude-opus-4-7",
max_tokens=4096,
messages=messages,
context_management={"edits": [{"type": "compact_20260112"}]},
) as stream:
for event in stream:
if event.type == "content_block_start":
if event.content_block.type == "compaction":
print("Compaction started...")
elif event.content_block.type == "text":
print("Text response started...")
elif event.type == "content_block_delta":
if event.delta.type == "compaction_delta":
print(f"Compaction complete: {len(event.delta.content or '')} chars")
elif event.delta.type == "text_delta":
print(event.delta.text, end="", flush=True)
# Get the final accumulated message
message = stream.get_final_message()
messages.append({"role": "assistant", "content": message.content})
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const messages: Anthropic.Beta.Messages.BetaMessageParam[] = [];
const stream = await client.beta.messages.stream({
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages,
context_management: {
edits: [{ type: "compact_20260112" }]
}
});
for await (const event of stream) {
if (event.type === "content_block_start") {
if (event.content_block.type === "compaction") {
console.log("Compaction started...");
} else if (event.content_block.type === "text") {
console.log("Text response started...");
}
} else if (event.type === "content_block_delta") {
if (event.delta.type === "compaction_delta") {
console.log(`Compaction complete: ${event.delta.content?.length ?? 0} chars`);
} else if (event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
}
// Get the final accumulated message
const message = await stream.finalMessage();
messages.push({
role: "assistant",
content: message.content
});
```
```csharp C# hidelines={1..13,-2..}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
class Program
{
static async Task Main(string[] args)
{
var client = new AnthropicClient();
List messages = [new() { Role = Role.User, Content = "Hello" }];
var parameters = new MessageCreateParams
{
Betas = ["compact-2026-01-12"],
Model = "claude-opus-4-7",
MaxTokens = 4096,
Messages = messages,
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit()]
}
};
await foreach (var streamEvent in client.Beta.Messages.CreateStreaming(parameters))
{
if (streamEvent.TryPickContentBlockStart(out var startEvent))
{
if (startEvent.ContentBlock.TryPickBetaCompaction(out _))
{
Console.WriteLine("Compaction started...");
}
else if (startEvent.ContentBlock.TryPickBetaText(out _))
{
Console.WriteLine("Text response started...");
}
}
else if (streamEvent.TryPickContentBlockDelta(out var deltaEvent))
{
if (deltaEvent.Delta.TryPickCompaction(out var compactionDelta))
{
Console.WriteLine($"Compaction complete: {compactionDelta.Content?.Length ?? 0} chars");
}
else if (deltaEvent.Delta.TryPickText(out var textDelta))
{
Console.Write(textDelta.Text);
}
}
}
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
messages := []anthropic.BetaMessageParam{anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Hello, Claude"))}
stream := client.Beta.Messages.NewStreaming(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: messages,
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{}},
},
},
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
for stream.Next() {
event := stream.Current()
switch eventVariant := event.AsAny().(type) {
case anthropic.BetaRawContentBlockStartEvent:
switch eventVariant.ContentBlock.AsAny().(type) {
case anthropic.BetaCompactionBlock:
fmt.Println("Compaction started...")
case anthropic.BetaTextBlock:
fmt.Println("Text response started...")
}
case anthropic.BetaRawContentBlockDeltaEvent:
switch deltaVariant := eventVariant.Delta.AsAny().(type) {
case anthropic.BetaCompactionContentBlockDelta:
fmt.Printf("Compaction complete: %d chars\n", len(deltaVariant.Content))
case anthropic.BetaTextDelta:
fmt.Print(deltaVariant.Text)
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
}
```
```java Java hidelines={1..3,6..8,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaCompact20260112Edit;
public class CompactionStreamingExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("compact-2026-01-12")
.addUserMessage("Hello, Claude")
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder().build())
.build())
.build();
try (var streamResponse = client.beta().messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
event.contentBlockStart().ifPresent(startEvent -> {
startEvent.contentBlock().compaction().ifPresent(c ->
System.out.println("Compaction started...")
);
startEvent.contentBlock().text().ifPresent(t ->
System.out.println("Text response started...")
);
});
event.contentBlockDelta().ifPresent(deltaEvent -> {
deltaEvent.delta().compaction().ifPresent(cd ->
System.out.println("Compaction complete: " + cd.content().map(String::length).orElse(0) + " chars")
);
deltaEvent.delta().text().ifPresent(td ->
System.out.print(td.text())
);
});
});
}
}
}
```
```php PHP hidelines={1..4}
'user', 'content' => 'Hello, Claude']];
$stream = $client->beta->messages->createStream(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [
['type' => 'compact_20260112']
]
]
);
foreach ($stream as $event) {
if ($event->type === 'content_block_start') {
if ($event->contentBlock->type === 'compaction') {
echo "Compaction started...\n";
} elseif ($event->contentBlock->type === 'text') {
echo "Text response started...\n";
}
} elseif ($event->type === 'content_block_delta') {
if ($event->delta->type === 'compaction_delta') {
echo "Compaction complete: " . strlen($event->delta->content ?? '') . " chars\n";
} elseif ($event->delta->type === 'text_delta') {
echo $event->delta->text;
}
}
}
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
messages = [{ role: "user", content: "Hello, Claude" }]
stream = client.beta.messages.stream(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages: messages,
context_management: {
edits: [{ type: "compact_20260112" }]
}
)
stream.each do |event|
case event.type
when :content_block_start
if event.content_block.type == :compaction
puts "Compaction started..."
elsif event.content_block.type == :text
puts "Text response started..."
end
when :content_block_delta
if event.delta.type == :compaction_delta
puts "Compaction complete: #{(event.delta.content || "").length} chars"
elsif event.delta.type == :text_delta
print event.delta.text
end
end
end
```
### Prompt caching
Compaction works well with [prompt caching](/docs/en/build-with-claude/prompt-caching). You can add a `cache_control` breakpoint on compaction blocks to cache the summarized content. The original compacted content is ignored.
```json
{
"role": "assistant",
"content": [
{
"type": "compaction",
"content": "[summary text]",
"cache_control": { "type": "ephemeral" }
},
{
"type": "text",
"text": "Based on our conversation..."
}
]
}
```
#### Maximizing cache hits with system prompts
When compaction occurs, the summary becomes new content that needs to be written to the cache. Without additional cache breakpoints, this would also invalidate any cached system prompt, requiring it to be re-cached along with the compaction summary.
To maximize cache hit rates, add a `cache_control` breakpoint at the end of your system prompt. This keeps the system prompt cached separately from the conversation, so when compaction occurs:
- The system prompt cache remains valid and is read from cache
- Only the compaction summary needs to be written as a new cache entry
```bash CLI
ant beta:messages create --beta compact-2026-01-12 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
system:
- type: text
text: You are a helpful coding assistant...
cache_control:
type: ephemeral
messages:
- role: user
content: Hello, Claude
context_management:
edits:
- type: compact_20260112
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Hello, Claude"}]
response = client.beta.messages.create(
betas=["compact-2026-01-12"],
model="claude-opus-4-7",
max_tokens=4096,
system=[
{
"type": "text",
"text": "You are a helpful coding assistant...",
"cache_control": {
"type": "ephemeral"
}, # Cache the system prompt separately
}
],
messages=messages,
context_management={"edits": [{"type": "compact_20260112"}]},
)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const messages: Anthropic.Beta.Messages.BetaMessageParam[] = [];
const response = await client.beta.messages.create({
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
system: [
{
type: "text",
text: "You are a helpful coding assistant...",
cache_control: { type: "ephemeral" } // Cache the system prompt separately
}
],
messages,
context_management: {
edits: [{ type: "compact_20260112" }]
}
});
```
```csharp C#
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
class Program
{
static async Task Main(string[] args)
{
var client = new AnthropicClient();
var parameters = new MessageCreateParams
{
Betas = ["compact-2026-01-12"],
Model = "claude-opus-4-7",
MaxTokens = 4096,
System = new List
{
new()
{
Text = "You are a helpful coding assistant...",
CacheControl = new BetaCacheControlEphemeral()
}
},
Messages = [],
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit()]
}
};
var response = await client.Beta.Messages.Create(parameters);
Console.WriteLine(response);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
System: []anthropic.BetaTextBlockParam{
{
Text: "You are a helpful coding assistant...",
CacheControl: anthropic.NewBetaCacheControlEphemeralParam(),
},
},
Messages: []anthropic.BetaMessageParam{anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Hello, Claude"))},
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{}},
},
},
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..5,9..12,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaTextBlockParam;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaCompact20260112Edit;
import com.anthropic.models.beta.messages.BetaCacheControlEphemeral;
import java.util.List;
public class CompactionExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("compact-2026-01-12")
.systemOfBetaTextBlockParams(List.of(
BetaTextBlockParam.builder()
.text("You are a helpful coding assistant...")
.cacheControl(BetaCacheControlEphemeral.builder().build())
.build()
))
.addUserMessage("Hello, Claude")
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder().build())
.build())
.build();
BetaMessage response = client.beta().messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..3}
beta->messages->create(
maxTokens: 4096,
messages: [['role' => 'user', 'content' => 'Hello, Claude']],
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
system: [
[
'type' => 'text',
'text' => 'You are a helpful coding assistant...',
'cache_control' => [
'type' => 'ephemeral'
]
]
],
contextManagement: [
'edits' => [
['type' => 'compact_20260112']
]
]
);
echo $response->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.beta.messages.create(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
system: [
{
type: "text",
text: "You are a helpful coding assistant...",
cache_control: {
type: "ephemeral"
}
}
],
messages: [],
context_management: {
edits: [{ type: "compact_20260112" }]
}
)
puts response
```
This approach is particularly beneficial for long system prompts, as they remain cached even across multiple compaction events throughout a conversation.
## Understanding usage
Compaction requires an additional sampling step, which contributes to rate limits and billing. The API returns detailed usage information in the response:
```json Output
{
"usage": {
"input_tokens": 23000,
"output_tokens": 1000,
"iterations": [
{
"type": "compaction",
"input_tokens": 180000,
"output_tokens": 3500
},
{
"type": "message",
"input_tokens": 23000,
"output_tokens": 1000
}
]
}
}
```
The `iterations` array shows usage for each sampling iteration. When compaction occurs, you'll see a `compaction` iteration followed by the main `message` iteration. The top-level `input_tokens` and `output_tokens` match the `message` iteration exactly in this example because there is only one non-compaction iteration. The final iteration's token counts reflect the effective context size after compaction.
The top-level `input_tokens` and `output_tokens` do not include compaction iteration usage. They reflect the sum of all non-compaction iterations. To calculate total tokens consumed and billed for a request, sum across all entries in the `usage.iterations` array.
If you previously relied on `usage.input_tokens` and `usage.output_tokens` for cost tracking or auditing, you'll need to update your tracking logic to aggregate across `usage.iterations` when compaction is enabled. The `iterations` array is only populated when a new compaction is triggered during the request. Re-applying a previous `compaction` block incurs no additional compaction cost, and the top-level usage fields remain accurate in that case.
## Combining with other features
### Server tools
When using server tools (like web search), the compaction trigger is checked at the start of each sampling iteration. Compaction may occur multiple times within a single request depending on your trigger threshold and the amount of output generated.
### Token counting
The token counting endpoint (`/v1/messages/count_tokens`) applies existing `compaction` blocks in your prompt but does not trigger new compactions. Use it to check your effective token count after previous compactions:
```bash CLI
cat > request.yaml <<'YAML'
model: claude-opus-4-7
messages:
- role: user
content: Hello, Claude
context_management:
edits:
- type: compact_20260112
YAML
CURRENT=$(ant beta:messages count-tokens \
--beta compact-2026-01-12 \
--transform input_tokens --raw-output < request.yaml)
ORIGINAL=$(ant beta:messages count-tokens \
--beta compact-2026-01-12 \
--transform context_management.original_input_tokens \
--raw-output < request.yaml)
printf 'Current tokens: %s\n' "$CURRENT"
printf 'Original tokens: %s\n' "$ORIGINAL"
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Hello, Claude"}]
count_response = client.beta.messages.count_tokens(
betas=["compact-2026-01-12"],
model="claude-opus-4-7",
messages=messages,
context_management={"edits": [{"type": "compact_20260112"}]},
)
print(f"Current tokens: {count_response.input_tokens}")
print(f"Original tokens: {count_response.context_management.original_input_tokens}")
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const messages: Anthropic.Beta.Messages.BetaMessageParam[] = [
{ role: "user", content: "Summarize the key points of our conversation so far." }
];
const countResponse = await client.beta.messages.countTokens({
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
messages,
context_management: {
edits: [{ type: "compact_20260112" }]
}
});
console.log(`Current tokens: ${countResponse.input_tokens}`);
console.log(`Original tokens: ${countResponse.context_management!.original_input_tokens}`);
```
```csharp C# hidelines={1..13,-2..}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
List messages = [new() { Role = Role.User, Content = "Hello" }];
var countParams = new MessageCountTokensParams
{
Model = "claude-opus-4-7",
Messages = messages,
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit()]
},
Betas = ["compact-2026-01-12"]
};
var countResponse = await client.Beta.Messages.CountTokens(countParams);
Console.WriteLine($"Current tokens: {countResponse.InputTokens}");
Console.WriteLine($"Original tokens: {countResponse.ContextManagement?.OriginalInputTokens}");
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
messages := []anthropic.BetaMessageParam{anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Hello, Claude"))}
countResponse, err := client.Beta.Messages.CountTokens(context.TODO(), anthropic.BetaMessageCountTokensParams{
Model: anthropic.ModelClaudeOpus4_7,
Messages: messages,
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{}},
},
},
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Current tokens: %d\n", countResponse.InputTokens)
fmt.Printf("Original tokens: %d\n", countResponse.ContextManagement.OriginalInputTokens)
}
```
```java Java hidelines={1..2,7..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.BetaMessageTokensCount;
import com.anthropic.models.beta.messages.MessageCountTokensParams;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaCompact20260112Edit;
public class Main {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCountTokensParams params = MessageCountTokensParams.builder()
.model("claude-opus-4-7")
.addUserMessage("Hello, Claude")
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder().build())
.build())
.addBeta("compact-2026-01-12")
.build();
BetaMessageTokensCount countResponse = client.beta().messages().countTokens(params);
System.out.println("Current tokens: " + countResponse.inputTokens());
System.out.println("Original tokens: " + countResponse.contextManagement().get().originalInputTokens());
}
}
```
```php PHP hidelines={1..4}
'user', 'content' => 'Hello, Claude']];
$countResponse = $client->beta->messages->countTokens(
messages: $messages,
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [
['type' => 'compact_20260112']
]
]
);
echo "Current tokens: " . $countResponse->inputTokens . "\n";
echo "Original tokens: " . $countResponse->contextManagement->originalInputTokens . "\n";
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
messages = [{ role: "user", content: "Hello, Claude" }]
count_response = client.beta.messages.count_tokens(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
messages: messages,
context_management: {
edits: [{ type: "compact_20260112" }]
}
)
puts "Current tokens: #{count_response.input_tokens}"
puts "Original tokens: #{count_response.context_management.original_input_tokens}"
```
## Examples
Here's a complete example of a long-running conversation with compaction:
```bash CLI
# The CLI handles individual turns; maintain the messages array in the
# calling script. See the SDK tabs for the full chat() loop. Single-turn
# request shape:
ant beta:messages create --beta compact-2026-01-12 \
--transform 'content.#(type=="text").text' --raw-output <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: Help me build a Python web scraper
context_management:
edits:
- type: compact_20260112
trigger:
type: input_tokens
value: 100000
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
messages: list[dict] = []
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-7",
max_tokens=4096,
messages=messages,
context_management={
"edits": [
{
"type": "compact_20260112",
"trigger": {"type": "input_tokens", "value": 100000},
}
]
},
)
# Append response (compaction blocks are automatically included)
messages.append({"role": "assistant", "content": response.content})
# Return the text content
return next(block.text for block in response.content if block.type == "text")
# Run a long conversation
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"))
# ... continue as long as needed
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const messages: Anthropic.Beta.BetaMessageParam[] = [];
async function chat(userMessage: string): Promise {
messages.push({ role: "user", content: userMessage });
const response = await client.beta.messages.create({
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages,
context_management: {
edits: [
{
type: "compact_20260112",
trigger: { type: "input_tokens", value: 100000 }
}
]
}
});
// Append response (compaction blocks are automatically included)
messages.push({ role: "assistant", content: response.content });
// Return the text content
const textBlock = response.content.find((block) => block.type === "text");
return textBlock?.text ?? "";
}
// Run a long conversation
console.log(await chat("Help me build a Python web scraper"));
console.log(await chat("Add support for JavaScript-rendered pages"));
console.log(await chat("Now add rate limiting and error handling"));
// ... continue as long as needed
```
```csharp C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
public class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
List messages = new();
Console.WriteLine(await Chat(client, messages, "Help me build a Python web scraper"));
Console.WriteLine(await Chat(client, messages, "Add support for JavaScript-rendered pages"));
Console.WriteLine(await Chat(client, messages, "Now add rate limiting and error handling"));
}
static async Task Chat(AnthropicClient client, List messages, string userMessage)
{
messages.Add(new() { Role = Role.User, Content = userMessage });
var parameters = new MessageCreateParams
{
Betas = ["compact-2026-01-12"],
Model = "claude-opus-4-7",
MaxTokens = 4096,
Messages = messages,
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit
{
Trigger = new BetaInputTokensTrigger(100000)
}]
}
};
var response = await client.Beta.Messages.Create(parameters);
messages.Add(new()
{
Role = Role.Assistant,
Content = response.Content.Select(b => new BetaContentBlockParam(b.Json)).ToList()
});
return response.Content
.Select(b => b.Value)
.OfType()
.Select(tb => tb.Text)
.FirstOrDefault() ?? "";
}
}
```
```go Go
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
var (
client = anthropic.NewClient()
messages []anthropic.BetaMessageParam
)
func chat(userMessage string) string {
messages = append(messages, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(userMessage)))
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: messages,
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{
Trigger: anthropic.BetaInputTokensTriggerParam{Value: 100000},
}},
},
},
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
messages = append(messages, response.ToParam())
for _, block := range response.Content {
if variant, ok := block.AsAny().(anthropic.BetaTextBlock); ok {
return variant.Text
}
}
return ""
}
func main() {
fmt.Println(chat("Help me build a Python web scraper"))
fmt.Println(chat("Add support for JavaScript-rendered pages"))
fmt.Println(chat("Now add rate limiting and error handling"))
}
```
```java Java hidelines={1..5,9..12,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaMessageParam;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaCompact20260112Edit;
import com.anthropic.models.beta.messages.BetaInputTokensTrigger;
import java.util.ArrayList;
import java.util.List;
public class CompactionExample {
private static final AnthropicClient client = AnthropicOkHttpClient.fromEnv();
private static final List messages = new ArrayList<>();
public static void main(String[] args) {
System.out.println(chat("Help me build a Python web scraper"));
System.out.println(chat("Add support for JavaScript-rendered pages"));
System.out.println(chat("Now add rate limiting and error handling"));
}
private static String chat(String userMessage) {
messages.add(BetaMessageParam.builder()
.role(BetaMessageParam.Role.USER)
.content(userMessage)
.build());
MessageCreateParams params = MessageCreateParams.builder()
.addBeta("compact-2026-01-12")
.model("claude-opus-4-7")
.maxTokens(4096L)
.messages(messages)
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder()
.trigger(BetaInputTokensTrigger.builder()
.value(100000L)
.build())
.build())
.build())
.build();
BetaMessage response = client.beta().messages().create(params);
// Append response (compaction blocks are automatically included)
messages.add(response.toParam());
return response.content().stream()
.filter(block -> block.text().isPresent())
.map(block -> block.text().get().text())
.findFirst()
.orElse("");
}
}
```
```php PHP hidelines={1..3}
'user', 'content' => $userMessage];
$response = $client->beta->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [
[
'type' => 'compact_20260112',
'trigger' => ['type' => 'input_tokens', 'value' => 100000]
]
]
]
);
$messages[] = ['role' => 'assistant', 'content' => $response->content];
foreach ($response->content as $block) {
if ($block->type === 'text') {
return $block->text;
}
}
return '';
}
echo chat($client, $messages, "Help me build a Python web scraper") . "\n";
echo chat($client, $messages, "Add support for JavaScript-rendered pages") . "\n";
echo chat($client, $messages, "Now add rate limiting and error handling") . "\n";
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
messages = []
def chat(client, messages, user_message)
messages << { role: "user", content: user_message }
response = client.beta.messages.create(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages: messages,
context_management: {
edits: [
{
type: "compact_20260112",
trigger: { type: "input_tokens", value: 100000 }
}
]
}
)
messages << { role: "assistant", content: response.content }
response.content.find { |block| block.type == :text }&.text || ""
end
puts chat(client, messages, "Help me build a Python web scraper")
puts chat(client, messages, "Add support for JavaScript-rendered pages")
puts chat(client, messages, "Now add rate limiting and error handling")
```
Here's an example that uses `pause_after_compaction` to preserve the prior exchange and the current user message (three messages total) verbatim instead of summarizing them:
```bash CLI
# The CLI handles individual turns; maintain the messages array in the
# calling script. See the SDK tabs for the full chat() loop with
# pause-and-preserve handling. Single-turn request shape:
ant beta:messages create --beta compact-2026-01-12 \
--transform 'content.#(type=="text").text' --raw-output <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: Help me build a Python web scraper
context_management:
edits:
- type: compact_20260112
trigger:
type: input_tokens
value: 100000
pause_after_compaction: true
YAML
```
```python Python hidelines={1}
import anthropic
from typing import Any
client = anthropic.Anthropic()
messages: list[dict[str, Any]] = []
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-7",
max_tokens=4096,
messages=messages,
context_management={
"edits": [
{
"type": "compact_20260112",
"trigger": {"type": "input_tokens", "value": 100000},
"pause_after_compaction": True,
}
]
},
)
# Check if compaction occurred and paused
if response.stop_reason == "compaction":
# Get the compaction block from the response
compaction_block = response.content[0]
# Preserve the prior exchange + current user message (3 messages)
# by including them after the compaction block
preserved_messages = messages[-3:] if len(messages) >= 3 else messages
# Build new message list: compaction + preserved messages
new_assistant_content = [compaction_block]
messages_after_compaction = [
{"role": "assistant", "content": new_assistant_content}
] + preserved_messages
# Continue the request with the compacted context + preserved messages
response = client.beta.messages.create(
betas=["compact-2026-01-12"],
model="claude-opus-4-7",
max_tokens=4096,
messages=messages_after_compaction,
context_management={"edits": [{"type": "compact_20260112"}]},
)
# Update our message list to reflect the compaction
messages.clear()
messages.extend(messages_after_compaction)
# Append the final response
messages.append({"role": "assistant", "content": response.content})
# Return the text content
return next(block.text for block in response.content if block.type == "text")
# Run a long conversation
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"))
# ... continue as long as needed
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
let messages: Anthropic.Beta.BetaMessageParam[] = [];
async function chat(userMessage: string): Promise {
messages.push({ role: "user", content: userMessage });
let response = await client.beta.messages.create({
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages,
context_management: {
edits: [
{
type: "compact_20260112",
trigger: { type: "input_tokens", value: 100000 },
pause_after_compaction: true
}
]
}
});
// Check if compaction occurred and paused
if (response.stop_reason === "compaction") {
// Get the compaction block from the response
const compactionBlock = response.content[0];
// Preserve the prior exchange + current user message (3 messages)
// by including them after the compaction block
const preservedMessages = messages.length >= 3 ? messages.slice(-3) : [...messages];
// Build new message list: compaction + preserved messages
const messagesAfterCompaction: Anthropic.Beta.BetaMessageParam[] = [
{ role: "assistant", content: [compactionBlock] },
...preservedMessages
];
// Continue the request with the compacted context + preserved messages
response = await client.beta.messages.create({
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages: messagesAfterCompaction,
context_management: {
edits: [{ type: "compact_20260112" }]
}
});
// Update our message list to reflect the compaction
messages = messagesAfterCompaction;
}
// Append the final response
messages.push({ role: "assistant", content: response.content });
// Return the text content
const textBlock = response.content.find((block) => block.type === "text");
return textBlock?.text ?? "";
}
// Run a long conversation
console.log(await chat("Help me build a Python web scraper"));
console.log(await chat("Add support for JavaScript-rendered pages"));
console.log(await chat("Now add rate limiting and error handling"));
// ... continue as long as needed
```
```csharp C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
public class CompactionExample
{
private static AnthropicClient client = new();
private static List messages = new();
static async Task Chat(string userMessage)
{
messages.Add(new() { Role = Role.User, Content = userMessage });
var response = await client.Beta.Messages.Create(new()
{
Betas = ["compact-2026-01-12"],
Model = "claude-opus-4-7",
MaxTokens = 4096,
Messages = messages,
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit
{
Trigger = new BetaInputTokensTrigger(100000),
PauseAfterCompaction = true
}]
}
});
if (response.StopReason == BetaStopReason.Compaction)
{
if (!response.Content[0].TryPickCompaction(out var cb))
throw new InvalidOperationException("Expected compaction block");
var preserved = messages.Count >= 3
? messages.Skip(messages.Count - 3).ToList()
: new List(messages);
var messagesAfterCompaction = new List
{
new()
{
Role = Role.Assistant,
Content = new List { new BetaCompactionBlockParam(cb.Content) }
}
};
messagesAfterCompaction.AddRange(preserved);
response = await client.Beta.Messages.Create(new()
{
Betas = ["compact-2026-01-12"],
Model = "claude-opus-4-7",
MaxTokens = 4096,
Messages = messagesAfterCompaction,
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit()]
}
});
messages = messagesAfterCompaction;
}
messages.Add(new()
{
Role = Role.Assistant,
Content = response.Content.Select(b => new BetaContentBlockParam(b.Json)).ToList()
});
return response.Content
.Select(b => b.Value)
.OfType()
.Select(tb => tb.Text)
.FirstOrDefault() ?? "";
}
static async Task Main()
{
Console.WriteLine(await Chat("Help me build a Python web scraper"));
Console.WriteLine(await Chat("Add support for JavaScript-rendered pages"));
Console.WriteLine(await Chat("Now add rate limiting and error handling"));
}
}
```
```go Go
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
var (
client = anthropic.NewClient()
messages []anthropic.BetaMessageParam
)
func chat(userMessage string) string {
messages = append(messages, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(userMessage)))
compactEdit := anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{
Trigger: anthropic.BetaInputTokensTriggerParam{Value: 100000},
PauseAfterCompaction: anthropic.Bool(true),
}},
},
}
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: messages,
ContextManagement: compactEdit,
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
if response.StopReason == "compaction" {
compactionParam := response.Content[0].ToParam()
var preserved []anthropic.BetaMessageParam
if len(messages) >= 3 {
preserved = messages[len(messages)-3:]
} else {
preserved = messages
}
messagesAfterCompaction := []anthropic.BetaMessageParam{
{Role: anthropic.BetaMessageParamRoleAssistant, Content: []anthropic.BetaContentBlockParamUnion{compactionParam}},
}
messagesAfterCompaction = append(messagesAfterCompaction, preserved...)
response, err = client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: messagesAfterCompaction,
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{}},
},
},
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
messages = messagesAfterCompaction
}
messages = append(messages, response.ToParam())
for _, block := range response.Content {
if textBlock, ok := block.AsAny().(anthropic.BetaTextBlock); ok {
return textBlock.Text
}
}
return ""
}
func main() {
fmt.Println(chat("Help me build a Python web scraper"))
fmt.Println(chat("Add support for JavaScript-rendered pages"))
fmt.Println(chat("Now add rate limiting and error handling"))
}
```
```java Java hidelines={1..5,10..13,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaMessageParam;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaCompact20260112Edit;
import com.anthropic.models.beta.messages.BetaInputTokensTrigger;
import com.anthropic.models.beta.messages.BetaStopReason;
import java.util.ArrayList;
import java.util.List;
public class CompactionExample {
private static final AnthropicClient client = AnthropicOkHttpClient.fromEnv();
private static final List messages = new ArrayList<>();
public static String chat(String userMessage) {
messages.add(BetaMessageParam.builder()
.role(BetaMessageParam.Role.USER)
.content(userMessage)
.build());
MessageCreateParams params = MessageCreateParams.builder()
.addBeta("compact-2026-01-12")
.model("claude-opus-4-7")
.maxTokens(4096L)
.messages(messages)
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder()
.trigger(BetaInputTokensTrigger.builder()
.value(100000L)
.build())
.pauseAfterCompaction(true)
.build())
.build())
.build();
BetaMessage response = client.beta().messages().create(params);
// Check if compaction occurred and paused
if (response.stopReason().isPresent()
&& response.stopReason().get().equals(BetaStopReason.COMPACTION)) {
// Preserve the prior exchange + current user message (3 messages)
List preservedMessages = messages.size() >= 3
? new ArrayList<>(messages.subList(messages.size() - 3, messages.size()))
: new ArrayList<>(messages);
// Build new message list: compaction + preserved messages
List messagesAfterCompaction = new ArrayList<>();
messagesAfterCompaction.add(response.toParam());
messagesAfterCompaction.addAll(preservedMessages);
// Continue the request with the compacted context + preserved messages
MessageCreateParams continueParams = MessageCreateParams.builder()
.addBeta("compact-2026-01-12")
.model("claude-opus-4-7")
.maxTokens(4096L)
.messages(messagesAfterCompaction)
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder().build())
.build())
.build();
response = client.beta().messages().create(continueParams);
// Update our message list to reflect the compaction
messages.clear();
messages.addAll(messagesAfterCompaction);
}
// Append the final response
messages.add(response.toParam());
return response.content().stream()
.filter(block -> block.text().isPresent())
.map(block -> block.text().get().text())
.findFirst()
.orElse("");
}
public static void main(String[] args) {
System.out.println(chat("Help me build a Python web scraper"));
System.out.println(chat("Add support for JavaScript-rendered pages"));
System.out.println(chat("Now add rate limiting and error handling"));
}
}
```
```php PHP nocheck hidelines={1..4}
'user', 'content' => $userMessage];
$response = $client->beta->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [
[
'type' => 'compact_20260112',
'trigger' => ['type' => 'input_tokens', 'value' => 100000],
'pause_after_compaction' => true
]
]
]
);
if ($response->stopReason === 'compaction') {
$compactionBlock = $response->content[0];
$preserved = count($messages) >= 3
? array_slice($messages, -3)
: $messages;
$messagesAfterCompaction = array_merge(
[['role' => 'assistant', 'content' => [$compactionBlock]]],
$preserved
);
$response = $client->beta->messages->create(
maxTokens: 4096,
messages: $messagesAfterCompaction,
model: 'claude-opus-4-7',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [['type' => 'compact_20260112']]
]
);
$messages = $messagesAfterCompaction;
}
$messages[] = ['role' => 'assistant', 'content' => $response->content];
foreach ($response->content as $block) {
if ($block->type === 'text') {
return $block->text;
}
}
return '';
}
echo chat($client, $messages, "Help me build a Python web scraper") . "\n";
echo chat($client, $messages, "Add support for JavaScript-rendered pages") . "\n";
echo chat($client, $messages, "Now add rate limiting and error handling") . "\n";
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
messages = []
def chat(client, messages, user_message)
messages << { role: "user", content: user_message }
response = client.beta.messages.create(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages: messages,
context_management: {
edits: [
{
type: "compact_20260112",
trigger: { type: "input_tokens", value: 100000 },
pause_after_compaction: true
}
]
}
)
if response.stop_reason == :compaction
compaction_block = response.content[0]
preserved = messages.length >= 3 ? messages[-3..-1] : messages.dup
messages_after_compaction = [
{ role: "assistant", content: [compaction_block] }
] + preserved
response = client.beta.messages.create(
betas: ["compact-2026-01-12"],
model: "claude-opus-4-7",
max_tokens: 4096,
messages: messages_after_compaction,
context_management: {
edits: [{ type: "compact_20260112" }]
}
)
messages.clear
messages.concat(messages_after_compaction)
end
messages << { role: "assistant", content: response.content }
response.content.find { |block| block.type == :text }&.text || ""
end
puts chat(client, messages, "Help me build a Python web scraper")
puts chat(client, messages, "Add support for JavaScript-rendered pages")
puts chat(client, messages, "Now add rate limiting and error handling")
```
## Current limitations
- **Same model for summarization:** The model specified in your request is used for summarization. There is no option to use a different (for example, cheaper) model for the summary.
## Next steps
Explore a practical implementation that manages long-running conversations with instant session memory compaction using background threading and prompt caching.
Learn about context window sizes and management strategies.
Explore other strategies for managing conversation context like tool result clearing and thinking block clearing.
---
# Context editing
URL: https://platform.claude.com/docs/en/build-with-claude/context-editing
# Context editing
Automatically manage conversation context as it grows with context editing.
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
## Overview
For most use cases, [server-side compaction](/docs/en/build-with-claude/compaction) is the primary strategy for managing context in long-running conversations. The strategies on this page are useful for specific scenarios where you need more fine-grained control over what content is cleared.
Context editing allows you to selectively clear specific content from conversation history as it grows. Beyond optimizing costs and staying within limits, this is about actively curating what Claude sees: context is a finite resource with diminishing returns, and irrelevant content degrades model focus. Context editing gives you fine-grained runtime control over that curation. For the broader principles behind context management, see [Effective context engineering](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents). This page covers:
- **Tool result clearing** - Best for agentic workflows with heavy tool use where old tool results are no longer needed
- **Thinking block clearing** - For managing thinking blocks when using extended thinking, with options to preserve recent thinking for context continuity
- **Client-side SDK compaction** - An SDK-based alternative for summary-based context management (server-side compaction is generally preferred)
| Approach | Where it runs | Strategies | How it works |
|----------|---------------|------------|--------------|
| **Server-side** | API | Tool result clearing (`clear_tool_uses_20250919`) Thinking block clearing (`clear_thinking_20251015`) | Applied before the prompt reaches Claude. Clears specific content from conversation history. Each strategy can be configured independently. |
| **Client-side** | SDK | Compaction | Available in [Python, TypeScript, and Ruby SDKs](/docs/en/api/client-sdks) when using [`tool_runner`](/docs/en/agents-and-tools/tool-use/tool-runner). Generates a summary and replaces full conversation history. See [Client-side compaction](#client-side-compaction-sdk) below. |
## Server-side strategies
Context editing is in beta with support for tool result clearing and thinking block clearing. To enable it, use the beta header `context-management-2025-06-27` in your API requests.
Share feedback on this feature through the [feedback form](https://forms.gle/YXC2EKGMhjN1c4L88).
### Tool result clearing
The `clear_tool_uses_20250919` strategy clears tool results when conversation context grows beyond your configured threshold. This is particularly useful for agentic workflows with heavy tool use. Older tool results (like file contents or search results) are no longer needed once Claude has processed them.
When activated, the API automatically clears the oldest tool results in chronological order. The API replaces each cleared result with placeholder text so Claude knows it was removed. By default, only tool results are cleared. You can optionally clear both tool results and tool calls (the tool use parameters) by setting `clear_tool_inputs` to true.
### Thinking block clearing
The `clear_thinking_20251015` strategy manages `thinking` blocks in conversations when extended thinking is enabled. This strategy gives you control over thinking preservation: you can choose to keep more thinking blocks to maintain reasoning continuity, or clear them more aggressively to save context space.
**Default behavior:** The default varies by model class.
| Model class | Keep all prior thinking | Keep only the last turn's thinking |
| --- | --- | --- |
| Opus | Claude Opus 4.5 and later | Claude Opus 4.1 and earlier |
| Sonnet | Claude Sonnet 4.6 and later | Claude Sonnet 4.5 and earlier |
| Haiku | (none) | All models through Claude Haiku 4.5 |
Use this strategy to override the default. If your code runs across multiple model tiers, set `keep` explicitly rather than relying on the per-model default.
An assistant conversation turn may include multiple content blocks (e.g. when using tools) and multiple thinking blocks (e.g. with [interleaved thinking](/docs/en/build-with-claude/extended-thinking#interleaved-thinking)).
### Context editing happens server-side
Context editing is applied server-side before the prompt reaches Claude. Your client application maintains the full, unmodified conversation history. You do not need to sync your client state with the edited version. Continue managing your full conversation history locally as you normally would.
### Context editing and prompt caching
Context editing's interaction with [prompt caching](/docs/en/build-with-claude/prompt-caching) varies by strategy:
- **Tool result clearing**: Invalidates cached prompt prefixes when content is cleared. To account for this, clear enough tokens to make the cache invalidation worthwhile. Use the `clear_at_least` parameter to ensure a minimum number of tokens is cleared each time. You'll incur cache write costs each time content is cleared, but subsequent requests can reuse the newly cached prefix.
- **Thinking block clearing**: When thinking blocks are **kept** in context (not cleared), the prompt cache is preserved, enabling cache hits and reducing input token costs. When thinking blocks are **cleared**, the cache is invalidated at the point where clearing occurs. Configure the `keep` parameter based on whether you want to prioritize cache performance or context window availability.
## Supported models
Context editing is available on all supported Claude models.
## Tool result clearing usage
The simplest way to enable tool result clearing is to specify only the strategy type. All other [configuration options](#configuration-options-for-tool-result-clearing) use their default values:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--header "anthropic-beta: context-management-2025-06-27" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": "Search for recent developments in AI"
}
],
"tools": [
{
"type": "web_search_20250305",
"name": "web_search"
}
],
"context_management": {
"edits": [
{"type": "clear_tool_uses_20250919"}
]
}
}'
```
```bash CLI
ant beta:messages create --beta context-management-2025-06-27 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: Search for recent developments in AI
tools:
- type: web_search_20250305
name: web_search
context_management:
edits:
- type: clear_tool_uses_20250919
YAML
```
```python Python
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
messages=[{"role": "user", "content": "Search for recent developments in AI"}],
tools=[{"type": "web_search_20250305", "name": "web_search"}],
betas=["context-management-2025-06-27"],
context_management={"edits": [{"type": "clear_tool_uses_20250919"}]},
)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
const response = await anthropic.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{
role: "user",
content: "Search for recent developments in AI"
}
],
tools: [
{
type: "web_search_20250305",
name: "web_search"
}
],
context_management: {
edits: [{ type: "clear_tool_uses_20250919" }]
},
betas: ["context-management-2025-06-27"]
});
```
```csharp C# nocheck
using Anthropic;
using Anthropic.Models.Beta.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 4096,
Messages = [
new() { Role = Role.User, Content = "Search for recent developments in AI" }
],
Tools = [
new() { Type = "web_search_20250305", Name = "web_search" }
],
ContextManagement = new BetaContextManagementConfig()
{
Edits = [new BetaClearToolUses20250919Edit()]
},
Betas = ["context-management-2025-06-27"]
};
var response = await client.Beta.Messages.Create(parameters);
Console.WriteLine(response);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Search for recent developments in AI")),
},
Tools: []anthropic.BetaToolUnionParam{
{OfWebSearchTool20250305: &anthropic.BetaWebSearchTool20250305Param{}},
},
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfClearToolUses20250919: &anthropic.BetaClearToolUses20250919EditParam{}},
},
},
Betas: []anthropic.AnthropicBeta{
anthropic.AnthropicBetaContextManagement2025_06_27,
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..4,9..12,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaWebSearchTool20250305;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaClearToolUses20250919Edit;
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.messages.Model;
public class WebSearchExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(4096L)
.addUserMessage("Search for recent developments in AI")
.addTool(BetaWebSearchTool20250305.builder().build())
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaClearToolUses20250919Edit.builder().build())
.build())
.addBeta(AnthropicBeta.CONTEXT_MANAGEMENT_2025_06_27)
.build();
BetaMessage response = client.beta().messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4}
beta->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Search for recent developments in AI']
],
model: 'claude-opus-4-7',
betas: ['context-management-2025-06-27'],
tools: [
['type' => 'web_search_20250305', 'name' => 'web_search']
],
contextManagement: [
'edits' => [
['type' => 'clear_tool_uses_20250919']
]
],
);
echo $response;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{ role: "user", content: "Search for recent developments in AI" }
],
tools: [
{ type: "web_search_20250305", name: "web_search" }
],
context_management: {
edits: [
{ type: "clear_tool_uses_20250919" }
]
},
betas: ["context-management-2025-06-27"]
)
puts response
```
### Advanced configuration
You can customize the tool result clearing behavior with additional parameters:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--header "anthropic-beta: context-management-2025-06-27" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": "Create a simple command line calculator app using Python"
}
],
"tools": [
{
"type": "text_editor_20250728",
"name": "str_replace_based_edit_tool",
"max_characters": 10000
},
{
"type": "web_search_20250305",
"name": "web_search",
"max_uses": 3
}
],
"context_management": {
"edits": [
{
"type": "clear_tool_uses_20250919",
"trigger": {
"type": "input_tokens",
"value": 30000
},
"keep": {
"type": "tool_uses",
"value": 3
},
"clear_at_least": {
"type": "input_tokens",
"value": 5000
},
"exclude_tools": ["web_search"]
}
]
}
}'
```
```bash CLI
ant beta:messages create --beta context-management-2025-06-27 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: Create a simple command line calculator app using Python
tools:
- type: text_editor_20250728
name: str_replace_based_edit_tool
max_characters: 10000
- type: web_search_20250305
name: web_search
max_uses: 3
context_management:
edits:
- type: clear_tool_uses_20250919
trigger:
type: input_tokens
value: 30000
keep:
type: tool_uses
value: 3
clear_at_least:
type: input_tokens
value: 5000
exclude_tools:
- web_search
YAML
```
```python Python
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
messages=[
{
"role": "user",
"content": "Create a simple command line calculator app using Python",
}
],
tools=[
{
"type": "text_editor_20250728",
"name": "str_replace_based_edit_tool",
"max_characters": 10000,
},
{"type": "web_search_20250305", "name": "web_search", "max_uses": 3},
],
betas=["context-management-2025-06-27"],
context_management={
"edits": [
{
"type": "clear_tool_uses_20250919",
# Trigger clearing when threshold is exceeded
"trigger": {"type": "input_tokens", "value": 30000},
# Number of tool uses to keep after clearing
"keep": {"type": "tool_uses", "value": 3},
# Optional: Clear at least this many tokens
"clear_at_least": {"type": "input_tokens", "value": 5000},
# Exclude these tools from being cleared
"exclude_tools": ["web_search"],
}
]
},
)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
const response = await anthropic.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
{
role: "user",
content: "Create a simple command line calculator app using Python"
}
],
tools: [
{
type: "text_editor_20250728",
name: "str_replace_based_edit_tool",
max_characters: 10000
},
{
type: "web_search_20250305",
name: "web_search",
max_uses: 3
}
],
betas: ["context-management-2025-06-27"],
context_management: {
edits: [
{
type: "clear_tool_uses_20250919",
// Trigger clearing when threshold is exceeded
trigger: {
type: "input_tokens",
value: 30000
},
// Number of tool uses to keep after clearing
keep: {
type: "tool_uses",
value: 3
},
// Optional: Clear at least this many tokens
clear_at_least: {
type: "input_tokens",
value: 5000
},
// Exclude these tools from being cleared
exclude_tools: ["web_search"]
}
]
}
});
```
```csharp C# nocheck
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 4096,
Messages = new List
{
new() { Role = Role.User, Content = "Create a simple command line calculator app using Python" }
},
Tools = new List
## Thinking block clearing usage
Enable thinking block clearing to manage context and prompt caching effectively when extended thinking is enabled:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--header "anthropic-beta: context-management-2025-06-27" \
--data '{
"model": "claude-opus-4-6",
"max_tokens": 16000,
"messages": [{"role": "user", "content": "Hello"}],
"thinking": {
"type": "enabled",
"budget_tokens": 10000
},
"context_management": {
"edits": [
{
"type": "clear_thinking_20251015",
"keep": {
"type": "thinking_turns",
"value": 2
}
}
]
}
}'
```
```bash CLI
ant beta:messages create --beta context-management-2025-06-27 <<'YAML'
model: claude-opus-4-6
max_tokens: 16000
messages:
- role: user
content: Hello
thinking:
type: enabled
budget_tokens: 10000
context_management:
edits:
- type: clear_thinking_20251015
keep:
type: thinking_turns
value: 2
YAML
```
```python Python nocheck
response = client.beta.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
messages=[...],
thinking={"type": "enabled", "budget_tokens": 10000},
betas=["context-management-2025-06-27"],
context_management={
"edits": [
{
"type": "clear_thinking_20251015",
"keep": {"type": "thinking_turns", "value": 2},
}
]
},
)
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
const response = await anthropic.beta.messages.create({
model: "claude-opus-4-6",
max_tokens: 16000,
messages: [
// ...
],
thinking: {
type: "enabled",
budget_tokens: 10000
},
betas: ["context-management-2025-06-27"],
context_management: {
edits: [
{
type: "clear_thinking_20251015",
keep: {
type: "thinking_turns",
value: 2
}
}
]
}
});
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = "claude-opus-4-6",
MaxTokens = 16000,
Messages = [],
Thinking = new BetaThinkingParam
{
Type = "enabled",
BudgetTokens = 10000
},
Betas = ["context-management-2025-06-27"],
ContextManagement = new BetaContextManagementConfig()
{
Edits = [
new BetaClearThinking20251015Edit()
{
Keep = new BetaThinkingTurnsKeep
{
Type = "thinking_turns",
Value = 2
}
}
]
}
};
var message = await client.Beta.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_6,
MaxTokens: 16000,
Messages: []anthropic.BetaMessageParam{},
Thinking: anthropic.BetaThinkingConfigParamOfEnabled(10000),
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaContextManagement2025_06_27},
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfClearThinking20251015: &anthropic.BetaClearThinking20251015EditParam{
Keep: anthropic.BetaClearThinking20251015EditKeepUnionParam{
OfThinkingTurns: &anthropic.BetaThinkingTurnsParam{
Value: 2,
},
},
}},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java nocheck hidelines={1..4,10..13,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaThinkingConfigEnabled;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaClearThinking20251015Edit;
import com.anthropic.models.beta.messages.BetaThinkingTurns;
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.messages.Model;
public class Main {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_6)
.maxTokens(16000L)
.thinking(BetaThinkingConfigEnabled.builder()
.budgetTokens(10000L)
.build())
.addBeta(AnthropicBeta.CONTEXT_MANAGEMENT_2025_06_27)
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaClearThinking20251015Edit.builder()
.keep(BetaThinkingTurns.builder()
.value(2L)
.build())
.build())
.build())
.build();
BetaMessage response = client.beta().messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4} nocheck
beta->messages->create(
maxTokens: 16000,
messages: [/* ... */],
model: 'claude-opus-4-6',
betas: ['context-management-2025-06-27'],
thinking: [
'type' => 'enabled',
'budget_tokens' => 10000
],
contextManagement: [
'edits' => [
[
'type' => 'clear_thinking_20251015',
'keep' => [
'type' => 'thinking_turns',
'value' => 2
]
]
]
],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
messages = [{ role: "user", content: "Explain quantum computing in simple terms" }]
response = client.beta.messages.create(
model: "claude-opus-4-6",
max_tokens: 16000,
messages: messages,
thinking: {
type: "enabled",
budget_tokens: 10000
},
betas: ["context-management-2025-06-27"],
context_management: {
edits: [
{
type: "clear_thinking_20251015",
keep: {
type: "thinking_turns",
value: 2
}
}
]
}
)
puts response
```
### Configuration options for thinking block clearing
The `clear_thinking_20251015` strategy supports the following configuration:
| Configuration option | Default | Description |
|---------------------|---------|-------------|
| `keep` | Model-specific | Defines how many recent assistant turns with thinking blocks to preserve. Use `{type: "thinking_turns", value: N}` where N must be > 0 to keep the last N turns, or `"all"` to keep all thinking blocks. Opus 4.5+ and Sonnet 4.6+: all turns. Earlier Opus/Sonnet and all Haiku: last turn only. |
**Example configurations:**
Keep thinking blocks from the last 3 assistant turns:
```json
{
"type": "clear_thinking_20251015",
"keep": {
"type": "thinking_turns",
"value": 3
}
}
```
Keep all thinking blocks (maximizes cache hits):
```json
{
"type": "clear_thinking_20251015",
"keep": "all"
}
```
### Combining strategies
You can use both thinking block clearing and tool result clearing together:
When using multiple strategies, the `clear_thinking_20251015` strategy must be listed first in the `edits` array.
```bash CLI
ant beta:messages create --beta context-management-2025-06-27 <<'YAML'
model: claude-opus-4-6
max_tokens: 16000
thinking:
type: enabled
budget_tokens: 10000
messages:
- role: user
content: Hello
tools:
- type: web_search_20250305
name: web_search
context_management:
edits:
- type: clear_thinking_20251015
keep:
type: thinking_turns
value: 2
- type: clear_tool_uses_20250919
trigger:
type: input_tokens
value: 50000
keep:
type: tool_uses
value: 5
YAML
```
```python Python nocheck
response = client.beta.messages.create(
model="claude-opus-4-6",
max_tokens=16000,
messages=[...],
thinking={"type": "enabled", "budget_tokens": 10000},
tools=[...],
betas=["context-management-2025-06-27"],
context_management={
"edits": [
{
"type": "clear_thinking_20251015",
"keep": {"type": "thinking_turns", "value": 2},
},
{
"type": "clear_tool_uses_20250919",
"trigger": {"type": "input_tokens", "value": 50000},
"keep": {"type": "tool_uses", "value": 5},
},
]
},
)
```
```typescript TypeScript nocheck
const response = await anthropic.beta.messages.create({
model: "claude-opus-4-6",
max_tokens: 16000,
messages: [
// ...
],
thinking: {
type: "enabled",
budget_tokens: 10000
},
tools: [
// ...
],
betas: ["context-management-2025-06-27"],
context_management: {
edits: [
{
type: "clear_thinking_20251015",
keep: {
type: "thinking_turns",
value: 2
}
},
{
type: "clear_tool_uses_20250919",
trigger: {
type: "input_tokens",
value: 50000
},
keep: {
type: "tool_uses",
value: 5
}
}
]
}
});
```
```csharp C# nocheck
using Anthropic;
using Anthropic.Models.Beta.Messages;
public class Program
{
public static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_6,
MaxTokens = 16000,
Messages = [],
Thinking = new BetaThinkingParam
{
Type = "enabled",
BudgetTokens = 10000
},
Tools = [],
Betas = ["context-management-2025-06-27"],
ContextManagement = new BetaContextManagementConfig()
{
Edits = [
new BetaClearThinking20251015Edit()
{
Keep = new BetaThinkingTurnsKeep
{
Type = "thinking_turns",
Value = 2
}
},
new BetaClearToolUses20250919Edit()
{
Trigger = new BetaInputTokensTrigger
{
Type = "input_tokens",
Value = 50000
},
Keep = new BetaToolUsesKeep
{
Type = "tool_uses",
Value = 5
}
}
]
}
};
var message = await client.Beta.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_6,
MaxTokens: 16000,
Messages: []anthropic.BetaMessageParam{},
Thinking: anthropic.BetaThinkingConfigParamOfEnabled(10000),
Tools: []anthropic.BetaToolUnionParam{},
Betas: []anthropic.AnthropicBeta{
anthropic.AnthropicBetaContextManagement2025_06_27,
},
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfClearThinking20251015: &anthropic.BetaClearThinking20251015EditParam{
Keep: anthropic.BetaClearThinking20251015EditKeepUnionParam{
OfThinkingTurns: &anthropic.BetaThinkingTurnsParam{
Value: 2,
},
},
}},
{OfClearToolUses20250919: &anthropic.BetaClearToolUses20250919EditParam{
Trigger: anthropic.BetaClearToolUses20250919EditTriggerUnionParam{
OfInputTokens: &anthropic.BetaInputTokensTriggerParam{
Value: 50000,
},
},
Keep: anthropic.BetaToolUsesKeepParam{
Value: 5,
},
}},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java nocheck hidelines={1..4,13..16,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaThinkingConfigEnabled;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaClearThinking20251015Edit;
import com.anthropic.models.beta.messages.BetaClearToolUses20250919Edit;
import com.anthropic.models.beta.messages.BetaThinkingTurns;
import com.anthropic.models.beta.messages.BetaInputTokensTrigger;
import com.anthropic.models.beta.messages.BetaToolUsesKeep;
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.messages.Model;
public class ContextManagementExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_6)
.maxTokens(16000L)
.thinking(BetaThinkingConfigEnabled.builder()
.budgetTokens(10000L)
.build())
.addBeta(AnthropicBeta.CONTEXT_MANAGEMENT_2025_06_27)
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaClearThinking20251015Edit.builder()
.keep(BetaThinkingTurns.builder()
.value(2L)
.build())
.build())
.addEdit(BetaClearToolUses20250919Edit.builder()
.trigger(BetaInputTokensTrigger.builder()
.value(50000L)
.build())
.keep(BetaToolUsesKeep.builder()
.value(5L)
.build())
.build())
.build())
.build();
BetaMessage response = client.beta().messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4} nocheck
beta->messages->create(
maxTokens: 16000,
messages: [/* ... */],
model: 'claude-opus-4-6',
betas: ['context-management-2025-06-27'],
thinking: ['type' => 'enabled', 'budget_tokens' => 10000],
tools: [/* ... */],
contextManagement: [
'edits' => [
[
'type' => 'clear_thinking_20251015',
'keep' => [
'type' => 'thinking_turns',
'value' => 2
]
],
[
'type' => 'clear_tool_uses_20250919',
'trigger' => [
'type' => 'input_tokens',
'value' => 50000
],
'keep' => [
'type' => 'tool_uses',
'value' => 5
]
]
]
],
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
messages = [{ role: "user", content: "Explain quantum computing in simple terms" }]
response = client.beta.messages.create(
model: "claude-opus-4-6",
max_tokens: 16000,
messages: messages,
thinking: {
type: "enabled",
budget_tokens: 10000
},
tools: [
# ...
],
betas: ["context-management-2025-06-27"],
context_management: {
edits: [
{
type: "clear_thinking_20251015",
keep: {
type: "thinking_turns",
value: 2
}
},
{
type: "clear_tool_uses_20250919",
trigger: {
type: "input_tokens",
value: 50000
},
keep: {
type: "tool_uses",
value: 5
}
}
]
}
)
puts response
```
## Configuration options for tool result clearing
| Configuration option | Default | Description |
|---------------------|---------|-------------|
| `trigger` | 100,000 input tokens | Defines when the context editing strategy activates. Once the prompt exceeds this threshold, clearing will begin. You can specify this value in either `input_tokens` or `tool_uses`. |
| `keep` | 3 tool uses | Defines how many recent tool use/result pairs to keep after clearing occurs. The API removes the oldest tool interactions first, preserving the most recent ones. |
| `clear_at_least` | None | Ensures a minimum number of tokens is cleared each time the strategy activates. If the API can't clear at least the specified amount, the strategy will not be applied. This helps determine if context clearing is worth breaking your prompt cache. |
| `exclude_tools` | None | List of tool names whose tool uses and results should never be cleared. Useful for preserving important context. |
| `clear_tool_inputs` | `false` | Controls whether the tool call parameters are cleared along with the tool results. By default, only the tool results are cleared while keeping Claude's original tool calls visible. |
## Context editing response
You can see which context edits were applied to your request using the `context_management` response field, along with helpful statistics about the content and input tokens cleared.
```json Output
{
"id": "msg_013Zva2CMHLNnXjNJJKqJ2EF",
"type": "message",
"role": "assistant",
"content": [
// ...
],
"usage": {
// ...
},
"context_management": {
"applied_edits": [
// When using `clear_thinking_20251015`
{
"type": "clear_thinking_20251015",
"cleared_thinking_turns": 3,
"cleared_input_tokens": 15000
},
// When using `clear_tool_uses_20250919`
{
"type": "clear_tool_uses_20250919",
"cleared_tool_uses": 8,
"cleared_input_tokens": 50000
}
]
}
}
```
For streaming responses, the context edits will be included in the final `message_delta` event:
```json Streaming Response
{
"type": "message_delta",
"delta": {
"stop_reason": "end_turn",
"stop_sequence": null
},
"usage": {
"output_tokens": 1024
},
"context_management": {
"applied_edits": [
// ...
]
}
}
```
## Token counting
The [token counting](/docs/en/build-with-claude/token-counting) endpoint supports context management, allowing you to preview how many tokens your prompt will use after context editing is applied.
```bash cURL
curl https://api.anthropic.com/v1/messages/count_tokens \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--header "anthropic-beta: context-management-2025-06-27" \
--data '{
"model": "claude-opus-4-7",
"messages": [
{
"role": "user",
"content": "Continue our conversation..."
}
],
"tools": [],
"context_management": {
"edits": [
{
"type": "clear_tool_uses_20250919",
"trigger": {
"type": "input_tokens",
"value": 30000
},
"keep": {
"type": "tool_uses",
"value": 5
}
}
]
}
}'
```
```bash CLI
cat > request.yaml <<'YAML'
model: claude-opus-4-7
messages:
- role: user
content: Continue our conversation...
tools: []
context_management:
edits:
- type: clear_tool_uses_20250919
trigger:
type: input_tokens
value: 30000
keep:
type: tool_uses
value: 5
YAML
ORIGINAL=$(ant beta:messages count-tokens \
--beta context-management-2025-06-27 \
--transform context_management.original_input_tokens \
--raw-output < request.yaml)
INPUT_TOKENS=$(ant beta:messages count-tokens \
--beta context-management-2025-06-27 \
--transform input_tokens --raw-output < request.yaml)
printf 'Original tokens: %s\n' "$ORIGINAL"
printf 'After clearing: %s\n' "$INPUT_TOKENS"
printf 'Savings: %s tokens\n' "$((ORIGINAL - INPUT_TOKENS))"
```
```python Python nocheck
response = client.beta.messages.count_tokens(
model="claude-opus-4-7",
messages=[{"role": "user", "content": "Continue our conversation..."}],
tools=[...], # Your tool definitions
betas=["context-management-2025-06-27"],
context_management={
"edits": [
{
"type": "clear_tool_uses_20250919",
"trigger": {"type": "input_tokens", "value": 30000},
"keep": {"type": "tool_uses", "value": 5},
}
]
},
)
print(f"Original tokens: {response.context_management['original_input_tokens']}")
print(f"After clearing: {response.input_tokens}")
print(
f"Savings: {response.context_management['original_input_tokens'] - response.input_tokens} tokens"
)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
const response = await anthropic.beta.messages.countTokens({
model: "claude-opus-4-7",
messages: [
{
role: "user",
content: "Continue our conversation..."
}
],
tools: [
// ...
], // Your tool definitions
betas: ["context-management-2025-06-27"],
context_management: {
edits: [
{
type: "clear_tool_uses_20250919",
trigger: {
type: "input_tokens",
value: 30000
},
keep: {
type: "tool_uses",
value: 5
}
}
]
}
});
console.log(`Original tokens: ${response.context_management?.original_input_tokens}`);
console.log(`After clearing: ${response.input_tokens}`);
console.log(
`Savings: ${
(response.context_management?.original_input_tokens || 0) - response.input_tokens
} tokens`
);
```
```csharp C# nocheck
using Anthropic;
using Anthropic.Models.Beta.Messages;
var client = new AnthropicClient
{
ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")
};
var parameters = new BetaMessageTokensCountParams
{
Model = "claude-opus-4-7",
Messages = [new() { Role = Role.User, Content = "Continue our conversation..." }],
Betas = ["context-management-2025-06-27"],
ContextManagement = new BetaContextManagementConfig
{
Edits = [
new BetaClearToolUses20250919Edit
{
Trigger = new BetaInputTokensTrigger { Value = 30000 },
Keep = new BetaToolUsesKeep { Value = 5 }
}
]
}
};
var response = await client.Beta.Messages.CountTokens(parameters);
Console.WriteLine($"Original tokens: {response.ContextManagement?.OriginalInputTokens}");
Console.WriteLine($"After clearing: {response.InputTokens}");
Console.WriteLine($"Savings: {(response.ContextManagement?.OriginalInputTokens ?? 0) - response.InputTokens} tokens");
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.CountTokens(context.TODO(), anthropic.BetaMessageCountTokensParams{
Model: anthropic.ModelClaudeOpus4_7,
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Continue our conversation...")),
},
Betas: []anthropic.AnthropicBeta{
anthropic.AnthropicBetaContextManagement2025_06_27,
},
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfClearToolUses20250919: &anthropic.BetaClearToolUses20250919EditParam{
Trigger: anthropic.BetaClearToolUses20250919EditTriggerUnionParam{
OfInputTokens: &anthropic.BetaInputTokensTriggerParam{
Value: 30000,
},
},
Keep: anthropic.BetaToolUsesKeepParam{
Value: 5,
},
}},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Original tokens: %d\n", response.ContextManagement.OriginalInputTokens)
fmt.Printf("After clearing: %d\n", response.InputTokens)
fmt.Printf("Savings: %d tokens\n", response.ContextManagement.OriginalInputTokens-response.InputTokens)
}
```
```java Java hidelines={1..2,10..13,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.BetaMessageTokensCount;
import com.anthropic.models.beta.messages.MessageCountTokensParams;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaClearToolUses20250919Edit;
import com.anthropic.models.beta.messages.BetaInputTokensTrigger;
import com.anthropic.models.beta.messages.BetaToolUsesKeep;
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.messages.Model;
public class TokenCountExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCountTokensParams params = MessageCountTokensParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.addUserMessage("Continue our conversation...")
.addBeta(AnthropicBeta.CONTEXT_MANAGEMENT_2025_06_27)
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaClearToolUses20250919Edit.builder()
.trigger(BetaInputTokensTrigger.builder()
.value(30000L)
.build())
.keep(BetaToolUsesKeep.builder()
.value(5L)
.build())
.build())
.build())
.build();
BetaMessageTokensCount response = client.beta().messages().countTokens(params);
System.out.println("Original tokens: " + response.contextManagement().get().originalInputTokens());
System.out.println("After clearing: " + response.inputTokens());
System.out.println("Savings: " + (response.contextManagement().get().originalInputTokens() - response.inputTokens()) + " tokens");
}
}
```
```php PHP hidelines={1..4}
beta->messages->countTokens(
messages: [
['role' => 'user', 'content' => 'Continue our conversation...']
],
model: 'claude-opus-4-7',
betas: ['context-management-2025-06-27'],
contextManagement: [
'edits' => [
[
'type' => 'clear_tool_uses_20250919',
'trigger' => [
'type' => 'input_tokens',
'value' => 30000
],
'keep' => [
'type' => 'tool_uses',
'value' => 5
]
]
]
],
);
echo "Original tokens: " . $response->contextManagement->originalInputTokens . "\n";
echo "After clearing: " . $response->inputTokens . "\n";
echo "Savings: " . ($response->contextManagement->originalInputTokens - $response->inputTokens) . " tokens\n";
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.beta.messages.count_tokens(
model: "claude-opus-4-7",
messages: [
{ role: "user", content: "Continue our conversation..." }
],
betas: ["context-management-2025-06-27"],
context_management: {
edits: [
{
type: "clear_tool_uses_20250919",
trigger: {
type: "input_tokens",
value: 30000
},
keep: {
type: "tool_uses",
value: 5
}
}
]
}
)
puts "Original tokens: #{response.context_management.original_input_tokens}"
puts "After clearing: #{response.input_tokens}"
puts "Savings: #{response.context_management.original_input_tokens - response.input_tokens} tokens"
```
```json Output
{
"input_tokens": 25000,
"context_management": {
"original_input_tokens": 70000
}
}
```
The response shows both the final token count after context management is applied (`input_tokens`) and the original token count before any clearing occurred (`original_input_tokens`).
## Using with the memory tool
Context editing can be combined with the [memory tool](/docs/en/agents-and-tools/tool-use/memory-tool). When your conversation context approaches the configured clearing threshold, Claude receives an automatic warning to preserve important information. This enables Claude to save tool results or context to its memory files before they're cleared from the conversation history.
This combination allows you to:
- **Preserve important context**: Claude can write essential information from tool results to memory files before those results are cleared
- **Maintain long-running workflows**: Enable agentic workflows that would otherwise exceed context limits by offloading information to persistent storage
- **Access information on demand**: Claude can look up previously cleared information from memory files when needed, rather than keeping everything in the active context window
For example, in a file editing workflow where Claude performs many operations, Claude can summarize completed changes to memory files as the context grows. When tool results are cleared, Claude retains access to that information through its memory system and can continue working effectively.
To use both features together, enable them in your API request:
```bash CLI
ant beta:messages create --beta context-management-2025-06-27 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
messages:
- role: user
content: Hello
tools:
- type: memory_20250818
name: memory
context_management:
edits:
- type: clear_tool_uses_20250919
YAML
```
```python Python nocheck
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
messages=[...],
tools=[
{"type": "memory_20250818", "name": "memory"},
# Your other tools
],
betas=["context-management-2025-06-27"],
context_management={"edits": [{"type": "clear_tool_uses_20250919"}]},
)
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
const response = await anthropic.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
// ...
],
tools: [
{
type: "memory_20250818",
name: "memory"
}
// Your other tools
],
betas: ["context-management-2025-06-27"],
context_management: {
edits: [{ type: "clear_tool_uses_20250919" }]
}
});
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Messages = [],
Tools = [
new() {
Type = "memory_20250818",
Name = "memory"
}
],
Betas = ["context-management-2025-06-27"],
ContextManagement = new BetaContextManagementConfig() {
Edits = [
new BetaClearToolUses20250919Edit()
]
}
};
var response = await client.Beta.Messages.Create(parameters);
Console.WriteLine(response);
}
}
```
```go Go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 4096,
Messages: []anthropic.BetaMessageParam{},
Tools: []anthropic.BetaToolUnionParam{
{OfMemoryTool20250818: &anthropic.BetaMemoryTool20250818Param{}},
},
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaContextManagement2025_06_27},
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfClearToolUses20250919: &anthropic.BetaClearToolUses20250919EditParam{}},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java nocheck hidelines={1..4,9..12,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaMemoryTool20250818;
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaClearToolUses20250919Edit;
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.messages.Model;
public class Main {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(4096L)
.addTool(BetaMemoryTool20250818.builder().build())
.addBeta(AnthropicBeta.CONTEXT_MANAGEMENT_2025_06_27)
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaClearToolUses20250919Edit.builder().build())
.build())
.build();
BetaMessage response = client.beta().messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4} nocheck
beta->messages->create(
maxTokens: 4096,
messages: [],
model: 'claude-opus-4-7',
betas: ['context-management-2025-06-27'],
tools: [
[
'type' => 'memory_20250818',
'name' => 'memory'
]
],
contextManagement: [
'edits' => [
['type' => 'clear_tool_uses_20250919']
]
],
);
echo $response;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
messages: [
# ...
],
tools: [
{
type: "memory_20250818",
name: "memory"
}
],
betas: ["context-management-2025-06-27"],
context_management: {
edits: [
{ type: "clear_tool_uses_20250919" }
]
}
)
puts response
```
For the full memory tool reference including commands and examples, see [Memory tool](/docs/en/agents-and-tools/tool-use/memory-tool).
## Client-side compaction (SDK)
**Anthropic recommends server-side compaction over SDK compaction.** [Server-side compaction](/docs/en/build-with-claude/compaction) handles context management automatically with less integration complexity, better token usage calculation, and no client-side limitations. Use SDK compaction only if you specifically need client-side control over the summarization process.
Compaction is available in the [Python, TypeScript, and Ruby SDKs](/docs/en/api/client-sdks) when using the [`tool_runner` method](/docs/en/agents-and-tools/tool-use/tool-runner).
Compaction is an SDK feature that automatically manages conversation context by generating summaries when token usage grows too large. Unlike server-side context editing strategies that clear content, compaction instructs Claude to summarize the conversation history, then replaces the full history with that summary. This allows Claude to continue working on long-running tasks that would otherwise exceed the [context window](/docs/en/build-with-claude/context-windows).
### How compaction works
When compaction is enabled, the SDK monitors token usage after each model response:
1. **Threshold check:** The SDK calculates total tokens as `input_tokens + cache_creation_input_tokens + cache_read_input_tokens + output_tokens`.
2. **Summary generation:** When the threshold is exceeded, a summary prompt is injected as a user turn, and Claude generates a structured summary wrapped in `` tags.
3. **Context replacement:** The SDK extracts the summary and replaces the entire message history with it.
4. **Continuation:** The conversation resumes from the summary, with Claude picking up where it left off.
### Using compaction
Add `compaction_control` to your `tool_runner` call to enable automatic summarization when token usage exceeds the threshold.
The CLI does not include a `tool_runner` helper. Use [server-side compaction](/docs/en/build-with-claude/compaction) instead, which handles compaction on Anthropic's servers without SDK-side integration.
```python Python hidelines={1..10}
import anthropic
from anthropic import beta_tool
@beta_tool
def read_file(path: str) -> str:
"""Read the contents of a file."""
return "file contents..."
client = anthropic.Anthropic()
runner = client.beta.messages.tool_runner(
model="claude-opus-4-7",
max_tokens=1024,
tools=[read_file],
messages=[{"role": "user", "content": "What's in config.json?"}],
compaction_control={"enabled": True, "context_token_threshold": 100000},
)
for message in runner:
print(f"Tokens used: {message.usage.input_tokens}")
```
```typescript TypeScript hidelines={1..14}
import Anthropic from "@anthropic-ai/sdk";
import { betaTool } from "@anthropic-ai/sdk/helpers/beta/json-schema";
const readFile = betaTool({
name: "read_file",
description: "Read the contents of a file",
inputSchema: {
type: "object",
properties: { path: { type: "string" } },
required: ["path"]
},
run: async () => "file contents..."
});
const client = new Anthropic();
const runner = client.beta.messages.toolRunner({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [readFile],
messages: [{ role: "user", content: "What's in config.json?" }],
compactionControl: { enabled: true, contextTokenThreshold: 100000 }
});
for await (const message of runner) {
console.log(`Tokens used: ${message.usage.input_tokens}`);
}
```
The C# SDK does not include a `tool_runner` helper. Use [server-side compaction](/docs/en/build-with-claude/compaction) instead, which handles compaction on Anthropic's servers without SDK-side integration.
The Go SDK does not include a `tool_runner` helper. Use [server-side compaction](/docs/en/build-with-claude/compaction) instead, which handles compaction on Anthropic's servers without SDK-side integration.
The Java SDK does not include a `tool_runner` helper. Use [server-side compaction](/docs/en/build-with-claude/compaction) instead, which handles compaction on Anthropic's servers without SDK-side integration.
The PHP SDK does not include a `tool_runner` helper. Use [server-side compaction](/docs/en/build-with-claude/compaction) instead, which handles compaction on Anthropic's servers without SDK-side integration.
```ruby Ruby hidelines={1..15}
require "anthropic"
class ReadFileInput < Anthropic::BaseModel
required :path, String, doc: "Path to the file"
end
class ReadFile < Anthropic::BaseTool
doc "Read the contents of a file"
input_schema ReadFileInput
def call(input)
"file contents..."
end
end
client = Anthropic::Client.new
runner = client.beta.messages.tool_runner(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [ReadFile.new],
messages: [{ role: "user", content: "What's in config.json?" }],
compaction_control: { enabled: true, context_token_threshold: 100000 }
)
runner.each_message do |message|
puts "Tokens used: #{message.usage.input_tokens}"
end
```
#### What happens during compaction
As the conversation grows, the message history accumulates:
**Before compaction (approaching 100k tokens):**
```json
[
{ "role": "user", "content": "Analyze all files and write a report..." },
{ "role": "assistant", "content": "I'll help. Let me start by reading..." },
{
"role": "user",
"content": [{ "type": "tool_result", "tool_use_id": "...", "content": "..." }]
},
{ "role": "assistant", "content": "Based on file1.txt, I see..." },
{
"role": "user",
"content": [{ "type": "tool_result", "tool_use_id": "...", "content": "..." }]
},
{ "role": "assistant", "content": "After analyzing file2.txt..." }
// ... 50 more exchanges like this ...
]
```
When tokens exceed the threshold, the SDK injects a summary request and Claude generates a summary. The entire history is then replaced:
**After compaction (back to ~2-3k tokens):**
```json
[
{
"role": "assistant",
"content": "# Task Overview\nThe user requested analysis of directory files to produce a summary report...\n\n# Current State\nAnalyzed 52 files across 3 subdirectories. Key findings documented in report.md...\n\n# Important Discoveries\n- Configuration files use YAML format\n- Found 3 deprecated dependencies\n- Test coverage at 67%\n\n# Next Steps\n1. Analyze remaining files in /src/legacy\n2. Complete final report sections...\n\n# Context to Preserve\nUser prefers markdown format with executive summary first..."
}
]
```
Claude continues working from this summary as if it were the original conversation history.
### Configuration options
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `enabled` | boolean | Yes | - | Whether to enable automatic compaction |
| `context_token_threshold` | number | No | 100,000 | Token count at which compaction triggers |
| `model` | string | No | Same as main model | Model to use for generating summaries |
| `summary_prompt` | string | No | See below | Custom prompt for summary generation |
#### Choosing a token threshold
The threshold determines when compaction occurs. A lower threshold means more frequent compactions with smaller context windows. A higher threshold allows more context but risks hitting limits.
```python Python
# More frequent compaction for memory-constrained scenarios
compaction_control = {"enabled": True, "context_token_threshold": 50000}
# Less frequent compaction when you need more context
compaction_control = {"enabled": True, "context_token_threshold": 150000}
```
```typescript TypeScript hidelines={1,7..9,-1}
const _ = {
// More frequent compaction for memory-constrained scenarios
compactionControl: {
enabled: true,
contextTokenThreshold: 50000
}
};
const __ = {
// Less frequent compaction when you need more context
compactionControl: {
enabled: true,
contextTokenThreshold: 150000
}
};
```
#### Using a different model for summaries
You can use a faster or cheaper model for generating summaries:
```python Python
compaction_control = {
"enabled": True,
"context_token_threshold": 100000,
"model": "claude-haiku-4-5",
}
```
```typescript TypeScript hidelines={1,-1}
const _ = {
compactionControl: {
enabled: true,
contextTokenThreshold: 100000,
model: "claude-haiku-4-5"
}
};
```
#### Custom summary prompts
You can provide a custom prompt for domain-specific needs. Your prompt should instruct Claude to wrap its summary in `` tags.
```python Python
compaction_control = {
"enabled": True,
"context_token_threshold": 100000,
"summary_prompt": """Summarize the research conducted so far, including:
- Sources consulted and key findings
- Questions answered and remaining unknowns
- Recommended next steps
Wrap your summary in tags.""",
}
```
```typescript TypeScript hidelines={1,-1}
const _ = {
compactionControl: {
enabled: true,
contextTokenThreshold: 100000,
summaryPrompt: `Summarize the research conducted so far, including:
- Sources consulted and key findings
- Questions answered and remaining unknowns
- Recommended next steps
Wrap your summary in tags.`
}
};
```
### Default summary prompt
The built-in summary prompt instructs Claude to create a structured continuation summary including:
1. **Task Overview:** The user's core request, success criteria, and constraints.
2. **Current State:** What has been completed, files modified, and artifacts produced.
3. **Important Discoveries:** Technical constraints, decisions made, errors resolved, and failed approaches.
4. **Next Steps:** Specific actions needed, blockers, and priority order.
5. **Context to Preserve:** User preferences, domain-specific details, and commitments made.
This structure enables Claude to resume work efficiently without losing important context or repeating mistakes.
```text
You have been working on the task described above but have not yet completed it. Write a continuation summary that will allow you (or another instance of yourself) to resume work efficiently in a future context window where the conversation history will be replaced with this summary. Your summary should be structured, concise, and actionable. Include:
1. Task Overview
The user's core request and success criteria
Any clarifications or constraints they specified
2. Current State
What has been completed so far
Files created, modified, or analyzed (with paths if relevant)
Key outputs or artifacts produced
3. Important Discoveries
Technical constraints or requirements uncovered
Decisions made and their rationale
Errors encountered and how they were resolved
What approaches were tried that didn't work (and why)
4. Next Steps
Specific actions needed to complete the task
Any blockers or open questions to resolve
Priority order if multiple steps remain
5. Context to Preserve
User preferences or style requirements
Domain-specific details that aren't obvious
Any promises made to the user
Be concise but complete—err on the side of including information that would prevent duplicate work or repeated mistakes. Write in a way that enables immediate resumption of the task.
Wrap your summary in tags.
```
### Limitations
#### Server-side tools
Compaction requires special consideration when using server-side tools such as [web search](/docs/en/agents-and-tools/tool-use/web-search-tool) or [web fetch](/docs/en/agents-and-tools/tool-use/web-fetch-tool).
When using server-side tools, the SDK may incorrectly calculate token usage, causing compaction to trigger at the wrong time.
For example, after a web search operation, the API response might show:
```json Output
{
"usage": {
"input_tokens": 63000,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 270000,
"output_tokens": 1400
}
}
```
The SDK calculates total usage as 63,000 + 0 + 270,000 + 1,400 = 334,400 tokens. However, the `cache_read_input_tokens` value includes accumulated reads from multiple internal API calls made by the server-side tool, not your actual conversation context. Your real context length might only be the 63,000 `input_tokens`, but the SDK sees 334k and triggers compaction prematurely.
**Workarounds:**
- Use the [token counting](/docs/en/build-with-claude/token-counting) endpoint to get accurate context length
- Avoid compaction when using server-side tools extensively
#### Tool use edge cases
When the SDK triggers compaction while a tool use response is pending, it removes the tool use block from the message history before generating the summary. Claude will re-issue the tool call after resuming from the summary if still needed.
### Monitoring compaction
Understanding when compaction triggers helps you tune thresholds and verify expected behavior.
The Python SDK logs compaction events at the INFO level. Enable the `anthropic.lib.tools` logger:
```python Python
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger("anthropic.lib.tools").setLevel(logging.INFO)
# Logs will show:
# INFO: Token usage 105000 has exceeded the threshold of 100000. Performing compaction.
# INFO: Compaction complete. New token usage: 2500
```
The TypeScript SDK's `toolRunner` supports compaction but does not log events. Detect compaction by watching `runner.params.messages.length` shrink between turns:
```typescript TypeScript hidelines={1..24}
import Anthropic from "@anthropic-ai/sdk";
import { betaTool } from "@anthropic-ai/sdk/helpers/beta/json-schema";
const readFile = betaTool({
name: "read_file",
description: "Read the contents of a file",
inputSchema: {
type: "object",
properties: { path: { type: "string" } },
required: ["path"]
},
run: async () => "file contents..."
});
const client = new Anthropic();
const runner = client.beta.messages.toolRunner({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [readFile],
messages: [{ role: "user", content: "What's in config.json?" }],
compactionControl: { enabled: true, contextTokenThreshold: 100000 }
});
let prevMsgCount = 0;
for await (const message of runner) {
const currMsgCount = runner.params.messages.length;
if (currMsgCount < prevMsgCount) {
console.log(`Compaction occurred: ${prevMsgCount} -> ${currMsgCount} messages`);
console.log(`Input tokens after compaction: ${message.usage.input_tokens}`);
}
prevMsgCount = currMsgCount;
}
```
The C# SDK does not include a `tool_runner` helper. Use [server-side compaction](/docs/en/build-with-claude/compaction) instead.
The Go SDK does not include a `tool_runner` helper. Use [server-side compaction](/docs/en/build-with-claude/compaction) instead.
The Java SDK does not include a `tool_runner` helper. Use [server-side compaction](/docs/en/build-with-claude/compaction) instead.
The PHP SDK does not include a `tool_runner` helper. Use [server-side compaction](/docs/en/build-with-claude/compaction) instead.
The Ruby SDK supports an `on_compact:` callback that fires when compaction occurs. Add it to your `compaction_control` configuration:
```ruby Ruby hidelines={1..15}
require "anthropic"
class ReadFileInput < Anthropic::BaseModel
required :path, String, doc: "Path to the file"
end
class ReadFile < Anthropic::BaseTool
doc "Read the contents of a file"
input_schema ReadFileInput
def call(input)
"file contents..."
end
end
client = Anthropic::Client.new
runner = client.beta.messages.tool_runner(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [ReadFile.new],
messages: [{ role: "user", content: "What's in config.json?" }],
compaction_control: {
enabled: true,
context_token_threshold: 100000,
on_compact: ->(tokens_before, tokens_after) do
puts "Compaction occurred: #{tokens_before} -> #{tokens_after} tokens"
end
}
)
runner.each_message do |message|
puts "Tokens: #{message.usage.input_tokens}"
end
```
### When to use compaction
**Good use cases:**
- Long-running agent tasks that process many files or data sources
- Research workflows that accumulate large amounts of information
- Multi-step tasks with clear, measurable progress
- Tasks that produce artifacts (files, reports) that persist outside the conversation
**Less ideal use cases:**
- Tasks requiring precise recall of early conversation details
- Workflows using server-side tools extensively
- Tasks that need to maintain exact state across many variables
---
# Context windows
URL: https://platform.claude.com/docs/en/build-with-claude/context-windows
# Context windows
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
As conversations grow, you'll eventually approach context window limits. This guide explains how context windows work and introduces strategies for managing them effectively.
For long-running conversations and agentic workflows, [server-side compaction](/docs/en/build-with-claude/compaction) is the primary strategy for context management. For more specialized needs, [context editing](/docs/en/build-with-claude/context-editing) offers additional strategies like tool result clearing and thinking block clearing.
## Understanding the context window
The "context window" refers to all the text a language model can reference when generating a response, including the response itself. This is different from the large corpus of data the language model was trained on, and instead represents a "working memory" for the model. A larger context window allows the model to handle more complex and lengthy prompts, but more context isn't automatically better. As token count grows, accuracy and recall degrade, a phenomenon known as *context rot*. This makes curating what's in context just as important as how much space is available.
Claude achieves state-of-the-art results on long-context retrieval benchmarks like [MRCR](https://arxiv.org/abs/2501.03276) and [GraphWalks](https://arxiv.org/abs/2412.04360), but these gains depend on what's in context, not just how much fits.
For a deep dive into why long contexts degrade and how to engineer around it, see [Effective context engineering](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents).
The diagram below illustrates the standard context window behavior for API requests1:

_1For chat interfaces, such as for [claude.ai](https://claude.ai/), context windows can also be set up on a rolling "first in, first out" system._
* **Progressive token accumulation:** As the conversation advances through turns, each user message and assistant response accumulates within the context window. Previous turns are preserved completely.
* **Linear growth pattern:** The context usage grows linearly with each turn, with previous turns preserved completely.
* **Context window capacity:** The total available context window (up to 1M tokens) represents the maximum capacity for storing conversation history and generating new output from Claude.
* **Input-output flow:** Each turn consists of:
- **Input phase:** Contains all previous conversation history plus the current user message
- **Output phase:** Generates a text response that becomes part of a future input
## The context window with extended thinking
When using [extended thinking](/docs/en/build-with-claude/extended-thinking), all input and output tokens, including the tokens used for thinking, count toward the context window limit, with a few nuances in multi-turn situations.
The thinking budget tokens are a subset of your `max_tokens` parameter, are billed as output tokens, and count towards rate limits. With [adaptive thinking](/docs/en/build-with-claude/adaptive-thinking), Claude dynamically decides its thinking allocation, so actual thinking token usage may vary per request.
However, previous thinking blocks are automatically stripped from the context window calculation by the Claude API and are not part of the conversation history that the model "sees" for subsequent turns, preserving token capacity for actual conversation content.
The diagram below demonstrates the specialized token management when extended thinking is enabled:

* **Stripping extended thinking:** Extended thinking blocks (shown in dark gray) are generated during each turn's output phase, **but are not carried forward as input tokens for subsequent turns**. You do not need to strip the thinking blocks yourself. The Claude API automatically does this for you if you pass them back.
* **Technical implementation details:**
- The API automatically excludes thinking blocks from previous turns when you pass them back as part of the conversation history.
- Extended thinking tokens are billed as output tokens only once, during their generation.
- The effective context window calculation becomes: `context_window = (input_tokens - previous_thinking_tokens) + current_turn_tokens`.
- Thinking tokens include `thinking` blocks.
This architecture is token efficient and allows for extensive reasoning without token waste, as thinking blocks can be substantial in length.
You can read more about the context window and extended thinking in the [extended thinking guide](/docs/en/build-with-claude/extended-thinking).
## The context window with extended thinking and tool use
The diagram below illustrates the context window token management when combining extended thinking with tool use:

- **Input components:** Tools configuration and user message
- **Output components:** Extended thinking + text response + tool use request
- **Token calculation:** All input and output components count toward the context window, and all output components are billed as output tokens.
- **Input components:** Every block in the first turn and the `tool_result`. The extended thinking block **must** be returned with the corresponding tool results. This is the only case wherein you **have to** return thinking blocks.
- **Output components:** After tool results have been passed back to Claude, Claude responds with only text (no additional extended thinking until the next `user` message, unless [interleaved thinking](/docs/en/build-with-claude/extended-thinking#interleaved-thinking) is enabled).
- **Token calculation:** All input and output components count toward the context window, and all output components are billed as output tokens.
- **Input components:** All inputs and the output from the previous turn are carried forward with the exception of the thinking block, which can be dropped now that Claude has completed the entire tool use cycle. The API will automatically strip the thinking block for you if you pass it back, or you can feel free to strip it yourself at this stage. This is also where you would add the next `user` turn.
- **Output components:** Because there is a new `user` turn outside of the tool use cycle, Claude generates a new extended thinking block and continues from there.
- **Token calculation:** Previous thinking tokens are automatically stripped from context window calculations. All other previous blocks still count as part of the token window, and the thinking block in the current `assistant` turn counts as part of the context window.
* **Considerations for tool use with extended thinking:**
- When posting tool results, the entire unmodified thinking block that accompanies that specific tool request (including signature portions) must be included.
- The effective context window calculation for extended thinking with tool use becomes: `context_window = input_tokens + current_turn_tokens`.
- The system uses cryptographic signatures to verify thinking block authenticity. Failing to preserve thinking blocks during tool use can break Claude's reasoning continuity. Thus, if you modify thinking blocks, the API returns an error.
Claude 4 models support [interleaved thinking](/docs/en/build-with-claude/extended-thinking#interleaved-thinking), which enables Claude to think between tool calls and make more sophisticated reasoning after receiving tool results.
For more information about using tools with extended thinking, see the [extended thinking guide](/docs/en/build-with-claude/extended-thinking#extended-thinking-with-tool-use).
[Claude Mythos Preview](https://anthropic.com/glasswing), Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6 have a 1M-token context window. Other Claude models, including Claude Sonnet 4.5 and Sonnet 4 (deprecated), have a 200k-token context window.
A single request can include up to 600 images or PDF pages (100 for models with a 200k-token context window). When sending many images or large documents, you may approach [request size limits](/docs/en/api/overview#request-size-limits) before the token limit.
## Context awareness in Claude Sonnet 4.6, Sonnet 4.5, and Haiku 4.5
Claude Sonnet 4.6, Claude Sonnet 4.5, and Claude Haiku 4.5 feature **context awareness**. This capability lets these models track their remaining context window (that is, "token budget") throughout a conversation. This enables Claude to execute tasks and manage context more effectively by understanding how much space it has to work. Claude is trained to use this context precisely, persisting in the task until the very end rather than guessing how many tokens remain. For a model, lacking context awareness is like competing in a cooking show without a clock. Context-aware models change this by explicitly receiving information about remaining context, so they can take maximum advantage of the available tokens.
**How it works:**
At the start of a conversation, Claude receives information about its total context window:
```xml
1000000
```
The budget is set to 1M tokens (200k for models with a smaller context window).
After each tool call, Claude receives an update on remaining capacity:
```xml
Token usage: 35000/1000000; 965000 remaining
```
This awareness helps Claude determine how much capacity remains for work and enables more effective execution on long-running tasks. Image tokens are included in these budgets.
**Benefits:**
Context awareness is particularly valuable for:
- Long-running agent sessions that require sustained focus
- Multi-context-window workflows where state transitions matter
- Complex tasks requiring careful token management
For agents that span multiple sessions, design your state artifacts so that context recovery is fast when a new session starts. The [memory tool's multi-session pattern](/docs/en/agents-and-tools/tool-use/memory-tool#multi-session-software-development-pattern) walks through a concrete approach. See also [Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents).
For prompting guidance on leveraging context awareness, see the [prompting best practices guide](/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#context-awareness-and-multi-window-workflows).
## Managing context with compaction
If your conversations regularly approach context window limits, [server-side compaction](/docs/en/build-with-claude/compaction) is the recommended approach. Compaction provides server-side summarization that automatically condenses earlier parts of a conversation, enabling long-running conversations beyond context limits with minimal integration work. It is currently available in beta for Claude Mythos Preview, Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6.
For more specialized needs, [context editing](/docs/en/build-with-claude/context-editing) offers additional strategies:
- **Tool result clearing** - Clear old tool results in agentic workflows
- **Thinking block clearing** - Manage thinking blocks with extended thinking
## Context window overflow behavior
On Claude 4.5 models and newer, if input tokens plus `max_tokens` exceeds the context window size, the API accepts the request. If generation then reaches the context window limit, it stops with `stop_reason: "model_context_window_exceeded"`. On earlier models, the API returns a validation error instead; opt in to the `model_context_window_exceeded` behavior with the `model-context-window-exceeded-2025-08-26` beta header. See [Handling stop reasons](/docs/en/build-with-claude/handling-stop-reasons) for details.
To stay within context window limits, use the [token counting API](/docs/en/build-with-claude/token-counting) to estimate token usage before sending messages to Claude.
See the [model comparison](/docs/en/about-claude/models/overview#latest-models-comparison) table for a list of context window sizes by model.
## Next steps
The recommended strategy for managing context in long-running conversations.
Fine-grained strategies like tool result clearing and thinking block clearing.
See the model comparison table for a list of context window sizes and input / output token pricing by model.
Learn more about how extended thinking works and how to implement it alongside other features such as tool use and prompt caching.
---
# Prompt caching
URL: https://platform.claude.com/docs/en/build-with-claude/prompt-caching
# Prompt caching
---
Prompt caching optimizes your API usage by allowing resuming from specific prefixes in your prompts. This significantly reduces processing time and costs for repetitive tasks or prompts with consistent elements.
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
There are two ways to enable prompt caching:
- **[Automatic caching](#automatic-caching)**: Add a single `cache_control` field at the top level of your request. The system automatically applies the cache breakpoint to the last cacheable block and moves it forward as conversations grow. Best for multi-turn conversations where the growing message history should be cached automatically.
- **[Explicit cache breakpoints](#explicit-cache-breakpoints)**: Place `cache_control` directly on individual content blocks for fine-grained control over exactly what gets cached.
The simplest way to start is with automatic caching:
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"cache_control": {"type": "ephemeral"},
"system": "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.",
"messages": [
{
"role": "user",
"content": "Analyze the major themes in Pride and Prejudice."
}
]
}'
```
```bash CLI
ant messages create --transform usage <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
cache_control:
type: ephemeral
system: >-
You are an AI assistant tasked with analyzing literary works. Your goal is
to provide insightful commentary on themes, characters, and writing style.
messages:
- role: user
content: Analyze the major themes in Pride and Prejudice.
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
cache_control={"type": "ephemeral"},
system="You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.",
messages=[
{
"role": "user",
"content": "Analyze the major themes in 'Pride and Prejudice'.",
}
],
)
print(response.usage.model_dump_json())
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
cache_control: { type: "ephemeral" },
system:
"You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.",
messages: [
{
role: "user",
content: "Analyze the major themes in 'Pride and Prejudice'."
}
]
});
console.log(response.usage);
```
```csharp C# hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
CacheControl = new CacheControlEphemeral(),
System = "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.",
Messages =
[
new()
{
Role = Role.User,
Content = "Analyze the major themes in 'Pride and Prejudice'."
}
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message.Usage);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
CacheControl: anthropic.NewCacheControlEphemeralParam(),
System: []anthropic.TextBlockParam{
{Text: "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style."},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the major themes in 'Pride and Prejudice'.")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Usage)
}
```
```java Java hidelines={1..2,4..10,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
public class PromptCachingExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.cacheControl(CacheControlEphemeral.builder().build())
.system("You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.")
.addUserMessage("Analyze the major themes in 'Pride and Prejudice'.")
.build();
Message message = client.messages().create(params);
System.out.println(message.usage());
}
}
```
```php PHP hidelines={1..3,5}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => "Analyze the major themes in 'Pride and Prejudice'."]
],
model: 'claude-opus-4-7',
cacheControl: CacheControlEphemeral::with(),
system: "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.",
);
echo json_encode($response->usage);
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
cache_control: {type: "ephemeral"},
system: "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.",
messages: [
{
role: "user",
content: "Analyze the major themes in 'Pride and Prejudice'."
}
]
)
puts response.usage
```
With automatic caching, the system caches all content up to and including the last cacheable block. On subsequent requests with the same prefix, cached content is reused automatically.
---
## How prompt caching works
When you send a request with prompt caching enabled:
1. The system checks if a prompt prefix, up to a specified cache breakpoint, is already cached from a recent query.
2. If found, it uses the cached version, reducing processing time and costs.
3. Otherwise, it processes the full prompt and caches the prefix once the response begins.
This is especially useful for:
- Prompts with many examples
- Large amounts of context or background information
- Repetitive tasks with consistent instructions
- Long multi-turn conversations
By default, the cache has a 5-minute lifetime. The cache is refreshed for no additional cost each time the cached content is used.
If you find that 5 minutes is too short, Anthropic also offers a 1-hour cache duration [at additional cost](#pricing).
For more information, see [1-hour cache duration](#1-hour-cache-duration).
**Prompt caching caches the full prefix**
Prompt caching references the entire prompt - `tools`, `system`, and `messages` (in that order) up to and including the block designated with `cache_control`.
---
## Pricing
Prompt caching introduces a new pricing structure. The table below shows the price per million tokens for each supported model:
| Model | Base Input Tokens | 5m Cache Writes | 1h Cache Writes | Cache Hits & Refreshes | Output Tokens |
|-------------------|-------------------|-----------------|-----------------|----------------------|---------------|
| Claude Opus 4.7 | $5 / MTok | $6.25 / MTok | $10 / MTok | $0.50 / MTok | $25 / MTok |
| Claude Opus 4.6 | $5 / MTok | $6.25 / MTok | $10 / MTok | $0.50 / MTok | $25 / MTok |
| Claude Opus 4.5 | $5 / MTok | $6.25 / MTok | $10 / MTok | $0.50 / MTok | $25 / MTok |
| Claude Opus 4.1 | $15 / MTok | $18.75 / MTok | $30 / MTok | $1.50 / MTok | $75 / MTok |
| Claude Opus 4 ([deprecated](/docs/en/about-claude/model-deprecations)) | $15 / MTok | $18.75 / MTok | $30 / MTok | $1.50 / MTok | $75 / MTok |
| Claude Sonnet 4.6 | $3 / MTok | $3.75 / MTok | $6 / MTok | $0.30 / MTok | $15 / MTok |
| Claude Sonnet 4.5 | $3 / MTok | $3.75 / MTok | $6 / MTok | $0.30 / MTok | $15 / MTok |
| Claude Sonnet 4 ([deprecated](/docs/en/about-claude/model-deprecations)) | $3 / MTok | $3.75 / MTok | $6 / MTok | $0.30 / MTok | $15 / MTok |
| Claude Haiku 4.5 | $1 / MTok | $1.25 / MTok | $2 / MTok | $0.10 / MTok | $5 / MTok |
| Claude Haiku 3.5 ([retired, except on Bedrock and Vertex AI](/docs/en/about-claude/model-deprecations)) | $0.80 / MTok | $1 / MTok | $1.60 / MTok | $0.08 / MTok | $4 / MTok |
The table above reflects the following pricing multipliers for prompt caching:
- 5-minute cache write tokens are 1.25 times the base input tokens price
- 1-hour cache write tokens are 2 times the base input tokens price
- Cache read tokens are 0.1 times the base input tokens price
These multipliers stack with other pricing modifiers such as the Batch API discount and data residency. See [pricing](/docs/en/about-claude/pricing) for full details.
---
## Supported models
Prompt caching (both automatic and explicit) is supported on all [active Claude models](/docs/en/about-claude/models/overview).
---
## Automatic caching
Automatic caching is the simplest way to enable prompt caching. Instead of placing `cache_control` on individual content blocks, add a single `cache_control` field at the top level of your request body. The system automatically applies the cache breakpoint to the last cacheable block.
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"cache_control": {"type": "ephemeral"},
"system": "You are a helpful assistant that remembers our conversation.",
"messages": [
{"role": "user", "content": "My name is Alex. I work on machine learning."},
{"role": "assistant", "content": "Nice to meet you, Alex! How can I help with your ML work today?"},
{"role": "user", "content": "What did I say I work on?"}
]
}'
```
```bash CLI
ant messages create --transform usage <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
cache_control:
type: ephemeral
system: You are a helpful assistant that remembers our conversation.
messages:
- role: user
content: My name is Alex. I work on machine learning.
- role: assistant
content: Nice to meet you, Alex! How can I help with your ML work today?
- role: user
content: What did I say I work on?
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
cache_control={"type": "ephemeral"},
system="You are a helpful assistant that remembers our conversation.",
messages=[
{"role": "user", "content": "My name is Alex. I work on machine learning."},
{
"role": "assistant",
"content": "Nice to meet you, Alex! How can I help with your ML work today?",
},
{"role": "user", "content": "What did I say I work on?"},
],
)
print(response.usage.model_dump_json())
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
cache_control: { type: "ephemeral" },
system: "You are a helpful assistant that remembers our conversation.",
messages: [
{ role: "user", content: "My name is Alex. I work on machine learning." },
{
role: "assistant",
content: "Nice to meet you, Alex! How can I help with your ML work today?"
},
{ role: "user", content: "What did I say I work on?" }
]
});
console.log(response.usage);
```
```csharp C# hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
CacheControl = new CacheControlEphemeral(),
System = "You are a helpful assistant that remembers our conversation.",
Messages =
[
new()
{
Role = Role.User,
Content = "My name is Alex. I work on machine learning."
},
new()
{
Role = Role.Assistant,
Content = "Nice to meet you, Alex! How can I help with your ML work today?"
},
new()
{
Role = Role.User,
Content = "What did I say I work on?"
}
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message.Usage);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
CacheControl: anthropic.NewCacheControlEphemeralParam(),
System: []anthropic.TextBlockParam{
{Text: "You are a helpful assistant that remembers our conversation."},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("My name is Alex. I work on machine learning.")),
anthropic.NewAssistantMessage(anthropic.NewTextBlock("Nice to meet you, Alex! How can I help with your ML work today?")),
anthropic.NewUserMessage(anthropic.NewTextBlock("What did I say I work on?")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Usage)
}
```
```java Java hidelines={1..2,4..10,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
public class AutomaticCachingExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.cacheControl(CacheControlEphemeral.builder().build())
.system("You are a helpful assistant that remembers our conversation.")
.addUserMessage("My name is Alex. I work on machine learning.")
.addAssistantMessage("Nice to meet you, Alex! How can I help with your ML work today?")
.addUserMessage("What did I say I work on?")
.build();
Message message = client.messages().create(params);
System.out.println(message.usage());
}
}
```
```php PHP hidelines={1..3,5}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'My name is Alex. I work on machine learning.'],
['role' => 'assistant', 'content' => 'Nice to meet you, Alex! How can I help with your ML work today?'],
['role' => 'user', 'content' => 'What did I say I work on?'],
],
model: 'claude-opus-4-7',
cacheControl: CacheControlEphemeral::with(),
system: 'You are a helpful assistant that remembers our conversation.',
);
echo json_encode($response->usage);
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
cache_control: {type: "ephemeral"},
system: "You are a helpful assistant that remembers our conversation.",
messages: [
{role: "user", content: "My name is Alex. I work on machine learning."},
{role: "assistant", content: "Nice to meet you, Alex! How can I help with your ML work today?"},
{role: "user", content: "What did I say I work on?"}
]
)
puts response.usage
```
### How automatic caching works in multi-turn conversations
With automatic caching, the cache point moves forward automatically as conversations grow. Each new request caches everything up to the last cacheable block, and previous content is read from cache.
| Request | Content | Cache behavior |
|---------|---------|----------------|
| Request 1 | System + User(1) + Asst(1) + **User(2)** ◀ cache | Everything written to cache |
| Request 2 | System + User(1) + Asst(1) + User(2) + Asst(2) + **User(3)** ◀ cache | System through User(2) read from cache; Asst(2) + User(3) written to cache |
| Request 3 | System + User(1) + Asst(1) + User(2) + Asst(2) + User(3) + Asst(3) + **User(4)** ◀ cache | System through User(3) read from cache; Asst(3) + User(4) written to cache |
The cache breakpoint automatically moves to the last cacheable block in each request, so you don't need to update any `cache_control` markers as the conversation grows.
### TTL support
By default, automatic caching uses a 5-minute TTL. You can specify a 1-hour TTL at 2x the base input token price:
```json
{ "cache_control": { "type": "ephemeral", "ttl": "1h" } }
```
### Combining with block-level caching
Automatic caching is compatible with [explicit cache breakpoints](#explicit-cache-breakpoints). When used together, the automatic cache breakpoint uses one of the 4 available breakpoint slots.
This lets you combine both approaches. For example, use an explicit breakpoint to cache your system prompt, while automatic caching handles the conversation:
```json
{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"cache_control": { "type": "ephemeral" },
"system": [
{
"type": "text",
"text": "You are a helpful assistant.",
"cache_control": { "type": "ephemeral" }
}
],
"messages": [{ "role": "user", "content": "What are the key terms?" }]
}
```
### What stays the same
Automatic caching uses the same underlying caching infrastructure. Pricing, minimum token thresholds, context ordering requirements, and the 20-block lookback window all apply the same as with explicit breakpoints.
### Edge cases
- If the last block already has an explicit `cache_control` with the same TTL, automatic caching is a no-op.
- If the last block has an explicit `cache_control` with a different TTL, the API returns a 400 error.
- If 4 explicit block-level breakpoints already exist, the API returns a 400 error (no slots left for automatic caching).
- If the last block is not eligible as an automatic cache breakpoint target, the system silently walks backwards to find the nearest eligible block. If none is found, caching is skipped.
Automatic caching is available on the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry) (beta). Bedrock and Vertex AI do not support automatic caching.
---
## Explicit cache breakpoints
For more control over caching, you can place `cache_control` directly on individual content blocks. This is useful when you need to cache different sections that change at different frequencies, or need fine-grained control over exactly what gets cached.
### Structuring your prompt
Place static content (tool definitions, system instructions, context, examples) at the beginning of your prompt. Mark the end of the reusable content for caching using the `cache_control` parameter.
Cache prefixes are created in the following order: `tools`, `system`, then `messages`. This order forms a hierarchy where each level builds upon the previous ones.
#### How automatic prefix checking works
You can use just one cache breakpoint at the end of your static content, and the system will automatically find the longest prefix that a prior request already wrote to the cache. Understanding how this works helps you optimize your caching strategy.
**Three core principles:**
1. **Cache writes happen only at your breakpoint.** Marking a block with `cache_control` writes exactly one cache entry: a hash of the prefix ending at that block. The system does not write entries for any earlier position. Because the hash is cumulative, covering everything up to and including the breakpoint, changing any block at or before the breakpoint produces a different hash on the next request.
2. **Cache reads look backward for entries that prior requests wrote.** On each request the system computes the prefix hash at your breakpoint and checks for a matching cache entry. If none exists, it walks backward one block at a time, checking whether the prefix hash at each earlier position matches something already in the cache. It is looking for prior writes, not for stable content.
3. **The lookback window is 20 blocks.** The system checks at most 20 positions per breakpoint, counting the breakpoint itself as the first. If the system finds no matching entry in that window, checking stops (or resumes from the next explicit breakpoint, if any).
**Example: Lookback in a growing conversation**
You append new blocks each turn and set `cache_control` on the final block of each request:
- **Turn 1:** 10 blocks, breakpoint on block 10. No prior cache entries exist. The system writes an entry at block 10.
- **Turn 2:** 15 blocks, breakpoint on block 15. Block 15 has no entry, so the system walks back to block 10 and finds the turn-1 entry. Cache hit at block 10; the system processes only blocks 11 through 15 fresh and writes a new entry at block 15.
- **Turn 3:** 35 blocks, breakpoint on block 35. The system checks 20 positions (blocks 35 through 16) and finds nothing. The turn-2 entry at block 15 is one position outside the window, so there is no cache hit. Adding a second breakpoint at block 15 starts a second lookback window there, which finds the turn-2 entry.
**Common mistake: Breakpoint on content that changes every request**
Your prompt has a large static system context (blocks 1 through 5) followed by a per-request block containing a timestamp and the user message (block 6). You set `cache_control` on block 6:
- **Request 1:** Cache write at block 6. The hash includes the timestamp.
- **Request 2:** The timestamp differs, so the prefix hash at block 6 differs. The lookback walks through blocks 5, 4, 3, 2, and 1, but the system never wrote an entry at any of those positions. No cache hit. You pay for a fresh cache write on every request and never get a read.
The lookback does not find stable content behind your breakpoint and cache it. It finds entries that prior requests already wrote, and writes happen only at breakpoints. Move `cache_control` to block 5, the last block that stays the same across requests, and every subsequent request reads the cached prefix. [Automatic caching](#automatic-caching) hits the same trap: it places the breakpoint on the last cacheable block, which in this structure is the one that changes every request, so use an explicit breakpoint on block 5 instead.
**Key takeaway:** Place `cache_control` on the last block whose prefix is identical across the requests you want to share a cache. In a growing conversation the final block works as long as each turn adds fewer than 20 blocks: earlier content never changes, so the next request's lookback finds the prior write. For a prompt with a varying suffix (timestamps, per-request context, the incoming message), place the breakpoint at the end of the static prefix, not on the varying block.
#### When to use multiple breakpoints
You can define up to 4 cache breakpoints if you want to:
- Cache different sections that change at different frequencies (for example, tools rarely change, but context updates daily)
- Have more control over exactly what gets cached
- Ensure a cache hit when a growing conversation pushes your breakpoint 20 or more blocks past the last cache write
**Important limitation:** The lookback can only find entries that earlier requests already wrote. If a growing conversation pushes your breakpoint 20 or more blocks past the last write, the lookback window misses it. Add a second breakpoint closer to that position from the start so a write accumulates there before you need it.
### Understanding cache breakpoint costs
**Cache breakpoints themselves don't add any cost.** You are only charged for:
- **Cache writes**: When new content is written to the cache (25% more than base input tokens for 5-minute TTL)
- **Cache reads**: When cached content is used (10% of base input token price)
- **Regular input tokens**: For any uncached content
Adding more `cache_control` breakpoints doesn't increase your costs - you still pay the same amount based on what content is actually cached and read. The breakpoints simply give you control over what sections can be cached independently.
---
## Caching strategies and considerations
### Cache limitations
On the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), [Vertex AI](/docs/en/build-with-claude/claude-on-vertex-ai), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry) (beta), the minimum cacheable prompt length is:
- 4,096 tokens for [Claude Mythos Preview](https://anthropic.com/glasswing), Claude Opus 4.7, Claude Opus 4.6, and Claude Opus 4.5
- 1,024 tokens for Claude Sonnet 4.6, Claude Sonnet 4.5, Claude Opus 4.1, Claude Opus 4 ([deprecated](/docs/en/about-claude/model-deprecations)), and Claude Sonnet 4 ([deprecated](/docs/en/about-claude/model-deprecations))
- 4,096 tokens for Claude Haiku 4.5
- 2,048 tokens for Claude Haiku 3.5 ([retired, except on Vertex AI](/docs/en/about-claude/model-deprecations))
Model availability varies by platform.
Shorter prompts cannot be cached, even if marked with `cache_control`. Any requests to cache fewer than this number of tokens will be processed without caching, and no error is returned. To verify whether a prompt was cached, check the response usage [fields](/docs/en/build-with-claude/prompt-caching#tracking-cache-performance): if both `cache_creation_input_tokens` and `cache_read_input_tokens` are 0, the prompt was not cached (likely because it did not meet the minimum length requirement).
If your prompt falls just short of the minimum for your model and platform, expanding the cached content to reach the threshold is often worthwhile. Cache reads cost significantly less than uncached input tokens, so reaching the minimum can reduce costs for frequently reused prompts.
[Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock) is an AWS-operated platform. On Bedrock, see the [Bedrock prompt caching documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html) for the per-model minimums, failure behavior, and usage-field names that apply.
For concurrent requests, note that a cache entry only becomes available after the first response begins. If you need cache hits for parallel requests, wait for the first response before sending subsequent requests.
Currently, "ephemeral" is the only supported cache type, which by default has a 5-minute lifetime.
### What can be cached
Most blocks in the request can be cached. This includes:
- Tools: Tool definitions in the `tools` array
- System messages: Content blocks in the `system` array
- Text messages: Content blocks in the `messages.content` array, for both user and assistant turns
- Images & Documents: Content blocks in the `messages.content` array, in user turns
- Tool use and tool results: Content blocks in the `messages.content` array, in both user and assistant turns
Each of these elements can be cached, either automatically or by marking them with `cache_control`.
### What cannot be cached
While most request blocks can be cached, there are some exceptions:
- Thinking blocks cannot be cached directly with `cache_control`. However, thinking blocks CAN be cached alongside other content when they appear in previous assistant turns. When cached this way, they DO count as input tokens when read from cache.
- Sub-content blocks (like [citations](/docs/en/build-with-claude/citations)) themselves cannot be cached directly. Instead, cache the top-level block.
In the case of citations, the top-level document content blocks that serve as the source material for citations can be cached. This allows you to use prompt caching with citations effectively by caching the documents that citations will reference.
- Empty text blocks cannot be cached.
### What invalidates the cache
Modifications to cached content can invalidate some or all of the cache.
As described in [Structuring your prompt](#structuring-your-prompt), the cache follows the hierarchy: `tools` → `system` → `messages`. Changes at each level invalidate that level and all subsequent levels.
The following table shows which parts of the cache are invalidated by different types of changes. ✘ indicates that the cache is invalidated, while ✓ indicates that the cache remains valid.
| What changes | Tools cache | System cache | Messages cache | Impact |
|------------|------------------|---------------|----------------|-------------|
| **Tool definitions** | ✘ | ✘ | ✘ | Modifying tool definitions (names, descriptions, parameters) invalidates the entire cache |
| **Web search toggle** | ✓ | ✘ | ✘ | Enabling/disabling web search modifies the system prompt |
| **Citations toggle** | ✓ | ✘ | ✘ | Enabling/disabling citations modifies the system prompt |
| **Speed setting** | ✓ | ✘ | ✘ | Switching between [`speed: "fast"` and standard speed](/docs/en/build-with-claude/fast-mode) invalidates system and message caches |
| **Tool choice** | ✓ | ✓ | ✘ | Changes to `tool_choice` parameter only affect message blocks |
| **Images** | ✓ | ✓ | ✘ | Adding/removing images anywhere in the prompt affects message blocks |
| **Thinking parameters** | ✓ | ✓ | ✘ | Changes to extended thinking settings (enable/disable, budget) affect message blocks |
| **Non-tool results passed to extended thinking requests** | ✓ | ✓ | Model-specific | On Opus 4.5+ and Sonnet 4.6+, thinking blocks are preserved by default, so the cache remains valid (✓). On earlier Opus/Sonnet models and all Haiku models, all previously-cached thinking blocks are stripped from context, and any messages that follow those thinking blocks are removed from the cache (✘). For more details, see [Caching with thinking blocks](#caching-with-thinking-blocks). |
### Tracking cache performance
Monitor cache performance using these API response fields, within `usage` in the response (or `message_start` event if [streaming](/docs/en/build-with-claude/streaming)):
- `cache_creation_input_tokens`: Number of tokens written to the cache when creating a new entry.
- `cache_read_input_tokens`: Number of tokens retrieved from the cache for this request.
- `input_tokens`: Number of input tokens which were not read from or used to create a cache (that is, tokens after the last cache breakpoint).
**Understanding the token breakdown**
The `input_tokens` field represents only the tokens that come **after the last cache breakpoint** in your request - not all the input tokens you sent.
To calculate total input tokens:
```text
total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens
```
**Spatial explanation:**
- `cache_read_input_tokens` = tokens before breakpoint already cached (reads)
- `cache_creation_input_tokens` = tokens before breakpoint being cached now (writes)
- `input_tokens` = tokens after your last breakpoint (not eligible for cache)
**Example:** If you have a request with 100,000 tokens of cached content (read from cache), 0 tokens of new content being cached, and 50 tokens in your user message (after the cache breakpoint):
- `cache_read_input_tokens`: 100,000
- `cache_creation_input_tokens`: 0
- `input_tokens`: 50
- **Total input tokens processed**: 100,050 tokens
This is important for understanding both costs and rate limits, as `input_tokens` will typically be much smaller than your total input when using caching effectively.
### Caching with thinking blocks
When using [extended thinking](/docs/en/build-with-claude/extended-thinking) with prompt caching, thinking blocks have special behavior:
**Automatic caching alongside other content**: While thinking blocks cannot be explicitly marked with `cache_control`, they get cached as part of the request content when you make subsequent API calls with tool results. This commonly happens during tool use when you pass thinking blocks back to continue the conversation.
**Input token counting**: When thinking blocks are read from cache, they count as input tokens in your usage metrics. This is important for cost calculation and token budgeting.
**Cache invalidation patterns**:
- Cache remains valid when only tool results are provided as user messages
- On Opus 4.5+ and Sonnet 4.6+, thinking blocks are preserved by default even when non-tool-result user content is added, so the cache remains valid
- On earlier Opus/Sonnet models and all Haiku models, cache gets invalidated when non-tool-result user content is added, causing all previous thinking blocks to be stripped from context
- This caching behavior occurs even without explicit `cache_control` markers
For more details on cache invalidation, see [What invalidates the cache](#what-invalidates-the-cache).
**Example with tool use**:
```text
Request 1: User: "What's the weather in Paris?"
Response: [thinking_block_1] + [tool_use block 1]
Request 2:
User: ["What's the weather in Paris?"],
Assistant: [thinking_block_1] + [tool_use block 1],
User: [tool_result_1, cache=True]
Response: [thinking_block_2] + [text block 2]
# Request 2 caches its request content (not the response)
# The cache includes: user message, thinking_block_1, tool_use block 1, and tool_result_1
Request 3:
User: ["What's the weather in Paris?"],
Assistant: [thinking_block_1] + [tool_use block 1],
User: [tool_result_1, cache=True],
Assistant: [thinking_block_2] + [text block 2],
User: [Text response, cache=True]
# On earlier Opus/Sonnet and all Haiku models, non-tool-result user block causes prior thinking blocks to be stripped; on Opus 4.5+/Sonnet 4.6+ they are kept
```
On earlier Opus/Sonnet models and all Haiku models, all previous thinking blocks are removed from context at this point. On Opus 4.5+ and Sonnet 4.6+, prior thinking blocks are kept by default and remain part of the cached prefix.
For more detailed information, see the [extended thinking documentation](/docs/en/build-with-claude/extended-thinking#understanding-thinking-block-caching-behavior).
### Cache storage and sharing
As of February 5, 2026, prompt caching uses [workspace](/docs/en/manage-claude/workspaces)-level isolation instead of organization-level isolation. Caches are isolated per workspace, ensuring data separation between workspaces within the same organization. This applies to the Claude API, Claude Platform on AWS, and Microsoft Foundry (beta); Bedrock and Vertex AI maintain organization-level cache isolation. If you use multiple workspaces, review your caching strategy to account for this difference.
- **Organization and workspace isolation:** Caches are isolated between organizations. Different organizations never share caches, even if they use identical prompts. As of February 5, 2026, caches are also isolated per workspace within an organization on the Claude API, Claude Platform on AWS, and Microsoft Foundry (beta); Bedrock and Vertex AI continue to use organization-level isolation only.
- **Exact matching:** Cache hits require 100% identical prompt segments, including all text and images up to and including the block marked with cache control.
- **Output token generation:** Prompt caching has no effect on output token generation. The response you receive is identical to what you would get if prompt caching were not used.
### Best practices for effective caching
To optimize prompt caching performance:
- Start with [automatic caching](#automatic-caching) for multi-turn conversations. It handles breakpoint management automatically.
- Use [explicit block-level breakpoints](#explicit-cache-breakpoints) when you need to cache different sections with different change frequencies.
- Cache stable, reusable content like system instructions, background information, large contexts, or frequent tool definitions.
- Place cached content at the prompt's beginning for best performance.
- Use cache breakpoints strategically to separate different cacheable prefix sections.
- Place the breakpoint on the last block that stays identical across requests. For a prompt with a static prefix and a varying suffix (timestamps, per-request context, the incoming message), that is the end of the prefix, not the varying block.
- Regularly analyze cache hit rates and adjust your strategy as needed.
### Optimizing for different use cases
Tailor your prompt caching strategy to your scenario:
- Conversational agents: Reduce cost and latency for extended conversations, especially those with long instructions or uploaded documents.
- Coding assistants: Improve autocomplete and codebase Q&A by keeping relevant sections or a summarized version of the codebase in the prompt.
- Large document processing: Incorporate complete long-form material including images in your prompt without increasing response latency.
- Detailed instruction sets: Share extensive lists of instructions, procedures, and examples to fine-tune Claude's responses. Developers often include an example or two in the prompt, but with prompt caching you can get even better performance by including 20+ diverse examples of high quality answers.
- Agentic tool use: Enhance performance for scenarios involving multiple tool calls and iterative code changes, where each step typically requires a new API call.
- Talk to books, papers, documentation, podcast transcripts, and other longform content: Bring any knowledge base alive by embedding the entire document(s) into the prompt, and letting users ask it questions.
### Troubleshooting common issues
If experiencing unexpected behavior:
- Ensure cached sections are identical across calls. For explicit breakpoints, verify that `cache_control` markers are in the same locations
- Check that calls are made within the cache lifetime (5 minutes by default)
- Verify that `tool_choice` and image usage remain consistent between calls
- Validate that you are caching at least the minimum number of tokens for your model and platform (see [Cache limitations](#cache-limitations))
- Confirm your breakpoint is on a block that stays identical across requests. Cache writes happen only at the breakpoint, and if that block changes (timestamps, per-request context, the incoming message), the prefix hash never matches. The lookback does not find stable content behind the breakpoint; it only finds entries that earlier requests wrote at their own breakpoints
- Verify that the keys in your `tool_use` content blocks have stable ordering as some languages (for example, Swift, Go) randomize key order during JSON conversion, breaking caches
Changes to `tool_choice` or the presence/absence of images anywhere in the prompt will invalidate the cache, requiring a new cache entry to be created. For more details on cache invalidation, see [What invalidates the cache](#what-invalidates-the-cache).
---
## 1-hour cache duration
If you find that 5 minutes is too short, Anthropic also offers a 1-hour cache duration [at additional cost](#pricing).
The 1-hour cache duration is available on the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), [Vertex AI](/docs/en/build-with-claude/claude-on-vertex-ai), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry) (beta). Bedrock does not support the 1-hour cache duration.
To use the extended cache, include `ttl` in the `cache_control` definition like this:
```json hidelines={1,-1}
{
"cache_control": {
"type": "ephemeral",
"ttl": "1h"
}
}
```
The response will include detailed cache information like the following:
```json Output
{
"usage": {
"input_tokens": 2048,
"cache_read_input_tokens": 1800,
"cache_creation_input_tokens": 248,
"output_tokens": 503,
"cache_creation": {
"ephemeral_5m_input_tokens": 148,
"ephemeral_1h_input_tokens": 100
}
}
}
```
Note that the current `cache_creation_input_tokens` field equals the sum of the values in the `cache_creation` object.
### When to use the 1-hour cache
If you have prompts that are used at a regular cadence (that is, system prompts that are used more frequently than every 5 minutes), continue to use the 5-minute cache, since this will continue to be refreshed at no additional charge.
The 1-hour cache is best used in the following scenarios:
- When you have prompts that are likely used less frequently than 5 minutes, but more frequently than every hour. For example, when an agentic side-agent will take longer than 5 minutes, or when storing a long chat conversation with a user and you generally expect that user may not respond in the next 5 minutes.
- When latency is important and your follow up prompts may be sent beyond 5 minutes.
- When you want to improve your rate limit utilization, since cache hits are not deducted against your rate limit.
The 5-minute and 1-hour cache behave the same with respect to latency. You will generally see improved time-to-first-token for long documents.
### Mixing different TTLs
You can use both 1-hour and 5-minute cache controls in the same request, but with an important constraint: Cache entries with longer TTL must appear before shorter TTLs (that is, a 1-hour cache entry must appear before any 5-minute cache entries).
When mixing TTLs, the API determines three billing locations in your prompt:
1. Position `A`: The token count at the highest cache hit (or 0 if no hits).
2. Position `B`: The token count at the highest 1-hour `cache_control` block after `A` (or equals `A` if none exist).
3. Position `C`: The token count at the last `cache_control` block.
If `B` and/or `C` are larger than `A`, they will necessarily be cache misses, because `A` is the highest cache hit.
You'll be charged for:
1. Cache read tokens for `A`.
2. 1-hour cache write tokens for `(B - A)`.
3. 5-minute cache write tokens for `(C - B)`.
Here are 3 examples. This depicts the input tokens of 3 requests, each of which has different cache hits and cache misses. Each has a different calculated pricing, shown in the colored boxes, as a result.

---
## Pre-warming the cache
Cache pre-warming lets you load your system prompt or tool definitions into the prompt cache before a user triggers a real request. This eliminates the cache-miss latency penalty on the first user interaction, reducing time-to-first-token (TTFT) for latency-sensitive applications.
### How it works
Set `max_tokens: 0` in your request. The API runs the full prefill phase (reading your prompt into the model and writing the cache at any `cache_control` breakpoint), then returns immediately without generating any output. The response has an empty `content` array, `stop_reason: "max_tokens"`, and a fully populated `usage` block.
Place the `cache_control` breakpoint on the last block that is shared with the follow-up request (typically your system prompt or tool definitions), not on the placeholder user message. Otherwise the cache entry is keyed to the placeholder and the follow-up request won't hit it. This means using an [explicit cache breakpoint](#explicit-cache-breakpoints) rather than [automatic caching](#automatic-caching), since automatic caching places the breakpoint on the last block, which here is the placeholder. The placeholder user message can be any string with non-whitespace content (the examples here use `"warmup"`); its content is read during prefill but never answered.
A pre-warm request incurs a **cache write** charge if the prefix is not already cached, the same as any other request. Check `usage.cache_creation_input_tokens` in the response to confirm a write occurred. Zero output tokens are billed.
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 0,
"system": [
{
"type": "text",
"text": "You are an expert software engineer with deep knowledge of distributed systems...",
"cache_control": {"type": "ephemeral"}
}
],
"messages": [{"role": "user", "content": "warmup"}]
}'
```
```bash CLI
ant messages create \
--transform '{stop_reason,content,usage}' --format yaml <<'YAML'
model: claude-opus-4-7
max_tokens: 0
system:
- type: text
text: >-
You are an expert software engineer with deep knowledge of
distributed systems...
cache_control:
type: ephemeral
messages:
- role: user
content: warmup
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
# Fire this before users arrive to warm the shared system-prompt cache.
prewarm = client.messages.create(
model="claude-opus-4-7",
max_tokens=0,
system=[
{
"type": "text",
"text": "You are an expert software engineer with deep knowledge of distributed systems...",
"cache_control": {"type": "ephemeral"},
}
],
messages=[{"role": "user", "content": "warmup"}],
)
print(prewarm.stop_reason) # "max_tokens"
print(prewarm.content) # []
print(prewarm.usage)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// Fire this before users arrive to warm the shared system-prompt cache.
const prewarm = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 0,
system: [
{
type: "text",
text: "You are an expert software engineer with deep knowledge of distributed systems...",
cache_control: { type: "ephemeral" }
}
],
messages: [{ role: "user", content: "warmup" }]
});
console.log(prewarm.stop_reason); // "max_tokens"
console.log(prewarm.content); // []
console.log(prewarm.usage);
```
```csharp C# hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var prewarm = await client.Messages.Create(
new()
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 0,
System = new(
[
new TextBlockParam
{
Text = "You are an expert software engineer with deep knowledge of distributed systems...",
CacheControl = new(),
},
]
),
Messages = [new() { Role = Role.User, Content = "warmup" }],
}
);
Console.WriteLine(prewarm.StopReason?.Raw()); // "max_tokens"
Console.WriteLine(prewarm.Content.Count); // 0
Console.WriteLine(prewarm.Usage);
```
```go Go hidelines={1..10,-1}
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
prewarm, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 0,
System: []anthropic.TextBlockParam{
{
Text: "You are an expert software engineer with deep knowledge of distributed systems...",
CacheControl: anthropic.NewCacheControlEphemeralParam(),
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("warmup")),
},
})
if err != nil {
panic(err)
}
fmt.Println(prewarm.StopReason) // "max_tokens"
fmt.Println(prewarm.Content) // []
fmt.Println(prewarm.Usage.RawJSON())
}
```
```java Java hidelines={1..9,-1..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
Message prewarm = client.messages().create(MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(0)
.systemOfTextBlockParams(List.of(TextBlockParam.builder()
.text("You are an expert software engineer with deep knowledge of distributed systems...")
.cacheControl(CacheControlEphemeral.builder().build())
.build()))
.addUserMessage("warmup")
.build());
IO.println(prewarm.stopReason()); // Optional[max_tokens]
IO.println(prewarm.content()); // []
IO.println(prewarm.usage());
}
```
```php PHP hidelines={1..5}
messages->create(
model: Model::CLAUDE_OPUS_4_7,
maxTokens: 0,
system: [
[
'type' => 'text',
'text' => 'You are an expert software engineer with deep knowledge of distributed systems...',
'cache_control' => ['type' => 'ephemeral'],
],
],
messages: [['role' => 'user', 'content' => 'warmup']],
);
echo $prewarm->stopReason->value, PHP_EOL; // "max_tokens"
echo json_encode($prewarm->content), PHP_EOL; // []
echo json_encode($prewarm->usage), PHP_EOL;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
prewarm = client.messages.create(
model: Anthropic::Model::CLAUDE_OPUS_4_7,
max_tokens: 0,
system_: [
{
type: "text",
text: "You are an expert software engineer with deep knowledge of distributed systems...",
cache_control: {type: "ephemeral"}
}
],
messages: [{role: "user", content: "warmup"}]
)
puts prewarm.stop_reason # :max_tokens
puts prewarm.content # []
puts prewarm.usage
```
The API returns an empty `content` array:
```json Output
{
"id": "msg_01XFDUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"content": [],
"model": "claude-opus-4-7-20251101",
"stop_reason": "max_tokens",
"stop_sequence": null,
"usage": {
"input_tokens": 8,
"cache_creation_input_tokens": 5120,
"cache_read_input_tokens": 0,
"cache_creation": {
"ephemeral_5m_input_tokens": 5120,
"ephemeral_1h_input_tokens": 0
},
"iterations": [
{
"input_tokens": 8,
"output_tokens": 0,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 5120,
"cache_creation": {
"ephemeral_5m_input_tokens": 5120,
"ephemeral_1h_input_tokens": 0
},
"type": "message"
}
],
"output_tokens": 0,
"service_tier": "standard",
"inference_geo": "global"
}
}
```
### Typical usage pattern
Fire a pre-warm request when your application starts (or on a scheduled interval), then send real user requests after the pre-warm completes:
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
SYSTEM_PROMPT = [
{
"type": "text",
"text": "You are an expert software engineer with deep knowledge of distributed systems...",
"cache_control": {"type": "ephemeral"},
}
]
def prewarm_cache() -> None:
"""Call this at application startup or on a scheduled interval."""
client.messages.create(
model="claude-opus-4-7",
max_tokens=0,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": "warmup"}],
)
def respond(user_message: str) -> anthropic.types.Message:
"""The real user request; benefits from a warm cache."""
return client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": user_message}],
)
# Warm the cache before any user traffic arrives.
prewarm_cache()
# Later, when the user submits a message, the system-prompt prefix is already cached.
response = respond("How do I implement a binary search tree?")
print(response.content[0].text)
```
Keep in mind that the cache TTL still applies. For the default 5-minute cache, send a new pre-warm request at least every 5 minutes to keep the cache warm. For longer gaps between user requests, use the [1-hour cache duration](#1-hour-cache-duration) instead.
### Limitations
A `max_tokens: 0` request is rejected with an `invalid_request_error` if any of the following are set, since each implies output that a zero-token budget cannot produce:
- `stream: true`
- [Extended thinking](/docs/en/build-with-claude/extended-thinking) (`thinking.type: "enabled"`)
- [Structured outputs](/docs/en/build-with-claude/structured-outputs) (`output_config.format`)
- `tool_choice` of `{"type": "tool", ...}` or `{"type": "any"}`
`max_tokens: 0` is also rejected inside a [Message Batches](/docs/en/build-with-claude/batch-processing) request. Pre-warming targets time-to-first-token, which does not apply to batch processing, and a cache entry written during batch processing would likely expire before the follow-up request runs.
### Replacing the max_tokens=1 workaround
Before `max_tokens: 0` was available, some applications used `max_tokens: 1` warm-up calls to achieve the same effect. The `max_tokens: 0` approach is preferred: no output is produced, so there is no single-token reply to discard, no output tokens are billed, and the intent of the request is unambiguous.
---
## Prompt caching examples
To help you get started with prompt caching, the [prompt caching cookbook](https://platform.claude.com/cookbook/misc-prompt-caching) provides detailed examples and best practices.
The following code snippets showcase various prompt caching patterns. These examples demonstrate how to implement caching in different scenarios, helping you understand the practical applications of this feature:
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"system": [
{
"type": "text",
"text": "You are an AI assistant tasked with analyzing legal documents."
},
{
"type": "text",
"text": "Here is the full text of a complex legal agreement: [Insert full text of a 50-page legal agreement here]",
"cache_control": {"type": "ephemeral"}
}
],
"messages": [
{
"role": "user",
"content": "What are the key terms and conditions in this agreement?"
}
]
}'
```
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
system:
- type: text
text: You are an AI assistant tasked with analyzing legal documents.
- type: text
text: >-
Here is the full text of a complex legal agreement:
[Insert full text of a 50-page legal agreement here]
cache_control:
type: ephemeral
messages:
- role: user
content: What are the key terms and conditions in this agreement?
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
system=[
{
"type": "text",
"text": "You are an AI assistant tasked with analyzing legal documents.",
},
{
"type": "text",
"text": "Here is the full text of a complex legal agreement: [Insert full text of a 50-page legal agreement here]",
"cache_control": {"type": "ephemeral"},
},
],
messages=[
{
"role": "user",
"content": "What are the key terms and conditions in this agreement?",
}
],
)
print(response.usage.model_dump_json())
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
system: [
{
type: "text",
text: "You are an AI assistant tasked with analyzing legal documents."
},
{
type: "text",
text: "Here is the full text of a complex legal agreement: [Insert full text of a 50-page legal agreement here]",
cache_control: { type: "ephemeral" }
}
],
messages: [
{
role: "user",
content: "What are the key terms and conditions in this agreement?"
}
]
});
console.log(response);
```
```csharp C# hidelines={1..3}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new()
{
ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")
};
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
System = new MessageCreateParamsSystem(new List
{
new TextBlockParam()
{
Text = "You are an AI assistant tasked with analyzing legal documents.",
},
new TextBlockParam()
{
Text = "Here is the full text of a complex legal agreement: [Insert full text of a 50-page legal agreement here]",
CacheControl = new CacheControlEphemeral(),
},
}),
Messages =
[
new()
{
Role = Role.User,
Content = "What are the key terms and conditions in this agreement?"
}
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
System: []anthropic.TextBlockParam{
{
Text: "You are an AI assistant tasked with analyzing legal documents.",
},
{
Text: "Here is the full text of a complex legal agreement: [Insert full text of a 50-page legal agreement here]",
CacheControl: anthropic.NewCacheControlEphemeralParam(),
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What are the key terms and conditions in this agreement?")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Usage)
}
```
```java Java hidelines={1..2,4..12,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import java.util.List;
public class LegalDocumentAnalysisExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.systemOfTextBlockParams(
List.of(
TextBlockParam.builder()
.text("You are an AI assistant tasked with analyzing legal documents.")
.build(),
TextBlockParam.builder()
.text(
"Here is the full text of a complex legal agreement: [Insert full text of a 50-page legal agreement here]"
)
.cacheControl(CacheControlEphemeral.builder().build())
.build()
)
)
.addUserMessage("What are the key terms and conditions in this agreement?")
.build();
Message message = client.messages().create(params);
System.out.println(message);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => 'What are the key terms and conditions in this agreement?'
]
],
model: 'claude-opus-4-7',
system: [
[
'type' => 'text',
'text' => 'You are an AI assistant tasked with analyzing legal documents.'
],
[
'type' => 'text',
'text' => 'Here is the full text of a complex legal agreement: [Insert full text of a 50-page legal agreement here]',
'cache_control' => ['type' => 'ephemeral']
]
],
);
echo $message->content[0]->text;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
system: [
{
type: "text",
text: "You are an AI assistant tasked with analyzing legal documents."
},
{
type: "text",
text: "Here is the full text of a complex legal agreement: [Insert full text of a 50-page legal agreement here]",
cache_control: { type: "ephemeral" }
}
],
messages: [
{
role: "user",
content: "What are the key terms and conditions in this agreement?"
}
]
)
puts message
```
This example demonstrates basic prompt caching usage, caching the full text of the legal agreement as a prefix while keeping the user instruction uncached.
For the first request:
- `input_tokens`: Number of tokens in the user message only
- `cache_creation_input_tokens`: Number of tokens in the entire system message, including the legal document
- `cache_read_input_tokens`: 0 (no cache hit on first request)
For subsequent requests within the cache lifetime:
- `input_tokens`: Number of tokens in the user message only
- `cache_creation_input_tokens`: 0 (no new cache creation)
- `cache_read_input_tokens`: Number of tokens in the entire cached system message
Tool definitions can be cached by placing `cache_control` on the last tool in your `tools` array. All tools defined before and including that tool are cached as a single prefix.
```json
{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"tools": [
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": { "location": { "type": "string" } },
"required": ["location"]
}
},
{
"name": "get_time",
"description": "Get the current time in a given time zone",
"input_schema": {
"type": "object",
"properties": { "timezone": { "type": "string" } },
"required": ["timezone"]
},
"cache_control": { "type": "ephemeral" }
}
],
"messages": [{ "role": "user", "content": "What is the weather and time in New York?" }]
}
```
On the first request, `cache_creation_input_tokens` reflects the token count of all tool definitions. On subsequent requests within the cache lifetime, those tokens appear under `cache_read_input_tokens` instead.
For detailed interaction between tool definitions, `defer_loading`, and cache invalidation, see [Tool use with prompt caching](/docs/en/agents-and-tools/tool-use/tool-use-with-prompt-caching).
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"system": [
{
"type": "text",
"text": "...long system prompt",
"cache_control": {"type": "ephemeral"}
}
],
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "Hello, can you tell me more about the solar system?"
}
]
},
{
"role": "assistant",
"content": "Certainly! The solar system is the collection of celestial bodies that orbit our Sun. It consists of eight planets, numerous moons, asteroids, comets, and other objects. The planets, in order from closest to farthest from the Sun, are: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, and Neptune. Each planet has its own unique characteristics and features. Is there a specific aspect of the solar system you would like to know more about?"
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "Good to know."
},
{
"type": "text",
"text": "Tell me more about Mars.",
"cache_control": {"type": "ephemeral"}
}
]
}
]
}'
```
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
system:
- type: text
text: "...long system prompt"
cache_control:
type: ephemeral
messages:
- role: user
content:
- type: text
text: Hello, can you tell me more about the solar system?
- role: assistant
content: >-
Certainly! The solar system is the collection of celestial bodies that
orbit our Sun. It consists of eight planets, numerous moons, asteroids,
comets, and other objects. The planets, in order from closest to farthest
from the Sun, are: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus,
and Neptune. Each planet has its own unique characteristics and features.
Is there a specific aspect of the solar system you would like to know
more about?
- role: user
content:
- type: text
text: Good to know.
- type: text
text: Tell me more about Mars.
cache_control:
type: ephemeral
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
system=[
{
"type": "text",
"text": "...long system prompt",
"cache_control": {"type": "ephemeral"},
}
],
messages=[
# ...long conversation so far
{
"role": "user",
"content": [
{
"type": "text",
"text": "Hello, can you tell me more about the solar system?",
}
],
},
{
"role": "assistant",
"content": "Certainly! The solar system is the collection of celestial bodies that orbit our Sun. It consists of eight planets, numerous moons, asteroids, comets, and other objects. The planets, in order from closest to farthest from the Sun, are: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, and Neptune. Each planet has its own unique characteristics and features. Is there a specific aspect of the solar system you'd like to know more about?",
},
{
"role": "user",
"content": [
{"type": "text", "text": "Good to know."},
{
"type": "text",
"text": "Tell me more about Mars.",
"cache_control": {"type": "ephemeral"},
},
],
},
],
)
print(response.model_dump_json())
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
system: [
{
type: "text",
text: "...long system prompt",
cache_control: { type: "ephemeral" }
}
],
messages: [
// ...long conversation so far
{
role: "user",
content: [
{
type: "text",
text: "Hello, can you tell me more about the solar system?"
}
]
},
{
role: "assistant",
content:
"Certainly! The solar system is the collection of celestial bodies that orbit our Sun. It consists of eight planets, numerous moons, asteroids, comets, and other objects. The planets, in order from closest to farthest from the Sun, are: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, and Neptune. Each planet has its own unique characteristics and features. Is there a specific aspect of the solar system you'd like to know more about?"
},
{
role: "user",
content: [
{
type: "text",
text: "Good to know."
},
{
type: "text",
text: "Tell me more about Mars.",
cache_control: { type: "ephemeral" }
}
]
}
]
});
console.log(response);
```
```csharp C# hidelines={1..6}
using Anthropic;
using Anthropic.Models.Messages;
using System.Collections.Generic;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
System = new MessageCreateParamsSystem(new List
{
new TextBlockParam()
{
Text = "...long system prompt",
CacheControl = new CacheControlEphemeral(),
},
}),
Messages =
[
new()
{
Role = Role.User,
Content = new MessageParamContent(new List
{
new ContentBlockParam(new TextBlockParam("Hello, can you tell me more about the solar system?")),
}),
},
new()
{
Role = Role.Assistant,
Content = "Certainly! The solar system is the collection of celestial bodies that orbit our Sun. It consists of eight planets, numerous moons, asteroids, comets, and other objects. The planets, in order from closest to farthest from the Sun, are: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, and Neptune. Each planet has its own unique characteristics and features. Is there a specific aspect of the solar system you would like to know more about?"
},
new()
{
Role = Role.User,
Content = new MessageParamContent(new List
{
new ContentBlockParam(new TextBlockParam("Good to know.")),
new ContentBlockParam(new TextBlockParam()
{
Text = "Tell me more about Mars.",
CacheControl = new CacheControlEphemeral(),
}),
})
}
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
System: []anthropic.TextBlockParam{
{
Text: "...long system prompt",
CacheControl: anthropic.NewCacheControlEphemeralParam(),
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, can you tell me more about the solar system?")),
anthropic.NewAssistantMessage(anthropic.NewTextBlock("Certainly! The solar system is the collection of celestial bodies that orbit our Sun. It consists of eight planets, numerous moons, asteroids, comets, and other objects. The planets, in order from closest to farthest from the Sun, are: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, and Neptune. Each planet has its own unique characteristics and features. Is there a specific aspect of the solar system you would like to know more about?")),
{
Role: anthropic.MessageParamRoleUser,
Content: []anthropic.ContentBlockParamUnion{
anthropic.NewTextBlock("Good to know."),
{OfText: &anthropic.TextBlockParam{
Text: "Tell me more about Mars.",
CacheControl: anthropic.NewCacheControlEphemeralParam(),
}},
},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..2,4..13,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import java.util.List;
public class ConversationWithCacheControlExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Create ephemeral system prompt
TextBlockParam systemPrompt = TextBlockParam.builder()
.text("...long system prompt")
.cacheControl(CacheControlEphemeral.builder().build())
.build();
// Create message params
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.systemOfTextBlockParams(List.of(systemPrompt))
// First user message (without cache control)
.addUserMessage("Hello, can you tell me more about the solar system?")
// Assistant response
.addAssistantMessage(
"Certainly! The solar system is the collection of celestial bodies that orbit our Sun. It consists of eight planets, numerous moons, asteroids, comets, and other objects. The planets, in order from closest to farthest from the Sun, are: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, and Neptune. Each planet has its own unique characteristics and features. Is there a specific aspect of the solar system you would like to know more about?"
)
// Second user message (with cache control)
.addUserMessageOfBlockParams(
List.of(
ContentBlockParam.ofText(TextBlockParam.builder().text("Good to know.").build()),
ContentBlockParam.ofText(
TextBlockParam.builder()
.text("Tell me more about Mars.")
.cacheControl(CacheControlEphemeral.builder().build())
.build()
)
)
)
.build();
Message message = client.messages().create(params);
System.out.println(message);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'text',
'text' => 'Hello, can you tell me more about the solar system?'
]
]
],
[
'role' => 'assistant',
'content' => "Certainly! The solar system is the collection of celestial bodies that orbit our Sun. It consists of eight planets, numerous moons, asteroids, comets, and other objects. The planets, in order from closest to farthest from the Sun, are: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, and Neptune. Each planet has its own unique characteristics and features. Is there a specific aspect of the solar system you would like to know more about?"
],
[
'role' => 'user',
'content' => [
['type' => 'text', 'text' => 'Good to know.'],
[
'type' => 'text',
'text' => 'Tell me more about Mars.',
'cache_control' => ['type' => 'ephemeral']
]
]
]
],
model: 'claude-opus-4-7',
system: [
[
'type' => 'text',
'text' => '...long system prompt',
'cache_control' => ['type' => 'ephemeral']
]
],
);
echo $message->content[0]->text;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
system: [
{
type: "text",
text: "...long system prompt",
cache_control: { type: "ephemeral" }
}
],
messages: [
{
role: "user",
content: [
{
type: "text",
text: "Hello, can you tell me more about the solar system?"
}
]
},
{
role: "assistant",
content: "Certainly! The solar system is the collection of celestial bodies that orbit our Sun. It consists of eight planets, numerous moons, asteroids, comets, and other objects. The planets, in order from closest to farthest from the Sun, are: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, and Neptune. Each planet has its own unique characteristics and features. Is there a specific aspect of the solar system you would like to know more about?"
},
{
role: "user",
content: [
{ type: "text", text: "Good to know." },
{
type: "text",
text: "Tell me more about Mars.",
cache_control: { type: "ephemeral" }
}
]
}
]
)
puts message
```
This example demonstrates how to use prompt caching in a multi-turn conversation.
During each turn, the final block of the final message is marked with `cache_control` so the conversation can be incrementally cached. The system will automatically lookup and use the longest previously cached sequence of blocks for follow-up messages. That is, blocks that were previously marked with a `cache_control` block are later not marked with this, but they will still be considered a cache hit (and also a cache refresh!) if they are hit within 5 minutes.
In addition, note that the `cache_control` parameter is placed on the system message. This is to ensure that if this gets evicted from the cache (after not being used for more than 5 minutes), it will get added back to the cache on the next request.
This approach is useful for maintaining context in ongoing conversations without repeatedly processing the same information.
When this is set up properly, you should see the following in the usage response of each request:
- `input_tokens`: Number of tokens in the new user message (will be minimal)
- `cache_creation_input_tokens`: Number of tokens in the new assistant and user turns
- `cache_read_input_tokens`: Number of tokens in the conversation up to the previous turn
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"tools": [
{
"name": "search_documents",
"description": "Search through the knowledge base",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query"
}
},
"required": ["query"]
}
},
{
"name": "get_document",
"description": "Retrieve a specific document by ID",
"input_schema": {
"type": "object",
"properties": {
"doc_id": {
"type": "string",
"description": "Document ID"
}
},
"required": ["doc_id"]
},
"cache_control": {"type": "ephemeral"}
}
],
"system": [
{
"type": "text",
"text": "You are a helpful research assistant with access to a document knowledge base.\n\n# Instructions\n- Always search for relevant documents before answering\n- Provide citations for your sources\n- Be objective and accurate in your responses\n- If multiple documents contain relevant information, synthesize them\n- Acknowledge when information is not available in the knowledge base",
"cache_control": {"type": "ephemeral"}
},
{
"type": "text",
"text": "# Knowledge Base Context\n\nHere are the relevant documents for this conversation:\n\n## Document 1: Solar System Overview\nThe solar system consists of the Sun and all objects that orbit it...\n\n## Document 2: Planetary Characteristics\nEach planet has unique features. Mercury is the smallest planet...\n\n## Document 3: Mars Exploration\nMars has been a target of exploration for decades...\n\n[Additional documents...]",
"cache_control": {"type": "ephemeral"}
}
],
"messages": [
{
"role": "user",
"content": "Can you search for information about Mars rovers?"
},
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "tool_1",
"name": "search_documents",
"input": {"query": "Mars rovers"}
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "tool_1",
"content": "Found 3 relevant documents: Document 3 (Mars Exploration), Document 7 (Rover Technology), Document 9 (Mission History)"
}
]
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "I found 3 relevant documents about Mars rovers. Let me get more details from the Mars Exploration document."
}
]
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "Yes, please tell me about the Perseverance rover specifically.",
"cache_control": {"type": "ephemeral"}
}
]
}
]
}'
```
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
tools:
- name: search_documents
description: Search through the knowledge base
input_schema:
type: object
properties:
query:
type: string
description: Search query
required: [query]
- name: get_document
description: Retrieve a specific document by ID
input_schema:
type: object
properties:
doc_id:
type: string
description: Document ID
required: [doc_id]
cache_control:
type: ephemeral
system:
- type: text
text: |-
You are a helpful research assistant with access to a document knowledge base.
# Instructions
- Always search for relevant documents before answering
- Provide citations for your sources
- Be objective and accurate in your responses
- If multiple documents contain relevant information, synthesize them
- Acknowledge when information is not available in the knowledge base
cache_control:
type: ephemeral
- type: text
text: |-
# Knowledge Base Context
Here are the relevant documents for this conversation:
## Document 1: Solar System Overview
The solar system consists of the Sun and all objects that orbit it...
## Document 2: Planetary Characteristics
Each planet has unique features. Mercury is the smallest planet...
## Document 3: Mars Exploration
Mars has been a target of exploration for decades...
[Additional documents...]
cache_control:
type: ephemeral
messages:
- role: user
content: Can you search for information about Mars rovers?
- role: assistant
content:
- type: tool_use
id: tool_1
name: search_documents
input:
query: Mars rovers
- role: user
content:
- type: tool_result
tool_use_id: tool_1
content: >-
Found 3 relevant documents: Document 3 (Mars Exploration),
Document 7 (Rover Technology), Document 9 (Mission History)
- role: assistant
content:
- type: text
text: >-
I found 3 relevant documents about Mars rovers. Let me get more
details from the Mars Exploration document.
- role: user
content:
- type: text
text: Yes, please tell me about the Perseverance rover specifically.
cache_control:
type: ephemeral
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
tools=[
{
"name": "search_documents",
"description": "Search through the knowledge base",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"}
},
"required": ["query"],
},
},
{
"name": "get_document",
"description": "Retrieve a specific document by ID",
"input_schema": {
"type": "object",
"properties": {
"doc_id": {"type": "string", "description": "Document ID"}
},
"required": ["doc_id"],
},
"cache_control": {"type": "ephemeral"},
},
],
system=[
{
"type": "text",
"text": "You are a helpful research assistant with access to a document knowledge base.\n\n# Instructions\n- Always search for relevant documents before answering\n- Provide citations for your sources\n- Be objective and accurate in your responses\n- If multiple documents contain relevant information, synthesize them\n- Acknowledge when information is not available in the knowledge base",
"cache_control": {"type": "ephemeral"},
},
{
"type": "text",
"text": "# Knowledge Base Context\n\nHere are the relevant documents for this conversation:\n\n## Document 1: Solar System Overview\nThe solar system consists of the Sun and all objects that orbit it...\n\n## Document 2: Planetary Characteristics\nEach planet has unique features. Mercury is the smallest planet...\n\n## Document 3: Mars Exploration\nMars has been a target of exploration for decades...\n\n[Additional documents...]",
"cache_control": {"type": "ephemeral"},
},
],
messages=[
{
"role": "user",
"content": "Can you search for information about Mars rovers?",
},
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "tool_1",
"name": "search_documents",
"input": {"query": "Mars rovers"},
}
],
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "tool_1",
"content": "Found 3 relevant documents: Document 3 (Mars Exploration), Document 7 (Rover Technology), Document 9 (Mission History)",
}
],
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "I found 3 relevant documents about Mars rovers. Let me get more details from the Mars Exploration document.",
}
],
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "Yes, please tell me about the Perseverance rover specifically.",
"cache_control": {"type": "ephemeral"},
}
],
},
],
)
print(response.model_dump_json())
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [
{
name: "search_documents",
description: "Search through the knowledge base",
input_schema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query"
}
},
required: ["query"]
}
},
{
name: "get_document",
description: "Retrieve a specific document by ID",
input_schema: {
type: "object",
properties: {
doc_id: {
type: "string",
description: "Document ID"
}
},
required: ["doc_id"]
},
cache_control: { type: "ephemeral" }
}
],
system: [
{
type: "text",
text: "You are a helpful research assistant with access to a document knowledge base.\n\n# Instructions\n- Always search for relevant documents before answering\n- Provide citations for your sources\n- Be objective and accurate in your responses\n- If multiple documents contain relevant information, synthesize them\n- Acknowledge when information is not available in the knowledge base",
cache_control: { type: "ephemeral" }
},
{
type: "text",
text: "# Knowledge Base Context\n\nHere are the relevant documents for this conversation:\n\n## Document 1: Solar System Overview\nThe solar system consists of the Sun and all objects that orbit it...\n\n## Document 2: Planetary Characteristics\nEach planet has unique features. Mercury is the smallest planet...\n\n## Document 3: Mars Exploration\nMars has been a target of exploration for decades...\n\n[Additional documents...]",
cache_control: { type: "ephemeral" }
}
],
messages: [
{
role: "user",
content: "Can you search for information about Mars rovers?"
},
{
role: "assistant",
content: [
{
type: "tool_use",
id: "tool_1",
name: "search_documents",
input: { query: "Mars rovers" }
}
]
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "tool_1",
content:
"Found 3 relevant documents: Document 3 (Mars Exploration), Document 7 (Rover Technology), Document 9 (Mission History)"
}
]
},
{
role: "assistant",
content: [
{
type: "text",
text: "I found 3 relevant documents about Mars rovers. Let me get more details from the Mars Exploration document."
}
]
},
{
role: "user",
content: [
{
type: "text",
text: "Yes, please tell me about the Perseverance rover specifically.",
cache_control: { type: "ephemeral" }
}
]
}
]
});
console.log(response);
```
```csharp C# hidelines={1..4}
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new()
{
ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")
};
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Tools =
[
new ToolUnion(new Tool()
{
Name = "search_documents",
Description = "Search through the knowledge base",
InputSchema = new InputSchema()
{
Properties = new Dictionary
{
["query"] = JsonSerializer.SerializeToElement(new { type = "string", description = "Search query" }),
},
Required = ["query"],
},
}),
new ToolUnion(new Tool()
{
Name = "get_document",
Description = "Retrieve a specific document by ID",
InputSchema = new InputSchema()
{
Properties = new Dictionary
{
["doc_id"] = JsonSerializer.SerializeToElement(new { type = "string", description = "Document ID" }),
},
Required = ["doc_id"],
},
CacheControl = new CacheControlEphemeral(),
}),
],
System = new MessageCreateParamsSystem(new List
{
new TextBlockParam()
{
Text = "You are a helpful research assistant with access to a document knowledge base.\n\n# Instructions\n- Always search for relevant documents before answering\n- Provide citations for your sources\n- Be objective and accurate in your responses\n- If multiple documents contain relevant information, synthesize them\n- Acknowledge when information is not available in the knowledge base",
CacheControl = new CacheControlEphemeral(),
},
new TextBlockParam()
{
Text = "# Knowledge Base Context\n\nHere are the relevant documents for this conversation:\n\n## Document 1: Solar System Overview\nThe solar system consists of the Sun and all objects that orbit it...\n\n## Document 2: Planetary Characteristics\nEach planet has unique features. Mercury is the smallest planet...\n\n## Document 3: Mars Exploration\nMars has been a target of exploration for decades...\n\n[Additional documents...]",
CacheControl = new CacheControlEphemeral(),
},
}),
Messages =
[
new() { Role = Role.User, Content = "Can you search for information about Mars rovers?" },
new()
{
Role = Role.Assistant,
Content = new MessageParamContent(new List
{
new ContentBlockParam(new ToolUseBlockParam()
{
ID = "tool_1",
Name = "search_documents",
Input = new Dictionary
{
["query"] = JsonSerializer.SerializeToElement("Mars rovers"),
},
}),
}),
},
new()
{
Role = Role.User,
Content = new MessageParamContent(new List
{
new ContentBlockParam(new ToolResultBlockParam()
{
ToolUseID = "tool_1",
Content = "Found 3 relevant documents: Document 3 (Mars Exploration), Document 7 (Rover Technology), Document 9 (Mission History)",
}),
}),
},
new()
{
Role = Role.Assistant,
Content = "I found 3 relevant documents about Mars rovers. Let me get more details from the Mars Exploration document.",
},
new()
{
Role = Role.User,
Content = new MessageParamContent(new List
{
new ContentBlockParam(new TextBlockParam()
{
Text = "Yes, please tell me about the Perseverance rover specifically.",
CacheControl = new CacheControlEphemeral(),
}),
}),
},
]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Tools: []anthropic.ToolUnionParam{
{OfTool: &anthropic.ToolParam{
Name: "search_documents",
Description: anthropic.String("Search through the knowledge base"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"query": map[string]any{
"type": "string",
"description": "Search query",
},
},
Required: []string{"query"},
},
}},
{OfTool: &anthropic.ToolParam{
Name: "get_document",
Description: anthropic.String("Retrieve a specific document by ID"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"doc_id": map[string]any{
"type": "string",
"description": "Document ID",
},
},
Required: []string{"doc_id"},
},
CacheControl: anthropic.NewCacheControlEphemeralParam(),
}},
},
System: []anthropic.TextBlockParam{
{
Text: "You are a helpful research assistant with access to a document knowledge base.\n\n# Instructions\n- Always search for relevant documents before answering\n- Provide citations for your sources\n- Be objective and accurate in your responses\n- If multiple documents contain relevant information, synthesize them\n- Acknowledge when information is not available in the knowledge base",
CacheControl: anthropic.NewCacheControlEphemeralParam(),
},
{
Text: "# Knowledge Base Context\n\nHere are the relevant documents for this conversation:\n\n## Document 1: Solar System Overview\nThe solar system consists of the Sun and all objects that orbit it...\n\n## Document 2: Planetary Characteristics\nEach planet has unique features. Mercury is the smallest planet...\n\n## Document 3: Mars Exploration\nMars has been a target of exploration for decades...\n\n[Additional documents...]",
CacheControl: anthropic.NewCacheControlEphemeralParam(),
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Can you search for information about Mars rovers?")),
anthropic.NewAssistantMessage(anthropic.NewToolUseBlock(
"tool_1",
map[string]any{"query": "Mars rovers"},
"search_documents",
)),
anthropic.NewUserMessage(anthropic.NewToolResultBlock(
"tool_1",
"Found 3 relevant documents: Document 3 (Mars Exploration), Document 7 (Rover Technology), Document 9 (Mission History)",
false,
)),
anthropic.NewAssistantMessage(anthropic.NewTextBlock("I found 3 relevant documents about Mars rovers. Let me get more details from the Mars Exploration document.")),
{
Role: anthropic.MessageParamRoleUser,
Content: []anthropic.ContentBlockParamUnion{
{OfText: &anthropic.TextBlockParam{
Text: "Yes, please tell me about the Perseverance rover specifically.",
CacheControl: anthropic.NewCacheControlEphemeralParam(),
}},
},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..3,5..19,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.Tool.InputSchema;
import com.anthropic.models.messages.ToolResultBlockParam;
import com.anthropic.models.messages.ToolUseBlockParam;
import java.util.List;
import java.util.Map;
public class MultipleCacheBreakpointsExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Search tool schema
InputSchema searchSchema = InputSchema.builder()
.properties(
JsonValue.from(
Map.of("query", Map.of("type", "string", "description", "Search query"))
)
)
.putAdditionalProperty("required", JsonValue.from(List.of("query")))
.build();
// Get document tool schema
InputSchema getDocSchema = InputSchema.builder()
.properties(
JsonValue.from(
Map.of("doc_id", Map.of("type", "string", "description", "Document ID"))
)
)
.putAdditionalProperty("required", JsonValue.from(List.of("doc_id")))
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
// Tools with cache control on the last one
.addTool(
Tool.builder()
.name("search_documents")
.description("Search through the knowledge base")
.inputSchema(searchSchema)
.build()
)
.addTool(
Tool.builder()
.name("get_document")
.description("Retrieve a specific document by ID")
.inputSchema(getDocSchema)
.cacheControl(CacheControlEphemeral.builder().build())
.build()
)
// System prompts with cache control on instructions and context separately
.systemOfTextBlockParams(
List.of(
TextBlockParam.builder()
.text(
"You are a helpful research assistant with access to a document knowledge base.\n\n# Instructions\n- Always search for relevant documents before answering\n- Provide citations for your sources\n- Be objective and accurate in your responses\n- If multiple documents contain relevant information, synthesize them\n- Acknowledge when information is not available in the knowledge base"
)
.cacheControl(CacheControlEphemeral.builder().build())
.build(),
TextBlockParam.builder()
.text(
"# Knowledge Base Context\n\nHere are the relevant documents for this conversation:\n\n## Document 1: Solar System Overview\nThe solar system consists of the Sun and all objects that orbit it...\n\n## Document 2: Planetary Characteristics\nEach planet has unique features. Mercury is the smallest planet...\n\n## Document 3: Mars Exploration\nMars has been a target of exploration for decades...\n\n[Additional documents...]"
)
.cacheControl(CacheControlEphemeral.builder().build())
.build()
)
)
// Conversation history
.addUserMessage("Can you search for information about Mars rovers?")
.addAssistantMessageOfBlockParams(
List.of(
ContentBlockParam.ofToolUse(
ToolUseBlockParam.builder()
.id("tool_1")
.name("search_documents")
.input(JsonValue.from(Map.of("query", "Mars rovers")))
.build()
)
)
)
.addUserMessageOfBlockParams(
List.of(
ContentBlockParam.ofToolResult(
ToolResultBlockParam.builder()
.toolUseId("tool_1")
.content(
"Found 3 relevant documents: Document 3 (Mars Exploration), Document 7 (Rover Technology), Document 9 (Mission History)"
)
.build()
)
)
)
.addAssistantMessageOfBlockParams(
List.of(
ContentBlockParam.ofText(
TextBlockParam.builder()
.text(
"I found 3 relevant documents about Mars rovers. Let me get more details from the Mars Exploration document."
)
.build()
)
)
)
.addUserMessageOfBlockParams(
List.of(
ContentBlockParam.ofText(
TextBlockParam.builder()
.text("Yes, please tell me about the Perseverance rover specifically.")
.cacheControl(CacheControlEphemeral.builder().build())
.build()
)
)
)
.build();
Message message = client.messages().create(params);
System.out.println(message);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => 'Can you search for information about Mars rovers?'
],
[
'role' => 'assistant',
'content' => [
[
'type' => 'tool_use',
'id' => 'tool_1',
'name' => 'search_documents',
'input' => ['query' => 'Mars rovers']
]
]
],
[
'role' => 'user',
'content' => [
[
'type' => 'tool_result',
'tool_use_id' => 'tool_1',
'content' => 'Found 3 relevant documents: Document 3 (Mars Exploration), Document 7 (Rover Technology), Document 9 (Mission History)'
]
]
],
[
'role' => 'assistant',
'content' => [
[
'type' => 'text',
'text' => 'I found 3 relevant documents about Mars rovers. Let me get more details from the Mars Exploration document.'
]
]
],
[
'role' => 'user',
'content' => [
[
'type' => 'text',
'text' => 'Yes, please tell me about the Perseverance rover specifically.',
'cache_control' => ['type' => 'ephemeral']
]
]
]
],
model: 'claude-opus-4-7',
system: [
[
'type' => 'text',
'text' => "You are a helpful research assistant with access to a document knowledge base.\n\n# Instructions\n- Always search for relevant documents before answering\n- Provide citations for your sources\n- Be objective and accurate in your responses\n- If multiple documents contain relevant information, synthesize them\n- Acknowledge when information is not available in the knowledge base",
'cache_control' => ['type' => 'ephemeral']
],
[
'type' => 'text',
'text' => "# Knowledge Base Context\n\nHere are the relevant documents for this conversation:\n\n## Document 1: Solar System Overview\nThe solar system consists of the Sun and all objects that orbit it...\n\n## Document 2: Planetary Characteristics\nEach planet has unique features. Mercury is the smallest planet...\n\n## Document 3: Mars Exploration\nMars has been a target of exploration for decades...\n\n[Additional documents...]",
'cache_control' => ['type' => 'ephemeral']
]
],
tools: [
[
'name' => 'search_documents',
'description' => 'Search through the knowledge base',
'input_schema' => [
'type' => 'object',
'properties' => [
'query' => [
'type' => 'string',
'description' => 'Search query'
]
],
'required' => ['query']
]
],
[
'name' => 'get_document',
'description' => 'Retrieve a specific document by ID',
'input_schema' => [
'type' => 'object',
'properties' => [
'doc_id' => [
'type' => 'string',
'description' => 'Document ID'
]
],
'required' => ['doc_id']
],
'cache_control' => ['type' => 'ephemeral']
]
],
);
echo json_encode($message->usage), PHP_EOL;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
tools: [
{
name: "search_documents",
description: "Search through the knowledge base",
input_schema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query"
}
},
required: ["query"]
}
},
{
name: "get_document",
description: "Retrieve a specific document by ID",
input_schema: {
type: "object",
properties: {
doc_id: {
type: "string",
description: "Document ID"
}
},
required: ["doc_id"]
},
cache_control: { type: "ephemeral" }
}
],
system: [
{
type: "text",
text: "You are a helpful research assistant with access to a document knowledge base.\n\n# Instructions\n- Always search for relevant documents before answering\n- Provide citations for your sources\n- Be objective and accurate in your responses\n- If multiple documents contain relevant information, synthesize them\n- Acknowledge when information is not available in the knowledge base",
cache_control: { type: "ephemeral" }
},
{
type: "text",
text: "# Knowledge Base Context\n\nHere are the relevant documents for this conversation:\n\n## Document 1: Solar System Overview\nThe solar system consists of the Sun and all objects that orbit it...\n\n## Document 2: Planetary Characteristics\nEach planet has unique features. Mercury is the smallest planet...\n\n## Document 3: Mars Exploration\nMars has been a target of exploration for decades...\n\n[Additional documents...]",
cache_control: { type: "ephemeral" }
}
],
messages: [
{
role: "user",
content: "Can you search for information about Mars rovers?"
},
{
role: "assistant",
content: [
{
type: "tool_use",
id: "tool_1",
name: "search_documents",
input: { query: "Mars rovers" }
}
]
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "tool_1",
content: "Found 3 relevant documents: Document 3 (Mars Exploration), Document 7 (Rover Technology), Document 9 (Mission History)"
}
]
},
{
role: "assistant",
content: [
{
type: "text",
text: "I found 3 relevant documents about Mars rovers. Let me get more details from the Mars Exploration document."
}
]
},
{
role: "user",
content: [
{
type: "text",
text: "Yes, please tell me about the Perseverance rover specifically.",
cache_control: { type: "ephemeral" }
}
]
}
]
)
puts message
```
This comprehensive example demonstrates how to use all 4 available cache breakpoints to optimize different parts of your prompt:
1. **Tools cache** (cache breakpoint 1): The `cache_control` parameter on the last tool definition caches all tool definitions.
2. **Reusable instructions cache** (cache breakpoint 2): The static instructions in the system prompt are cached separately. These instructions rarely change between requests.
3. **RAG context cache** (cache breakpoint 3): The knowledge base documents are cached independently, allowing you to update the RAG documents without invalidating the tools or instructions cache.
4. **Conversation history cache** (cache breakpoint 4): The final user message is marked with `cache_control` to enable incremental caching of the conversation as it progresses.
This approach provides maximum flexibility:
- If you append a new turn to the conversation without changing earlier content, all four cache segments are reused
- If you update the RAG documents but keep the same tools and instructions, the first two cache segments are reused
- If you change the conversation but keep the same tools, instructions, and documents, the first three segments are reused
- Changes at any breakpoint invalidate that segment and everything after it, while earlier cached segments remain valid
For the first request:
- `input_tokens`: Minimal (tokens after the final cache breakpoint, near 0 in this example)
- `cache_creation_input_tokens`: Tokens in all cached segments (tools + instructions + RAG documents + conversation history)
- `cache_read_input_tokens`: 0 (no cache hits)
For subsequent requests with only a new user message (and the fourth breakpoint moved to that new final message, as in the example):
- `input_tokens`: Minimal (tokens after the final cache breakpoint, near 0 in this example)
- `cache_creation_input_tokens`: Tokens in the new user message and the previous assistant turn (the new conversation segment being cached)
- `cache_read_input_tokens`: All previously cached tokens (tools + instructions + RAG documents + previous conversation)
This pattern is especially powerful for:
- RAG applications with large document contexts
- Agent systems that use multiple tools
- Long-running conversations that need to maintain context
- Applications that need to optimize different parts of the prompt independently
## Data retention
Prompt caching (both automatic and explicit) is ZDR eligible. Anthropic does not store the raw text of your prompts or Claude's responses.
KV (key-value) cache representations and cryptographic hashes of cached content are held in memory only and are not stored at rest. Cached entries have a minimum lifetime of 5 minutes (standard) or 1 hour (extended), after which they are promptly, though not immediately, deleted. Cache entries are isolated between organizations and, on the Claude API, Claude Platform on AWS, and Microsoft Foundry (beta), between workspaces within an organization.
For ZDR eligibility across all features, see [API and data retention](/docs/en/manage-claude/api-and-data-retention).
---
## FAQ
**In most cases, a single cache breakpoint at the end of your static content is sufficient.** Cache writes happen only at the block you mark. Place it on the last block that stays identical across requests, and every subsequent request reads that same entry. If a later block varies per request (a timestamp, the incoming message), keep the breakpoint before it, on the last stable block.
You only need multiple breakpoints if:
- A growing conversation pushes your breakpoint 20 or more blocks past the last cache write, putting the prior entry outside the lookback window
- You want to cache sections that update at different frequencies independently
- You need explicit control over what gets cached for cost optimization
Example: If you have system instructions (rarely change) and RAG context (changes daily), you might use two breakpoints to cache them separately.
No, cache breakpoints themselves are free. You only pay for:
- Writing content to cache (25% more than base input tokens for 5-minute TTL)
- Reading from cache (10% of base input token price)
- Regular input tokens for uncached content
The number of breakpoints doesn't affect pricing - only the amount of content cached and read matters.
The usage response includes three separate input token fields that together represent your total input:
```text
total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens
```
- `cache_read_input_tokens`: Tokens retrieved from cache (everything before cache breakpoints that was cached)
- `cache_creation_input_tokens`: New tokens being written to cache (at cache breakpoints)
- `input_tokens`: Tokens **after the last cache breakpoint** that aren't cached
**Important:** `input_tokens` does NOT represent all input tokens - only the portion after your last cache breakpoint. If you have cached content, `input_tokens` will typically be much smaller than your total input.
**Example:** With a 200k token document cached and a 50 token user question:
- `cache_read_input_tokens`: 200,000
- `cache_creation_input_tokens`: 0
- `input_tokens`: 50
- **Total**: 200,050 tokens
This breakdown is critical for understanding both your costs and rate limit usage. See [Tracking cache performance](#tracking-cache-performance) for more details.
The cache's default minimum lifetime (TTL) is 5 minutes. This lifetime is refreshed each time the cached content is used.
If you find that 5 minutes is too short, Anthropic also offers a [1-hour cache TTL](#1-hour-cache-duration).
You can define up to 4 cache breakpoints (using `cache_control` parameters) in your prompt.
Prompt caching is supported on all [active Claude models](/docs/en/about-claude/models/overview).
Cached system prompts and tools will be reused when thinking parameters change. However, thinking changes (enabling/disabling or budget changes) will invalidate previously cached prompt prefixes with messages content.
For more details on cache invalidation, see [What invalidates the cache](#what-invalidates-the-cache).
For more on extended thinking, including its interaction with tool use and prompt caching, see the [extended thinking documentation](/docs/en/build-with-claude/extended-thinking#extended-thinking-and-prompt-caching).
The easiest way is to add `"cache_control": {"type": "ephemeral"}` at the top level of your request body ([automatic caching](#automatic-caching)). Alternatively, include at least one `cache_control` breakpoint on individual content blocks ([explicit cache breakpoints](#explicit-cache-breakpoints)).
Yes, prompt caching can be used alongside other API features like tool use and vision capabilities. However, changing whether there are images in a prompt or modifying tool use settings will break the cache.
For more details on cache invalidation, see [What invalidates the cache](#what-invalidates-the-cache).
Prompt caching introduces a new pricing structure where 5-minute cache writes cost 25% more than base input tokens, 1-hour cache writes cost 2x base input tokens, and cache hits cost only 10% of the base input token price.
Currently, there's no way to manually clear the cache. Cached prefixes automatically expire after a minimum of 5 minutes of inactivity.
You can monitor cache performance using the `cache_creation_input_tokens` and `cache_read_input_tokens` fields in the API response.
See [What invalidates the cache](#what-invalidates-the-cache) for more details on cache invalidation, including a list of changes that require creating a new cache entry.
Prompt caching is designed with strong privacy and data separation measures:
1. Cache keys are generated using a cryptographic hash of the prompts up to the cache control point. This means only requests with identical prompts can access a specific cache.
2. On the Claude API, Claude Platform on AWS, and Microsoft Foundry (beta), caches are isolated per workspace within an organization. On Bedrock and Vertex AI, caches are isolated per organization. In every case, caches are never shared across organizations, even for identical prompts. See [Cache storage and sharing](#cache-storage-and-sharing) for details.
3. The caching mechanism is designed to maintain the integrity and privacy of each unique conversation or context.
4. It's safe to use `cache_control` anywhere in your prompts. For caching to produce reads, place the breakpoint at the end of a stable prefix: placing it on a block that changes every request (such as a timestamp or the user's arbitrary input) writes a fresh entry each time and never hits.
These measures ensure that prompt caching maintains data privacy and security while offering performance benefits.
Yes, it is possible to use prompt caching with your [Batches API](/docs/en/build-with-claude/batch-processing) requests. However, because asynchronous batch requests can be processed concurrently and in any order, cache hits are provided on a best-effort basis.
The [1-hour cache](#1-hour-cache-duration) can help improve your cache hits. The most cost effective way of using it is the following:
- Gather a set of message requests that have a shared prefix.
- Send a batch request with a single request that has this shared prefix and a 1-hour cache block. This writes the prefix to the 1-hour cache.
- As soon as this is complete, submit the rest of the requests. You will have to monitor the job to know when it completes.
This is typically better than using the 5-minute cache because it's common for batch requests to take between 5 minutes and 1 hour to complete.
This error typically appears when you have upgraded your SDK or you are using outdated code examples. Prompt caching no longer requires the beta prefix. Instead of:
```python Python nocheck
client.beta.prompt_caching.messages.create(**params)
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.beta.promptCaching.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
system: [
{
type: "text",
text: "You are an expert on this large document...",
cache_control: { type: "ephemeral" }
}
],
messages: [{ role: "user", content: "Summarize the key points" }]
});
console.log(response);
```
```php PHP hidelines={1..4} nocheck
beta->promptCaching->messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Summarize the key points']
],
model: 'claude-opus-4-7',
system: [
[
'type' => 'text',
'text' => 'You are an expert on this large document...',
'cache_control' => ['type' => 'ephemeral']
]
],
);
echo $message->content[0]->text;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.beta.prompt_caching.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
system: [
{
type: "text",
text: "You are an expert on this large document...",
cache_control: { type: "ephemeral" }
}
],
messages: [
{ role: "user", content: "Summarize the key points" }
]
)
puts message.content.first.text
```
Use:
```python Python nocheck
client.messages.create(**params)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
system: [
{
type: "text",
text: "You are an expert on this large document...",
cache_control: { type: "ephemeral" }
}
],
messages: [{ role: "user", content: "Summarize the key points" }]
});
console.log(response);
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Summarize the key points']
],
model: 'claude-opus-4-7',
system: [
[
'type' => 'text',
'text' => 'You are an expert on this large document...',
'cache_control' => ['type' => 'ephemeral']
]
],
);
echo $message->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
system: [
{
type: "text",
text: "You are an expert on this large document...",
cache_control: { type: "ephemeral" }
}
],
messages: [
{ role: "user", content: "Summarize the key points" }
]
)
puts message.content.first.text
```
This error typically appears when you have upgraded your SDK or you are using outdated code examples. Prompt caching no longer requires the beta prefix. Instead of:
```typescript TypeScript
client.beta.promptCaching.messages.create(/* ... */);
```
Simply use:
```typescript
client.messages.create(/* ... */);
```
---
# Token counting
URL: https://platform.claude.com/docs/en/build-with-claude/token-counting
# Token counting
---
Token counting enables you to determine the number of tokens in a message before sending it to Claude, helping you make informed decisions about your prompts and usage. With token counting, you can
- Proactively manage rate limits and costs
- Make smart model routing decisions
- Optimize prompts to be a specific length
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
---
## How to count message tokens
The [token counting](/docs/en/api/messages-count-tokens) endpoint accepts the same structured list of inputs for creating a message, including support for system prompts, [tools](/docs/en/agents-and-tools/tool-use/overview), [images](/docs/en/build-with-claude/vision), and [PDFs](/docs/en/build-with-claude/pdf-support). The response contains the total number of input tokens.
The token count should be considered an **estimate**. In some cases, the actual number of input tokens used when creating a message may differ by a small amount.
Token counts may include tokens added automatically by Anthropic for system optimizations. **You are not billed for system-added tokens**. Billing reflects only your content.
### Supported models
All [active models](/docs/en/about-claude/models/overview) support token counting.
### Count tokens in basic messages
```bash cURL
curl https://api.anthropic.com/v1/messages/count_tokens \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "content-type: application/json" \
--header "anthropic-version: 2023-06-01" \
--data '{
"model": "claude-opus-4-7",
"system": "You are a scientist",
"messages": [{
"role": "user",
"content": "Hello, Claude"
}]
}'
```
```bash CLI
ant messages count-tokens \
--model claude-opus-4-7 \
--system "You are a scientist" \
--message '{role: user, content: "Hello, Claude"}'
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.count_tokens(
model="claude-opus-4-7",
system="You are a scientist",
messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(response.json())
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.countTokens({
model: "claude-opus-4-7",
system: "You are a scientist",
messages: [
{
role: "user",
content: "Hello, Claude"
}
]
});
console.log(response);
```
```csharp C#
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCountTokensParams
{
Model = Model.ClaudeOpus4_7,
System = "You are a scientist",
Messages = [new() { Role = Role.User, Content = "Hello, Claude" }]
};
var response = await client.Messages.CountTokens(parameters);
Console.WriteLine(response);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.CountTokens(context.TODO(), anthropic.MessageCountTokensParams{
Model: anthropic.ModelClaudeOpus4_7,
System: anthropic.MessageCountTokensParamsSystemUnion{
OfString: anthropic.String("You are a scientist"),
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, Claude")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..2,5..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCountTokensParams;
import com.anthropic.models.messages.MessageTokensCount;
import com.anthropic.models.messages.Model;
public class CountTokensExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCountTokensParams params = MessageCountTokensParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.system("You are a scientist")
.addUserMessage("Hello, Claude")
.build();
MessageTokensCount count = client.messages().countTokens(params);
System.out.println(count);
}
}
```
```php PHP hidelines={1..4}
messages->countTokens(
messages: [
['role' => 'user', 'content' => 'Hello, Claude']
],
model: 'claude-opus-4-7',
system: 'You are a scientist',
);
echo json_encode($response);
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.messages.count_tokens(
model: "claude-opus-4-7",
system: "You are a scientist",
messages: [
{ role: "user", content: "Hello, Claude" }
]
)
puts response
```
```json Output
{ "input_tokens": 14 }
```
### Count tokens in messages with tools
[Server tool](/docs/en/agents-and-tools/tool-use/server-tools) token counts only apply to the first sampling call.
```bash cURL
curl https://api.anthropic.com/v1/messages/count_tokens \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "content-type: application/json" \
--header "anthropic-version: 2023-06-01" \
--data '{
"model": "claude-opus-4-7",
"tools": [
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
}
},
"required": ["location"]
}
}
],
"messages": [
{
"role": "user",
"content": "What'\''s the weather like in San Francisco?"
}
]
}'
```
```bash CLI
ant messages count-tokens <<'YAML'
model: claude-opus-4-7
tools:
- name: get_weather
description: Get the current weather in a given location
input_schema:
type: object
properties:
location:
type: string
description: The city and state, e.g. San Francisco, CA
required:
- location
messages:
- role: user
content: What's the weather like in San Francisco?
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.count_tokens(
model="claude-opus-4-7",
tools=[
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
}
},
"required": ["location"],
},
}
],
messages=[{"role": "user", "content": "What's the weather like in San Francisco?"}],
)
print(response.json())
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.countTokens({
model: "claude-opus-4-7",
tools: [
{
name: "get_weather",
description: "Get the current weather in a given location",
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
}
},
required: ["location"]
}
}
],
messages: [{ role: "user", content: "What's the weather like in San Francisco?" }]
});
console.log(response);
```
```csharp C#
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCountTokensParams
{
Model = Model.ClaudeOpus4_7,
Tools =
[
new MessageCountTokensTool(new Tool()
{
Name = "get_weather",
Description = "Get the current weather in a given location",
InputSchema = new InputSchema()
{
Properties = new Dictionary
{
["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "The city and state, e.g. San Francisco, CA" }),
},
Required = ["location"],
},
}),
],
Messages = [new() { Role = Role.User, Content = "What's the weather like in San Francisco?" }]
};
var count = await client.Messages.CountTokens(parameters);
Console.WriteLine(count);
}
}
```
```go Go hidelines={1..14,-1}
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Messages.CountTokens(context.TODO(), anthropic.MessageCountTokensParams{
Model: anthropic.ModelClaudeOpus4_7,
Tools: []anthropic.MessageCountTokensToolUnionParam{
{OfTool: &anthropic.ToolParam{
Name: "get_weather",
Description: anthropic.String("Get the current weather in a given location"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"location": map[string]any{
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
},
Required: []string{"location"},
},
}},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather like in San Francisco?")),
},
})
if err != nil {
log.Fatal(err)
}
jsonData, _ := json.MarshalIndent(response, "", " ")
fmt.Println(string(jsonData))
}
```
```java Java hidelines={1..3,6..14,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.MessageCountTokensParams;
import com.anthropic.models.messages.MessageTokensCount;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.Tool.InputSchema;
import java.util.List;
import java.util.Map;
public class CountTokensWithToolsExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
InputSchema schema = InputSchema.builder()
.properties(
JsonValue.from(
Map.of(
"location",
Map.of(
"type",
"string",
"description",
"The city and state, e.g. San Francisco, CA"
)
)
)
)
.putAdditionalProperty("required", JsonValue.from(List.of("location")))
.build();
MessageCountTokensParams params = MessageCountTokensParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.addTool(
Tool.builder()
.name("get_weather")
.description("Get the current weather in a given location")
.inputSchema(schema)
.build()
)
.addUserMessage("What's the weather like in San Francisco?")
.build();
MessageTokensCount count = client.messages().countTokens(params);
System.out.println(count);
}
}
```
```php PHP hidelines={1..4}
messages->countTokens(
messages: [
['role' => 'user', 'content' => "What's the weather like in San Francisco?"]
],
model: 'claude-opus-4-7',
tools: [
[
'name' => 'get_weather',
'description' => 'Get the current weather in a given location',
'input_schema' => [
'type' => 'object',
'properties' => [
'location' => [
'type' => 'string',
'description' => 'The city and state, e.g. San Francisco, CA'
]
],
'required' => ['location']
]
]
],
);
echo json_encode($response, JSON_PRETTY_PRINT);
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.messages.count_tokens(
model: "claude-opus-4-7",
tools: [
{
name: "get_weather",
description: "Get the current weather in a given location",
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
}
},
required: ["location"]
}
}
],
messages: [
{ role: "user", content: "What's the weather like in San Francisco?" }
]
)
puts response
```
```json Output
{ "input_tokens": 403 }
```
### Count tokens in messages with images
```bash cURL
#!/bin/sh
IMAGE_URL="https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
IMAGE_MEDIA_TYPE="image/jpeg"
IMAGE_BASE64=$(curl -s "$IMAGE_URL" | base64 | tr -d '\n')
curl https://api.anthropic.com/v1/messages/count_tokens \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data @- <
{
new ContentBlockParam(new ImageBlockParam(
new ImageBlockParamSource(new Base64ImageSource()
{
Data = imageData,
MediaType = MediaType.ImageJpeg,
})
)),
new ContentBlockParam(new TextBlockParam("Describe this image")),
}),
}
]
};
var count = await client.Messages.CountTokens(parameters);
Console.WriteLine(count);
}
}
```
```go Go nocheck hidelines={1..14,-1}
package main
import (
"context"
"encoding/base64"
"fmt"
"io"
"log"
"net/http"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
imageURL := "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
req, err := http.NewRequest("GET", imageURL, nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("User-Agent", "AnthropicDocsBot/1.0")
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
imageBytes, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
imageData := base64.StdEncoding.EncodeToString(imageBytes)
client := anthropic.NewClient()
response, err := client.Messages.CountTokens(context.TODO(), anthropic.MessageCountTokensParams{
Model: anthropic.ModelClaudeOpus4_7,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(
anthropic.NewImageBlockBase64("image/jpeg", imageData),
anthropic.NewTextBlock("Describe this image"),
),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java nocheck hidelines={1..2,4..5,8..19,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Base64ImageSource;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.ImageBlockParam;
import com.anthropic.models.messages.MessageCountTokensParams;
import com.anthropic.models.messages.MessageTokensCount;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Base64;
import java.util.List;
public class CountTokensImageExample {
public static void main(String[] args) throws Exception {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
String imageUrl =
"https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg";
String imageMediaType = "image/jpeg";
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(imageUrl)).build();
byte[] imageBytes = httpClient
.send(request, HttpResponse.BodyHandlers.ofByteArray())
.body();
String imageBase64 = Base64.getEncoder().encodeToString(imageBytes);
ContentBlockParam imageBlock = ContentBlockParam.ofImage(
ImageBlockParam.builder()
.source(
Base64ImageSource.builder()
.mediaType(Base64ImageSource.MediaType.IMAGE_JPEG)
.data(imageBase64)
.build()
)
.build()
);
ContentBlockParam textBlock = ContentBlockParam.ofText(
TextBlockParam.builder().text("Describe this image").build()
);
MessageCountTokensParams params = MessageCountTokensParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.addUserMessageOfBlockParams(List.of(imageBlock, textBlock))
.build();
MessageTokensCount count = client.messages().countTokens(params);
System.out.println(count);
}
}
```
```php PHP hidelines={1..4} nocheck
messages->countTokens(
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'image',
'source' => [
'type' => 'base64',
'media_type' => $imageMediaType,
'data' => $imageData
]
],
['type' => 'text', 'text' => 'Describe this image']
]
]
],
model: 'claude-opus-4-7',
);
print_r($response);
```
```ruby Ruby nocheck hidelines={1}
require "anthropic"
require "base64"
require "net/http"
image_url = "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
image_media_type = "image/jpeg"
uri = URI(image_url)
image_data = Base64.strict_encode64(Net::HTTP.get(uri))
client = Anthropic::Client.new
response = client.messages.count_tokens(
model: "claude-opus-4-7",
messages: [
{
role: "user",
content: [
{
type: "image",
source: {
type: "base64",
media_type: image_media_type,
data: image_data
}
},
{ type: "text", text: "Describe this image" }
]
}
]
)
puts response
```
```json Output
{ "input_tokens": 1551 }
```
### Count tokens in messages with extended thinking
See [how the context window is calculated with extended thinking](/docs/en/build-with-claude/extended-thinking#how-context-window-is-calculated-with-extended-thinking) for more details
- Thinking blocks from **previous** assistant turns are ignored and **do not** count toward your input tokens
- **Current** assistant turn thinking **does** count toward your input tokens
```bash cURL nocheck
curl https://api.anthropic.com/v1/messages/count_tokens \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "content-type: application/json" \
--header "anthropic-version: 2023-06-01" \
--data '{
"model": "claude-sonnet-4-6",
"thinking": {
"type": "enabled",
"budget_tokens": 16000
},
"messages": [
{
"role": "user",
"content": "Are there an infinite number of prime numbers such that n mod 4 == 3?"
},
{
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": "This is a nice number theory question. Lets think about it step by step...",
"signature": "EuYBCkQYAiJAgCs1le6/Pol5Z4/JMomVOouGrWdhYNsH3ukzUECbB6iWrSQtsQuRHJID6lWV..."
},
{
"type": "text",
"text": "Yes, there are infinitely many prime numbers p such that p mod 4 = 3..."
}
]
},
{
"role": "user",
"content": "Can you write a formal proof?"
}
]
}'
```
```bash CLI nocheck
ant messages count-tokens <<'YAML'
model: claude-sonnet-4-6
thinking:
type: enabled
budget_tokens: 16000
messages:
- role: user
content: Are there an infinite number of prime numbers such that n mod 4 == 3?
- role: assistant
content:
- type: thinking
thinking: >-
This is a nice number theory question. Lets think about it step by step...
signature: >-
EuYBCkQYAiJAgCs1le6/Pol5Z4/JMomVOouGrWdhYNsH3ukzUECbB6iWrSQtsQuRHJID6lWV...
- type: text
text: Yes, there are infinitely many prime numbers p such that p mod 4 = 3...
- role: user
content: Can you write a formal proof?
YAML
```
```python Python nocheck hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.count_tokens(
model="claude-sonnet-4-6",
thinking={"type": "enabled", "budget_tokens": 16000},
messages=[
{
"role": "user",
"content": "Are there an infinite number of prime numbers such that n mod 4 == 3?",
},
{
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": "This is a nice number theory question. Let's think about it step by step...",
"signature": "EuYBCkQYAiJAgCs1le6/Pol5Z4/JMomVOouGrWdhYNsH3ukzUECbB6iWrSQtsQuRHJID6lWV...",
},
{
"type": "text",
"text": "Yes, there are infinitely many prime numbers p such that p mod 4 = 3...",
},
],
},
{"role": "user", "content": "Can you write a formal proof?"},
],
)
print(response.json())
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.countTokens({
model: "claude-sonnet-4-6",
thinking: {
type: "enabled",
budget_tokens: 16000
},
messages: [
{
role: "user",
content: "Are there an infinite number of prime numbers such that n mod 4 == 3?"
},
{
role: "assistant",
content: [
{
type: "thinking",
thinking:
"This is a nice number theory question. Let's think about it step by step...",
signature:
"EuYBCkQYAiJAgCs1le6/Pol5Z4/JMomVOouGrWdhYNsH3ukzUECbB6iWrSQtsQuRHJID6lWV..."
},
{
type: "text",
text: "Yes, there are infinitely many prime numbers p such that p mod 4 = 3..."
}
]
},
{
role: "user",
content: "Can you write a formal proof?"
}
]
});
console.log(response);
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Anthropic;
using Anthropic.Models.Messages;
public class Program
{
public static async Task Main(string[] args)
{
AnthropicClient client = new()
{
ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")
};
var parameters = new MessageCountTokensParams
{
Model = Model.ClaudeSonnet4_6,
Thinking = new ThinkingConfigEnabled(budgetTokens: 16000),
Messages =
[
new()
{
Role = Role.User,
Content = "Are there an infinite number of prime numbers such that n mod 4 == 3?"
},
new()
{
Role = Role.Assistant,
Content = new MessageParamContent(new List
{
new ContentBlockParam(new ThinkingBlockParam()
{
Thinking = "This is a nice number theory question. Let's think about it step by step...",
Signature = "EuYBCkQYAiJAgCs1le6/Pol5Z4/JMomVOouGrWdhYNsH3ukzUECbB6iWrSQtsQuRHJID6lWV...",
}),
new ContentBlockParam(new TextBlockParam("Yes, there are infinitely many prime numbers p such that p mod 4 = 3...")),
}),
},
new()
{
Role = Role.User,
Content = "Can you write a formal proof?"
}
]
};
var response = await client.Messages.CountTokens(parameters);
Console.WriteLine(response);
}
}
```
```go Go nocheck hidelines={1..13,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
thinkingBlock := anthropic.NewThinkingBlock(
"EuYBCkQYAiJAgCs1le6/Pol5Z4/JMomVOouGrWdhYNsH3ukzUECbB6iWrSQtsQuRHJID6lWV...",
"This is a nice number theory question. Let's think about it step by step...",
)
textBlock := anthropic.NewTextBlock(
"Yes, there are infinitely many prime numbers p such that p mod 4 = 3...",
)
response, err := client.Messages.CountTokens(context.TODO(), anthropic.MessageCountTokensParams{
Model: anthropic.Model("claude-sonnet-4-6"),
Thinking: anthropic.ThinkingConfigParamOfEnabled(16000),
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Are there an infinite number of prime numbers such that n mod 4 == 3?")),
anthropic.NewAssistantMessage(thinkingBlock, textBlock),
anthropic.NewUserMessage(anthropic.NewTextBlock("Can you write a formal proof?")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", response)
}
```
```java Java nocheck hidelines={1..3,6..7,9..13,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.MessageCountTokensParams;
import com.anthropic.models.messages.MessageTokensCount;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import com.anthropic.models.messages.ThinkingBlockParam;
import java.util.List;
public class CountTokensThinkingExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
List assistantBlocks = List.of(
ContentBlockParam.ofThinking(
ThinkingBlockParam.builder()
.thinking(
"This is a nice number theory question. Let's think about it step by step..."
)
.signature(
"EuYBCkQYAiJAgCs1le6/Pol5Z4/JMomVOouGrWdhYNsH3ukzUECbB6iWrSQtsQuRHJID6lWV..."
)
.build()
),
ContentBlockParam.ofText(
TextBlockParam.builder()
.text("Yes, there are infinitely many prime numbers p such that p mod 4 = 3...")
.build()
)
);
MessageCountTokensParams params = MessageCountTokensParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.enabledThinking(16000)
.addUserMessage("Are there an infinite number of prime numbers such that n mod 4 == 3?")
.addAssistantMessageOfBlockParams(assistantBlocks)
.addUserMessage("Can you write a formal proof?")
.build();
MessageTokensCount count = client.messages().countTokens(params);
System.out.println(count);
}
}
```
```php PHP hidelines={1..4} nocheck
messages->countTokens(
messages: [
[
'role' => 'user',
'content' => 'Are there an infinite number of prime numbers such that n mod 4 == 3?'
],
[
'role' => 'assistant',
'content' => [
[
'type' => 'thinking',
'thinking' => 'This is a nice number theory question. Let\'s think about it step by step...',
'signature' => 'EuYBCkQYAiJAgCs1le6/Pol5Z4/JMomVOouGrWdhYNsH3ukzUECbB6iWrSQtsQuRHJID6lWV...'
],
[
'type' => 'text',
'text' => 'Yes, there are infinitely many prime numbers p such that p mod 4 = 3...'
]
]
],
[
'role' => 'user',
'content' => 'Can you write a formal proof?'
]
],
model: 'claude-sonnet-4-6',
thinking: [
'type' => 'enabled',
'budget_tokens' => 16000
],
);
echo json_encode($response);
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.messages.count_tokens(
model: "claude-sonnet-4-6",
thinking: {
type: "enabled",
budget_tokens: 16000
},
messages: [
{
role: "user",
content: "Are there an infinite number of prime numbers such that n mod 4 == 3?"
},
{
role: "assistant",
content: [
{
type: "thinking",
thinking: "This is a nice number theory question. Let's think about it step by step...",
signature: "EuYBCkQYAiJAgCs1le6/Pol5Z4/JMomVOouGrWdhYNsH3ukzUECbB6iWrSQtsQuRHJID6lWV..."
},
{
type: "text",
text: "Yes, there are infinitely many prime numbers p such that p mod 4 = 3..."
}
]
},
{
role: "user",
content: "Can you write a formal proof?"
}
]
)
puts response
```
```json Output
{ "input_tokens": 88 }
```
### Count tokens in messages with PDFs
Token counting supports PDFs with the same [limitations](/docs/en/build-with-claude/pdf-support#pdf-support-limitations) as the Messages API.
```bash cURL hidelines={1..3}
PDF_URL="https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf"
PDF_BASE64=$(curl -s "$PDF_URL" | base64 | tr -d '\n')
curl https://api.anthropic.com/v1/messages/count_tokens \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "content-type: application/json" \
--header "anthropic-version: 2023-06-01" \
--data @- <
{
new ContentBlockParam(new DocumentBlockParam(
new DocumentBlockParamSource(new Base64PdfSource()
{
Data = pdfBase64,
MediaType = MediaType.ApplicationPdf,
})
)),
new ContentBlockParam(new TextBlockParam("Please summarize this document.")),
}),
}
]
};
var count = await client.Messages.CountTokens(parameters);
Console.WriteLine(count);
}
}
```
```go Go nocheck hidelines={1..15,-1}
package main
import (
"context"
"encoding/base64"
"fmt"
"log"
"os"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
pdfBytes, err := os.ReadFile("document.pdf")
if err != nil {
log.Fatal(err)
}
pdfBase64 := base64.StdEncoding.EncodeToString(pdfBytes)
response, err := client.Messages.CountTokens(context.TODO(), anthropic.MessageCountTokensParams{
Model: anthropic.ModelClaudeOpus4_7,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(
anthropic.NewDocumentBlock(anthropic.Base64PDFSourceParam{
Data: pdfBase64,
}),
anthropic.NewTextBlock("Please summarize this document."),
),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java nocheck hidelines={1..2,4,8..17,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Base64PdfSource;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.DocumentBlockParam;
import com.anthropic.models.messages.MessageCountTokensParams;
import com.anthropic.models.messages.MessageTokensCount;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
import java.util.List;
public class CountTokensPdfExample {
public static void main(String[] args) throws Exception {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
byte[] fileBytes = Files.readAllBytes(Path.of("document.pdf"));
String pdfBase64 = Base64.getEncoder().encodeToString(fileBytes);
ContentBlockParam documentBlock = ContentBlockParam.ofDocument(
DocumentBlockParam.builder()
.source(
Base64PdfSource.builder()
.mediaType(Base64PdfSource.MediaType.APPLICATION_PDF)
.data(pdfBase64)
.build()
)
.build()
);
ContentBlockParam textBlock = ContentBlockParam.ofText(
TextBlockParam.builder().text("Please summarize this document.").build()
);
MessageCountTokensParams params = MessageCountTokensParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.addUserMessageOfBlockParams(List.of(documentBlock, textBlock))
.build();
MessageTokensCount count = client.messages().countTokens(params);
System.out.println(count);
}
}
```
```php PHP hidelines={1..4} nocheck
messages->countTokens(
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'document',
'source' => [
'type' => 'base64',
'media_type' => 'application/pdf',
'data' => $pdfBase64
]
],
[
'type' => 'text',
'text' => 'Please summarize this document.'
]
]
]
],
model: 'claude-opus-4-7',
);
echo json_encode($response);
```
```ruby Ruby nocheck hidelines={1}
require "anthropic"
require "base64"
client = Anthropic::Client.new
pdf_base64 = Base64.strict_encode64(File.binread("document.pdf"))
response = client.messages.count_tokens(
model: "claude-opus-4-7",
messages: [
{
role: "user",
content: [
{
type: "document",
source: {
type: "base64",
media_type: "application/pdf",
data: pdf_base64
}
},
{
type: "text",
text: "Please summarize this document."
}
]
}
]
)
puts response
```
```json Output
{ "input_tokens": 2188 }
```
---
## Pricing and rate limits
Token counting is **free to use** but subject to requests per minute rate limits based on your [usage tier](/docs/en/api/rate-limits#rate-limits). If you need higher limits, contact sales through the [Claude Console](/settings/limits).
| Usage tier | Requests per minute (RPM) |
|------------|---------------------------|
| 1 | 100 |
| 2 | 2,000 |
| 3 | 4,000 |
| 4 | 8,000 |
Token counting and message creation have separate and independent rate limits. Usage of one does not count against the limits of the other.
---
## FAQ
No, token counting provides an estimate without using caching logic. While you may provide `cache_control` blocks in your token counting request, prompt caching only occurs during actual message creation.
### Working with files
---
# Files API
URL: https://platform.claude.com/docs/en/build-with-claude/files
# Files API
---
The Files API lets you upload and manage files to use with the Claude API without re-uploading content with each request. This is particularly useful when using the [code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool) to provide inputs (e.g. datasets and documents) and then download outputs (e.g. charts). You can also use the Files API to prevent having to continually re-upload frequently used documents and images across multiple API calls. You can [explore the API reference directly](/docs/en/api/files-create), in addition to this guide.
The Files API is in beta. Reach out through the [feedback form](https://forms.gle/tisHyierGwgN4DUE9) to share your experience with the Files API.
This feature is **not** eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). Data is retained according to the feature's standard retention policy.
## Supported models
Referencing a `file_id` in a Messages request is supported on all models that support the given file type. [Images](/docs/en/build-with-claude/vision) are supported on all current Claude models. For [PDFs](/docs/en/build-with-claude/pdf-support) and [other file types with the code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool#model-compatibility), see the linked pages for model support.
The Files API is available on the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry). It is not currently available on Amazon Bedrock or Vertex AI.
## How the Files API works
The Files API provides a simple create-once, use-many-times approach for working with files:
- **Upload files** to Anthropic's secure storage and receive a unique `file_id`
- **Download files** that are created from skills or the code execution tool
- **Reference files** in [Messages](/docs/en/api/messages/create) requests using the `file_id` instead of re-uploading content
- **Manage your files** with list, retrieve, and delete operations
## How to use the Files API
To use the Files API, you'll need to include the beta feature header: `anthropic-beta: files-api-2025-04-14`.
### Uploading a file
Upload a file to be referenced in future API calls:
````bash
curl -X POST https://api.anthropic.com/v1/files \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14" \
-F "file=@/path/to/document.pdf"
````
````bash
FILE_ID=$(ant beta:files upload \
--file /path/to/document.pdf \
--transform id --raw-output)
````
````python
uploaded = client.beta.files.upload(
file=("document.pdf", open("/path/to/document.pdf", "rb"), "application/pdf"),
)
````
````typescript
const uploaded = await anthropic.beta.files.upload({
file: await toFile(
fs.createReadStream("/path/to/document.pdf"),
undefined,
{ type: "application/pdf" },
),
});
````
````csharp
var uploaded = await client.Beta.Files.Upload(
new FileUploadParams
{
File = File.OpenRead("/path/to/document.pdf")
});
Console.WriteLine(uploaded.Id);
````
````go
f, err := os.Open("/path/to/document.pdf")
if err != nil {
log.Fatal(err)
}
defer f.Close()
response, err := client.Beta.Files.Upload(context.Background(),
anthropic.BetaFileUploadParams{
File: anthropic.File(f, "document.pdf", "application/pdf"),
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.ID)
````
````java
FileMetadata file = client.beta().files().upload(
FileUploadParams.builder()
.file(MultipartField.builder()
.value(Files.newInputStream(Path.of("/path/to/document.pdf")))
.filename("document.pdf")
.contentType("application/pdf")
.build())
.build()
);
System.out.println(file.id());
````
````php
$file = $client->beta->files->upload(
FileParam::fromResource(fopen('/path/to/document.pdf', 'rb'), contentType: 'application/pdf'),
);
echo $file->id;
````
````ruby
file = client.beta.files.upload(
file: Anthropic::FilePart.new(
Pathname("/path/to/document.pdf"),
content_type: "application/pdf"
)
)
puts file.id
````
The response from uploading a file will include:
```json Output
{
"id": "file_011CNha8iCJcU1wXNR6q4V8w",
"type": "file",
"filename": "document.pdf",
"mime_type": "application/pdf",
"size_bytes": 1024000,
"created_at": "2025-01-01T00:00:00Z",
"downloadable": false
}
```
### Using a file in messages
Once uploaded, reference the file using its `file_id`:
````bash
curl -X POST https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14" \
-H "content-type: application/json" \
-d @- <beta->messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => [
['type' => 'text', 'text' => 'Please summarize this document for me.'],
[
'type' => 'document',
'source' => [
'type' => 'file',
'file_id' => $fileId
]
]
]
]
],
model: 'claude-opus-4-6',
betas: ['files-api-2025-04-14'],
);
print_r($response);
````
````ruby
response = client.beta.messages.create(
model: "claude-opus-4-6",
max_tokens: 1024,
betas: ["files-api-2025-04-14"],
messages: [
{
role: "user",
content: [
{ type: "text", text: "Please summarize this document for me." },
{
type: "document",
source: {
type: "file",
file_id: file_id
}
}
]
}
]
)
puts response
````
### File types and content blocks
The Files API supports different file types that correspond to different content block types:
| File Type | MIME Type | Content Block Type | Use Case |
| :--- | :--- | :--- | :--- |
| PDF | `application/pdf` | `document` | Text analysis, document processing |
| Plain text | `text/plain` | `document` | Text analysis, processing |
| Images | `image/jpeg`, `image/png`, `image/gif`, `image/webp` | `image` | Image analysis, visual tasks |
| [Datasets, others](/docs/en/agents-and-tools/tool-use/code-execution-tool#upload-and-analyze-your-own-files) | Varies | `container_upload` | Analyze data, create visualizations |
### Working with other file formats
For file types that are not supported as `document` blocks (.csv, .txt, .md, .docx, .xlsx), convert the files to plain text, and include the content directly in your message:
```bash cURL hidelines={3..4}
# Example: Reading a text file and sending it as plain text
# Note: For files with special characters, consider base64 encoding
TEXT_CONTENT="This is a sample document. It has multiple lines."
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 @- < document.txt
# The "@./path" reference inlines the file contents directly into the field.
ant messages create \
--model claude-opus-4-7 \
--max-tokens 1024 \
--transform 'content.0.text' --raw-output <<'YAML'
messages:
- role: user
content:
- type: text
text: "Here's the document content:"
- type: text
text: "@./document.txt"
- type: text
text: "Please summarize this document."
YAML
```
```python Python nocheck hidelines={2..5}
import pandas as pd
import anthropic
client = anthropic.Anthropic()
# Example: Reading a CSV file
df = pd.read_csv("data.csv")
csv_content = df.to_string()
# Send as plain text in the message
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": f"Here's the CSV data:\n\n{csv_content}\n\nPlease analyze this data.",
}
],
}
],
)
print(response.content[0].text)
```
```typescript TypeScript nocheck hidelines={1}
import Anthropic from "@anthropic-ai/sdk";
import fs from "fs/promises";
const anthropic = new Anthropic();
async function analyzeDocument() {
// Example: Reading a text file
const textContent = await fs.readFile("document.txt", "utf-8");
// Send as plain text in the message
const response = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "text",
text: `Here's the document content:\n\n${textContent}\n\nPlease summarize this document.`
}
]
}
]
});
const block = response.content[0];
if (block.type === "text") {
console.log(block.text);
}
}
analyzeDocument();
```
```csharp C# nocheck
using System;
using System.IO;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
// Example: Reading a text file
string textContent = await File.ReadAllTextAsync("document.txt");
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = [new()
{
Role = Role.User,
Content = $"Here's the document content:\n\n{textContent}\n\nPlease summarize this document."
}]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go hidelines={11..15}
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/anthropics/anthropic-sdk-go"
)
func init() {
os.WriteFile("document.txt", []byte("This is a test document for upload."), 0644)
}
func main() {
client := anthropic.NewClient()
// Example: Reading a text file
textContent, err := os.ReadFile("document.txt")
if err != nil {
log.Fatal(err)
}
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(
fmt.Sprintf("Here's the document content:\n\n%s\n\nPlease summarize this document.", string(textContent)),
)),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Content[0].Text)
}
```
```java Java nocheck hidelines={1..11,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
public class FileUploadExample {
public static void main(String[] args) throws IOException {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Example: Reading a text file
String textContent = Files.readString(Paths.get("document.txt"));
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.addUserMessage("Here's the document content:\n\n" + textContent + "\n\nPlease summarize this document.")
.build();
Message response = client.messages().create(params);
response.content().stream()
.flatMap(block -> block.text().stream())
.forEach(textBlock -> System.out.println(textBlock.text()));
}
}
```
```php PHP hidelines={1..4} nocheck
messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'text',
'text' => "Here's the document content:\n\n{$textContent}\n\nPlease summarize this document."
]
]
]
],
model: 'claude-opus-4-7',
);
echo $message->content[0]->text;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
# Example: Reading a text file
text_content = File.read("document.txt")
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "text",
text: "Here's the document content:\n\n#{text_content}\n\nPlease summarize this document."
}
]
}
]
)
puts message.content.first.text
```
For .docx files containing images, convert them to PDF format first, then use [PDF support](/docs/en/build-with-claude/pdf-support) to take advantage of the built-in image parsing. This allows using citations from the PDF document.
#### Document blocks
For PDFs and text files, use the `document` content block:
```json
{
"type": "document",
"source": {
"type": "file",
"file_id": "file_011CNha8iCJcU1wXNR6q4V8w"
},
"title": "Document Title", // Optional
"context": "Context about the document", // Optional
"citations": { "enabled": true } // Optional, enables citations
}
```
#### Image blocks
For images, use the `image` content block:
```json
{
"type": "image",
"source": {
"type": "file",
"file_id": "file_011CPMxVD3fHLUhvTqtsQA5w"
}
}
```
### Managing files
#### List files
Retrieve a list of your uploaded files:
```bash cURL
curl https://api.anthropic.com/v1/files \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14"
```
```bash CLI nocheck
ant beta:files list
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
files = client.beta.files.list()
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const files = await anthropic.beta.files.list();
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Files;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var files = await client.Beta.Files.List();
Console.WriteLine(files);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
files, err := client.Beta.Files.List(context.TODO(), anthropic.BetaFileListParams{})
if err != nil {
log.Fatal(err)
}
fmt.Println(files)
}
```
```java Java hidelines={1..2,4..6,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.files.FileListPage;
public class ListFiles {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
FileListPage files = client.beta().files().list();
System.out.println(files);
}
}
```
```php PHP hidelines={1..4}
beta->files->list();
print_r($files);
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
files = client.beta.files.list
puts files
```
#### Get file metadata
Retrieve information about a specific file:
````bash
curl "https://api.anthropic.com/v1/files/$FILE_ID" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14"
````
````bash
ant beta:files retrieve-metadata \
--file-id "$FILE_ID"
````
````python
file = client.beta.files.retrieve_metadata(file_id)
````
````typescript
const file = await anthropic.beta.files.retrieveMetadata(uploaded.id);
````
````csharp
var file = await client.Beta.Files.RetrieveMetadata(fileId);
Console.WriteLine(file);
````
````go
metadata, err := client.Beta.Files.GetMetadata(
context.TODO(),
fileID,
anthropic.BetaFileGetMetadataParams{},
)
if err != nil {
log.Fatal(err)
}
fmt.Println(metadata)
````
````java
FileMetadata metadata = client.beta().files().retrieveMetadata(fileId);
System.out.println(metadata);
````
````php
$file = $client->beta->files->retrieveMetadata($fileId);
echo $file;
````
````ruby
file = client.beta.files.retrieve_metadata(file_id)
puts file
````
#### Delete a file
Remove a file from your workspace:
````bash
curl -X DELETE "https://api.anthropic.com/v1/files/$FILE_ID" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14"
````
````bash
ant beta:files delete \
--file-id "$FILE_ID"
````
````python
result = client.beta.files.delete(file_id)
````
````typescript
await anthropic.beta.files.delete(uploaded.id);
````
````csharp
await client.Beta.Files.Delete(fileId);
````
````go
_, err = client.Beta.Files.Delete(
context.TODO(),
fileID,
anthropic.BetaFileDeleteParams{},
)
if err != nil {
log.Fatal(err)
}
````
````java
client.beta().files().delete(fileId);
````
````php
$result = $client->beta->files->delete($fileId);
````
````ruby
result = client.beta.files.delete(file_id)
````
### Downloading a file
Download files that have been created by skills or the code execution tool:
````bash
curl -X GET "https://api.anthropic.com/v1/files/$FILE_ID/content" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14" \
--output downloaded_file.txt
````
````bash
ant beta:files download \
--file-id "$FILE_ID" \
--output downloaded_file.txt
````
````python
file_content = client.beta.files.download(file_id)
# Save to file
file_content.write_to_file("downloaded_file.txt")
````
````typescript
const content = await anthropic.beta.files.download(uploaded.id);
const bytes = Buffer.from(await content.arrayBuffer());
await fsp.writeFile("downloaded_file.txt", bytes);
````
````csharp
byte[] fileContent = await client.Beta.Files.Download(fileId);
await File.WriteAllBytesAsync("downloaded_file.txt", fileContent);
````
````go
func downloadFile(client anthropic.Client, fileID string) error {
resp, err := client.Beta.Files.Download(
context.TODO(),
fileID,
anthropic.BetaFileDownloadParams{},
)
if err != nil {
return err
}
defer resp.Body.Close()
fileContent, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
return os.WriteFile("downloaded_file.txt", fileContent, 0644)
}
````
````java
try (HttpResponse response = client.beta().files().download(fileId)) {
try (InputStream body = response.body()) {
Files.copy(body, Path.of("downloaded_file.txt"),
StandardCopyOption.REPLACE_EXISTING);
}
}
````
````php
$fileContent = $client->beta->files->download($fileId);
file_put_contents("downloaded_file.txt", $fileContent);
````
````ruby
file_content = client.beta.files.download(file_id)
File.binwrite("downloaded_file.txt", file_content.read)
````
You can only download files that were created by [skills](/docs/en/build-with-claude/skills-guide) or the [code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool). Files that you uploaded cannot be downloaded.
---
## File storage and limits
### Storage limits
- **Maximum file size:** 500 MB per file
- **Total storage:** 500 GB per organization
### File lifecycle
- Files are scoped to the workspace of the API key. Other API keys can use files created by any other API key associated with the same workspace
- Files persist until you delete them
- Deleted files cannot be recovered
- Files are inaccessible via the API shortly after deletion, but they may persist in active `Messages` API calls and associated tool uses
- Files that users delete will be deleted in accordance with Anthropic's [data retention policy](https://privacy.claude.com/en/articles/7996866-how-long-do-you-store-my-organization-s-data).
---
## Data retention
Files uploaded via the Files API are retained until explicitly deleted using the `DELETE /v1/files/{file_id}` endpoint. Files are stored for reuse across multiple API requests.
For ZDR eligibility across all features, see [API and data retention](/docs/en/manage-claude/api-and-data-retention).
## Error handling
Common errors when using the Files API include:
- **File not found (404):** The specified `file_id` doesn't exist or you don't have access to it
- **Invalid file type (400):** The file type doesn't match the content block type (e.g., using an image file in a document block)
- **Exceeds context window size (400):** The file is larger than the context window size (e.g. using a 500 MB plaintext file in a `/v1/messages` request)
- **Invalid filename (400):** Filename doesn't meet the length requirements (1-255 characters) or contains forbidden characters (`<`, `>`, `:`, `"`, `|`, `?`, `*`, `\`, `/`, or unicode characters 0-31)
- **File too large (413):** File exceeds the 500 MB limit
- **Storage limit exceeded (403):** Your organization has reached the 500 GB storage limit
```json Output
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "File not found: file_011CNha8iCJcU1wXNR6q4V8w"
}
}
```
## Usage and billing
File API operations are **free**:
- Uploading files
- Downloading files
- Listing files
- Getting file metadata
- Deleting files
File content used in `Messages` requests are priced as input tokens. You can only download files created by [skills](/docs/en/build-with-claude/skills-guide) or the [code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool).
### Rate limits
During the beta period:
- File-related API calls are limited to approximately 100 requests per minute
- [Contact us](mailto:sales@anthropic.com) if you need higher limits for your use case
---
# PDF support
URL: https://platform.claude.com/docs/en/build-with-claude/pdf-support
# PDF support
Process PDFs with Claude. Extract text, analyze charts, and understand visual content from your documents.
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
You can ask Claude about any text, pictures, charts, and tables in PDFs you provide. Some sample use cases:
- Analyzing financial reports and understanding charts/tables
- Extracting key information from legal documents
- Translation assistance for documents
- Converting document information into structured formats
## Before you begin
### Check PDF requirements
Claude works with any standard PDF. Ensure your request size meets these requirements:
| Requirement | Limit |
|------------|--------|
| Maximum request size | 32 MB ([varies by platform](/docs/en/api/overview#request-size-limits)) |
| Maximum pages per request | 600 (100 for models with a 200k-token context window) |
| Format | Standard PDF (no passwords/encryption) |
Both limits are on the entire request payload, including any other content sent alongside PDFs. For large PDFs, consider uploading with the [Files API](#option-3-files-api) and referencing by `file_id` to keep request payloads small.
Dense PDFs (many small-font pages, complex tables, or heavy graphics) can fill the context window before reaching the page limit. Requests with large PDFs can also fail before reaching the page limit, even when using the Files API. Try splitting the document into sections; for large files, since each page is processed as an image, downsampling embedded images can also help.
Since PDF support relies on Claude's vision capabilities, it is subject to the same [limitations and considerations](/docs/en/build-with-claude/vision#limitations) as other vision tasks.
### Supported platforms and models
PDF support is available on the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), [Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock) (see [Amazon Bedrock PDF support](#amazon-bedrock-pdf-support)), [Vertex AI](/docs/en/build-with-claude/claude-on-vertex-ai), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry). All [active models](/docs/en/about-claude/models/overview) support PDF processing.
### Amazon Bedrock PDF support
When using PDF support through Bedrock's Converse API, there are two distinct document processing modes:
**Important:** To access Claude's full visual PDF understanding capabilities in the Converse API, you must enable citations. Without citations enabled, the API falls back to basic text extraction only. Learn more about [working with citations](/docs/en/build-with-claude/citations).
#### Document processing modes
1. **Converse Document Chat** (Original mode - Text extraction only)
- Provides basic text extraction from PDFs
- Cannot analyze images, charts, or visual layouts within PDFs
- Uses approximately 1,000 tokens for a 3-page PDF
- Automatically used when citations are not enabled
2. **Claude PDF Chat** (New mode - Full visual understanding)
- Provides complete visual analysis of PDFs
- Can understand and analyze charts, graphs, images, and visual layouts
- Processes each page as both text and image for comprehensive understanding
- Uses approximately 7,000 tokens for a 3-page PDF
- **Requires citations to be enabled** in the Converse API
#### Key limitations
- **Converse API**: Visual PDF analysis requires citations to be enabled. There is currently no option to use visual analysis without citations (unlike the InvokeModel API).
- **InvokeModel API**: Provides full control over PDF processing without forced citations.
#### Common issues
If Claude isn't seeing images or charts in your PDFs when using the Converse API, you likely need to enable the citations flag. Without it, Converse falls back to basic text extraction only.
This is a known constraint with the Converse API. For applications that require visual PDF analysis without citations, consider using the InvokeModel API instead.
For non-PDF files like .csv, .xlsx, .docx, .md, or .txt files, see [Working with other file formats](/docs/en/build-with-claude/files#working-with-other-file-formats).
***
## Process PDFs with Claude
### Send your first PDF request
Let's start with a simple example using the Messages API. You can provide PDFs to Claude in three ways:
1. As a URL reference to a PDF hosted online
2. As a base64-encoded PDF in `document` content blocks
3. By a `file_id` from the [Files API](/docs/en/build-with-claude/files)
#### Option 1: URL-based PDF document
The simplest approach is to reference a PDF directly from a URL:
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": [{
"type": "document",
"source": {
"type": "url",
"url": "https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf"
}
},
{
"type": "text",
"text": "What are the key findings in this document?"
}]
}]
}'
```
```bash CLI
ant messages create --transform content --format yaml <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content:
- type: document
source:
type: url
url: https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf
- type: text
text: What are the key findings in this document?
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "url",
"url": "https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf",
},
},
{"type": "text", "text": "What are the key findings in this document?"},
],
}
],
)
print(message.content)
```
```typescript TypeScript hidelines={1..4}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const response = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "document",
source: {
type: "url",
url: "https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf"
}
},
{
type: "text",
text: "What are the key findings in this document?"
}
]
}
]
});
console.log(response);
```
```java Java hidelines={1..8,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.*;
import java.util.List;
public class PdfUrlExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Create document block with URL
DocumentBlockParam documentParam = DocumentBlockParam.builder()
.source(
UrlPdfSource.builder()
.url(
"https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf"
)
.build()
)
.build();
// Create a message with document and text content blocks
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addUserMessageOfBlockParams(
List.of(
ContentBlockParam.ofDocument(documentParam),
ContentBlockParam.ofText(
TextBlockParam.builder()
.text("What are the key findings in this document?")
.build()
)
)
)
.build();
Message message = client.messages().create(params);
System.out.println(message.content());
}
}
```
#### Option 2: Base64-encoded PDF document
If you need to send PDFs from your local system or when a URL isn't available:
```bash cURL hidelines={1}
cd "$(mktemp -d)"
# Method 1: Fetch and encode a remote PDF
curl -s "https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf" | base64 | tr -d '\n' > pdf_base64.txt
# Method 2: Encode a local PDF file
# base64 document.pdf | tr -d '\n' > pdf_base64.txt
# Create a JSON request file using the pdf_base64.txt content
jq -n --rawfile PDF_BASE64 pdf_base64.txt '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": [{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": $PDF_BASE64
}
},
{
"type": "text",
"text": "What are the key findings in this document?"
}]
}]
}' > request.json
# Send the API request using the JSON file
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 @request.json
```
```bash CLI hidelines={1..2}
cd "$(mktemp -d)"
curl -sSo document.pdf https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf
ant messages create \
--model claude-opus-4-7 \
--max-tokens 1024 \
--transform content --format yaml <<'YAML'
messages:
- role: user
content:
- type: document
source:
type: base64
media_type: application/pdf
data: "@./document.pdf"
- type: text
text: What are the key findings in this document?
YAML
```
```python Python hidelines={1}
import anthropic
import base64
import httpx
# First, load and encode the PDF
pdf_url = "https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf"
pdf_data = base64.standard_b64encode(httpx.get(pdf_url).content).decode("utf-8")
# Alternative: Load from a local file
# with open("document.pdf", "rb") as f:
# pdf_data = base64.standard_b64encode(f.read()).decode("utf-8")
# Send to Claude using base64 encoding
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": pdf_data,
},
},
{"type": "text", "text": "What are the key findings in this document?"},
],
}
],
)
print(message.content)
```
```typescript TypeScript hidelines={1..3,-3..-1}
import Anthropic from "@anthropic-ai/sdk";
async function main() {
// Method 1: Fetch and encode a remote PDF
const pdfURL =
"https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf";
const pdfResponse = await fetch(pdfURL);
const arrayBuffer = await pdfResponse.arrayBuffer();
const pdfBase64 = Buffer.from(arrayBuffer).toString("base64");
// Method 2: Load from a local file
// import { readFile } from "node:fs/promises";
// const pdfBase64 = (await readFile('document.pdf')).toString('base64');
// Send the API request with base64-encoded PDF
const anthropic = new Anthropic();
const response = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "document",
source: {
type: "base64",
media_type: "application/pdf",
data: pdfBase64
}
},
{
type: "text",
text: "What are the key findings in this document?"
}
]
}
]
});
console.log(response);
}
main();
```
```java Java hidelines={1..2,4,6..22,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Base64PdfSource;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.DocumentBlockParam;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Base64;
import java.util.List;
public class PdfBase64Example {
public static void main(String[] args) throws IOException, InterruptedException {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Method 1: Download and encode a remote PDF
String pdfUrl =
"https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf";
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(pdfUrl)).GET().build();
HttpResponse response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofByteArray()
);
String pdfBase64 = Base64.getEncoder().encodeToString(response.body());
// Method 2: Load from a local file
// byte[] fileBytes = Files.readAllBytes(Path.of("document.pdf"));
// String pdfBase64 = Base64.getEncoder().encodeToString(fileBytes);
// Create document block with base64 data
DocumentBlockParam documentParam = DocumentBlockParam.builder()
.source(Base64PdfSource.builder().data(pdfBase64).build())
.build();
// Create a message with document and text content blocks
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addUserMessageOfBlockParams(
List.of(
ContentBlockParam.ofDocument(documentParam),
ContentBlockParam.ofText(
TextBlockParam.builder()
.text("What are the key findings in this document?")
.build()
)
)
)
.build();
Message message = client.messages().create(params);
System.out.println(message.content());
}
}
```
#### Option 3: Files API
For PDFs you'll use repeatedly, or when you want to avoid encoding overhead, use the [Files API](/docs/en/build-with-claude/files):
```bash cURL hidelines={1..2}
cd "$(mktemp -d)"
curl -sSo document.pdf https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf
# First, upload your PDF to the Files API
curl -X POST https://api.anthropic.com/v1/files \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14" \
-F "file=@document.pdf"
# Then use the returned file_id in your message
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" \
-H "anthropic-beta: files-api-2025-04-14" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": [{
"type": "document",
"source": {
"type": "file",
"file_id": "file_abc123"
}
},
{
"type": "text",
"text": "What are the key findings in this document?"
}]
}]
}'
```
```bash CLI nocheck hidelines={1..2}
cd "$(mktemp -d)"
curl -sSo document.pdf https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf
# First, upload your PDF to the Files API
FILE_ID=$(ant beta:files upload \
--file ./document.pdf \
--transform id --raw-output)
# Then use the returned file_id in your message
ant beta:messages create \
--beta files-api-2025-04-14 \
--transform content --format yaml <
### How PDF support works
When you send a PDF to Claude, the following steps occur:
- The system converts each page of the document into an image.
- The text from each page is extracted and provided alongside each page's image.
- Documents are provided as a combination of text and images for analysis.
- This allows users to ask for insights on visual elements of a PDF, such as charts, diagrams, and other non-textual content.
Claude can reference both textual and visual content when it responds. You can further improve performance by integrating PDF support with:
- **Prompt caching**: To improve performance for repeated analysis.
- **Batch processing**: For high-volume document processing.
- **Tool use**: To extract specific information from documents for use as tool inputs.
### Estimate your costs
The token count of a PDF file depends on the total text extracted from the document as well as the number of pages:
- Text token costs: Each page typically uses 1,500-3,000 tokens per page depending on content density. Standard API pricing applies with no additional PDF fees.
- Image token costs: Since each page is converted into an image, the same [image-based cost calculations](/docs/en/build-with-claude/vision#evaluate-image-size) are applied.
You can use [token counting](/docs/en/build-with-claude/token-counting) to estimate costs for your specific PDFs.
***
## Optimize PDF processing
### Improve performance
Follow these best practices for optimal results:
- Place PDFs before text in your requests
- Use standard fonts
- Ensure text is clear and legible
- Rotate pages to proper upright orientation
- Use logical page numbers (from PDF viewer) in prompts
- Split large PDFs into chunks when needed
- Enable prompt caching for repeated analysis
### Scale your implementation
For high-volume processing, consider these approaches:
#### Use prompt caching
Cache PDFs to improve performance on repeated queries:
```bash cURL hidelines={1..2}
cd "$(mktemp -d)"
curl -s "https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf" | base64 | tr -d '\n' > pdf_base64.txt
# Create a JSON request file using the pdf_base64.txt content
jq -n --rawfile PDF_BASE64 pdf_base64.txt '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": [{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": $PDF_BASE64
},
"cache_control": {
"type": "ephemeral"
}
},
{
"type": "text",
"text": "Which model has the highest human preference win rates across each use-case?"
}]
}]
}' > request.json
# Then make the API call using the JSON file
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 @request.json
```
```bash CLI hidelines={1..2}
cd "$(mktemp -d)"
curl -sSo document.pdf https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content:
- type: document
source:
type: base64
media_type: application/pdf
data: "@./document.pdf"
cache_control:
type: ephemeral
- type: text
text: Which model has the highest human preference win rates across each use-case?
YAML
```
```python Python nocheck hidelines={1..5,7..13}
import anthropic
import base64
from pypdf import PdfWriter
import io
client = anthropic.Anthropic()
buf = io.BytesIO()
writer = PdfWriter()
writer.add_blank_page(width=72, height=72)
writer.write(buf)
pdf_data = base64.standard_b64encode(buf.getvalue()).decode("utf-8")
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": pdf_data,
},
"cache_control": {"type": "ephemeral"},
},
{"type": "text", "text": "Analyze this document."},
],
}
],
)
```
```typescript TypeScript nocheck
const response = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
content: [
{
type: "document",
source: {
media_type: "application/pdf",
type: "base64",
data: pdfBase64
},
cache_control: { type: "ephemeral" }
},
{
type: "text",
text: "Which model has the highest human preference win rates across each use-case?"
}
],
role: "user"
}
]
});
console.log(response);
```
```java Java nocheck hidelines={1..2,5,7..20,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Base64PdfSource;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.DocumentBlockParam;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
public class MessagesDocumentExample {
public static void main(String[] args) throws IOException {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Read PDF file as base64
byte[] pdfBytes = Files.readAllBytes(Paths.get("pdf_base64.txt"));
String pdfBase64 = new String(pdfBytes);
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addUserMessageOfBlockParams(
List.of(
ContentBlockParam.ofDocument(
DocumentBlockParam.builder()
.source(Base64PdfSource.builder().data(pdfBase64).build())
.cacheControl(CacheControlEphemeral.builder().build())
.build()
),
ContentBlockParam.ofText(
TextBlockParam.builder()
.text(
"Which model has the highest human preference win rates across each use-case?"
)
.build()
)
)
)
.build();
Message message = client.messages().create(params);
System.out.println(message);
}
}
```
#### Process document batches
Use the Message Batches API for high-volume workflows:
```bash cURL hidelines={1..2}
cd "$(mktemp -d)"
curl -s "https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf" | base64 | tr -d '\n' > pdf_base64.txt
# Create a JSON request file using the pdf_base64.txt content
jq -n --rawfile PDF_BASE64 pdf_base64.txt '
{
"requests": [
{
"custom_id": "my-first-request",
"params": {
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": $PDF_BASE64
}
},
{
"type": "text",
"text": "Which model has the highest human preference win rates across each use-case?"
}
]
}
]
}
},
{
"custom_id": "my-second-request",
"params": {
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": $PDF_BASE64
}
},
{
"type": "text",
"text": "Extract 5 key insights from this document."
}
]
}
]
}
}
]
}
' > request.json
# Then make the API call using the JSON file
curl https://api.anthropic.com/v1/messages/batches \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d @request.json
```
```bash CLI hidelines={1..2}
cd "$(mktemp -d)"
curl -sSo document.pdf https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf
ant messages:batches create <<'YAML'
requests:
- custom_id: my-first-request
params:
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content:
- type: document
source:
type: base64
media_type: application/pdf
data: "@./document.pdf"
- type: text
text: >-
Which model has the highest human preference win rates
across each use-case?
- custom_id: my-second-request
params:
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content:
- type: document
source:
type: base64
media_type: application/pdf
data: "@./document.pdf"
- type: text
text: Extract 5 key insights from this document.
YAML
```
```python Python nocheck hidelines={1..5,7..13}
import anthropic
import base64
from pypdf import PdfWriter
import io
client = anthropic.Anthropic()
buf = io.BytesIO()
writer = PdfWriter()
writer.add_blank_page(width=72, height=72)
writer.write(buf)
pdf_data = base64.standard_b64encode(buf.getvalue()).decode("utf-8")
message_batch = client.messages.batches.create(
requests=[
{
"custom_id": "doc1",
"params": {
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": pdf_data,
},
},
{"type": "text", "text": "Summarize this document."},
],
}
],
},
}
]
)
```
```typescript TypeScript nocheck
const response = await anthropic.messages.batches.create({
requests: [
{
custom_id: "my-first-request",
params: {
max_tokens: 1024,
messages: [
{
content: [
{
type: "document",
source: {
media_type: "application/pdf",
type: "base64",
data: pdfBase64
}
},
{
type: "text",
text: "Which model has the highest human preference win rates across each use-case?"
}
],
role: "user"
}
],
model: "claude-opus-4-7"
}
},
{
custom_id: "my-second-request",
params: {
max_tokens: 1024,
messages: [
{
content: [
{
type: "document",
source: {
media_type: "application/pdf",
type: "base64",
data: pdfBase64
}
},
{
type: "text",
text: "Extract 5 key insights from this document."
}
],
role: "user"
}
],
model: "claude-opus-4-7"
}
}
]
});
console.log(response);
```
```java Java nocheck hidelines={1..3,5..14,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.*;
import com.anthropic.models.messages.batches.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
public class MessagesBatchDocumentExample {
public static void main(String[] args) throws IOException {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Read PDF file as base64
byte[] pdfBytes = Files.readAllBytes(Paths.get("pdf_base64.txt"));
String pdfBase64 = new String(pdfBytes);
BatchCreateParams params = BatchCreateParams.builder()
.addRequest(
BatchCreateParams.Request.builder()
.customId("my-first-request")
.params(
BatchCreateParams.Request.Params.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addUserMessageOfBlockParams(
List.of(
ContentBlockParam.ofDocument(
DocumentBlockParam.builder()
.source(Base64PdfSource.builder().data(pdfBase64).build())
.build()
),
ContentBlockParam.ofText(
TextBlockParam.builder()
.text(
"Which model has the highest human preference win rates across each use-case?"
)
.build()
)
)
)
.build()
)
.build()
)
.addRequest(
BatchCreateParams.Request.builder()
.customId("my-second-request")
.params(
BatchCreateParams.Request.Params.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addUserMessageOfBlockParams(
List.of(
ContentBlockParam.ofDocument(
DocumentBlockParam.builder()
.source(Base64PdfSource.builder().data(pdfBase64).build())
.build()
),
ContentBlockParam.ofText(
TextBlockParam.builder()
.text("Extract 5 key insights from this document.")
.build()
)
)
)
.build()
)
.build()
)
.build();
MessageBatch batch = client.messages().batches().create(params);
System.out.println(batch);
}
}
```
## Next steps
Explore practical examples of PDF processing in the cookbook recipe.
See complete API documentation for PDF support.
---
# Vision
URL: https://platform.claude.com/docs/en/build-with-claude/vision
# Vision
Claude's vision capabilities allow it to understand and analyze images, opening up exciting possibilities for multimodal interaction.
---
This guide describes how to work with images in Claude, including best practices, code examples, and limitations to keep in mind.
---
## How to use vision
Use Claude's vision capabilities through:
- [claude.ai](https://claude.ai/). Upload an image like you would a file, or drag and drop an image directly into the chat window.
- The [Console Workbench](/workbench/). A button to add images appears at the top right of every User message block.
- API request. See the examples in this guide.
Multiple images can be included in a single request, which Claude will analyze jointly when formulating its response. This can be helpful for comparing or contrasting images.
---
## Before you upload
### General limits
The maximal number of images per message or request is:
- 20 per message on [claude.ai](https://claude.ai/).
- 100 per request on the API, for models with a 200k-token context window.
- 600 per request on the API, for all other models.
The maximal dimensions per image are 8000x8000 px. If you submit more than 20 images in one API request, this limit is reduced to 2000x2000 px.
While the API supports up to 600 images per request, [request size limits](/docs/en/api/overview#request-size-limits) (32 MB for standard endpoints; lower on some partner-operated platforms, for example, Amazon Bedrock and Vertex AI) can be reached first. For many images, consider uploading with the [Files API](#files-api-image-example) and referencing by `file_id` to keep request payloads small.
Even when using the Files API, requests with many large images can fail before reaching the 600-image count. Reduce image dimensions or file sizes (for example, by downsampling) before uploading (see [Evaluate image size](#evaluate-image-size)).
### Evaluate image size
An image uses approximately `width * height / 750` tokens, where the width and height are expressed in pixels.
The maximal native image resolution is:
- For Claude Opus 4.7: 4784 tokens, and at most 2576 pixels on the long edge.
- For other models: 1568 tokens, and at most 1568 pixels on the long edge.
If your input image is larger than this native resolution, it will first be resized to the largest possible size while preserving the aspect ratio. Moreover, images are padded on the bottom and right corners to a multiple of 28 pixels.
When asking Claude to output coordinates (points, bounding boxes, etc.), they will be expressed with respect to the resized/padded image and will need to be rescaled/translated accordingly client-side based on the original and resized dimensions.
To minimize latency and to simplify coordinate-based workflows, you should prefer resizing images before uploading them.
### Calculate image costs
Each image you include in a request to Claude counts toward your token usage. To calculate the approximate cost, multiply the approximate number of image tokens computed as above by the [per-token price of the model](https://claude.com/pricing) you're using.
Here are examples of approximate tokenization and costs for different image sizes within the API's size constraints based on Claude Sonnet 4.6 per-token price of $3 per million input tokens:
| Image size | \# of Tokens | Cost / image | Cost / 1k images |
| ----------------------------- | ------------ | ------------ | ---------------- |
| 200x200 px(0.04 megapixels) | \~54 | \~$0.00016 | \~$0.16 |
| 1000x1000 px(1 megapixel) | \~1334 | \~$0.004 | \~$4.00 |
| 1092x1092 px(1.19 megapixels) | \~1568 | \~$0.0047 | \~$4.70 |
| 1920x1080 px(2.07 megapixels) | \~1568 | \~$0.0047 | \~$4.70 |
| 2000x1500 px(3 megapixels) | \~1568 | \~$0.0047 | \~$4.70 |
Note that the last three images are downscaled before processing.
#### High-resolution image support on Claude Opus 4.7
Claude Opus 4.7 is the first Claude model with high-resolution image support. The maximum image resolution is 2576 pixels on the long edge, up from 1568 px on prior models. This unlocks performance gains on vision-heavy workloads and is particularly valuable for computer use, screenshot understanding, and document analysis.
High-resolution support is automatic on Claude Opus 4.7 and requires no beta header or client-side opt-in.
High-resolution images on Claude Opus 4.7 can use up to approximately 3x more image tokens than on prior models (4784 versus 1568 tokens per image). If you don't need the additional fidelity, downsample images before sending to control token costs.
Here are the same image sizes tokenized for Claude Opus 4.7, based on its per-token price of $5 per million input tokens:
| Image size | \# of Tokens | Cost / image | Cost / 1k images |
| ----------------------------- | ------------ | ------------ | ---------------- |
| 200x200 px(0.04 megapixels) | \~54 | \~$0.00027 | \~$0.27 |
| 1000x1000 px(1 megapixel) | \~1334 | \~$0.0067 | \~$6.70 |
| 1092x1092 px(1.19 megapixels) | \~1590 | \~$0.0080 | \~$8.00 |
| 1920x1080 px(2.07 megapixels) | \~2765 | \~$0.014 | \~$14.00 |
| 2000x1500 px(3 megapixels) | \~4000 | \~$0.020 | \~$20.00 |
### Ensure image quality
When providing images to Claude, keep the following in mind for best results:
- **Image format**: Use a supported image format: JPEG, PNG, GIF, or WebP.\
Animations are unsupported, and only the first frame will be used.
- **Image clarity**: Ensure images are clear and not too blurry or pixelated.
- **Text**: If the image contains important text, make sure it's legible and not too small. Avoid cropping out key visual context just to enlarge the text.
- **Resizing**: Take into account that your image might be resized if it is too large (see above); this might for example make text less legible. Consider pre-resizing your images, cropping them, or both.
- **Image compression**: Compressing images before sending them, using a lossy format such as JPEG or WebP (lossy mode), can reduce latency by reducing the size of requests. However, this can introduce artifacts that are detrimental to model performance, especially when multiple compression passes are applied. For example, heavy JPEG compression can make text difficult to read. Confirm your compression settings are appropriate for the task by inspecting the actual images sent to the API.
---
## Prompt examples
Many of the [prompting techniques](/docs/en/build-with-claude/prompt-engineering/overview) that work well for text-based interactions with Claude can also be applied to image-based prompts.
These examples demonstrate best practice prompt structures involving images.
Just as [placing long documents before your query](/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#long-context-prompting) improves results in text prompts, Claude works best when images come before text. Images placed after text or interpolated with text still perform well, but if your use case allows it, prefer an image-then-text structure.
### About the prompt examples
The following examples demonstrate how to use Claude's vision capabilities using various programming languages and approaches. You can provide images to Claude in three ways:
1. As a base64-encoded image in `image` content blocks
2. As a URL reference to an image hosted online
3. Using the Files API (upload once, use multiple times)
The base64 example prompts use these variables:
```bash cURL
# For URL-based images, you can use the URL directly in your JSON request
# For base64-encoded images, you need to first encode the image
# Example of how to encode an image to base64 in bash:
BASE64_IMAGE_DATA=$(curl -s "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg" | base64)
# The encoded data can now be used in your API calls
```
```python Python
import base64
import httpx
# For base64-encoded images
image1_url = "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
image1_media_type = "image/jpeg"
image1_data = base64.standard_b64encode(httpx.get(image1_url).content).decode("utf-8")
image2_url = "https://upload.wikimedia.org/wikipedia/commons/b/b5/Iridescent.green.sweat.bee1.jpg"
image2_media_type = "image/jpeg"
image2_data = base64.standard_b64encode(httpx.get(image2_url).content).decode("utf-8")
# For URL-based images, you can use the URLs directly in your requests
```
```typescript TypeScript nocheck
import axios from "axios";
// For base64-encoded images
async function getBase64Image(url: string): Promise {
const response = await axios.get(url, { responseType: "arraybuffer" });
return Buffer.from(response.data, "binary").toString("base64");
}
// Usage
async function prepareImages() {
const imageData = await getBase64Image(
"https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
);
// Now you can use imageData in your API calls
}
// For URL-based images, you can use the URLs directly in your requests
```
```csharp C#
using System;
using System.Net.Http;
using System.Threading.Tasks;
// For base64-encoded images
async Task DownloadAndEncodeImageAsync(string url)
{
using var client = new HttpClient();
var bytes = await client.GetByteArrayAsync(url);
return Convert.ToBase64String(bytes);
}
// Usage:
// var imageData = await DownloadAndEncodeImageAsync("https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg");
// For URL-based images, you can use the URLs directly in your requests
```
```go Go hidelines={1..9,-8..}
package main
import (
"encoding/base64"
"fmt"
"io"
"net/http"
)
func downloadAndEncodeImage(url string) (string, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("User-Agent", "AnthropicDocsBot/1.0")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(data), nil
}
func main() {
imageData, err := downloadAndEncodeImage("https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg")
if err != nil {
panic(err)
}
fmt.Println(imageData[:50])
}
```
```java Java nocheck hidelines={1..7,-1}
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Base64;
public class ImageHandlingExample {
public static void main(String[] args) throws IOException, InterruptedException {
// For base64-encoded images
String image1Url =
"https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg";
String image1MediaType = "image/jpeg";
String image1Data = downloadAndEncodeImage(image1Url);
String image2Url =
"https://upload.wikimedia.org/wikipedia/commons/b/b5/Iridescent.green.sweat.bee1.jpg";
String image2MediaType = "image/jpeg";
String image2Data = downloadAndEncodeImage(image2Url);
// For URL-based images, you can use the URLs directly in your requests
}
private static String downloadAndEncodeImage(String imageUrl) throws IOException {
try (InputStream inputStream = new URL(imageUrl).openStream()) {
return Base64.getEncoder().encodeToString(inputStream.readAllBytes());
}
}
}
```
```php PHP nocheck hidelines={1}
Below are examples of how to include images in a Messages API request using base64-encoded images and URL references:
### Base64-encoded image example
```bash cURL hidelines={1..2}
BASE64_IMAGE_DATA=$(curl -s "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg" | base64 | tr -d '\n')
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d @- <
{
new ContentBlockParam(new ImageBlockParam(
new ImageBlockParamSource(new Base64ImageSource()
{
Data = imageData,
MediaType = MediaType.ImagePng,
})
)),
new ContentBlockParam(new TextBlockParam("Describe this image.")),
}),
}
]
});
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
imageData := "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC"
message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(
anthropic.NewImageBlockBase64("image/png", imageData),
anthropic.NewTextBlock("Describe this image."),
),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(message)
}
```
```java Java nocheck hidelines={1..8,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.*;
import java.util.List;
public class VisionExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
String imageData = ""; // Base64-encoded image data as string
List contentBlockParams = List.of(
ContentBlockParam.ofImage(
ImageBlockParam.builder()
.source(
Base64ImageSource.builder()
.mediaType(Base64ImageSource.MediaType.IMAGE_JPEG)
.data(imageData)
.build()
)
.build()
),
ContentBlockParam.ofText(TextBlockParam.builder().text("Describe this image.").build())
);
Message message = client
.messages()
.create(
MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addUserMessageOfBlockParams(contentBlockParams)
.build()
);
System.out.println(message);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'image',
'source' => [
'type' => 'base64',
'media_type' => 'image/png',
'data' => $imageData,
],
],
['type' => 'text', 'text' => 'Describe this image.'],
],
],
],
model: 'claude-opus-4-7',
);
echo $message->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
image_data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC"
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "image",
source: {
type: "base64",
media_type: "image/png",
data: image_data
}
},
{ type: "text", text: "Describe this image." }
]
}
]
)
puts message
```
### URL-based image example
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "url",
"url": "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
}
},
{
"type": "text",
"text": "Describe this image."
}
]
}
]
}'
```
```bash CLI
ant messages create <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
messages:
- role: user
content:
- type: image
source:
type: url
url: https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg
- type: text
text: Describe this image.
YAML
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "url",
"url": "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg",
},
},
{"type": "text", "text": "Describe this image."},
],
}
],
)
print(message)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
const message = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "image",
source: {
type: "url",
url: "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
}
},
{
type: "text",
text: "Describe this image."
}
]
}
]
});
console.log(message);
```
```csharp C#
using System.Collections.Generic;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var message = await client.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages =
[
new()
{
Role = Role.User,
Content = new MessageParamContent(new List
{
new ContentBlockParam(new ImageBlockParam(
new ImageBlockParamSource(new UrlImageSource()
{
Url = "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg",
})
)),
new ContentBlockParam(new TextBlockParam("Describe this image.")),
}),
}
]
});
Console.WriteLine(message);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(
anthropic.NewImageBlock(anthropic.URLImageSourceParam{
URL: "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg",
}),
anthropic.NewTextBlock("Describe this image."),
),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(message)
}
```
```java Java hidelines={1..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.*;
import java.io.IOException;
import java.util.List;
public class VisionExample {
public static void main(String[] args) throws IOException, InterruptedException {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
List contentBlockParams = List.of(
ContentBlockParam.ofImage(
ImageBlockParam.builder()
.source(
UrlImageSource.builder()
.url(
"https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
)
.build()
)
.build()
),
ContentBlockParam.ofText(TextBlockParam.builder().text("Describe this image.").build())
);
Message message = client
.messages()
.create(
MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.addUserMessageOfBlockParams(contentBlockParams)
.build()
);
System.out.println(message);
}
}
```
```php PHP hidelines={1..4}
messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'image',
'source' => [
'type' => 'url',
'url' => 'https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg',
],
],
['type' => 'text', 'text' => 'Describe this image.'],
],
],
],
model: 'claude-opus-4-7',
);
echo $message->content[0]->text;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "image",
source: {
type: "url",
url: "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
}
},
{ type: "text", text: "Describe this image." }
]
}
]
)
puts message
```
### Files API image example
For images you'll use repeatedly or when you want to avoid encoding overhead, use the [Files API](/docs/en/build-with-claude/files). Upload the image once, then reference the returned `file_id` in subsequent messages instead of resending base64 data.
In multi-turn conversations and agentic workflows, each request resends the
full conversation history. If images are base64-encoded, the full image bytes
are included in the payload on every turn, which can significantly increase
request size and latency as the conversation grows. Uploading images to the
Files API and referencing them by `file_id` keeps request payloads small
regardless of how many images accumulate in the conversation history.
```bash cURL hidelines={1..2}
cd "$(mktemp -d)"
curl -sSo image.jpg https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg
# First, upload your image to the Files API
curl -X POST https://api.anthropic.com/v1/files \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14" \
-F "file=@image.jpg"
# Then use the returned file_id in your message
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "file",
"file_id": "file_abc123"
}
},
{
"type": "text",
"text": "Describe this image."
}
]
}
]
}'
```
```bash CLI nocheck hidelines={1}
cd "$(mktemp -d)"
curl -sSo image.jpg \
https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg
# First, upload your image to the Files API
FILE_ID=$(ant beta:files upload \
--file ./image.jpg \
--transform id --raw-output)
# Then use the returned file_id in your message
ant beta:messages create \
--beta files-api-2025-04-14 \
--transform content --format yaml <beta->files->upload(
file: fopen('image.jpg', 'r'),
);
// Use the uploaded file in a message
$message = $client->beta->messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'image',
'source' => ['type' => 'file', 'file_id' => $fileUpload->id],
],
['type' => 'text', 'text' => 'Describe this image.'],
],
],
],
model: 'claude-opus-4-7',
betas: ['files-api-2025-04-14'],
);
echo $message->content[0]->text;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
# Upload the image file
file_upload = client.beta.files.upload(
file: File.open("image.jpg", "rb")
)
# Use the uploaded file in a message
message = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
betas: ["files-api-2025-04-14"],
messages: [
{
role: "user",
content: [
{
type: "image",
source: { type: "file", file_id: file_upload.id }
},
{ type: "text", text: "Describe this image." }
]
}
]
)
puts message.content
```
See [Messages API examples](/docs/en/api/messages/create) for more example code and parameter details.
It's best to place images earlier in the prompt than questions about them or instructions for tasks that use them.
Ask Claude to describe one image.
| Role | Content |
| ---- | ------------------------------ |
| User | \[Image\] Describe this image. |
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
image1_data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC"
image1_media_type = "image/png"
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": image1_media_type,
"data": image1_data,
},
},
{"type": "text", "text": "Describe this image."},
],
}
],
)
```
```python Python
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "url",
"url": "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg",
},
},
{"type": "text", "text": "Describe this image."},
],
}
],
)
```
In situations where there are multiple images, introduce each image with `Image 1:` and `Image 2:` and so on. You don't need newlines between images or between images and the prompt.
Ask Claude to describe the differences between multiple images.
| Role | Content |
| ---- | ------------------------------------------------------------------------- |
| User | Image 1: \[Image 1\] Image 2: \[Image 2\] How are these images different? |
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
image1_data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC"
image1_media_type = "image/png"
image2_data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC"
image2_media_type = "image/png"
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Image 1:"},
{
"type": "image",
"source": {
"type": "base64",
"media_type": image1_media_type,
"data": image1_data,
},
},
{"type": "text", "text": "Image 2:"},
{
"type": "image",
"source": {
"type": "base64",
"media_type": image2_media_type,
"data": image2_data,
},
},
{"type": "text", "text": "How are these images different?"},
],
}
],
)
```
```python Python
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Image 1:"},
{
"type": "image",
"source": {
"type": "url",
"url": "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg",
},
},
{"type": "text", "text": "Image 2:"},
{
"type": "image",
"source": {
"type": "url",
"url": "https://upload.wikimedia.org/wikipedia/commons/b/b5/Iridescent.green.sweat.bee1.jpg",
},
},
{"type": "text", "text": "How are these images different?"},
],
}
],
)
```
Ask Claude to describe the differences between multiple images, while giving it a system prompt for how to respond.
| Content | |
| ------- | ------------------------------------------------------------------------- |
| System | Respond only in Spanish. |
| User | Image 1: \[Image 1\] Image 2: \[Image 2\] How are these images different? |
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
image1_data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC"
image1_media_type = "image/png"
image2_data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC"
image2_media_type = "image/png"
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
system="Respond only in Spanish.",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Image 1:"},
{
"type": "image",
"source": {
"type": "base64",
"media_type": image1_media_type,
"data": image1_data,
},
},
{"type": "text", "text": "Image 2:"},
{
"type": "image",
"source": {
"type": "base64",
"media_type": image2_media_type,
"data": image2_data,
},
},
{"type": "text", "text": "How are these images different?"},
],
}
],
)
```
```python Python
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
system="Respond only in Spanish.",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Image 1:"},
{
"type": "image",
"source": {
"type": "url",
"url": "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg",
},
},
{"type": "text", "text": "Image 2:"},
{
"type": "image",
"source": {
"type": "url",
"url": "https://upload.wikimedia.org/wikipedia/commons/b/b5/Iridescent.green.sweat.bee1.jpg",
},
},
{"type": "text", "text": "How are these images different?"},
],
}
],
)
```
Claude's vision capabilities shine in multimodal conversations that mix images and text. You can have extended back-and-forth exchanges with Claude, adding new images or follow-up questions at any point. This enables powerful workflows for iterative image analysis, comparison, or combining visuals with other knowledge.
Ask Claude to contrast two images, then ask a follow-up question comparing the first images to two new images.
| Role | Content |
| --------- | ------------------------------------------------------------------------------------ |
| User | Image 1: \[Image 1\] Image 2: \[Image 2\] How are these images different? |
| Assistant | \[Claude's response\] |
| User | Image 1: \[Image 3\] Image 2: \[Image 4\] Are these images similar to the first two? |
| Assistant | \[Claude's response\] |
When using the API, insert new images into the array of Messages in the `user` role as part of any standard [multiturn conversation](/docs/en/api/messages/create) structure.
---
## Limitations
While Claude's image understanding capabilities are cutting-edge, there are some limitations to be aware of:
- **People identification**: Claude [cannot be used](https://www.anthropic.com/legal/aup) to name people in images and refuses to do so.
- **Accuracy**: Claude may hallucinate or make mistakes when interpreting low-quality, rotated, or very small images under 200 pixels.
- **Spatial reasoning**: Claude's spatial reasoning abilities are limited. It may struggle with tasks requiring precise localization or layouts, like reading an analog clock face or describing exact positions of chess pieces.
- **Counting**: Claude can give approximate counts of objects in an image but may not always be precisely accurate, especially with large numbers of small objects.
- **AI generated images**: Claude does not know if an image is AI-generated and may be incorrect if asked. Do not rely on it to detect fake or synthetic images.
- **Inappropriate content**: Claude does not process inappropriate or explicit images that violate the [Acceptable Use Policy](https://www.anthropic.com/legal/aup).
- **Healthcare applications**: While Claude can analyze general medical images, it is not designed to interpret complex diagnostic scans such as CTs or MRIs. Claude's outputs should not be considered a substitute for professional medical advice or diagnosis.
Always carefully review and verify Claude's image interpretations, especially for high-stakes use cases. Do not use Claude for tasks requiring perfect precision or sensitive image analysis without human oversight.
---
## FAQ
Claude currently supports JPEG, PNG, GIF, and WebP image formats, specifically:
- `image/jpeg`
- `image/png`
- `image/gif`
- `image/webp`
{" "}
Yes, Claude can process images from URLs with URL image source blocks in the API.
Simply use the "url" source type instead of "base64" in your API requests.
Example:
```json
{
"type": "image",
"source": {
"type": "url",
"url": "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
}
}
```
Yes, there are limits:
- API: Maximum 5 MB per image
- claude.ai: Maximum 10 MB per image
Images larger than these limits are rejected and return an error when using the API.
The image limits are:
- Messages API: Up to 600 images per request (100 for models with a 200k-token context window)
- claude.ai: Up to 20 images per turn
Requests exceeding these limits are rejected and return an error. Requests with many large images may also fail before reaching these limits; see [General limits](#general-limits) for details.
{" "}
No, Claude does not parse or receive any metadata from images passed to it.
{" "}
No. Image uploads are ephemeral and not stored beyond the duration of the API
request. Uploaded images are automatically deleted after they have been
processed.
{" "}
Refer to the Anthropic privacy policy page for information on how uploaded
images and other data are handled. Anthropic does not use uploaded images to
train models.
If Claude's image interpretation seems incorrect:
1. Ensure the image is clear, high-quality, and correctly oriented.
2. Try prompt engineering techniques to improve results.
3. If the issue persists, flag the output in claude.ai (thumbs up/down) or contact the [support team](https://support.claude.com/).
Your feedback helps improve Claude!
No, Claude is an image understanding model only. It can interpret and analyze images, but it cannot generate, produce, edit, manipulate, or create images.
---
## Dive deeper into vision
Ready to start building with images using Claude? Here are a few helpful resources:
- [Multimodal cookbook](https://platform.claude.com/cookbook/multimodal-getting-started-with-vision): This cookbook has tips on [getting started with images](https://platform.claude.com/cookbook/multimodal-getting-started-with-vision) and [best practice techniques](https://platform.claude.com/cookbook/multimodal-best-practices-for-vision) to ensure the highest quality performance with images. See how you can effectively prompt Claude with images to carry out tasks such as [interpreting and analyzing charts](https://platform.claude.com/cookbook/multimodal-reading-charts-graphs-powerpoints) or [extracting content from forms](https://platform.claude.com/cookbook/multimodal-how-to-transcribe-text).
- [API reference](/docs/en/api/messages/create): Documentation for the Messages API, including example [API calls involving images](/docs/en/build-with-claude/working-with-messages#vision).
If you have any other questions, reach out to the [support team](https://support.claude.com/). You can also join the [developer community](https://www.anthropic.com/discord) to connect with other creators and get help from Anthropic experts.
### Skills
---
# Agent Skills
URL: https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview
# Agent Skills
Agent Skills are modular capabilities that extend Claude's functionality. Each Skill packages instructions, metadata, and optional resources (scripts, templates) that Claude uses automatically when relevant.
---
This feature is **not** eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). Data is retained according to the feature's standard retention policy.
## Why use Skills
Skills are reusable, filesystem-based resources that provide Claude with domain-specific expertise: workflows, context, and best practices that transform general-purpose agents into specialists. Unlike prompts (conversation-level instructions for one-off tasks), Skills load on-demand and eliminate the need to repeatedly provide the same guidance across multiple conversations.
**Key benefits**:
- **Specialize Claude**: Tailor capabilities for domain-specific tasks
- **Reduce repetition**: Create once, use automatically
- **Compose capabilities**: Combine Skills to build complex workflows
For a deep dive into the architecture and real-world applications of Agent Skills, see the engineering blog post [Equipping agents for the real world with Agent Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills).
## Using Skills
Anthropic provides pre-built Agent Skills for common document tasks (PowerPoint, Excel, Word, PDF), and you can create your own custom Skills. Both work the same way. Claude automatically uses them when relevant to your request.
**Pre-built Agent Skills** are available on claude.ai, the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry). See [Available Skills](#available-skills) for the complete list.
**Custom Skills** let you package domain expertise and organizational knowledge. They're available across Claude's products: create them in Claude Code, upload them through the Claude API, or add them in claude.ai settings. On [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws) and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry), upload custom Skills through the Skills API.
**Get started:**
- For pre-built Agent Skills: See the [quickstart tutorial](/docs/en/agents-and-tools/agent-skills/quickstart) to start using PowerPoint, Excel, Word, and PDF skills in the API
- For custom Skills: See the [Agent Skills Cookbook](https://platform.claude.com/cookbook/skills-notebooks-01-skills-introduction) to learn how to create your own Skills
## How Skills work
Skills leverage Claude's VM environment to provide capabilities beyond what's possible with prompts alone. Claude operates in a virtual machine with filesystem access, allowing Skills to exist as directories containing instructions, executable code, and reference materials, organized like an onboarding guide you'd create for a new team member.
This filesystem-based architecture enables **progressive disclosure**: Claude loads information in stages as needed, rather than consuming context upfront.
### Three types of Skill content, three levels of loading
Skills can contain three types of content, each loaded at different times:
### Level 1: Metadata (always loaded)
**Content type: Instructions**. The Skill's YAML frontmatter provides discovery information:
```yaml
---
name: pdf-processing
description: Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction.
---
```
Claude loads this metadata at startup and includes it in the system prompt. This lightweight approach means you can install many Skills without context penalty; Claude only knows each Skill exists and when to use it.
### Level 2: Instructions (loaded when triggered)
**Content type: Instructions**. The main body of SKILL.md contains procedural knowledge: workflows, best practices, and guidance:
````markdown
# PDF Processing
## Quick start
Use pdfplumber to extract text from PDFs:
```python
import pdfplumber
with pdfplumber.open("document.pdf") as pdf:
text = pdf.pages[0].extract_text()
```
For advanced form filling, see [FORMS.md](FORMS.md).
````
When you request something that matches a Skill's description, Claude reads SKILL.md from the filesystem via bash. Only then does this content enter the context window.
### Level 3: Resources and code (loaded as needed)
**Content types: Instructions, code, and resources**. Skills can bundle additional materials:
```text
pdf-skill/
├── SKILL.md (main instructions)
├── FORMS.md (form-filling guide)
├── REFERENCE.md (detailed API reference)
└── scripts/
└── fill_form.py (utility script)
```
**Instructions**: Additional markdown files (FORMS.md, REFERENCE.md) containing specialized guidance and workflows
**Code**: Executable scripts (fill_form.py, validate.py) that Claude runs via bash; scripts provide deterministic operations without consuming context
**Resources**: Reference materials like database schemas, API documentation, templates, or examples
Claude accesses these files only when referenced. The filesystem model means each content type has different strengths: instructions for flexible guidance, code for reliability, resources for factual lookup.
| Level | When Loaded | Token Cost | Content |
|-------|------------|------------|---------|
| **Level 1: Metadata** | Always (at startup) | ~100 tokens per Skill | `name` and `description` from YAML frontmatter |
| **Level 2: Instructions** | When Skill is triggered | Under 5k tokens | SKILL.md body with instructions and guidance |
| **Level 3+: Resources** | As needed | Effectively unlimited | Bundled files executed via bash without loading contents into context |
Progressive disclosure ensures only relevant content occupies the context window at any given time.
### The Skills architecture
Skills run in a code execution environment where Claude has filesystem access, bash commands, and code execution capabilities. Think of it like this: Skills exist as directories on a virtual machine, and Claude interacts with them using the same bash commands you'd use to navigate files on your computer.

**How Claude accesses Skill content:**
When a Skill is triggered, Claude uses bash to read SKILL.md from the filesystem, bringing its instructions into the context window. If those instructions reference other files (like FORMS.md or a database schema), Claude reads those files too using additional bash commands. When instructions mention executable scripts, Claude runs them via bash and receives only the output (the script code itself never enters context).
**What this architecture enables:**
**On-demand file access**: Claude reads only the files needed for each specific task. A Skill can include dozens of reference files, but if your task only needs the sales schema, Claude loads just that one file. The rest remain on the filesystem consuming zero tokens.
**Efficient script execution**: When Claude runs `validate_form.py`, the script's code never loads into the context window. Only the script's output (like "Validation passed" or specific error messages) consumes tokens. This makes scripts far more efficient than having Claude generate equivalent code on the fly.
**No practical limit on bundled content**: Because files don't consume context until accessed, Skills can include comprehensive API documentation, large datasets, extensive examples, or any reference materials you need. There's no context penalty for bundled content that isn't used.
This filesystem-based model is what makes progressive disclosure work. Claude navigates your Skill like you'd reference specific sections of an onboarding guide, accessing exactly what each task requires.
### Example: Loading a PDF processing skill
Here's how Claude loads and uses a PDF processing skill:
1. **Startup**: System prompt includes: `PDF Processing - Extract text and tables from PDF files, fill forms, merge documents`
2. **User request**: "Extract the text from this PDF and summarize it"
3. **Claude invokes**: `bash: read pdf-skill/SKILL.md` → Instructions loaded into context
4. **Claude determines**: Form filling is not needed, so FORMS.md is not read
5. **Claude executes**: Uses instructions from SKILL.md to complete the task

The diagram shows:
1. Default state with system prompt and skill metadata pre-loaded
2. Claude triggers the skill by reading SKILL.md via bash
3. Claude optionally reads additional bundled files like FORMS.md as needed
4. Claude proceeds with the task
This dynamic loading ensures only relevant skill content occupies the context window.
## Where Skills work
Skills are available across Claude's agent products:
Claude Platform on AWS and Microsoft Foundry inherit the same Skills behavior as the Claude API in all following sections.
### Claude API
The Claude API supports both pre-built Agent Skills and custom Skills. Both work identically: specify the relevant `skill_id` in the `container` parameter along with the code execution tool.
**Prerequisites**: Using Skills via the API requires three beta headers:
- `code-execution-2025-08-25` - Skills run in the code execution container
- `skills-2025-10-02` - Enables Skills functionality
- `files-api-2025-04-14` - Required for uploading/downloading files to/from the container
Use pre-built Agent Skills by referencing their `skill_id` (for example, `pptx`, `xlsx`), or create and upload your own through the Skills API (`/v1/skills` endpoints). Custom Skills are shared workspace-wide; all workspace members can access them.
To learn more, see [Use Skills with the Claude API](/docs/en/build-with-claude/skills-guide).
### Claude Code
[Claude Code](https://code.claude.com/docs/en/overview) supports only Custom Skills.
**Custom Skills**: Create Skills as directories with SKILL.md files. Claude discovers and uses them automatically.
Custom Skills in Claude Code are filesystem-based and don't require API uploads.
To learn more, see [Use Skills in Claude Code](https://code.claude.com/docs/en/skills).
### claude.ai
[claude.ai](https://claude.ai) supports both pre-built Agent Skills and custom Skills.
**Pre-built Agent Skills**: These Skills are already working behind the scenes when you create documents. Claude uses them without requiring any setup.
**Custom Skills**: Upload your own Skills as zip files through Settings > Features. Available on Pro, Max, Team, and Enterprise plans with code execution enabled. Custom Skills are individual to each user; they are not shared organization-wide and cannot be centrally managed by admins.
To learn more about using Skills in claude.ai, see the following resources in the Claude Help Center:
- [What are Skills?](https://support.claude.com/en/articles/12512176-what-are-skills)
- [Using Skills in Claude](https://support.claude.com/en/articles/12512180-using-skills-in-claude)
- [How to create custom Skills](https://support.claude.com/en/articles/12512198-creating-custom-skills)
- [Teach Claude your way of working using Skills](https://support.claude.com/en/articles/12580051-teach-claude-your-way-of-working-using-skills)
## Skill structure
Every Skill requires a `SKILL.md` file with YAML frontmatter:
```yaml
---
name: your-skill-name
description: Brief description of what this Skill does and when to use it
---
# Your Skill Name
## Instructions
[Clear, step-by-step guidance for Claude to follow]
## Examples
[Concrete examples of using this Skill]
```
**Required fields**: `name` and `description`
**Field requirements**:
`name`:
- Maximum 64 characters
- Must contain only lowercase letters, numbers, and hyphens
- Cannot contain XML tags
- Cannot contain reserved words: "anthropic", "claude"
`description`:
- Must be non-empty
- Maximum 1024 characters
- Cannot contain XML tags
The `description` should include both what the Skill does and when Claude should use it. For complete authoring guidance, see the [best practices guide](/docs/en/agents-and-tools/agent-skills/best-practices).
## Security considerations
Use Skills only from trusted sources: those you created yourself or obtained from Anthropic. Skills provide Claude with new capabilities through instructions and code, and while this makes them powerful, it also means a malicious Skill can direct Claude to invoke tools or execute code in ways that don't match the Skill's stated purpose.
If you must use a Skill from an untrusted or unknown source, exercise extreme caution and thoroughly audit it before use. Depending on what access Claude has when executing the Skill, malicious Skills could lead to data exfiltration, unauthorized system access, or other security risks.
**Key security considerations**:
- **Audit thoroughly**: Review all files bundled in the Skill: SKILL.md, scripts, images, and other resources. Look for unusual patterns like unexpected network calls, file access patterns, or operations that don't match the Skill's stated purpose
- **External sources are risky**: Skills that fetch data from external URLs pose particular risk, as fetched content may contain malicious instructions. Even trustworthy Skills can be compromised if their external dependencies change over time
- **Tool misuse**: Malicious Skills can invoke tools (file operations, bash commands, code execution) in harmful ways
- **Data exposure**: Skills with access to sensitive data could be designed to leak information to external systems
- **Treat like installing software**: Only use Skills from trusted sources. Be especially careful when integrating Skills into production systems with access to sensitive data or critical operations
## Available Skills
### Pre-built Agent Skills
The following pre-built Agent Skills are available for immediate use:
- **PowerPoint (pptx)**: Create presentations, edit slides, analyze presentation content
- **Excel (xlsx)**: Create spreadsheets, analyze data, generate reports with charts
- **Word (docx)**: Create documents, edit content, format text
- **PDF (pdf)**: Generate formatted PDF documents and reports
These Skills are available on the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry), and claude.ai. See the [quickstart tutorial](/docs/en/agents-and-tools/agent-skills/quickstart) to start using them in the API.
### Open-source Skills
Anthropic also publishes open-source Skills in the [skills repository](https://github.com/anthropics/skills):
- **[Claude API](/docs/en/agents-and-tools/agent-skills/claude-api-skill)**: Provides Claude with up-to-date API reference material, SDK documentation, and best practices for 8 programming languages. Bundled with Claude Code and also available for installation from the skills repository.
### Custom Skills examples
For complete examples of custom Skills, see the [Skills cookbook](https://platform.claude.com/cookbook/skills-notebooks-01-skills-introduction).
## Data retention
Agent Skills is not covered by ZDR arrangements. Skill definitions and execution data are retained according to Anthropic's standard data retention policy.
For ZDR eligibility across all features, see [API and data retention](/docs/en/manage-claude/api-and-data-retention).
## Limitations and constraints
Understanding these limitations helps you plan your Skills deployment effectively. Claude Platform on AWS and Microsoft Foundry follow the same limitations as the Claude API in the following subsections.
### Cross-surface availability
**Custom Skills do not sync across surfaces**. Skills uploaded to one surface are not automatically available on others:
- Skills uploaded to claude.ai must be separately uploaded to the API
- Skills uploaded through the API are not available on claude.ai
- Claude Code Skills are filesystem-based and separate from both claude.ai and API
You'll need to manage and upload Skills separately for each surface where you want to use them.
### Sharing scope
Skills have different sharing models depending on where you use them:
- **claude.ai**: Individual user only; each team member must upload separately
- **Claude API**: Workspace-wide; all workspace members can access uploaded Skills
- **Claude Code**: Personal (`~/.claude/skills/`) or project-based (`.claude/skills/`); can also be shared via Claude Code Plugins
claude.ai does not support centralized admin management or org-wide distribution of custom Skills.
### Runtime environment constraints
The exact runtime environment available to your skill depends on the product surface where you use it.
- **claude.ai**:
- **Varying network access**: Depending on user/admin settings, Skills may have full, partial, or no network access. For more details, see the [Create and Edit Files](https://support.claude.com/en/articles/12111783-create-and-edit-files-with-claude#h_6b7e833898) support article.
- **Claude API**:
- **No network access**: Skills cannot make external API calls or access the internet
- **No runtime package installation**: Only pre-installed packages are available. You cannot install new packages during execution.
- **Pre-configured dependencies only**: Check the [code execution tool documentation](/docs/en/agents-and-tools/tool-use/code-execution-tool) for the list of available packages
- **Claude Code**:
- **Full network access**: Skills have the same network access as any other program on the user's computer
- **Global package installation discouraged**: Skills should only install packages locally in order to avoid interfering with the user's computer
Plan your Skills to work within these constraints.
## Next steps
Create your first Skill
Use Skills with the Claude API
Create and manage custom Skills in Claude Code
Write Skills that Claude can use effectively
---
# Get started with Agent Skills in the API
URL: https://platform.claude.com/docs/en/agents-and-tools/agent-skills/quickstart
# Get started with Agent Skills in the API
Learn how to use Agent Skills to create documents with the Claude API in under 10 minutes.
---
This tutorial shows you how to use Agent Skills to create a PowerPoint presentation. You'll learn how to enable Skills, make a simple request, and access the generated file.
## Prerequisites
- [Claude API key](/settings/keys)
- Python 3.7+ or curl installed
- Basic familiarity with making API requests
## Agent Skills overview
Pre-built Agent Skills extend Claude's capabilities with specialized expertise for tasks like creating documents, analyzing data, and processing files. Anthropic provides the following pre-built Agent Skills in the API:
- **PowerPoint (pptx):** Create and edit presentations
- **Excel (xlsx):** Create and analyze spreadsheets
- **Word (docx):** Create and edit documents
- **PDF (pdf):** Generate PDF documents
**Want to create custom Skills?** See the [Agent Skills Cookbook](https://platform.claude.com/cookbook/skills-notebooks-01-skills-introduction) for examples of building your own Skills with domain-specific expertise.
## Step 1: List available Skills
First, check what Skills are available. Use the Skills API to list all Anthropic-managed Skills:
```bash cURL
curl "https://api.anthropic.com/v1/skills?source=anthropic" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: skills-2025-10-02"
```
```bash CLI
ant beta:skills list --source anthropic
```
```python Python
import anthropic
client = anthropic.Anthropic()
# List Anthropic-managed Skills
skills = client.beta.skills.list(source="anthropic")
for skill in skills.data:
print(f"{skill.id}: {skill.display_title}")
```
```typescript TypeScript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// List Anthropic-managed Skills
const skills = await client.beta.skills.list({
source: "anthropic"
});
for (const skill of skills.data) {
console.log(`${skill.id}: ${skill.display_title}`);
}
```
You see the following Skills: `pptx`, `xlsx`, `docx`, and `pdf`.
This API returns each Skill's metadata: its name and description. Claude loads this metadata at startup to know what Skills are available. This is the first level of **progressive disclosure**, where Claude discovers Skills without loading their full instructions yet.
## Step 2: Create a presentation
Now use the PowerPoint Skill to create a presentation about renewable energy. Specify Skills using the `container` parameter in the Messages API:
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"container": {
"skills": [
{
"type": "anthropic",
"skill_id": "pptx",
"version": "latest"
}
]
},
"messages": [{
"role": "user",
"content": "Create a presentation about renewable energy with 5 slides"
}],
"tools": [{
"type": "code_execution_20250825",
"name": "code_execution"
}]
}'
```
```bash CLI
ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 \
--transform content <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- type: anthropic
skill_id: pptx
version: latest
messages:
- role: user
content: Create a presentation about renewable energy with 5 slides
tools:
- type: code_execution_20250825
name: code_execution
YAML
```
```python Python
import anthropic
client = anthropic.Anthropic()
# Create a message with the PowerPoint Skill
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
betas=["code-execution-2025-08-25", "skills-2025-10-02"],
container={
"skills": [{"type": "anthropic", "skill_id": "pptx", "version": "latest"}]
},
messages=[
{
"role": "user",
"content": "Create a presentation about renewable energy with 5 slides",
}
],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
print(response.content)
```
```typescript TypeScript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// Create a message with the PowerPoint Skill
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [
{
type: "anthropic",
skill_id: "pptx",
version: "latest"
}
]
},
messages: [
{
role: "user",
content: "Create a presentation about renewable energy with 5 slides"
}
],
tools: [
{
type: "code_execution_20250825",
name: "code_execution"
}
]
});
console.log(response.content);
```
Let's break down what each part does:
- **`container.skills`:** Specifies which Skills Claude can use
- **`type: "anthropic"`:** Indicates this is an Anthropic-managed Skill
- **`skill_id: "pptx"`:** The PowerPoint Skill identifier
- **`version: "latest"`:** The Skill version set to the most recently published
- **`tools`:** Enables code execution (required for Skills)
- **Beta headers:** `code-execution-2025-08-25` and `skills-2025-10-02`
When you make this request, Claude automatically matches your task to the relevant Skill. Since you asked for a presentation, Claude determines the PowerPoint Skill is relevant and loads its full instructions: the second level of progressive disclosure. Then Claude executes the Skill's code to create your presentation.
## Step 3: Download the created file
The presentation was created in the code execution container and saved as a file. The response includes a file reference with a file ID. Extract the file ID and download it using the Files API:
```bash cURL nocheck
# Extract file_id from response (using jq)
FILE_ID=$(echo "$RESPONSE" | jq -r '.content[] | select(.type=="tool_use" and .name=="code_execution") | .content[] | select(.file_id) | .file_id')
# Download the file
curl "https://api.anthropic.com/v1/files/$FILE_ID/content" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14" \
--output renewable_energy.pptx
echo "Presentation saved to renewable_energy.pptx"
```
```bash CLI nocheck
# Extract file_id with --transform on the messages create call
FILE_ID=$(ant beta:messages create \
--beta code-execution-2025-08-25 --beta skills-2025-10-02 \
--transform 'content.#.content.content.#.file_id|@flatten|0' \
--raw-output <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- type: anthropic
skill_id: pptx
version: latest
messages:
- role: user
content: Create a presentation about renewable energy with 5 slides
tools:
- type: code_execution_20250825
name: code_execution
YAML
)
# Download the file
ant beta:files download \
--file-id "$FILE_ID" \
--output renewable_energy.pptx
printf 'Presentation saved to renewable_energy.pptx\n'
```
```python Python nocheck
from typing import Any
response: Any = None
# Extract file ID from response
file_id = None
for block in response.content:
if block.type == "tool_use" and block.name == "code_execution":
# File ID is in the tool result
for result_block in block.content:
if hasattr(result_block, "file_id"):
file_id = result_block.file_id
break
if file_id:
# Download the file
file_content = client.beta.files.download(file_id=file_id)
# Save to disk
with open("renewable_energy.pptx", "wb") as f:
file_content.write_to_file(f.name)
print(f"Presentation saved to renewable_energy.pptx")
```
```typescript TypeScript nocheck
// Extract file ID from response
let fileId: string | null = null;
for (const block of response.content) {
if (block.type === "tool_use" && block.name === "code_execution") {
// File ID is in the tool result
for (const resultBlock of block.content) {
if ("file_id" in resultBlock) {
fileId = resultBlock.file_id;
break;
}
}
}
}
if (fileId) {
// Download the file
const fileContent = await client.beta.files.download(fileId);
// Save to disk
const fs = require("fs/promises");
await fs.writeFile("renewable_energy.pptx", Buffer.from(await fileContent.arrayBuffer()));
console.log("Presentation saved to renewable_energy.pptx");
}
```
For complete details on working with generated files, see the [code execution tool documentation](/docs/en/agents-and-tools/tool-use/code-execution-tool#retrieve-generated-files).
## Try more examples
Now that you've created your first document with Skills, try these variations:
### Create a spreadsheet
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"container": {
"skills": [
{
"type": "anthropic",
"skill_id": "xlsx",
"version": "latest"
}
]
},
"messages": [{
"role": "user",
"content": "Create a quarterly sales tracking spreadsheet with sample data"
}],
"tools": [{
"type": "code_execution_20250825",
"name": "code_execution"
}]
}'
```
```bash CLI
ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- type: anthropic
skill_id: xlsx
version: latest
messages:
- role: user
content: Create a quarterly sales tracking spreadsheet with sample data
tools:
- type: code_execution_20250825
name: code_execution
YAML
```
```python Python
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
betas=["code-execution-2025-08-25", "skills-2025-10-02"],
container={
"skills": [{"type": "anthropic", "skill_id": "xlsx", "version": "latest"}]
},
messages=[
{
"role": "user",
"content": "Create a quarterly sales tracking spreadsheet with sample data",
}
],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
```
```typescript TypeScript
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [
{
type: "anthropic",
skill_id: "xlsx",
version: "latest"
}
]
},
messages: [
{
role: "user",
content: "Create a quarterly sales tracking spreadsheet with sample data"
}
],
tools: [
{
type: "code_execution_20250825",
name: "code_execution"
}
]
});
```
### Create a Word document
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"container": {
"skills": [
{
"type": "anthropic",
"skill_id": "docx",
"version": "latest"
}
]
},
"messages": [{
"role": "user",
"content": "Write a 2-page report on the benefits of renewable energy"
}],
"tools": [{
"type": "code_execution_20250825",
"name": "code_execution"
}]
}'
```
```bash CLI nocheck
ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- type: anthropic
skill_id: docx
version: latest
messages:
- role: user
content: Write a 2-page report on the benefits of renewable energy
tools:
- type: code_execution_20250825
name: code_execution
YAML
```
```python Python
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
betas=["code-execution-2025-08-25", "skills-2025-10-02"],
container={
"skills": [{"type": "anthropic", "skill_id": "docx", "version": "latest"}]
},
messages=[
{
"role": "user",
"content": "Write a 2-page report on the benefits of renewable energy",
}
],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
```
```typescript TypeScript
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [
{
type: "anthropic",
skill_id: "docx",
version: "latest"
}
]
},
messages: [
{
role: "user",
content: "Write a 2-page report on the benefits of renewable energy"
}
],
tools: [
{
type: "code_execution_20250825",
name: "code_execution"
}
]
});
```
### Generate a PDF
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"container": {
"skills": [
{
"type": "anthropic",
"skill_id": "pdf",
"version": "latest"
}
]
},
"messages": [{
"role": "user",
"content": "Generate a PDF invoice template"
}],
"tools": [{
"type": "code_execution_20250825",
"name": "code_execution"
}]
}'
```
```bash CLI
ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- type: anthropic
skill_id: pdf
version: latest
messages:
- role: user
content: Generate a PDF invoice template
tools:
- type: code_execution_20250825
name: code_execution
YAML
```
```python Python
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
betas=["code-execution-2025-08-25", "skills-2025-10-02"],
container={
"skills": [{"type": "anthropic", "skill_id": "pdf", "version": "latest"}]
},
messages=[{"role": "user", "content": "Generate a PDF invoice template"}],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
```
```typescript TypeScript
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [
{
type: "anthropic",
skill_id: "pdf",
version: "latest"
}
]
},
messages: [
{
role: "user",
content: "Generate a PDF invoice template"
}
],
tools: [
{
type: "code_execution_20250825",
name: "code_execution"
}
]
});
```
## Next steps
Now that you've used pre-built Agent Skills, you can:
Use Skills with the Claude API
Upload your own Skills for specialized tasks
Learn best practices for writing effective Skills
Learn about Skills in Claude Code
Explore example Skills and implementation patterns
---
# Skill authoring best practices
URL: https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices
# Skill authoring best practices
Learn how to write effective Skills that Claude can discover and use successfully.
---
Good Skills are concise, well-structured, and tested with real usage. This guide provides practical authoring decisions to help you write Skills that Claude can discover and use effectively.
For conceptual background on how Skills work, see the [Skills overview](/docs/en/agents-and-tools/agent-skills/overview).
## Core principles
### Concise is key
The [context window](/docs/en/build-with-claude/context-windows) is a public good. Your Skill shares the context window with everything else Claude needs to know, including:
- The system prompt
- Conversation history
- Other Skills' metadata
- Your actual request
Not every token in your Skill has an immediate cost. At startup, only the metadata (name and description) from all Skills is pre-loaded. Claude reads SKILL.md only when the Skill becomes relevant, and reads additional files only as needed. However, being concise in SKILL.md still matters: once Claude loads it, every token competes with conversation history and other context.
**Default assumption:** Claude is already very smart
Only add context Claude doesn't already have. Challenge each piece of information:
- "Does Claude really need this explanation?"
- "Can I assume Claude knows this?"
- "Does this paragraph justify its token cost?"
**Good example: Concise** (approximately 50 tokens):
````markdown
## Extract PDF text
Use pdfplumber for text extraction:
```python
import pdfplumber
with pdfplumber.open("file.pdf") as pdf:
text = pdf.pages[0].extract_text()
```
````
**Bad example: Too verbose** (approximately 150 tokens):
```markdown
## Extract PDF text
PDF (Portable Document Format) files are a common file format that contains
text, images, and other content. To extract text from a PDF, you'll need to
use a library. There are many libraries available for PDF processing, but
pdfplumber is recommended because it's easy to use and handles most cases well.
First, you'll need to install it using pip. Then you can use the code below...
```
The concise version assumes Claude knows what PDFs are and how libraries work.
### Set appropriate degrees of freedom
Match the level of specificity to the task's fragility and variability.
**High freedom** (text-based instructions):
Use when:
- Multiple approaches are valid
- Decisions depend on context
- Heuristics guide the approach
Example:
```markdown
## Code review process
1. Analyze the code structure and organization
2. Check for potential bugs or edge cases
3. Suggest improvements for readability and maintainability
4. Verify adherence to project conventions
```
**Medium freedom** (pseudocode or scripts with parameters):
Use when:
- A preferred pattern exists
- Some variation is acceptable
- Configuration affects behavior
Example:
````markdown
## Generate report
Use this template and customize as needed:
```python
def generate_report(data, format="markdown", include_charts=True):
# Process data
# Generate output in specified format
# Optionally include visualizations
```
````
**Low freedom** (specific scripts, few or no parameters):
Use when:
- Operations are fragile and error-prone
- Consistency is critical
- A specific sequence must be followed
Example:
````markdown
## Database migration
Run exactly this script:
```bash
python scripts/migrate.py --verify --backup
```
Do not modify the command or add additional flags.
````
**Analogy:** Think of Claude as a robot exploring a path:
- **Narrow bridge with cliffs on both sides:** There's only one safe way forward. Provide specific guardrails and exact instructions (low freedom). Example: database migrations that must run in exact sequence.
- **Open field with no hazards:** Many paths lead to success. Give general direction and trust Claude to find the best route (high freedom). Example: code reviews where context determines the best approach.
### Test with all models you plan to use
Skills act as additions to models, so effectiveness depends on the underlying model. Test your Skill with all the models you plan to use it with.
**Testing considerations by model:**
- **Claude Haiku** (fast, economical): Does the Skill provide enough guidance?
- **Claude Sonnet** (balanced): Is the Skill clear and efficient?
- **Claude Opus** (powerful reasoning): Does the Skill avoid over-explaining?
What works perfectly for Opus might need more detail for Haiku. If you plan to use your Skill across multiple models, aim for instructions that work well with all of them.
## Skill structure
**YAML Frontmatter:** The SKILL.md frontmatter requires two fields:
`name`:
- Maximum 64 characters
- Must contain only lowercase letters, numbers, and hyphens
- Cannot contain XML tags
- Cannot contain reserved words: "anthropic", "claude"
`description`:
- Must be non-empty
- Maximum 1024 characters
- Cannot contain XML tags
- Should describe what the Skill does and when to use it
For complete Skill structure details, see the [Skills overview](/docs/en/agents-and-tools/agent-skills/overview#skill-structure).
### Naming conventions
Use consistent naming patterns to make Skills easier to reference and discuss. Consider using **gerund form** (verb + -ing) for Skill names, as this clearly describes the activity or capability the Skill provides.
Remember that the `name` field must use lowercase letters, numbers, and hyphens only.
**Good naming examples (gerund form):**
- `processing-pdfs`
- `analyzing-spreadsheets`
- `managing-databases`
- `testing-code`
- `writing-documentation`
**Acceptable alternatives:**
- Noun phrases: `pdf-processing`, `spreadsheet-analysis`
- Action-oriented: `process-pdfs`, `analyze-spreadsheets`
**Avoid:**
- Vague names: `helper`, `utils`, `tools`
- Overly generic: `documents`, `data`, `files`
- Reserved words: `anthropic-helper`, `claude-tools`
- Inconsistent patterns within your skill collection
Consistent naming makes it easier to:
- Reference Skills in documentation and conversations
- Understand what a Skill does at a glance
- Organize and search through multiple Skills
- Maintain a professional, cohesive skill library
### Writing effective descriptions
The `description` field enables Skill discovery and should include both what the Skill does and when to use it.
**Always write in third person**. The description is injected into the system prompt, and inconsistent point-of-view can cause discovery problems.
- **Good:** "Processes Excel files and generates reports"
- **Avoid:** "I can help you process Excel files"
- **Avoid:** "You can use this to process Excel files"
**Be specific and include key terms**. Include both what the Skill does and specific triggers/contexts for when to use it.
Each Skill has exactly one description field. The description is critical for skill selection: Claude uses it to choose the right Skill from potentially 100+ available Skills. Your description must provide enough detail for Claude to know when to select this Skill, while the rest of SKILL.md provides the implementation details.
Effective examples:
**PDF Processing skill:**
```yaml
description: Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction.
```
**Excel Analysis skill:**
```yaml
description: Analyze Excel spreadsheets, create pivot tables, generate charts. Use when analyzing Excel files, spreadsheets, tabular data, or .xlsx files.
```
**Git Commit Helper skill:**
```yaml
description: Generate descriptive commit messages by analyzing git diffs. Use when the user asks for help writing commit messages or reviewing staged changes.
```
Avoid vague descriptions like these:
```yaml
description: Helps with documents
```
```yaml
description: Processes data
```
```yaml
description: Does stuff with files
```
### Progressive disclosure patterns
SKILL.md serves as an overview that points Claude to detailed materials as needed, like a table of contents in an onboarding guide. For an explanation of how progressive disclosure works, see [How Skills work](/docs/en/agents-and-tools/agent-skills/overview#how-skills-work) in the overview.
**Practical guidance:**
- Keep SKILL.md body under 500 lines for optimal performance
- Split content into separate files when approaching this limit
- Use the patterns below to organize instructions, code, and resources effectively
#### Visual overview: From simple to complex
A basic Skill starts with just a SKILL.md file containing metadata and instructions:

As your Skill grows, you can bundle additional content that Claude loads only when needed:

The complete Skill directory structure might look like this:
```text
pdf/
├── SKILL.md # Main instructions (loaded when triggered)
├── FORMS.md # Form-filling guide (loaded as needed)
├── reference.md # API reference (loaded as needed)
├── examples.md # Usage examples (loaded as needed)
└── scripts/
├── analyze_form.py # Utility script (executed, not loaded)
├── fill_form.py # Form filling script
└── validate.py # Validation script
```
#### Pattern 1: High-level guide with references
````markdown
---
name: pdf-processing
description: Extracts text and tables from PDF files, fills forms, and merges documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction.
---
# PDF Processing
## Quick start
Extract text with pdfplumber:
```python
import pdfplumber
with pdfplumber.open("file.pdf") as pdf:
text = pdf.pages[0].extract_text()
```
## Advanced features
**Form filling**: See [FORMS.md](FORMS.md) for complete guide
**API reference**: See [REFERENCE.md](REFERENCE.md) for all methods
**Examples**: See [EXAMPLES.md](EXAMPLES.md) for common patterns
````
Claude loads FORMS.md, REFERENCE.md, or EXAMPLES.md only when needed.
#### Pattern 2: Domain-specific organization
For Skills with multiple domains, organize content by domain to avoid loading irrelevant context. When a user asks about sales metrics, Claude only needs to read sales-related schemas, not finance or marketing data. This keeps token usage low and context focused.
```text
bigquery-skill/
├── SKILL.md (overview and navigation)
└── reference/
├── finance.md (revenue, billing metrics)
├── sales.md (opportunities, pipeline)
├── product.md (API usage, features)
└── marketing.md (campaigns, attribution)
```
````markdown SKILL.md
# BigQuery Data Analysis
## Available datasets
**Finance**: Revenue, ARR, billing → See [reference/finance.md](reference/finance.md)
**Sales**: Opportunities, pipeline, accounts → See [reference/sales.md](reference/sales.md)
**Product**: API usage, features, adoption → See [reference/product.md](reference/product.md)
**Marketing**: Campaigns, attribution, email → See [reference/marketing.md](reference/marketing.md)
## Quick search
Find specific metrics using grep:
```bash
grep -i "revenue" reference/finance.md
grep -i "pipeline" reference/sales.md
grep -i "api usage" reference/product.md
```
````
#### Pattern 3: Conditional details
Show basic content, link to advanced content:
```markdown
# DOCX Processing
## Creating documents
Use docx-js for new documents. See [DOCX-JS.md](DOCX-JS.md).
## Editing documents
For simple edits, modify the XML directly.
**For tracked changes**: See [REDLINING.md](REDLINING.md)
**For OOXML details**: See [OOXML.md](OOXML.md)
```
Claude reads REDLINING.md or OOXML.md only when the user needs those features.
### Avoid deeply nested references
Claude may partially read files when they're referenced from other referenced files. When encountering nested references, Claude might use commands like `head -100` to preview content rather than reading entire files, resulting in incomplete information.
**Keep references one level deep from SKILL.md**. All reference files should link directly from SKILL.md to ensure Claude reads complete files when needed.
**Bad example: Too deep**:
```markdown
# SKILL.md
See [advanced.md](advanced.md)...
# advanced.md
See [details.md](details.md)...
# details.md
Here's the actual information...
```
**Good example: One level deep**:
```markdown
# SKILL.md
**Basic usage**: [instructions in SKILL.md]
**Advanced features**: See [advanced.md](advanced.md)
**API reference**: See [reference.md](reference.md)
**Examples**: See [examples.md](examples.md)
```
### Structure longer reference files with table of contents
For reference files longer than 100 lines, include a table of contents at the top. This ensures Claude can see the full scope of available information even when previewing with partial reads.
**Example:**
```markdown
# API Reference
## Contents
- Authentication and setup
- Core methods (create, read, update, delete)
- Advanced features (batch operations, webhooks)
- Error handling patterns
- Code examples
## Authentication and setup
...
## Core methods
...
```
Claude can then read the complete file or jump to specific sections as needed.
For details on how this filesystem-based architecture enables progressive disclosure, see the [Runtime environment](#runtime-environment) section in the Advanced section below.
## Workflows and feedback loops
### Use workflows for complex tasks
Break complex operations into clear, sequential steps. For particularly complex workflows, provide a checklist that Claude can copy into its response and check off as it progresses.
**Example 1: Research synthesis workflow** (for Skills without code):
````markdown
## Research synthesis workflow
Copy this checklist and track your progress:
```
Research Progress:
- [ ] Step 1: Read all source documents
- [ ] Step 2: Identify key themes
- [ ] Step 3: Cross-reference claims
- [ ] Step 4: Create structured summary
- [ ] Step 5: Verify citations
```
**Step 1: Read all source documents**
Review each document in the `sources/` directory. Note the main arguments and supporting evidence.
**Step 2: Identify key themes**
Look for patterns across sources. What themes appear repeatedly? Where do sources agree or disagree?
**Step 3: Cross-reference claims**
For each major claim, verify it appears in the source material. Note which source supports each point.
**Step 4: Create structured summary**
Organize findings by theme. Include:
- Main claim
- Supporting evidence from sources
- Conflicting viewpoints (if any)
**Step 5: Verify citations**
Check that every claim references the correct source document. If citations are incomplete, return to Step 3.
````
This example shows how workflows apply to analysis tasks that don't require code. The checklist pattern works for any complex, multi-step process.
**Example 2: PDF form filling workflow** (for Skills with code):
````markdown
## PDF form filling workflow
Copy this checklist and check off items as you complete them:
```
Task Progress:
- [ ] Step 1: Analyze the form (run analyze_form.py)
- [ ] Step 2: Create field mapping (edit fields.json)
- [ ] Step 3: Validate mapping (run validate_fields.py)
- [ ] Step 4: Fill the form (run fill_form.py)
- [ ] Step 5: Verify output (run verify_output.py)
```
**Step 1: Analyze the form**
Run: `python scripts/analyze_form.py input.pdf`
This extracts form fields and their locations, saving to `fields.json`.
**Step 2: Create field mapping**
Edit `fields.json` to add values for each field.
**Step 3: Validate mapping**
Run: `python scripts/validate_fields.py fields.json`
Fix any validation errors before continuing.
**Step 4: Fill the form**
Run: `python scripts/fill_form.py input.pdf fields.json output.pdf`
**Step 5: Verify output**
Run: `python scripts/verify_output.py output.pdf`
If verification fails, return to Step 2.
````
Clear steps prevent Claude from skipping critical validation. The checklist helps both Claude and you track progress through multi-step workflows.
### Implement feedback loops
**Common pattern:** Run validator → fix errors → repeat
This pattern greatly improves output quality.
**Example 1: Style guide compliance** (for Skills without code):
```markdown
## Content review process
1. Draft your content following the guidelines in STYLE_GUIDE.md
2. Review against the checklist:
- Check terminology consistency
- Verify examples follow the standard format
- Confirm all required sections are present
3. If issues found:
- Note each issue with specific section reference
- Revise the content
- Review the checklist again
4. Only proceed when all requirements are met
5. Finalize and save the document
```
This shows the validation loop pattern using reference documents instead of scripts. The "validator" is STYLE_GUIDE.md, and Claude performs the check by reading and comparing.
**Example 2: Document editing process** (for Skills with code):
```markdown
## Document editing process
1. Make your edits to `word/document.xml`
2. **Validate immediately**: `python ooxml/scripts/validate.py unpacked_dir/`
3. If validation fails:
- Review the error message carefully
- Fix the issues in the XML
- Run validation again
4. **Only proceed when validation passes**
5. Rebuild: `python ooxml/scripts/pack.py unpacked_dir/ output.docx`
6. Test the output document
```
The validation loop catches errors early.
## Content guidelines
### Avoid time-sensitive information
Don't include information that will become outdated:
**Bad example: Time-sensitive** (will become wrong):
```markdown
If you're doing this before August 2025, use the old API.
After August 2025, use the new API.
```
**Good example** (use "old patterns" section):
```markdown
## Current method
Use the v2 API endpoint: `api.example.com/v2/messages`
## Old patterns
Legacy v1 API (deprecated 2025-08)
The v1 API used: `api.example.com/v1/messages`
This endpoint is no longer supported.
```
The old patterns section provides historical context without cluttering the main content.
### Use consistent terminology
Choose one term and use it throughout the Skill:
**Good - Consistent:**
- Always "API endpoint"
- Always "field"
- Always "extract"
**Bad - Inconsistent:**
- Mix "API endpoint", "URL", "API route", "path"
- Mix "field", "box", "element", "control"
- Mix "extract", "pull", "get", "retrieve"
Consistency helps Claude understand and follow instructions.
## Common patterns
### Template pattern
Provide templates for output format. Match the level of strictness to your needs.
**For strict requirements** (like API responses or data formats):
````markdown
## Report structure
ALWAYS use this exact template structure:
```markdown
# [Analysis Title]
## Executive summary
[One-paragraph overview of key findings]
## Key findings
- Finding 1 with supporting data
- Finding 2 with supporting data
- Finding 3 with supporting data
## Recommendations
1. Specific actionable recommendation
2. Specific actionable recommendation
```
````
**For flexible guidance** (when adaptation is useful):
````markdown
## Report structure
Here is a sensible default format, but use your best judgment based on the analysis:
```markdown
# [Analysis Title]
## Executive summary
[Overview]
## Key findings
[Adapt sections based on what you discover]
## Recommendations
[Tailor to the specific context]
```
Adjust sections as needed for the specific analysis type.
````
### Examples pattern
For Skills where output quality depends on seeing examples, provide input/output pairs just like in regular prompting:
````markdown
## Commit message format
Generate commit messages following these examples:
**Example 1:**
Input: Added user authentication with JWT tokens
Output:
```
feat(auth): implement JWT-based authentication
Add login endpoint and token validation middleware
```
**Example 2:**
Input: Fixed bug where dates displayed incorrectly in reports
Output:
```
fix(reports): correct date formatting in timezone conversion
Use UTC timestamps consistently across report generation
```
**Example 3:**
Input: Updated dependencies and refactored error handling
Output:
```
chore: update dependencies and refactor error handling
- Upgrade lodash to 4.17.21
- Standardize error response format across endpoints
```
Follow this style: type(scope): brief description, then detailed explanation.
````
Examples help Claude understand the desired style and level of detail more clearly than descriptions alone.
### Conditional workflow pattern
Guide Claude through decision points:
```markdown
## Document modification workflow
1. Determine the modification type:
**Creating new content?** → Follow "Creation workflow" below
**Editing existing content?** → Follow "Editing workflow" below
2. Creation workflow:
- Use docx-js library
- Build document from scratch
- Export to .docx format
3. Editing workflow:
- Unpack existing document
- Modify XML directly
- Validate after each change
- Repack when complete
```
If workflows become large or complicated with many steps, consider pushing them into separate files and tell Claude to read the appropriate file based on the task at hand.
## Evaluation and iteration
### Build evaluations first
**Create evaluations BEFORE writing extensive documentation.** This ensures your Skill solves real problems rather than documenting imagined ones.
**Evaluation-driven development:**
1. **Identify gaps:** Run Claude on representative tasks without a Skill. Document specific failures or missing context
2. **Create evaluations:** Build three scenarios that test these gaps
3. **Establish baseline:** Measure Claude's performance without the Skill
4. **Write minimal instructions:** Create just enough content to address the gaps and pass evaluations
5. **Iterate:** Execute evaluations, compare against baseline, and refine
This approach ensures you're solving actual problems rather than anticipating requirements that may never materialize.
**Evaluation structure:**
```json
{
"skills": ["pdf-processing"],
"query": "Extract all text from this PDF file and save it to output.txt",
"files": ["test-files/document.pdf"],
"expected_behavior": [
"Successfully reads the PDF file using an appropriate PDF processing library or command-line tool",
"Extracts text content from all pages in the document without missing any pages",
"Saves the extracted text to a file named output.txt in a clear, readable format"
]
}
```
This example demonstrates a data-driven evaluation with a simple testing rubric. There is not currently a built-in way to run these evaluations. Users can create their own evaluation system. Evaluations are your source of truth for measuring Skill effectiveness.
### Develop Skills iteratively with Claude
The most effective Skill development process involves Claude itself. Work with one instance of Claude ("Claude A") to create a Skill that is used by other instances ("Claude B"). Claude A helps you design and refine instructions, while Claude B tests them in real tasks. This works because Claude models understand both how to write effective agent instructions and what information agents need.
**Creating a new Skill:**
1. **Complete a task without a Skill:** Work through a problem with Claude A using normal prompting. As you work, you'll naturally provide context, explain preferences, and share procedural knowledge. Notice what information you repeatedly provide.
2. **Identify the reusable pattern:** After completing the task, identify what context you provided that would be useful for similar future tasks.
**Example:** If you worked through a BigQuery analysis, you might have provided table names, field definitions, filtering rules (like "always exclude test accounts"), and common query patterns.
3. **Ask Claude A to create a Skill:** "Create a Skill that captures this BigQuery analysis pattern we just used. Include the table schemas, naming conventions, and the rule about filtering test accounts."
Claude models understand the Skill format and structure natively. You don't need special system prompts or a "writing skills" skill to get Claude to help create Skills. Simply ask Claude to create a Skill and it generates properly structured SKILL.md content with appropriate frontmatter and body content.
4. **Review for conciseness:** Check that Claude A hasn't added unnecessary explanations. Ask: "Remove the explanation about what win rate means - Claude already knows that."
5. **Improve information architecture:** Ask Claude A to organize the content more effectively. For example: "Organize this so the table schema is in a separate reference file. We might add more tables later."
6. **Test on similar tasks:** Use the Skill with Claude B (a fresh instance with the Skill loaded) on related use cases. Observe whether Claude B finds the right information, applies rules correctly, and handles the task successfully.
7. **Iterate based on observation:** If Claude B struggles or misses something, return to Claude A with specifics: "When Claude used this Skill, it forgot to filter by date for Q4. Should we add a section about date filtering patterns?"
**Iterating on existing Skills:**
The same hierarchical pattern continues when improving Skills. You alternate between:
- **Working with Claude A** (the expert who helps refine the Skill)
- **Testing with Claude B** (the agent using the Skill to perform real work)
- **Observing Claude B's behavior** and bringing insights back to Claude A
1. **Use the Skill in real workflows:** Give Claude B (with the Skill loaded) actual tasks, not test scenarios
2. **Observe Claude B's behavior:** Note where it struggles, succeeds, or makes unexpected choices
**Example observation:** "When I asked Claude B for a regional sales report, it wrote the query but forgot to filter out test accounts, even though the Skill mentions this rule."
3. **Return to Claude A for improvements:** Share the current SKILL.md and describe what you observed. Ask: "I noticed Claude B forgot to filter test accounts when I asked for a regional report. The Skill mentions filtering, but maybe it's not prominent enough?"
4. **Review Claude A's suggestions:** Claude A might suggest reorganizing to make rules more prominent, using stronger language like "MUST filter" instead of "always filter", or restructuring the workflow section.
5. **Apply and test changes:** Update the Skill with Claude A's refinements, then test again with Claude B on similar requests
6. **Repeat based on usage:** Continue this observe-refine-test cycle as you encounter new scenarios. Each iteration improves the Skill based on real agent behavior, not assumptions.
**Gathering team feedback:**
1. Share Skills with teammates and observe their usage
2. Ask: Does the Skill activate when expected? Are instructions clear? What's missing?
3. Incorporate feedback to address blind spots in your own usage patterns
**Why this approach works:** Claude A understands agent needs, you provide domain expertise, Claude B reveals gaps through real usage, and iterative refinement improves Skills based on observed behavior rather than assumptions.
### Observe how Claude navigates Skills
As you iterate on Skills, pay attention to how Claude actually uses them in practice. Watch for:
- **Unexpected exploration paths:** Does Claude read files in an order you didn't anticipate? This might indicate your structure isn't as intuitive as you thought
- **Missed connections:** Does Claude fail to follow references to important files? Your links might need to be more explicit or prominent
- **Overreliance on certain sections:** If Claude repeatedly reads the same file, consider whether that content should be in the main SKILL.md instead
- **Ignored content:** If Claude never accesses a bundled file, it might be unnecessary or poorly signaled in the main instructions
Iterate based on these observations rather than assumptions. The 'name' and 'description' in your Skill's metadata are particularly critical. Claude uses these when deciding whether to trigger the Skill in response to the current task. Make sure they clearly describe what the Skill does and when it should be used.
## Anti-patterns to avoid
### Avoid Windows-style paths
Always use forward slashes in file paths, even on Windows:
- ✓ **Good:** `scripts/helper.py`, `reference/guide.md`
- ✗ **Avoid:** `scripts\helper.py`, `reference\guide.md`
Unix-style paths work across all platforms, while Windows-style paths cause errors on Unix systems.
### Avoid offering too many options
Don't present multiple approaches unless necessary:
````markdown
**Bad example: Too many choices** (confusing):
"You can use pypdf, or pdfplumber, or PyMuPDF, or pdf2image, or..."
**Good example: Provide a default** (with escape hatch):
"Use pdfplumber for text extraction:
```python
import pdfplumber
```
For scanned PDFs requiring OCR, use pdf2image with pytesseract instead."
````
## Advanced: Skills with executable code
The sections below focus on Skills that include executable scripts. If your Skill uses only markdown instructions, skip to [Checklist for effective Skills](#checklist-for-effective-skills).
### Solve, don't punt
When writing scripts for Skills, handle error conditions rather than punting to Claude.
**Good example: Handle errors explicitly:**
```python nocheck
def process_file(path):
"""Process a file, creating it if it doesn't exist."""
try:
with open(path) as f:
return f.read()
except FileNotFoundError:
# Create file with default content instead of failing
print(f"File {path} not found, creating default")
with open(path, "w") as f:
f.write("")
return ""
except PermissionError:
# Provide alternative instead of failing
print(f"Cannot access {path}, using default")
return ""
```
**Bad example: Punt to Claude:**
```python nocheck
def process_file(path):
# Just fail and let Claude figure it out
return open(path).read()
```
Configuration parameters should also be justified and documented to avoid "voodoo constants" (Ousterhout's law). If you don't know the right value, how will Claude determine it?
**Good example: Self-documenting:**
```python nocheck
# HTTP requests typically complete within 30 seconds
# Longer timeout accounts for slow connections
REQUEST_TIMEOUT = 30
# Three retries balances reliability vs speed
# Most intermittent failures resolve by the second retry
MAX_RETRIES = 3
```
**Bad example: Magic numbers:**
```python nocheck
TIMEOUT = 47 # Why 47?
RETRIES = 5 # Why 5?
```
### Provide utility scripts
Even if Claude could write a script, pre-made scripts offer advantages:
**Benefits of utility scripts:**
- More reliable than generated code
- Save tokens (no need to include code in context)
- Save time (no code generation required)
- Ensure consistency across uses

The diagram above shows how executable scripts work alongside instruction files. The instruction file (forms.md) references the script, and Claude can execute it without loading its contents into context.
**Important distinction:** Make clear in your instructions whether Claude should:
- **Execute the script** (most common): "Run `analyze_form.py` to extract fields"
- **Read it as reference** (for complex logic): "See `analyze_form.py` for the field extraction algorithm"
For most utility scripts, execution is preferred because it's more reliable and efficient. See the [Runtime environment](#runtime-environment) section below for details on how script execution works.
**Example:**
````markdown
## Utility scripts
**analyze_form.py**: Extract all form fields from PDF
```bash
python scripts/analyze_form.py input.pdf > fields.json
```
Output format:
```json
{
"field_name": {"type": "text", "x": 100, "y": 200},
"signature": {"type": "sig", "x": 150, "y": 500}
}
```
**validate_boxes.py**: Check for overlapping bounding boxes
```bash
python scripts/validate_boxes.py fields.json
# Returns: "OK" or lists conflicts
```
**fill_form.py**: Apply field values to PDF
```bash
python scripts/fill_form.py input.pdf fields.json output.pdf
```
````
### Use visual analysis
When inputs can be rendered as images, have Claude analyze them:
````markdown
## Form layout analysis
1. Convert PDF to images:
```bash
python scripts/pdf_to_images.py form.pdf
```
2. Analyze each page image to identify form fields
3. Claude can see field locations and types visually
````
In this example, you'd need to write the `pdf_to_images.py` script.
Claude's vision capabilities help understand layouts and structures.
### Create verifiable intermediate outputs
When Claude performs complex, open-ended tasks, it can make mistakes. The "plan-validate-execute" pattern catches errors early by having Claude first create a plan in a structured format, then validate that plan with a script before executing it.
**Example:** Imagine asking Claude to update 50 form fields in a PDF based on a spreadsheet. Without validation, Claude might reference non-existent fields, create conflicting values, miss required fields, or apply updates incorrectly.
**Solution:** Use the workflow pattern shown above (PDF form filling), but add an intermediate `changes.json` file that gets validated before applying changes. The workflow becomes: analyze → **create plan file** → **validate plan** → execute → verify.
**Why this pattern works:**
- **Catches errors early:** Validation finds problems before changes are applied
- **Machine-verifiable:** Scripts provide objective verification
- **Reversible planning:** Claude can iterate on the plan without touching originals
- **Clear debugging:** Error messages point to specific problems
**When to use:** Batch operations, destructive changes, complex validation rules, high-stakes operations.
**Implementation tip:** Make validation scripts verbose with specific error messages like "Field 'signature_date' not found. Available fields: customer_name, order_total, signature_date_signed" to help Claude fix issues.
### Package dependencies
Skills run in the code execution environment with platform-specific limitations:
- **claude.ai:** Can install packages from npm and PyPI and pull from GitHub repositories
- **Claude API:** Has no network access and no runtime package installation
List required packages in your SKILL.md and verify they're available in the [code execution tool documentation](/docs/en/agents-and-tools/tool-use/code-execution-tool).
### Runtime environment
Skills run in a code execution environment with filesystem access, bash commands, and code execution capabilities. For the conceptual explanation of this architecture, see [The Skills architecture](/docs/en/agents-and-tools/agent-skills/overview#the-skills-architecture) in the overview.
**How this affects your authoring:**
**How Claude accesses Skills:**
1. **Metadata pre-loaded:** At startup, the name and description from all Skills' YAML frontmatter are loaded into the system prompt
2. **Files read on-demand:** Claude uses bash Read tools to access SKILL.md and other files from the filesystem when needed
3. **Scripts executed efficiently:** Utility scripts can be executed via bash without loading their full contents into context. Only the script's output consumes tokens
4. **No context penalty for large files:** Reference files, data, or documentation don't consume context tokens until actually read
- **File paths matter:** Claude navigates your skill directory like a filesystem. Use forward slashes (`reference/guide.md`), not backslashes
- **Name files descriptively:** Use names that indicate content: `form_validation_rules.md`, not `doc2.md`
- **Organize for discovery:** Structure directories by domain or feature
- Good: `reference/finance.md`, `reference/sales.md`
- Bad: `docs/file1.md`, `docs/file2.md`
- **Bundle comprehensive resources:** Include complete API docs, extensive examples, large datasets; no context penalty until accessed
- **Prefer scripts for deterministic operations:** Write `validate_form.py` rather than asking Claude to generate validation code
- **Make execution intent clear:**
- "Run `analyze_form.py` to extract fields" (execute)
- "See `analyze_form.py` for the extraction algorithm" (read as reference)
- **Test file access patterns:** Verify Claude can navigate your directory structure by testing with real requests
**Example:**
```text
bigquery-skill/
├── SKILL.md (overview, points to reference files)
└── reference/
├── finance.md (revenue metrics)
├── sales.md (pipeline data)
└── product.md (usage analytics)
```
When the user asks about revenue, Claude reads SKILL.md, sees the reference to `reference/finance.md`, and invokes bash to read just that file. The sales.md and product.md files remain on the filesystem, consuming zero context tokens until needed. This filesystem-based model is what enables progressive disclosure. Claude can navigate and selectively load exactly what each task requires.
For complete details on the technical architecture, see [How Skills work](/docs/en/agents-and-tools/agent-skills/overview#how-skills-work) in the Skills overview.
### MCP tool references
If your Skill uses MCP (Model Context Protocol) tools, always use fully qualified tool names to avoid "tool not found" errors.
**Format:** `ServerName:tool_name`
**Example:**
```markdown
Use the BigQuery:bigquery_schema tool to retrieve table schemas.
Use the GitHub:create_issue tool to create issues.
```
Where:
- `BigQuery` and `GitHub` are MCP server names
- `bigquery_schema` and `create_issue` are the tool names within those servers
Without the server prefix, Claude may fail to locate the tool, especially when multiple MCP servers are available.
### Avoid assuming tools are installed
Don't assume packages are available:
````markdown
**Bad example: Assumes installation**:
"Use the pdf library to process the file."
**Good example: Explicit about dependencies**:
"Install required package: `pip install pypdf`
Then use it:
```python
from pypdf import PdfReader
reader = PdfReader("file.pdf")
```"
````
## Technical notes
### YAML frontmatter requirements
The SKILL.md frontmatter requires `name` and `description` fields with specific validation rules:
- `name`: Maximum 64 characters, lowercase letters/numbers/hyphens only, no XML tags, no reserved words
- `description`: Maximum 1024 characters, non-empty, no XML tags
See the [Skills overview](/docs/en/agents-and-tools/agent-skills/overview#skill-structure) for complete structure details.
### Token budgets
Keep SKILL.md body under 500 lines for optimal performance. If your content exceeds this, split it into separate files using the progressive disclosure patterns described earlier. For architectural details, see the [Skills overview](/docs/en/agents-and-tools/agent-skills/overview#how-skills-work).
## Checklist for effective Skills
Before sharing a Skill, verify:
### Core quality
- [ ] Description is specific and includes key terms
- [ ] Description includes both what the Skill does and when to use it
- [ ] SKILL.md body is under 500 lines
- [ ] Additional details are in separate files (if needed)
- [ ] No time-sensitive information (or in "old patterns" section)
- [ ] Consistent terminology throughout
- [ ] Examples are concrete, not abstract
- [ ] File references are one level deep
- [ ] Progressive disclosure used appropriately
- [ ] Workflows have clear steps
### Code and scripts
- [ ] Scripts solve problems rather than punt to Claude
- [ ] Error handling is explicit and helpful
- [ ] No "voodoo constants" (all values justified)
- [ ] Required packages listed in instructions and verified as available
- [ ] Scripts have clear documentation
- [ ] No Windows-style paths (all forward slashes)
- [ ] Validation/verification steps for critical operations
- [ ] Feedback loops included for quality-critical tasks
### Testing
- [ ] At least three evaluations created
- [ ] Tested with Haiku, Sonnet, and Opus
- [ ] Tested with real usage scenarios
- [ ] Team feedback incorporated (if applicable)
## Next steps
Create your first Skill
Create and manage Skills in Claude Code
Upload and use Skills programmatically
---
# Skills for enterprise
URL: https://platform.claude.com/docs/en/agents-and-tools/agent-skills/enterprise
# Skills for enterprise
Governance, security review, evaluation, and organizational guidance for deploying Agent Skills at enterprise scale.
---
This guide is for enterprise admins and architects who need to govern Agent Skills across an organization. It covers how to vet, evaluate, deploy, and manage Skills at scale. For authoring guidance, see [best practices](/docs/en/agents-and-tools/agent-skills/best-practices). For architecture details, see the [Skills overview](/docs/en/agents-and-tools/agent-skills/overview).
## Security review and vetting
Deploying Skills in an enterprise requires answering two distinct questions:
1. **Are Skills safe in general?** See the [security considerations](/docs/en/agents-and-tools/agent-skills/overview#security-considerations) section in the overview for platform-level security details.
2. **How do I vet a specific Skill?** Use the risk assessment and review checklist below.
### Risk tier assessment
Evaluate each Skill against these risk indicators before approving deployment:
| Risk indicator | What to look for | Concern level |
|---|---|---|
| Code execution | Scripts in the Skill directory (`*.py`, `*.sh`, `*.js`) | High: scripts run with full environment access |
| Instruction manipulation | Directives to ignore safety rules, hide actions from users, or alter Claude's behavior conditionally | High: can bypass security controls |
| MCP server references | Instructions referencing MCP tools (`ServerName:tool_name`) | High: extends access beyond the Skill itself |
| Network access patterns | URLs, API endpoints, `fetch`, `curl`, or `requests` calls | High: potential data exfiltration vector |
| Hardcoded credentials | API keys, tokens, or passwords in Skill files or scripts | High: secrets exposed in Git history and context window |
| File system access scope | Paths outside the Skill directory, broad glob patterns, path traversal (`../`) | Medium: may access unintended data |
| Tool invocations | Instructions directing Claude to use bash, file operations, or other tools | Medium: review what operations are performed |
### Review checklist
Before deploying any Skill from a third party or internal contributor, complete these steps:
1. **Read all Skill directory content.** Review SKILL.md, all referenced markdown files, and any bundled scripts or resources.
2. **Verify script behavior matches stated purpose.** Run scripts in a sandboxed environment and confirm outputs align with the Skill's description.
3. **Check for adversarial instructions.** Look for directives that tell Claude to ignore safety rules, hide actions from users, exfiltrate data through responses, or alter behavior based on specific inputs.
4. **Check for external URL fetches or network calls.** Search scripts and instructions for network access patterns (`http`, `requests.get`, `urllib`, `curl`, `fetch`).
5. **Verify no hardcoded credentials.** Check for API keys, tokens, or passwords in Skill files. Credentials should use environment variables or secure credential stores, never appear in Skill content.
6. **Identify tools and commands the Skill instructs Claude to invoke.** List all bash commands, file operations, and tool references. Consider the combined risk when a Skill uses both file-read and network tools together.
7. **Confirm redirect destinations.** If the Skill references external URLs, verify they point to expected domains.
8. **Verify no data exfiltration patterns.** Look for instructions that read sensitive data and then write, send, or encode it for external transmission, including through Claude's conversational responses.
Never deploy Skills from untrusted sources without a full audit. A malicious Skill can direct Claude to execute arbitrary code, access sensitive files, or transmit data externally. Treat Skill installation with the same rigor as installing software on production systems.
## Evaluating Skills before deployment
Skills can degrade agent performance if they trigger incorrectly, conflict with other Skills, or provide poor instructions. Require evaluation before any production deployment.
### What to evaluate
Establish approval gates for these dimensions before deploying any Skill:
| Dimension | What it measures | Example failure |
|---|---|---|
| Triggering accuracy | Does the Skill activate for the right queries and stay inactive for unrelated ones? | Skill triggers on every spreadsheet mention, even when the user just wants to discuss data |
| Isolation behavior | Does the Skill work correctly on its own? | Skill references files that don't exist in its directory |
| Coexistence | Does adding this Skill degrade other Skills? | New Skill's description is too broad, stealing triggers from existing Skills |
| Instruction following | Does Claude follow the Skill's instructions accurately? | Claude skips validation steps or uses wrong libraries |
| Output quality | Does the Skill produce correct, useful results? | Generated reports have formatting errors or missing data |
### Evaluation requirements
Require Skill authors to submit evaluation suites with 3-5 representative queries per Skill, covering cases where the Skill should trigger, should not trigger, and ambiguous edge cases. Require testing across the models your organization uses (Haiku, Sonnet, Opus), since Skill effectiveness varies by model.
For detailed guidance on building evaluations, see [evaluation and iteration](/docs/en/agents-and-tools/agent-skills/best-practices#evaluation-and-iteration) in best practices. For general evaluation methodology, see [develop test cases](/docs/en/test-and-evaluate/develop-tests).
### Using evaluations for lifecycle decisions
Evaluation results signal when to act:
- **Declining trigger accuracy:** Update the Skill's description or instructions
- **Coexistence conflicts:** Consolidate overlapping Skills or narrow descriptions
- **Consistently low output quality:** Rewrite instructions or add validation steps
- **Persistent failures across updates:** Deprecate the Skill
## Skill lifecycle management
Identify workflows that are repetitive, error-prone, or require specialized knowledge. Map these to organizational roles and determine which are candidates for Skills.
Ensure the Skill author follows [best practices](/docs/en/agents-and-tools/agent-skills/best-practices). Require a security review using the [review checklist](#review-checklist) above. Require an evaluation suite before approval. Establish separation of duties: Skill authors should not be their own reviewers.
Require evaluations in isolation (Skill alone) and alongside existing Skills (coexistence testing). Verify triggering accuracy, output quality, and absence of regressions across your active Skill set before approving for production.
Upload via the Skills API for workspace-wide access. See [Using Skills with the API](/docs/en/build-with-claude/skills-guide) for upload and version management. Document the Skill in your internal registry with purpose, owner, and version.
Track usage patterns and collect feedback from users. Re-run evaluations periodically to detect drift or regressions as workflows and models evolve. Usage analytics are not currently available via the Skills API. Implement application-level logging to track which Skills are included in requests.
Require the full evaluation suite to pass before promoting new versions. Update Skills when workflows change or evaluation scores decline. Deprecate Skills when evaluations consistently fail or the workflow is retired.
## Organizing Skills at scale
### Recall limits
As a general guideline, limit the number of Skills loaded simultaneously to maintain reliable recall accuracy. Each Skill's metadata (name and description) competes for attention in the system prompt. With too many Skills active, Claude may fail to select the right Skill or miss relevant ones entirely. Use your evaluation suite to measure recall accuracy as you add Skills, and stop adding when performance degrades.
Note that API requests support a maximum of 8 Skills per request (see [Using Skills with the API](/docs/en/build-with-claude/skills-guide)). If a role requires more Skills than a single request supports, consider consolidating narrow Skills into broader ones or routing requests to different Skill sets based on task type.
### Start specific, consolidate later
Encourage teams to start with narrow, workflow-specific Skills rather than broad, multi-purpose ones. As patterns emerge across your organization, consolidate related Skills into role-based bundles.
Use evaluations to decide when to consolidate. Merge narrow Skills into a broader one only when the consolidated Skill's evaluations confirm equivalent performance to the individual Skills it replaces.
**Example progression**:
- Start: `formatting-sales-reports`, `querying-pipeline-data`, `updating-crm-records`
- Consolidate: `sales-operations` (when evals confirm equivalent performance)
### Naming and cataloging
Use consistent naming conventions across your organization. The [naming conventions](/docs/en/agents-and-tools/agent-skills/best-practices#naming-conventions) section in best practices provides formatting guidance.
Maintain an internal registry for each Skill with:
- **Purpose**: What workflow the Skill supports
- **Owner**: Team or individual responsible for maintenance
- **Version**: Current deployed version
- **Dependencies**: MCP servers, packages, or external services required
- **Evaluation status**: Last evaluation date and results
### Role-based bundles
Group Skills by organizational role to keep each user's active Skill set focused:
- **Sales team**: CRM operations, pipeline reporting, proposal generation
- **Engineering**: Code review, deployment workflows, incident response
- **Finance**: Report generation, data validation, audit preparation
Each role-based bundle should contain only the Skills relevant to that role's daily workflows.
## Distribution and version control
### Source control
Store Skill directories in Git for history tracking, code review via pull requests, and rollback capability. Each Skill directory (containing SKILL.md and any bundled files) maps naturally to a Git-tracked folder.
### API-based distribution
The Skills API provides workspace-scoped distribution. Skills uploaded via the API are available to all workspace members. See [Using Skills with the API](/docs/en/build-with-claude/skills-guide) for upload, versioning, and management endpoints.
### Versioning strategy
- **Production**: Pin Skills to specific versions. Run the full evaluation suite before promoting a new version. Treat every update as a new deployment requiring full security review.
- **Development and testing**: Use latest versions to validate changes before production promotion.
- **Rollback plan**: Maintain the previous version as a fallback. If a new version fails evaluations in production, revert to the last known-good version immediately.
- **Integrity verification**: Compute checksums of reviewed Skills and verify them at deployment time. Use signed commits in your Skill repository to ensure provenance.
### Cross-surface considerations
Custom Skills do not sync across surfaces. Skills uploaded to the API are not available on claude.ai or in Claude Code, and vice versa. Each surface requires separate uploads and management.
Maintain Skill source files in Git as the single source of truth. If your organization deploys Skills across multiple surfaces, implement your own synchronization process to keep them consistent. For full details, see [cross-surface availability](/docs/en/agents-and-tools/agent-skills/overview#cross-surface-availability).
## Next steps
Architecture and platform details
Authoring guidance for Skill creators
Upload and manage Skills programmatically
---
# Using Agent Skills with the API
URL: https://platform.claude.com/docs/en/build-with-claude/skills-guide
# Using Agent Skills with the API
Learn how to use Agent Skills to extend Claude's capabilities through the API.
---
Agent Skills extend Claude's capabilities through organized folders of instructions, scripts, and resources. This guide shows you how to use both pre-built and custom Skills with the Claude API.
For complete API reference including request/response schemas and all parameters, see:
- [Skill Management API Reference](/docs/en/api/skills/list-skills) - CRUD operations for Skills
- [Skill Versions API Reference](/docs/en/api/skills/list-skill-versions) - Version management
This feature is **not** eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). Data is retained according to the feature's standard retention policy.
## Quick links
Create your first Skill
Best practices for authoring Skills
## Overview
For a deep dive into the architecture and real-world applications of Agent Skills, read the engineering blog post: [Equipping agents for the real world with Agent Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills).
Skills integrate with the Messages API through the [code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool). Whether using pre-built Skills managed by Anthropic or custom Skills you've uploaded, the integration shape is identical: both require code execution and use the same `container` structure.
### Using Skills
Skills integrate identically in the Messages API regardless of source. You specify Skills in the `container` parameter with a `skill_id`, `type`, and optional `version`, and they execute in the code execution environment.
**You can use Skills from two sources:**
| Aspect | Anthropic Skills | Custom Skills |
|--------|------------------|---------------|
| **Type value** | `anthropic` | `custom` |
| **Skill IDs** | Short names: `pptx`, `xlsx`, `docx`, `pdf` | Generated: `skill_01AbCdEfGhIjKlMnOpQrStUv` |
| **Version format** | Date-based: `20251013` or `latest` | Epoch timestamp: `1759178010641129` or `latest` |
| **Management** | Pre-built and maintained by Anthropic | Upload and manage through the [Skills API](/docs/en/api/skills/create-skill) |
| **Availability** | Available to all users | Private to your workspace |
Both skill sources are returned by the [List Skills endpoint](/docs/en/api/skills/list-skills) (use the `source` parameter to filter). The integration shape and execution environment are identical. The only difference is where the Skills come from and how they're managed.
### Prerequisites
To use Skills, you need:
1. **Claude API key** from the [Console](/settings/keys)
2. **Beta headers:**
- `code-execution-2025-08-25` - Enables code execution (required for Skills)
- `skills-2025-10-02` - Enables Skills API
- `files-api-2025-04-14` - For uploading/downloading files to/from container
3. **[Code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool)** enabled in your requests
---
## Using Skills in Messages
### Container parameter
Skills are specified using the `container` parameter in the Messages API. You can include up to 8 Skills per request.
The structure is identical for both Anthropic and custom Skills. Specify the required `type` and `skill_id`, and optionally include `version` to pin to a specific version:
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"container": {
"skills": [
{
"type": "anthropic",
"skill_id": "pptx",
"version": "latest"
}
]
},
"messages": [{
"role": "user",
"content": "Create a presentation about renewable energy"
}],
"tools": [{
"type": "code_execution_20250825",
"name": "code_execution"
}]
}'
```
```bash CLI
ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- type: anthropic
skill_id: pptx
version: latest
messages:
- role: user
content: Create a presentation about renewable energy
tools:
- type: code_execution_20250825
name: code_execution
YAML
```
```python Python nocheck hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
betas=["code-execution-2025-08-25", "skills-2025-10-02"],
container={
"skills": [{"type": "anthropic", "skill_id": "pptx", "version": "latest"}]
},
messages=[
{"role": "user", "content": "Create a presentation about renewable energy"}
],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [
{
type: "anthropic",
skill_id: "pptx",
version: "latest"
}
]
},
messages: [
{
role: "user",
content: "Create a presentation about renewable energy"
}
],
tools: [
{
type: "code_execution_20250825",
name: "code_execution"
}
]
});
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
public class Program
{
public static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Betas = new[] { "code-execution-2025-08-25", "skills-2025-10-02" },
Container = new BetaContainerParams
{
Skills = new[]
{
new BetaAnthropicSkillParams
{
Type = "anthropic",
SkillId = "pptx",
Version = "latest"
}
}
},
Messages = new[]
{
new BetaMessageParam
{
Role = Role.User,
Content = "Create a presentation about renewable energy"
}
},
Tools = new[]
{
new BetaCodeExecutionToolParams
{
Type = "code_execution_20250825",
Name = "code_execution"
}
}
};
var message = await client.Beta.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 4096,
Betas: []anthropic.AnthropicBeta{
"code-execution-2025-08-25",
anthropic.AnthropicBetaSkills2025_10_02,
},
Container: anthropic.BetaMessageNewParamsContainerUnion{
OfContainers: &anthropic.BetaContainerParams{
Skills: []anthropic.BetaSkillParams{
{
Type: anthropic.BetaSkillParamsTypeAnthropic,
SkillID: "pptx",
Version: anthropic.String("latest"),
},
},
},
},
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Create a presentation about renewable energy")),
},
Tools: []anthropic.BetaToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.BetaCodeExecutionTool20250825Param{}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java hidelines={1..4,8..10,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaContainerParams;
import com.anthropic.models.beta.messages.BetaSkillParams;
import com.anthropic.models.beta.messages.BetaCodeExecutionTool20250825;
public class Main {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("code-execution-2025-08-25")
.addBeta("skills-2025-10-02")
.container(BetaContainerParams.builder()
.addSkill(BetaSkillParams.builder()
.type(BetaSkillParams.Type.ANTHROPIC)
.skillId("pptx")
.version("latest")
.build())
.build())
.addUserMessage("Create a presentation about renewable energy")
.addTool(BetaCodeExecutionTool20250825.builder().build())
.build();
BetaMessage response = client.beta().messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4}
beta->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Create a presentation about renewable energy']
],
model: 'claude-opus-4-7',
betas: ['code-execution-2025-08-25', 'skills-2025-10-02'],
container: [
'skills' => [
[
'type' => 'anthropic',
'skill_id' => 'pptx',
'version' => 'latest'
]
]
],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
]
);
echo $message;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [
{
type: "anthropic",
skill_id: "pptx",
version: "latest"
}
]
},
messages: [
{ role: "user", content: "Create a presentation about renewable energy" }
],
tools: [
{ type: "code_execution_20250825", name: "code_execution" }
]
)
puts message
```
### Downloading generated files
When Skills create documents (Excel, PowerPoint, PDF, Word), they return `file_id` attributes in the response. You must use the Files API to download these files.
**How it works:**
1. Skills create files during code execution
2. Response includes `file_id` for each created file
3. Use Files API to download the actual file content
4. Save locally or process as needed
**Example: Creating and downloading an Excel file**
```bash cURL hidelines={1}
cd "$(mktemp -d)"
# Step 1: Use a Skill to create a file
RESPONSE=$(curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"container": {
"skills": [
{"type": "anthropic", "skill_id": "xlsx", "version": "latest"}
]
},
"messages": [{
"role": "user",
"content": "Create an Excel file with a simple budget spreadsheet"
}],
"tools": [{
"type": "code_execution_20250825",
"name": "code_execution"
}]
}')
# Step 2: Extract file_id from response (using jq)
FILE_ID=$(echo "$RESPONSE" | jq -r '.content[] | select(.type=="bash_code_execution_tool_result") | .content | select(.type=="bash_code_execution_result") | .content[] | select(.file_id) | .file_id')
# Step 3: Get filename from metadata
FILENAME=$(curl "https://api.anthropic.com/v1/files/$FILE_ID" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14" | jq -r '.filename')
# Step 4: Download the file using Files API
curl "https://api.anthropic.com/v1/files/$FILE_ID/content" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14" \
--output "$FILENAME"
echo "Downloaded: $FILENAME"
```
```bash CLI nocheck hidelines={1}
cd "$(mktemp -d)"
# Step 1: Use the xlsx Skill to create a file
# Step 2: Extract file_id from the response with --transform (GJSON path)
FILE_ID=$(ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 \
--transform 'content.#.content.content.#.file_id|@flatten|0' \
--raw-output <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- type: anthropic
skill_id: xlsx
version: latest
messages:
- role: user
content: Create an Excel file with a simple budget spreadsheet
tools:
- type: code_execution_20250825
name: code_execution
YAML
)
# Step 3: Get the filename from file metadata
FILENAME=$(ant beta:files retrieve-metadata \
--file-id "$FILE_ID" \
--transform filename --raw-output)
# Step 4: Download the file using Files API
ant beta:files download \
--file-id "$FILE_ID" \
--output "$FILENAME" > /dev/null
printf 'Downloaded: %s\n' "$FILENAME"
```
```python Python nocheck hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
# Step 1: Use a Skill to create a file
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
betas=["code-execution-2025-08-25", "skills-2025-10-02"],
container={
"skills": [{"type": "anthropic", "skill_id": "xlsx", "version": "latest"}]
},
messages=[
{
"role": "user",
"content": "Create an Excel file with a simple budget spreadsheet",
}
],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
# Step 2: Extract file IDs from the response
def extract_file_ids(response):
file_ids = []
for item in response.content:
if item.type == "bash_code_execution_tool_result":
content_item = item.content
if content_item.type == "bash_code_execution_result":
# concrete-typed list: List[BashCodeExecutionOutputBlock]
for file in content_item.content:
file_ids.append(file.file_id)
return file_ids
# Step 3: Download the file using Files API
for file_id in extract_file_ids(response):
file_metadata = client.beta.files.retrieve_metadata(file_id=file_id)
file_content = client.beta.files.download(file_id=file_id)
# Step 4: Save to disk
file_content.write_to_file(file_metadata.filename)
print(f"Downloaded: {file_metadata.filename}")
```
```typescript TypeScript hidelines={1..3}
import Anthropic from "@anthropic-ai/sdk";
import fs from "node:fs/promises";
const client = new Anthropic();
// Step 1: Use a Skill to create a file
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [{ type: "anthropic", skill_id: "xlsx", version: "latest" }]
},
messages: [
{
role: "user",
content: "Create an Excel file with a simple budget spreadsheet"
}
],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
});
// Step 2: Extract file IDs from the response
function extractFileIds(response: any): string[] {
const fileIds: string[] = [];
for (const item of response.content) {
if (item.type === "bash_code_execution_tool_result") {
const contentItem = item.content;
if (contentItem.type === "bash_code_execution_result") {
for (const file of contentItem.content) {
if ("file_id" in file) {
fileIds.push(file.file_id);
}
}
}
}
}
return fileIds;
}
// Step 3: Download the file using Files API
for (const fileId of extractFileIds(response)) {
const fileMetadata = await client.beta.files.retrieveMetadata(fileId);
const fileContent = await client.beta.files.download(fileId);
// Step 4: Save to disk
await fs.writeFile(fileMetadata.filename, Buffer.from(await fileContent.arrayBuffer()));
console.log(`Downloaded: ${fileMetadata.filename}`);
}
```
```csharp C# nocheck
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using Anthropic;
using Anthropic.Models.Beta.Messages;
using Anthropic.Models.Beta.Files;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
// Step 1: Use a Skill to create a file
var parameters = new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 4096,
Betas = new[] { "code-execution-2025-08-25", "skills-2025-10-02" },
Container = new BetaContainer
{
Skills = new[]
{
new BetaSkill
{
Type = "anthropic",
SkillId = "xlsx",
Version = "latest"
}
}
},
Messages = new[]
{
new BetaMessage
{
Role = Role.User,
Content = "Create an Excel file with a simple budget spreadsheet"
}
},
Tools = new[]
{
new BetaTool
{
Type = "code_execution_20250825",
Name = "code_execution"
}
}
};
var response = await client.Beta.Messages.Create(parameters);
// Step 2: Extract file IDs from the response
var fileIds = ExtractFileIds(response);
// Step 3: Download the file using Files API
foreach (var fileId in fileIds)
{
var fileMetadata = await client.Beta.Files.RetrieveMetadata(fileId);
var fileContent = await client.Beta.Files.Download(fileId);
// Step 4: Save to disk
await File.WriteAllBytesAsync(fileMetadata.Filename, fileContent);
Console.WriteLine($"Downloaded: {fileMetadata.Filename}");
}
}
static List ExtractFileIds(BetaMessage response)
{
var fileIds = new List();
foreach (var item in response.Content)
{
if (item is BetaBashCodeExecutionToolResult toolResult)
{
if (toolResult.Content is BetaBashCodeExecutionResult result)
{
foreach (var content in result.Content)
{
if (content is BetaBashCodeExecutionResultFile file)
{
fileIds.Add(file.FileId);
}
}
}
}
}
return fileIds;
}
}
```
```go Go hidelines={1..15,68..69}
package main
import (
"context"
"fmt"
"io"
"log"
"os"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
// Step 1: Use a Skill to create a file
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 4096,
Betas: []anthropic.AnthropicBeta{"code-execution-2025-08-25", anthropic.AnthropicBetaSkills2025_10_02},
Container: anthropic.BetaMessageNewParamsContainerUnion{
OfContainers: &anthropic.BetaContainerParams{
Skills: []anthropic.BetaSkillParams{
{
Type: anthropic.BetaSkillParamsTypeAnthropic,
SkillID: "xlsx",
Version: anthropic.String("latest"),
},
},
},
},
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Create an Excel file with a simple budget spreadsheet")),
},
Tools: []anthropic.BetaToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.BetaCodeExecutionTool20250825Param{}},
},
})
if err != nil {
log.Fatal(err)
}
// Step 2: Extract file IDs from the response
fileIDs := extractFileIDs(response)
// Step 3: Download the file using Files API
for _, fileID := range fileIDs {
fileMetadata, err := client.Beta.Files.GetMetadata(context.TODO(), fileID, anthropic.BetaFileGetMetadataParams{})
if err != nil {
log.Fatal(err)
}
fileContent, err := client.Beta.Files.Download(context.TODO(), fileID, anthropic.BetaFileDownloadParams{})
if err != nil {
log.Fatal(err)
}
// Step 4: Save to disk
out, err := os.Create(fileMetadata.Filename)
if err != nil {
log.Fatal(err)
}
io.Copy(out, fileContent.Body)
out.Close()
fileContent.Body.Close()
fmt.Printf("Downloaded: %s\n", fileMetadata.Filename)
}
}
func extractFileIDs(response *anthropic.BetaMessage) []string {
var fileIDs []string
for _, item := range response.Content {
switch v := item.AsAny().(type) {
case anthropic.BetaBashCodeExecutionToolResultBlock:
for _, output := range v.Content.Content {
if output.FileID != "" {
fileIDs = append(fileIDs, output.FileID)
}
}
}
}
return fileIDs
}
```
```java Java nocheck hidelines={1..4,8,10..17,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaContainerParams;
import com.anthropic.models.beta.messages.BetaSkillParams;
import com.anthropic.models.beta.messages.BetaCodeExecutionTool20250825;
import com.anthropic.models.beta.messages.BetaContentBlock;
import com.anthropic.models.beta.files.FileMetadata;
import com.anthropic.core.http.HttpResponse;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class SkillsFileDownload {
public static void main(String[] args) throws Exception {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Step 1: Use a Skill to create a file
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("code-execution-2025-08-25")
.addBeta("skills-2025-10-02")
.container(BetaContainerParams.builder()
.addSkill(BetaSkillParams.builder()
.type(BetaSkillParams.Type.ANTHROPIC)
.skillId("xlsx")
.version("latest")
.build())
.build())
.addUserMessage("Create an Excel file with a simple budget spreadsheet")
.addTool(BetaCodeExecutionTool20250825.builder().build())
.build();
BetaMessage response = client.beta().messages().create(params);
// Step 2: Extract file IDs from the response
List fileIds = new ArrayList<>();
for (BetaContentBlock block : response.content()) {
if (block.isCodeExecutionToolResult()) {
var toolResult = block.asCodeExecutionToolResult();
for (var content : toolResult.content()) {
content.file().ifPresent(file -> fileIds.add(file.fileId()));
}
}
}
// Step 3: Download the file using Files API
for (String fileId : fileIds) {
FileMetadata fileMetadata = client.beta().files().retrieveMetadata(fileId);
HttpResponse fileContent = client.beta().files().download(fileId);
// Step 4: Save to disk
try (InputStream is = fileContent.body();
FileOutputStream fos = new FileOutputStream(fileMetadata.filename())) {
is.transferTo(fos);
}
System.out.println("Downloaded: " + fileMetadata.filename());
}
}
}
```
```php PHP nocheck hidelines={1..4}
beta->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Create an Excel file with a simple budget spreadsheet']
],
model: 'claude-opus-4-7',
betas: ['code-execution-2025-08-25', 'skills-2025-10-02'],
container: [
'skills' => [
['type' => 'anthropic', 'skill_id' => 'xlsx', 'version' => 'latest']
]
],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
]
);
// Step 2: Extract file IDs from the response
function extractFileIds($response) {
$fileIds = [];
foreach ($response->content as $item) {
if ($item->type === 'bash_code_execution_tool_result') {
$contentItem = $item->content;
if ($contentItem->type === 'bash_code_execution_result') {
foreach ($contentItem->content as $file) {
if (isset($file->fileID)) {
$fileIds[] = $file->fileID;
}
}
}
}
}
return $fileIds;
}
// Step 3: Download the file using Files API
foreach (extractFileIds($response) as $fileId) {
$fileMetadata = $client->beta->files->retrieveMetadata($fileId);
$fileContent = $client->beta->files->download($fileId);
// Step 4: Save to disk
file_put_contents($fileMetadata->filename, $fileContent);
echo "Downloaded: {$fileMetadata->filename}\n";
}
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
# Step 1: Use a Skill to create a file
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [{ type: "anthropic", skill_id: "xlsx", version: "latest" }]
},
messages: [
{
role: "user",
content: "Create an Excel file with a simple budget spreadsheet"
}
],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
)
# Step 2: Extract file IDs from the response
def extract_file_ids(response)
file_ids = []
response.content.each do |item|
if item.type == :bash_code_execution_tool_result
content_item = item.content
if content_item.type == :bash_code_execution_result
content_item.content.each do |file|
file_ids << file.file_id if file.respond_to?(:file_id)
end
end
end
end
file_ids
end
# Step 3: Download the file using Files API
extract_file_ids(response).each do |file_id|
file_metadata = client.beta.files.retrieve_metadata(file_id)
file_content = client.beta.files.download(file_id)
# Step 4: Save to disk
File.binwrite(file_metadata.filename, file_content.read)
puts "Downloaded: #{file_metadata.filename}"
end
```
**Additional Files API operations:**
```bash cURL
# Get file metadata
curl "https://api.anthropic.com/v1/files/$FILE_ID" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14"
# List all files
curl "https://api.anthropic.com/v1/files" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14"
# Delete a file
curl -X DELETE "https://api.anthropic.com/v1/files/$FILE_ID" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: files-api-2025-04-14"
```
```bash CLI nocheck
# Get file metadata
ant beta:files retrieve-metadata --file-id "$FILE_ID" \
--transform '{filename,size_bytes}' --format yaml \
| { read -r _ name; read -r _ size
printf 'Filename: %s, Size: %s bytes\n' "$name" "$size"; }
# List all files
ant beta:files list \
--transform '{filename,created_at}' --format yaml \
| while read -r _ name && read -r _ date; do
printf '%s - %s\n' "$name" "${date//\"/}"
done
# Delete a file
ant beta:files delete --file-id "$FILE_ID" >/dev/null
```
```python Python nocheck hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
file_id = "file_abc123"
# Get file metadata
file_info = client.beta.files.retrieve_metadata(file_id=file_id)
print(f"Filename: {file_info.filename}, Size: {file_info.size_bytes} bytes")
# List all files
files = client.beta.files.list()
for file in files.data:
print(f"{file.filename} - {file.created_at}")
# Delete a file
client.beta.files.delete(file_id=file_id)
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const fileId = "file_011CNha8iCJcU1wXNR6q4V8w";
// Get file metadata
const fileInfo = await client.beta.files.retrieveMetadata(fileId);
console.log(`Filename: ${fileInfo.filename}, Size: ${fileInfo.size_bytes} bytes`);
// List all files
const files = await client.beta.files.list();
for (const file of files.data) {
console.log(`${file.filename} - ${file.created_at}`);
}
// Delete a file
await client.beta.files.delete(fileId);
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Files;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
string fileId = "file_abc123";
// Get file metadata
var fileInfo = await client.Beta.Files.RetrieveMetadata(fileId);
Console.WriteLine($"Filename: {fileInfo.Filename}, Size: {fileInfo.SizeBytes} bytes");
// List all files
var files = await client.Beta.Files.List();
foreach (var file in files.Data)
{
Console.WriteLine($"{file.Filename} - {file.CreatedAt}");
}
// Delete a file
await client.Beta.Files.Delete(fileId);
}
}
```
```go Go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
fileID := "file_abc123"
// Get file metadata
fileInfo, err := client.Beta.Files.GetMetadata(context.TODO(), fileID, anthropic.BetaFileGetMetadataParams{})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Filename: %s, Size: %d bytes\n", fileInfo.Filename, fileInfo.SizeBytes)
// List all files
files := client.Beta.Files.ListAutoPaging(context.TODO(), anthropic.BetaFileListParams{})
for files.Next() {
file := files.Current()
fmt.Printf("%s - %s\n", file.Filename, file.CreatedAt)
}
// Delete a file
_, err = client.Beta.Files.Delete(context.TODO(), fileID, anthropic.BetaFileDeleteParams{})
if err != nil {
log.Fatal(err)
}
}
```
```java Java nocheck hidelines={1..2,5..7,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.files.FileMetadata;
import com.anthropic.models.beta.files.FileListPage;
public class FileManagement {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
String fileId = "file_abc123";
// Get file metadata
FileMetadata fileInfo = client.beta().files().retrieveMetadata(fileId);
System.out.println("Filename: " + fileInfo.filename() + ", Size: " + fileInfo.sizeBytes() + " bytes");
// List all files
FileListPage files = client.beta().files().list();
for (var file : files.data()) {
System.out.println(file.filename() + " - " + file.createdAt());
}
// Delete a file
client.beta().files().delete(fileId);
}
}
```
```php PHP hidelines={1..4} nocheck
beta->files->retrieveMetadata($fileId);
echo "Filename: {$fileInfo->filename}, Size: {$fileInfo->sizeBytes} bytes\n";
// List all files
$files = $client->beta->files->list();
foreach ($files->data as $file) {
echo "{$file->filename} - {$file->createdAt}\n";
}
// Delete a file
$client->beta->files->delete($fileId);
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
file_id = "file_abc123"
# Get file metadata
file_info = client.beta.files.retrieve_metadata(file_id)
puts "Filename: #{file_info.filename}, Size: #{file_info.size_bytes} bytes"
# List all files
files = client.beta.files.list
files.data.each do |file|
puts "#{file.filename} - #{file.created_at}"
end
# Delete a file
client.beta.files.delete(file_id)
```
For complete details on the Files API, see the [Files API documentation](/docs/en/api/files-content).
### Multi-turn conversations
Reuse the same container across multiple messages by specifying the container ID:
```bash CLI
# First request creates container
CONTAINER_ID=$(ant beta:messages create \
--beta code-execution-2025-08-25 --beta skills-2025-10-02 \
--transform container.id --raw-output <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- {type: anthropic, skill_id: xlsx, version: latest}
messages:
- role: user
content: Analyze this sales data
tools:
- {type: code_execution_20250825, name: code_execution}
YAML
)
# Continue conversation with same container
ant beta:messages create \
--beta code-execution-2025-08-25 --beta skills-2025-10-02 <beta->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Analyze this sales data']
],
model: 'claude-opus-4-7',
betas: ['code-execution-2025-08-25', 'skills-2025-10-02'],
container: [
'skills' => [
['type' => 'anthropic', 'skill_id' => 'xlsx', 'version' => 'latest']
]
],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
]
);
$messages = [
['role' => 'user', 'content' => 'Analyze this sales data'],
['role' => 'assistant', 'content' => $response1->content],
['role' => 'user', 'content' => 'What was the total revenue?']
];
$response2 = $client->beta->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
betas: ['code-execution-2025-08-25', 'skills-2025-10-02'],
container: [
'id' => $response1->container->id,
'skills' => [
['type' => 'anthropic', 'skill_id' => 'xlsx', 'version' => 'latest']
]
],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
]
);
echo $response2;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response1 = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [{ type: "anthropic", skill_id: "xlsx", version: "latest" }]
},
messages: [
{ role: "user", content: "Analyze this sales data" }
],
tools: [
{ type: "code_execution_20250825", name: "code_execution" }
]
)
messages = [
{ role: "user", content: "Analyze this sales data" },
{ role: "assistant", content: response1.content },
{ role: "user", content: "What was the total revenue?" }
]
response2 = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
id: response1.container.id,
skills: [
{ type: "anthropic", skill_id: "xlsx", version: "latest" }
]
},
messages: messages,
tools: [
{ type: "code_execution_20250825", name: "code_execution" }
]
)
puts response2
```
### Long-running operations
Skills may perform operations that require multiple turns. Handle `pause_turn` stop reasons:
```bash cURL nocheck
# Initial request
RESPONSE=$(curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"container": {
"skills": [
{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "latest"
}
]
},
"messages": [{
"role": "user",
"content": "Process this large dataset"
}],
"tools": [{
"type": "code_execution_20250825",
"name": "code_execution"
}]
}')
# Check stop_reason and handle pause_turn in a loop
STOP_REASON=$(echo "$RESPONSE" | jq -r '.stop_reason')
CONTAINER_ID=$(echo "$RESPONSE" | jq -r '.container.id')
while [ "$STOP_REASON" = "pause_turn" ]; do
# Continue with same container
RESPONSE=$(curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02" \
-H "content-type: application/json" \
-d "{
\"model\": \"claude-opus-4-7\",
\"max_tokens\": 4096,
\"container\": {
\"id\": \"$CONTAINER_ID\",
\"skills\": [{
\"type\": \"custom\",
\"skill_id\": \"skill_01AbCdEfGhIjKlMnOpQrStUv\",
\"version\": \"latest\"
}]
},
\"messages\": [/* include conversation history */],
\"tools\": [{
\"type\": \"code_execution_20250825\",
\"name\": \"code_execution\"
}]
}")
STOP_REASON=$(echo "$RESPONSE" | jq -r '.stop_reason')
done
```
```bash CLI nocheck
RESP=$(mktemp)
# Initial request: capture the full JSON response to a temp file
ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 \
> "$RESP" <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- type: custom
skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
version: latest
messages:
- role: user
content: Process this large dataset
tools:
- type: code_execution_20250825
name: code_execution
YAML
# Handle pause_turn for long operations (up to 10 iterations)
for _ in {1..10}; do
[[ $(jq -r '.stop_reason' "$RESP") == pause_turn ]] || break
CONTAINER_ID=$(jq -r '.container.id' "$RESP")
# Continue in the same container, appending the prior response's
# content array to messages as the assistant turn.
ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 \
> "$RESP" <
{
new() { Role = Role.User, Content = "Process this large dataset" }
};
int maxRetries = 10;
var response = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 4096,
Betas = ["code-execution-2025-08-25", "skills-2025-10-02"],
Container = new BetaContainerParams
{
Skills = [
new BetaSkillParam
{
Type = "custom",
SkillId = "skill_01AbCdEfGhIjKlMnOpQrStUv",
Version = "latest"
}
]
},
Messages = messages,
Tools = [new BetaToolParam { Type = "code_execution_20250825", Name = "code_execution" }]
});
for (int i = 0; i < maxRetries; i++)
{
if (response.StopReason != "pause_turn")
{
break;
}
messages.Add(new BetaMessageParam { Role = Role.Assistant, Content = response.Content });
response = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 4096,
Betas = ["code-execution-2025-08-25", "skills-2025-10-02"],
Container = new BetaContainerParams
{
Id = response.Container.Id,
Skills = [
new BetaSkillParam
{
Type = "custom",
SkillId = "skill_01AbCdEfGhIjKlMnOpQrStUv",
Version = "latest"
}
]
},
Messages = messages,
Tools = [new BetaToolParam { Type = "code_execution_20250825", Name = "code_execution" }]
});
}
```
```go Go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
messages := []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Process this large dataset")),
}
maxRetries := 10
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 4096,
Betas: []anthropic.AnthropicBeta{"code-execution-2025-08-25", anthropic.AnthropicBetaSkills2025_10_02},
Container: anthropic.BetaMessageNewParamsContainerUnion{
OfContainers: &anthropic.BetaContainerParams{
Skills: []anthropic.BetaSkillParams{
{
Type: anthropic.BetaSkillParamsTypeCustom,
SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
Version: anthropic.String("latest"),
},
},
},
},
Messages: messages,
Tools: []anthropic.BetaToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.BetaCodeExecutionTool20250825Param{}},
},
})
if err != nil {
log.Fatal(err)
}
for i := 0; i < maxRetries; i++ {
if response.StopReason != "pause_turn" {
break
}
messages = append(messages, response.ToParam())
response, err = client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 4096,
Betas: []anthropic.AnthropicBeta{"code-execution-2025-08-25", anthropic.AnthropicBetaSkills2025_10_02},
Container: anthropic.BetaMessageNewParamsContainerUnion{
OfString: anthropic.String(response.Container.ID),
},
Messages: messages,
Tools: []anthropic.BetaToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.BetaCodeExecutionTool20250825Param{}},
},
})
if err != nil {
log.Fatal(err)
}
}
fmt.Println(response)
}
```
```java Java nocheck hidelines={1..5,9..10,12..14,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaMessageParam;
import com.anthropic.models.beta.messages.BetaContainerParams;
import com.anthropic.models.beta.messages.BetaSkillParams;
import com.anthropic.models.beta.messages.BetaCodeExecutionTool20250825;
import java.util.ArrayList;
import java.util.List;
import com.anthropic.models.beta.messages.BetaStopReason;
public class Main {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
List messages = new ArrayList<>();
messages.add(
BetaMessageParam.builder()
.role(BetaMessageParam.Role.USER)
.content("Process this large dataset")
.build()
);
int maxRetries = 10;
BetaMessage response = client.beta().messages().create(
MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("code-execution-2025-08-25")
.addBeta("skills-2025-10-02")
.container(BetaContainerParams.builder()
.addSkill(BetaSkillParams.builder()
.type(BetaSkillParams.Type.CUSTOM)
.skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
.version("latest")
.build())
.build())
.messages(messages)
.addTool(BetaCodeExecutionTool20250825.builder().build())
.build());
for (int i = 0; i < maxRetries; i++) {
if (!response.stopReason().isPresent()
|| !response.stopReason().get().equals(BetaStopReason.PAUSE_TURN)) {
break;
}
messages.add(response.toParam());
response = client.beta().messages().create(
MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("code-execution-2025-08-25")
.addBeta("skills-2025-10-02")
.container(BetaContainerParams.builder()
.id(response.container().get().id())
.addSkill(BetaSkillParams.builder()
.type(BetaSkillParams.Type.CUSTOM)
.skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
.version("latest")
.build())
.build())
.messages(messages)
.addTool(BetaCodeExecutionTool20250825.builder().build())
.build());
}
}
}
```
```php PHP hidelines={1..4} nocheck
'user', 'content' => 'Process this large dataset']
];
$maxRetries = 10;
$response = $client->beta->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
betas: ['code-execution-2025-08-25', 'skills-2025-10-02'],
container: [
'skills' => [
[
'type' => 'custom',
'skill_id' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
'version' => 'latest'
]
]
],
tools: [['type' => 'code_execution_20250825', 'name' => 'code_execution']]
);
for ($i = 0; $i < $maxRetries; $i++) {
if ($response->stopReason !== 'pause_turn') {
break;
}
$messages[] = ['role' => 'assistant', 'content' => $response->content];
$response = $client->beta->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-4-7',
betas: ['code-execution-2025-08-25', 'skills-2025-10-02'],
container: [
'id' => $response->container->id,
'skills' => [
[
'type' => 'custom',
'skill_id' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
'version' => 'latest'
]
]
],
tools: [['type' => 'code_execution_20250825', 'name' => 'code_execution']]
);
}
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
messages = [
{ role: "user", content: "Process this large dataset" }
]
max_retries = 10
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [
{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: "latest"
}
]
},
messages: messages,
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
)
max_retries.times do
break if response.stop_reason != :pause_turn
messages << { role: "assistant", content: response.content }
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
id: response.container.id,
skills: [
{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: "latest"
}
]
},
messages: messages,
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
)
end
```
The response may include a `pause_turn` stop reason, which indicates that the API paused a long-running Skill operation. You can provide the response back as-is in a subsequent request to let Claude continue its turn, or modify the content if you wish to interrupt the conversation and provide additional guidance.
### Using Multiple Skills
Combine multiple Skills in a single request to handle complex workflows:
```bash cURL nocheck
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"container": {
"skills": [
{
"type": "anthropic",
"skill_id": "xlsx",
"version": "latest"
},
{
"type": "anthropic",
"skill_id": "pptx",
"version": "latest"
},
{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "latest"
}
]
},
"messages": [{
"role": "user",
"content": "Analyze sales data and create a presentation"
}],
"tools": [{
"type": "code_execution_20250825",
"name": "code_execution"
}]
}'
```
```bash CLI nocheck
ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- type: anthropic
skill_id: xlsx
version: latest
- type: anthropic
skill_id: pptx
version: latest
- type: custom
skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
version: latest
messages:
- role: user
content: Analyze sales data and create a presentation
tools:
- type: code_execution_20250825
name: code_execution
YAML
```
```python Python nocheck
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
betas=["code-execution-2025-08-25", "skills-2025-10-02"],
container={
"skills": [
{"type": "anthropic", "skill_id": "xlsx", "version": "latest"},
{"type": "anthropic", "skill_id": "pptx", "version": "latest"},
{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "latest",
},
]
},
messages=[
{"role": "user", "content": "Analyze sales data and create a presentation"}
],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
```
```typescript TypeScript nocheck
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [
{
type: "anthropic",
skill_id: "xlsx",
version: "latest"
},
{
type: "anthropic",
skill_id: "pptx",
version: "latest"
},
{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: "latest"
}
]
},
messages: [
{
role: "user",
content: "Analyze sales data and create a presentation"
}
],
tools: [
{
type: "code_execution_20250825",
name: "code_execution"
}
]
});
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
public class Program
{
public static async Task Main(string[] args)
{
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 4096,
Betas = new[] { "code-execution-2025-08-25", "skills-2025-10-02" },
Container = new BetaContainerParams
{
Skills = new object[]
{
new
{
type = "anthropic",
skill_id = "xlsx",
version = "latest"
},
new
{
type = "anthropic",
skill_id = "pptx",
version = "latest"
},
new
{
type = "custom",
skill_id = "skill_01AbCdEfGhIjKlMnOpQrStUv",
version = "latest"
}
}
},
Messages = new[]
{
new BetaMessageParam
{
Role = Role.User,
Content = "Analyze sales data and create a presentation"
}
},
Tools = new object[]
{
new
{
type = "code_execution_20250825",
name = "code_execution"
}
}
};
var message = await client.Beta.Messages.Create(parameters);
Console.WriteLine(message);
}
}
```
```go Go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 4096,
Betas: []anthropic.AnthropicBeta{
"code-execution-2025-08-25",
anthropic.AnthropicBetaSkills2025_10_02,
},
Container: anthropic.BetaMessageNewParamsContainerUnion{
OfContainers: &anthropic.BetaContainerParams{
Skills: []anthropic.BetaSkillParams{
{
Type: anthropic.BetaSkillParamsTypeAnthropic,
SkillID: "xlsx",
Version: anthropic.String("latest"),
},
{
Type: anthropic.BetaSkillParamsTypeAnthropic,
SkillID: "pptx",
Version: anthropic.String("latest"),
},
{
Type: anthropic.BetaSkillParamsTypeCustom,
SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
Version: anthropic.String("latest"),
},
},
},
},
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Analyze sales data and create a presentation")),
},
Tools: []anthropic.BetaToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.BetaCodeExecutionTool20250825Param{}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java nocheck hidelines={1..4,8..11,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaContainerParams;
import com.anthropic.models.beta.messages.BetaSkillParams;
import com.anthropic.models.beta.messages.BetaCodeExecutionTool20250825;
import java.util.List;
public class SkillsExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("code-execution-2025-08-25")
.addBeta("skills-2025-10-02")
.container(BetaContainerParams.builder()
.skills(List.of(
BetaSkillParams.builder()
.type(BetaSkillParams.Type.ANTHROPIC)
.skillId("xlsx")
.version("latest")
.build(),
BetaSkillParams.builder()
.type(BetaSkillParams.Type.ANTHROPIC)
.skillId("pptx")
.version("latest")
.build(),
BetaSkillParams.builder()
.type(BetaSkillParams.Type.CUSTOM)
.skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
.version("latest")
.build()
))
.build())
.addUserMessage("Analyze sales data and create a presentation")
.addTool(BetaCodeExecutionTool20250825.builder().build())
.build();
BetaMessage response = client.beta().messages().create(params);
System.out.println(response);
}
}
```
```php PHP hidelines={1..4} nocheck
beta->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Analyze sales data and create a presentation']
],
model: 'claude-opus-4-7',
betas: ['code-execution-2025-08-25', 'skills-2025-10-02'],
container: [
'skills' => [
[
'type' => 'anthropic',
'skill_id' => 'xlsx',
'version' => 'latest'
],
[
'type' => 'anthropic',
'skill_id' => 'pptx',
'version' => 'latest'
],
[
'type' => 'custom',
'skill_id' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
'version' => 'latest'
]
]
],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
]
);
echo $message;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
message = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [
{
type: "anthropic",
skill_id: "xlsx",
version: "latest"
},
{
type: "anthropic",
skill_id: "pptx",
version: "latest"
},
{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: "latest"
}
]
},
messages: [
{ role: "user", content: "Analyze sales data and create a presentation" }
],
tools: [
{ type: "code_execution_20250825", name: "code_execution" }
]
)
puts message
```
---
## Managing Custom Skills
### Creating a Skill
A Skill bundle is a directory containing a `SKILL.md` file at the top level with `name` and `description` YAML frontmatter, plus any supporting scripts or resources. See [Get started with Agent Skills in the API](/docs/en/agents-and-tools/agent-skills/quickstart) to author one, and the **Requirements** list following the examples for the full constraints.
Upload your custom Skill to make it available in your workspace. You can upload a zip archive or individual file objects; the Python SDK additionally provides a `files_from_dir` helper that accepts a directory path.
```bash cURL nocheck
curl -X POST "https://api.anthropic.com/v1/skills" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: skills-2025-10-02" \
-F "display_title=Financial Analysis" \
-F "files[]=@financial_skill/SKILL.md;filename=financial_skill/SKILL.md" \
-F "files[]=@financial_skill/analyze.py;filename=financial_skill/analyze.py"
```
```bash CLI nocheck
# Option 1: Upload individual files (one --file flag per file)
ant beta:skills create \
--display-title "Financial Analysis" \
--file financial_skill/SKILL.md \
--file financial_skill/analyze.py \
--beta skills-2025-10-02
# Option 2: Upload a zip archive
ant beta:skills create \
--display-title "Financial Analysis" \
--file financial_analysis_skill.zip \
--beta skills-2025-10-02
```
```python Python nocheck hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
# Option 1: Using files_from_dir helper (Python only, recommended)
from anthropic.lib import files_from_dir
skill = client.beta.skills.create(
display_title="Financial Analysis",
files=files_from_dir("/path/to/financial_analysis_skill"),
)
# Option 2: Using a zip file
skill = client.beta.skills.create(
display_title="Financial Analysis",
files=[("skill.zip", open("financial_analysis_skill.zip", "rb"))],
)
# Option 3: Using file tuples (filename, file_content, mime_type)
skill = client.beta.skills.create(
display_title="Financial Analysis",
files=[
(
"financial_skill/SKILL.md",
open("financial_skill/SKILL.md", "rb"),
"text/markdown",
),
(
"financial_skill/analyze.py",
open("financial_skill/analyze.py", "rb"),
"text/x-python",
),
],
)
print(f"Created skill: {skill.id}")
print(f"Latest version: {skill.latest_version}")
```
```typescript TypeScript nocheck
import Anthropic, { toFile } from "@anthropic-ai/sdk";
import fs from "fs";
const client = new Anthropic();
// Option 1: Using a zip file
const skillFromZip = await client.beta.skills.create({
display_title: "Financial Analysis",
files: [await toFile(fs.createReadStream("financial_analysis_skill.zip"), "skill.zip")]
});
// Option 2: Using individual file objects
const skill = await client.beta.skills.create({
display_title: "Financial Analysis",
files: [
await toFile(fs.createReadStream("financial_skill/SKILL.md"), "financial_skill/SKILL.md", {
type: "text/markdown"
}),
await toFile(
fs.createReadStream("financial_skill/analyze.py"),
"financial_skill/analyze.py",
{ type: "text/x-python" }
)
]
});
console.log(`Created skill: ${skill.id}`);
console.log(`Latest version: ${skill.latest_version}`);
```
```csharp C# nocheck
using System;
using System.IO;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Skills;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
// Option 1: Using a zip file
var parameters = new SkillCreateParams
{
DisplayTitle = "Financial Analysis",
Files = [
new FileStream("financial_analysis_skill.zip", FileMode.Open, FileAccess.Read)
],
};
var skill = await client.Beta.Skills.Create(parameters);
// Option 2: Using individual files
var parameters2 = new SkillCreateParams
{
DisplayTitle = "Financial Analysis",
Files = [
new FileStream("financial_skill/SKILL.md", FileMode.Open, FileAccess.Read),
new FileStream("financial_skill/analyze.py", FileMode.Open, FileAccess.Read)
],
};
var skill2 = await client.Beta.Skills.Create(parameters2);
Console.WriteLine($"Created skill: {skill.Id}");
Console.WriteLine($"Latest version: {skill.LatestVersion}");
}
}
```
```go Go nocheck hidelines={1..15,-1}
package main
import (
"context"
"fmt"
"io"
"log"
"os"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
// Option 1: Using a zip file
zipFile, err := os.Open("financial_analysis_skill.zip")
if err != nil {
log.Fatal(err)
}
defer zipFile.Close()
skill, err := client.Beta.Skills.New(context.TODO(), anthropic.BetaSkillNewParams{
DisplayTitle: anthropic.String("Financial Analysis"),
Files: []io.Reader{zipFile},
})
if err != nil {
log.Fatal(err)
}
// Option 2: Using individual files
skillMd, err := os.Open("financial_skill/SKILL.md")
if err != nil {
log.Fatal(err)
}
defer skillMd.Close()
analyzePy, err := os.Open("financial_skill/analyze.py")
if err != nil {
log.Fatal(err)
}
defer analyzePy.Close()
skill2, err := client.Beta.Skills.New(context.TODO(), anthropic.BetaSkillNewParams{
DisplayTitle: anthropic.String("Financial Analysis"),
Files: []io.Reader{skillMd, analyzePy},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created skill: %s\n", skill.ID)
fmt.Printf("Latest version: %s\n", skill.LatestVersion)
fmt.Printf("Created skill 2: %s\n", skill2.ID)
}
```
```java Java nocheck hidelines={1..2,5..10,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.skills.SkillCreateParams;
import com.anthropic.models.beta.skills.SkillCreateResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
public class SkillCreate {
public static void main(String[] args) throws IOException {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Option 1: Using a zip file
SkillCreateParams params = SkillCreateParams.builder()
.displayTitle("Financial Analysis")
.addFile(new FileInputStream("financial_analysis_skill.zip"))
.build();
SkillCreateResponse skill = client.beta().skills().create(params);
// Option 2: Using individual files
SkillCreateParams params2 = SkillCreateParams.builder()
.displayTitle("Financial Analysis")
.addFile(Path.of("financial_skill/SKILL.md"))
.addFile(Path.of("financial_skill/analyze.py"))
.build();
SkillCreateResponse skill2 = client.beta().skills().create(params2);
System.out.println("Created skill: " + skill.id());
System.out.println("Latest version: " + skill.latestVersion());
}
}
```
```php PHP hidelines={1..4} nocheck
beta->skills->create(
displayTitle: 'Financial Analysis',
files: [
fopen('financial_analysis_skill.zip', 'r')
],
);
// Option 2: Using individual files
$skill = $client->beta->skills->create(
displayTitle: 'Financial Analysis',
files: [
fopen('financial_skill/SKILL.md', 'r'),
fopen('financial_skill/analyze.py', 'r')
],
);
echo "Created skill: {$skill->id}\n";
echo "Latest version: {$skill->latestVersion}\n";
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
# Option 1: Using a zip file
skill = client.beta.skills.create(
display_title: "Financial Analysis",
files: [
File.open("financial_analysis_skill.zip", "rb")
]
)
# Option 2: Using individual files
skill = client.beta.skills.create(
display_title: "Financial Analysis",
files: [
File.open("financial_skill/SKILL.md", "rb"),
File.open("financial_skill/analyze.py", "rb")
]
)
puts "Created skill: #{skill.id}"
puts "Latest version: #{skill.latest_version}"
```
**Requirements:**
- Must include a SKILL.md file at the top level
- All files must specify a common root directory in their paths
- Total upload size must be under 30 MB
- YAML frontmatter requirements:
- `name`: Maximum 64 characters, lowercase letters/numbers/hyphens only, no XML tags, no reserved words ("anthropic", "claude")
- `description`: Maximum 1024 characters, non-empty, no XML tags
For complete request/response schemas, see the [Create Skill API reference](/docs/en/api/skills/create-skill).
### Listing Skills
Retrieve all Skills available to your workspace, including both Anthropic pre-built Skills and your custom Skills. Use the `source` parameter to filter by skill type:
```bash cURL
# List all Skills
curl "https://api.anthropic.com/v1/skills" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: skills-2025-10-02"
# List only custom Skills
curl "https://api.anthropic.com/v1/skills?source=custom" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: skills-2025-10-02"
```
```bash CLI
# List all Skills
ant beta:skills list
# List only custom Skills
ant beta:skills list --source custom
```
```python Python
# List all Skills
skills = client.beta.skills.list()
for skill in skills.data:
print(f"{skill.id}: {skill.display_title} (source: {skill.source})")
# List only custom Skills
custom_skills = client.beta.skills.list(source="custom")
```
```typescript TypeScript
// List all Skills
const skills = await client.beta.skills.list();
for (const skill of skills.data) {
console.log(`${skill.id}: ${skill.display_title} (source: ${skill.source})`);
}
// List only custom Skills
const customSkills = await client.beta.skills.list({
source: "custom"
});
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Skills;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
// List all Skills
var skills = await client.Beta.Skills.List();
foreach (var skill in skills.Data)
{
Console.WriteLine($"{skill.Id}: {skill.DisplayTitle} (source: {skill.Source})");
}
// List only custom Skills
var customSkills = await client.Beta.Skills.List(new SkillListParams
{
Source = "custom",
});
}
}
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
// List all Skills
skills := client.Beta.Skills.ListAutoPaging(context.TODO(), anthropic.BetaSkillListParams{})
for skills.Next() {
skill := skills.Current()
fmt.Printf("%s: %s (source: %s)\n", skill.ID, skill.DisplayTitle, skill.Source)
}
if skills.Err() != nil {
log.Fatal(skills.Err())
}
// List only custom Skills
customSkills := client.Beta.Skills.ListAutoPaging(context.TODO(), anthropic.BetaSkillListParams{
Source: anthropic.String("custom"),
})
for customSkills.Next() {
skill := customSkills.Current()
fmt.Printf("%s: %s (source: %s)\n", skill.ID, skill.DisplayTitle, skill.Source)
}
}
```
```java Java hidelines={1..2,6..8,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.skills.SkillListParams;
import com.anthropic.models.beta.skills.SkillListPage;
import com.anthropic.models.beta.skills.SkillListResponse;
public class ListSkills {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// List all Skills
SkillListPage skills = client.beta().skills().list();
for (SkillListResponse skill : skills.data()) {
System.out.println(skill.id() + ": " + skill.displayTitle() + " (source: " + skill.source() + ")");
}
// List only custom Skills
SkillListParams customParams = SkillListParams.builder()
.source("custom")
.build();
SkillListPage customSkills = client.beta().skills().list(customParams);
}
}
```
```php PHP hidelines={1..4}
beta->skills->list();
foreach ($skills->data as $skill) {
echo "{$skill->id}: {$skill->displayTitle} (source: {$skill->source})\n";
}
// List only custom Skills
$customSkills = $client->beta->skills->list(
source: 'custom',
);
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
# List all Skills
skills = client.beta.skills.list
skills.data.each do |skill|
puts "#{skill.id}: #{skill.display_title} (source: #{skill.source})"
end
# List only custom Skills
custom_skills = client.beta.skills.list(
source: "custom"
)
```
See the [List Skills API reference](/docs/en/api/skills/list-skills) for pagination and filtering options.
### Retrieving a Skill
Get details about a specific Skill:
```bash cURL nocheck
curl "https://api.anthropic.com/v1/skills/skill_01AbCdEfGhIjKlMnOpQrStUv" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: skills-2025-10-02"
```
```bash CLI nocheck
ant beta:skills retrieve \
--skill-id skill_01AbCdEfGhIjKlMnOpQrStUv
```
```python Python nocheck
skill = client.beta.skills.retrieve(skill_id="skill_01AbCdEfGhIjKlMnOpQrStUv")
print(f"Skill: {skill.display_title}")
print(f"Latest version: {skill.latest_version}")
print(f"Created: {skill.created_at}")
```
```typescript TypeScript nocheck
const skill = await client.beta.skills.retrieve("skill_01AbCdEfGhIjKlMnOpQrStUv");
console.log(`Skill: ${skill.display_title}`);
console.log(`Latest version: ${skill.latest_version}`);
console.log(`Created: ${skill.created_at}`);
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Skills;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
var skill = await client.Beta.Skills.Retrieve("skill_01AbCdEfGhIjKlMnOpQrStUv");
Console.WriteLine($"Skill: {skill.DisplayTitle}");
Console.WriteLine($"Latest version: {skill.LatestVersion}");
Console.WriteLine($"Created: {skill.CreatedAt}");
}
}
```
```go Go nocheck hidelines={1..13,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
skill, err := client.Beta.Skills.Get(
context.TODO(),
"skill_01AbCdEfGhIjKlMnOpQrStUv",
anthropic.BetaSkillGetParams{},
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Skill: %s\n", skill.DisplayTitle)
fmt.Printf("Latest version: %s\n", skill.LatestVersion)
fmt.Printf("Created: %s\n", skill.CreatedAt)
}
```
```java Java nocheck hidelines={1..2,4..6,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.skills.SkillRetrieveResponse;
public class RetrieveSkill {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
SkillRetrieveResponse skill = client.beta().skills().retrieve("skill_01AbCdEfGhIjKlMnOpQrStUv");
System.out.println("Skill: " + skill.displayTitle());
System.out.println("Latest version: " + skill.latestVersion());
System.out.println("Created: " + skill.createdAt());
}
}
```
```php PHP hidelines={1..4} nocheck
beta->skills->retrieve(
skillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
);
echo "Skill: " . $skill->displayTitle . "\n";
echo "Latest version: " . $skill->latestVersion . "\n";
echo "Created: " . $skill->createdAt . "\n";
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
skill = client.beta.skills.retrieve("skill_01AbCdEfGhIjKlMnOpQrStUv")
puts "Skill: #{skill.display_title}"
puts "Latest version: #{skill.latest_version}"
puts "Created: #{skill.created_at}"
```
### Deleting a Skill
To delete a Skill, you must first delete all its versions:
```bash cURL nocheck
# Delete all versions first, then delete the Skill
curl -X DELETE "https://api.anthropic.com/v1/skills/skill_01AbCdEfGhIjKlMnOpQrStUv" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: skills-2025-10-02"
```
```bash CLI nocheck
# Step 1: Delete all versions
ant beta:skills:versions list \
--skill-id skill_01AbCdEfGhIjKlMnOpQrStUv \
--transform version --raw-output \
| while read -r VERSION; do
ant beta:skills:versions delete \
--skill-id skill_01AbCdEfGhIjKlMnOpQrStUv \
--version "$VERSION" >/dev/null
done
# Step 2: Delete the Skill
ant beta:skills delete \
--skill-id skill_01AbCdEfGhIjKlMnOpQrStUv >/dev/null
```
```python Python nocheck
# Step 1: Delete all versions
versions = client.beta.skills.versions.list(skill_id="skill_01AbCdEfGhIjKlMnOpQrStUv")
for version in versions.data:
client.beta.skills.versions.delete(
skill_id="skill_01AbCdEfGhIjKlMnOpQrStUv",
version=version.version,
)
# Step 2: Delete the Skill
client.beta.skills.delete(skill_id="skill_01AbCdEfGhIjKlMnOpQrStUv")
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// Step 1: Delete all versions
const versions = await client.beta.skills.versions.list("skill_01AbCdEfGhIjKlMnOpQrStUv");
for (const version of versions.data) {
await client.beta.skills.versions.delete("skill_01AbCdEfGhIjKlMnOpQrStUv", version.version);
}
// Step 2: Delete the Skill
await client.beta.skills.delete("skill_01AbCdEfGhIjKlMnOpQrStUv");
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
class Program
{
static async Task Main(string[] args)
{
AnthropicClient client = new();
// Step 1: Delete all versions
var versions = await client.Beta.Skills.Versions.List("skill_01AbCdEfGhIjKlMnOpQrStUv");
foreach (var version in versions.Data)
{
await client.Beta.Skills.Versions.Delete(
"skill_01AbCdEfGhIjKlMnOpQrStUv",
version.Version
);
}
// Step 2: Delete the Skill
await client.Beta.Skills.Delete("skill_01AbCdEfGhIjKlMnOpQrStUv");
}
}
```
```go Go nocheck hidelines={1..12,-1}
package main
import (
"context"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
// Step 1: Delete all versions
versions := client.Beta.Skills.Versions.ListAutoPaging(
context.TODO(),
"skill_01AbCdEfGhIjKlMnOpQrStUv",
anthropic.BetaSkillVersionListParams{},
)
for versions.Next() {
version := versions.Current()
_, err := client.Beta.Skills.Versions.Delete(
context.TODO(),
version.Version,
anthropic.BetaSkillVersionDeleteParams{
SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
},
)
if err != nil {
log.Fatal(err)
}
}
// Step 2: Delete the Skill
_, err := client.Beta.Skills.Delete(
context.TODO(),
"skill_01AbCdEfGhIjKlMnOpQrStUv",
anthropic.BetaSkillDeleteParams{},
)
if err != nil {
log.Fatal(err)
}
}
```
```java Java nocheck hidelines={1..2,5..7,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.skills.versions.VersionListPage;
import com.anthropic.models.beta.skills.versions.VersionDeleteParams;
public class DeleteSkill {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Step 1: Delete all versions
VersionListPage versions = client.beta().skills().versions().list("skill_01AbCdEfGhIjKlMnOpQrStUv");
for (var version : versions.data()) {
client.beta().skills().versions().delete(
version.version(),
VersionDeleteParams.builder()
.skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
.build()
);
}
// Step 2: Delete the Skill
client.beta().skills().delete("skill_01AbCdEfGhIjKlMnOpQrStUv");
}
}
```
```php PHP hidelines={1..4} nocheck
beta->skills->versions->list(
skillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
);
foreach ($versions->data as $version) {
$client->beta->skills->versions->delete(
skillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: $version->version,
);
}
// Step 2: Delete the Skill
$client->beta->skills->delete(
skillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
);
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
# Step 1: Delete all versions
versions = client.beta.skills.versions.list("skill_01AbCdEfGhIjKlMnOpQrStUv")
versions.data.each do |version|
client.beta.skills.versions.delete(
version.version,
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv"
)
end
# Step 2: Delete the Skill
client.beta.skills.delete("skill_01AbCdEfGhIjKlMnOpQrStUv")
```
Attempting to delete a Skill with existing versions returns a 400 error.
### Versioning
Skills support versioning to manage updates safely:
**Anthropic Skills:**
- Versions use date format: `20251013`
- New versions released as updates are made
- Specify exact versions for stability
**Custom Skills:**
- Auto-generated epoch timestamps: `1759178010641129`
- Use `"latest"` to always get the most recent version
- Create new versions when updating Skill files
```bash cURL nocheck
# Create a new version
NEW_VERSION=$(curl -X POST "https://api.anthropic.com/v1/skills/skill_01AbCdEfGhIjKlMnOpQrStUv/versions" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: skills-2025-10-02" \
-F "files[]=@updated_skill/SKILL.md;filename=updated_skill/SKILL.md")
VERSION_NUMBER=$(echo "$NEW_VERSION" | jq -r '.version')
# Use specific version
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02" \
-H "content-type: application/json" \
-d "{
\"model\": \"claude-opus-4-7\",
\"max_tokens\": 4096,
\"container\": {
\"skills\": [{
\"type\": \"custom\",
\"skill_id\": \"skill_01AbCdEfGhIjKlMnOpQrStUv\",
\"version\": \"$VERSION_NUMBER\"
}]
},
\"messages\": [{\"role\": \"user\", \"content\": \"Use updated Skill\"}],
\"tools\": [{\"type\": \"code_execution_20250825\", \"name\": \"code_execution\"}]
}"
# Use latest version
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"container": {
"skills": [{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "latest"
}]
},
"messages": [{"role": "user", "content": "Use latest Skill version"}],
"tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
}'
```
```bash CLI nocheck
# Create a new version
VERSION_NUMBER=$(ant beta:skills:versions create \
--skill-id skill_01AbCdEfGhIjKlMnOpQrStUv \
--file updated_skill/SKILL.md \
--transform version --raw-output)
# Use specific version
ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 <beta->skills->versions->create(
skillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
files: [fopen("/path/to/updated_skill/SKILL.md", "r")],
);
// Use specific version
$response = $client->beta->messages->create(
maxTokens: 4096,
messages: [['role' => 'user', 'content' => 'Use updated Skill']],
model: 'claude-opus-4-7',
betas: ['code-execution-2025-08-25', 'skills-2025-10-02'],
container: [
'skills' => [[
'type' => 'custom',
'skill_id' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
'version' => $newVersion->version
]]
],
tools: [['type' => 'code_execution_20250825', 'name' => 'code_execution']]
);
echo $response;
// Use latest version
$latestResponse = $client->beta->messages->create(
maxTokens: 4096,
messages: [['role' => 'user', 'content' => 'Use latest Skill version']],
model: 'claude-opus-4-7',
betas: ['code-execution-2025-08-25', 'skills-2025-10-02'],
container: [
'skills' => [[
'type' => 'custom',
'skill_id' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
'version' => 'latest'
]]
],
tools: [['type' => 'code_execution_20250825', 'name' => 'code_execution']]
);
echo $latestResponse;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
# Create a new version
new_version = client.beta.skills.versions.create(
"skill_01AbCdEfGhIjKlMnOpQrStUv",
files: [File.open("/path/to/updated_skill/SKILL.md")]
)
# Use specific version
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: new_version.version
}]
},
messages: [{ role: "user", content: "Use updated Skill" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
)
puts response
# Use latest version
latest_response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: "latest"
}]
},
messages: [{ role: "user", content: "Use latest Skill version" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
)
puts latest_response
```
See the [Create Skill Version API reference](/docs/en/api/skills/create-skill-version) for complete details.
---
## How Skills are loaded
When you specify Skills in a container:
1. **Metadata Discovery:** Claude sees metadata for each Skill (name, description) in the system prompt
2. **File Loading:** Skill files are copied into the container at `/skills/{directory}/`
3. **Automatic Use:** Claude automatically loads and uses Skills when relevant to your request
4. **Composition:** Multiple Skills compose together for complex workflows
The progressive disclosure architecture ensures efficient context usage: Claude only loads full Skill instructions when needed.
---
## Use cases
### Organizational Skills
**Brand & Communications**
- Apply company-specific formatting (colors, fonts, layouts) to documents
- Generate communications following organizational templates
- Ensure consistent brand guidelines across all outputs
**Project Management**
- Structure notes with company-specific formats (OKRs, decision logs)
- Generate tasks following team conventions
- Create standardized meeting recaps and status updates
**Business Operations**
- Create company-standard reports, proposals, and analyses
- Execute company-specific analytical procedures
- Generate financial models following organizational templates
### Personal Skills
**Content Creation**
- Custom document templates
- Specialized formatting and styling
- Domain-specific content generation
**Data Analysis**
- Custom data processing pipelines
- Specialized visualization templates
- Industry-specific analytical methods
**Development & Automation**
- Code generation templates
- Testing frameworks
- Deployment workflows
### Example: financial modeling
Combine Excel and custom DCF analysis Skills:
```bash cURL nocheck
# Create custom DCF analysis Skill
DCF_SKILL=$(curl -X POST "https://api.anthropic.com/v1/skills" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: skills-2025-10-02" \
-F "display_title=DCF Analysis" \
-F "files[]=@dcf_skill/SKILL.md;filename=dcf_skill/SKILL.md")
DCF_SKILL_ID=$(echo "$DCF_SKILL" | jq -r '.id')
# Use with Excel to create financial model
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02" \
-H "content-type: application/json" \
-d "{
\"model\": \"claude-opus-4-7\",
\"max_tokens\": 4096,
\"container\": {
\"skills\": [
{
\"type\": \"anthropic\",
\"skill_id\": \"xlsx\",
\"version\": \"latest\"
},
{
\"type\": \"custom\",
\"skill_id\": \"$DCF_SKILL_ID\",
\"version\": \"latest\"
}
]
},
\"messages\": [{
\"role\": \"user\",
\"content\": \"Build a DCF valuation model for a SaaS company with the attached financials\"
}],
\"tools\": [{
\"type\": \"code_execution_20250825\",
\"name\": \"code_execution\"
}]
}"
```
```bash CLI nocheck
# Create custom DCF analysis Skill
DCF_SKILL_ID=$(ant beta:skills create \
--display-title "DCF Analysis" \
--file dcf_skill/SKILL.md \
--transform id --raw-output)
# Use with Excel to create financial model
ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 <beta->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Build a DCF valuation model for a SaaS company with the attached financials']
],
model: 'claude-opus-4-7',
betas: ['code-execution-2025-08-25', 'skills-2025-10-02'],
container: [
'skills' => [
['type' => 'anthropic', 'skill_id' => 'xlsx', 'version' => 'latest'],
['type' => 'custom', 'skill_id' => $dcfSkillId, 'version' => 'latest']
]
],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
]
);
echo $message;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
# Create custom DCF analysis Skill
dcf_skill = client.beta.skills.create(
display_title: "DCF Analysis",
files: [
File.open("dcf_skill/SKILL.md", "rb")
]
)
# Use with Excel to create financial model
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [
{ type: "anthropic", skill_id: "xlsx", version: "latest" },
{ type: "custom", skill_id: dcf_skill.id, version: "latest" }
]
},
messages: [
{ role: "user", content: "Build a DCF valuation model for a SaaS company with the attached financials" }
],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
)
puts response
```
---
## Limits and constraints
### Request limits
- **Maximum Skills per request:** 8
- **Maximum Skill upload size:** 30 MB (all files combined)
- **YAML frontmatter requirements:**
- `name`: Maximum 64 characters, lowercase letters/numbers/hyphens only, no XML tags, no reserved words ("anthropic", "claude")
- `description`: Maximum 1024 characters, non-empty, no XML tags
### Environment constraints
Skills run in the code execution container with these limitations:
- **No network access:** Cannot make external API calls
- **No runtime package installation:** Only pre-installed packages available
- **Isolated environment:** Containers are isolated; a fresh container is created unless you specify an existing container ID
See [Code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool) for available packages.
---
## Best practices
### When to use multiple Skills
Combine Skills when tasks involve multiple document types or domains:
**Good use cases:**
- Data analysis (Excel) + presentation creation (PowerPoint)
- Report generation (Word) + export to PDF
- Custom domain logic + document generation
**Avoid:**
- Including unused Skills (impacts performance)
### Version management strategy
**For production:**
```python nocheck
# Pin to specific versions for stability
container = {
"skills": [
{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "1759178010641129", # Specific version
}
]
}
```
**For development:**
```python nocheck
# Use latest for active development
container = {
"skills": [
{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "latest", # Always get newest
}
]
}
```
### Prompt caching considerations
When using prompt caching, note that changing the Skills list in your container breaks the cache:
```bash cURL
# First request creates cache
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02,prompt-caching-2024-07-31" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"container": {
"skills": [
{"type": "anthropic", "skill_id": "xlsx", "version": "latest"}
]
},
"messages": [{"role": "user", "content": "Analyze sales data"}],
"tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
}'
# Adding/removing Skills breaks cache
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: code-execution-2025-08-25,skills-2025-10-02,prompt-caching-2024-07-31" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"container": {
"skills": [
{"type": "anthropic", "skill_id": "xlsx", "version": "latest"},
{"type": "anthropic", "skill_id": "pptx", "version": "latest"}
]
},
"messages": [{"role": "user", "content": "Create a presentation"}],
"tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
}'
```
```bash CLI
# First request creates cache
ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 \
--beta prompt-caching-2024-07-31 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- type: anthropic
skill_id: xlsx
version: latest
messages:
- role: user
content: Analyze sales data
tools:
- type: code_execution_20250825
name: code_execution
YAML
# Adding/removing Skills breaks cache
ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 \
--beta prompt-caching-2024-07-31 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- type: anthropic
skill_id: xlsx
version: latest
- type: anthropic
skill_id: pptx
version: latest
messages:
- role: user
content: Create a presentation
tools:
- type: code_execution_20250825
name: code_execution
YAML
```
```python Python
# First request creates cache
response1 = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
betas=[
"code-execution-2025-08-25",
"skills-2025-10-02",
"prompt-caching-2024-07-31",
],
container={
"skills": [{"type": "anthropic", "skill_id": "xlsx", "version": "latest"}]
},
messages=[{"role": "user", "content": "Analyze sales data"}],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
# Adding/removing Skills breaks cache
response2 = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
betas=[
"code-execution-2025-08-25",
"skills-2025-10-02",
"prompt-caching-2024-07-31",
],
container={
"skills": [
{"type": "anthropic", "skill_id": "xlsx", "version": "latest"},
{
"type": "anthropic",
"skill_id": "pptx",
"version": "latest",
}, # Cache miss
]
},
messages=[{"role": "user", "content": "Create a presentation"}],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
```
```typescript TypeScript
// First request creates cache
const response1 = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02", "prompt-caching-2024-07-31"],
container: {
skills: [{ type: "anthropic", skill_id: "xlsx", version: "latest" }]
},
messages: [{ role: "user", content: "Analyze sales data" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
});
// Adding/removing Skills breaks cache
const response2 = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02", "prompt-caching-2024-07-31"],
container: {
skills: [
{ type: "anthropic", skill_id: "xlsx", version: "latest" },
{ type: "anthropic", skill_id: "pptx", version: "latest" } // Cache miss
]
},
messages: [{ role: "user", content: "Create a presentation" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
});
```
```csharp C# nocheck hidelines={1..7}
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
var client = new AnthropicClient();
// First request creates cache
var parameters1 = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Betas = new[] { "code-execution-2025-08-25", "skills-2025-10-02", "prompt-caching-2024-07-31" },
Container = new BetaContainer
{
Skills = new[]
{
new BetaSkill { Type = "anthropic", SkillId = "xlsx", Version = "latest" }
}
},
Messages = new[] { new BetaMessageParam { Role = Role.User, Content = "Analyze sales data" } },
Tools = new[] { new BetaTool { Type = "code_execution_20250825", Name = "code_execution" } }
};
var response1 = await client.Beta.Messages.Create(parameters1);
Console.WriteLine(response1);
// Adding/removing Skills breaks cache
var parameters2 = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 4096,
Betas = new[] { "code-execution-2025-08-25", "skills-2025-10-02", "prompt-caching-2024-07-31" },
Container = new BetaContainer
{
Skills = new[]
{
new BetaSkill { Type = "anthropic", SkillId = "xlsx", Version = "latest" },
new BetaSkill { Type = "anthropic", SkillId = "pptx", Version = "latest" }
}
},
Messages = new[] { new BetaMessageParam { Role = Role.User, Content = "Create a presentation" } },
Tools = new[] { new BetaTool { Type = "code_execution_20250825", Name = "code_execution" } }
};
var response2 = await client.Beta.Messages.Create(parameters2);
Console.WriteLine(response2);
```
```go Go hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
// First request creates cache
response1, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 4096,
Betas: []anthropic.AnthropicBeta{
"code-execution-2025-08-25",
anthropic.AnthropicBetaSkills2025_10_02,
anthropic.AnthropicBetaPromptCaching2024_07_31,
},
Container: anthropic.BetaMessageNewParamsContainerUnion{
OfContainers: &anthropic.BetaContainerParams{
Skills: []anthropic.BetaSkillParams{
{
Type: anthropic.BetaSkillParamsTypeAnthropic,
SkillID: "xlsx",
Version: anthropic.String("latest"),
},
},
},
},
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Analyze sales data")),
},
Tools: []anthropic.BetaToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.BetaCodeExecutionTool20250825Param{}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response1)
// Adding/removing Skills breaks cache
response2, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 4096,
Betas: []anthropic.AnthropicBeta{
"code-execution-2025-08-25",
anthropic.AnthropicBetaSkills2025_10_02,
anthropic.AnthropicBetaPromptCaching2024_07_31,
},
Container: anthropic.BetaMessageNewParamsContainerUnion{
OfContainers: &anthropic.BetaContainerParams{
Skills: []anthropic.BetaSkillParams{
{
Type: anthropic.BetaSkillParamsTypeAnthropic,
SkillID: "xlsx",
Version: anthropic.String("latest"),
},
{
Type: anthropic.BetaSkillParamsTypeAnthropic,
SkillID: "pptx",
Version: anthropic.String("latest"),
},
},
},
},
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Create a presentation")),
},
Tools: []anthropic.BetaToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.BetaCodeExecutionTool20250825Param{}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response2)
}
```
```java Java hidelines={1..4,8..11,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaContainerParams;
import com.anthropic.models.beta.messages.BetaSkillParams;
import com.anthropic.models.beta.messages.BetaCodeExecutionTool20250825;
import java.util.List;
public class SkillsCaching {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// First request creates cache
MessageCreateParams params1 = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("code-execution-2025-08-25")
.addBeta("skills-2025-10-02")
.addBeta("prompt-caching-2024-07-31")
.container(BetaContainerParams.builder()
.skills(List.of(
BetaSkillParams.builder()
.type(BetaSkillParams.Type.ANTHROPIC)
.skillId("xlsx")
.version("latest")
.build()
))
.build())
.addUserMessage("Analyze sales data")
.addTool(BetaCodeExecutionTool20250825.builder().build())
.build();
BetaMessage response1 = client.beta().messages().create(params1);
System.out.println(response1);
// Adding/removing Skills breaks cache
MessageCreateParams params2 = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("code-execution-2025-08-25")
.addBeta("skills-2025-10-02")
.addBeta("prompt-caching-2024-07-31")
.container(BetaContainerParams.builder()
.skills(List.of(
BetaSkillParams.builder()
.type(BetaSkillParams.Type.ANTHROPIC)
.skillId("xlsx")
.version("latest")
.build(),
BetaSkillParams.builder()
.type(BetaSkillParams.Type.ANTHROPIC)
.skillId("pptx")
.version("latest")
.build()
))
.build())
.addUserMessage("Create a presentation")
.addTool(BetaCodeExecutionTool20250825.builder().build())
.build();
BetaMessage response2 = client.beta().messages().create(params2);
System.out.println(response2);
}
}
```
```php PHP hidelines={1..4}
beta->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Analyze sales data']
],
model: 'claude-opus-4-7',
betas: [
'code-execution-2025-08-25',
'skills-2025-10-02',
'prompt-caching-2024-07-31'
],
container: [
'skills' => [
['type' => 'anthropic', 'skill_id' => 'xlsx', 'version' => 'latest']
]
],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
]
);
echo $response1;
// Adding/removing Skills breaks cache
$response2 = $client->beta->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Create a presentation']
],
model: 'claude-opus-4-7',
betas: [
'code-execution-2025-08-25',
'skills-2025-10-02',
'prompt-caching-2024-07-31'
],
container: [
'skills' => [
['type' => 'anthropic', 'skill_id' => 'xlsx', 'version' => 'latest'],
['type' => 'anthropic', 'skill_id' => 'pptx', 'version' => 'latest']
]
],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
]
);
echo $response2;
```
```ruby Ruby hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
# First request creates cache
response1 = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: [
"code-execution-2025-08-25",
"skills-2025-10-02",
"prompt-caching-2024-07-31"
],
container: {
skills: [{ type: "anthropic", skill_id: "xlsx", version: "latest" }]
},
messages: [{ role: "user", content: "Analyze sales data" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
)
puts response1
# Adding/removing Skills breaks cache
response2 = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: [
"code-execution-2025-08-25",
"skills-2025-10-02",
"prompt-caching-2024-07-31"
],
container: {
skills: [
{ type: "anthropic", skill_id: "xlsx", version: "latest" },
{ type: "anthropic", skill_id: "pptx", version: "latest" }
]
},
messages: [{ role: "user", content: "Create a presentation" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
)
puts response2
```
For best caching performance, keep your Skills list consistent across requests.
### Error handling
Handle Skill-related errors gracefully:
```bash CLI nocheck
if ! RESULT=$(ant beta:messages create \
--beta code-execution-2025-08-25 \
--beta skills-2025-10-02 \
--transform-error error.message --format-error yaml 2>&1 <<'YAML'
model: claude-opus-4-7
max_tokens: 4096
container:
skills:
- type: custom
skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
version: latest
messages:
- role: user
content: Process data
tools:
- type: code_execution_20250825
name: code_execution
YAML
); then
case "$RESULT" in
*skill*)
printf 'Skill error: %s\n' "$RESULT"
# Handle skill-specific errors
;;
*)
printf '%s\n' "$RESULT" >&2
exit 1
;;
esac
fi
```
```python Python nocheck hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
try:
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
betas=["code-execution-2025-08-25", "skills-2025-10-02"],
container={
"skills": [
{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "latest",
}
]
},
messages=[{"role": "user", "content": "Process data"}],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
except anthropic.BadRequestError as e:
if "skill" in str(e):
print(f"Skill error: {e}")
# Handle skill-specific errors
else:
raise
```
```typescript TypeScript nocheck
try {
const response = await client.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [
{ type: "custom", skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv", version: "latest" }
]
},
messages: [{ role: "user", content: "Process data" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
});
console.log(response);
} catch (error) {
if (error instanceof Anthropic.BadRequestError && error.message.includes("skill")) {
console.error(`Skill error: ${error.message}`);
// Handle skill-specific errors
} else {
throw error;
}
}
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Beta.Messages;
class Program
{
static async Task Main(string[] args)
{
var client = new AnthropicClient();
try
{
var parameters = new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 4096,
Betas = ["code-execution-2025-08-25", "skills-2025-10-02"],
Container = new BetaContainerParams
{
Skills = [
new BetaSkillParam
{
Type = "custom",
SkillId = "skill_01AbCdEfGhIjKlMnOpQrStUv",
Version = "latest"
}
]
},
Messages = [new() { Role = Role.User, Content = "Process data" }],
Tools = [new BetaToolParam { Type = "code_execution_20250825", Name = "code_execution" }]
};
var message = await client.Beta.Messages.Create(parameters);
Console.WriteLine(message);
}
catch (Exception e) when (e.Message.Contains("skill"))
{
Console.WriteLine($"Skill error: {e.Message}");
}
}
}
```
```go Go nocheck hidelines={1..14,-1}
package main
import (
"context"
"fmt"
"log"
"strings"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 4096,
Betas: []anthropic.AnthropicBeta{"code-execution-2025-08-25", anthropic.AnthropicBetaSkills2025_10_02},
Container: anthropic.BetaMessageNewParamsContainerUnion{
OfContainers: &anthropic.BetaContainerParams{
Skills: []anthropic.BetaSkillParams{
{
Type: anthropic.BetaSkillParamsTypeCustom,
SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
Version: anthropic.String("latest"),
},
},
},
},
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Process data")),
},
Tools: []anthropic.BetaToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.BetaCodeExecutionTool20250825Param{}},
},
})
if err != nil {
if strings.Contains(err.Error(), "skill") {
fmt.Printf("Skill error: %v\n", err)
} else {
log.Fatal(err)
}
return
}
fmt.Println(response)
}
```
```java Java nocheck hidelines={1..4,8..10,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaContainerParams;
import com.anthropic.models.beta.messages.BetaSkillParams;
import com.anthropic.models.beta.messages.BetaCodeExecutionTool20250825;
public class SkillErrorHandling {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
try {
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(4096L)
.addBeta("code-execution-2025-08-25")
.addBeta("skills-2025-10-02")
.container(BetaContainerParams.builder()
.addSkill(BetaSkillParams.builder()
.type(BetaSkillParams.Type.CUSTOM)
.skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
.version("latest")
.build())
.build())
.addUserMessage("Process data")
.addTool(BetaCodeExecutionTool20250825.builder().build())
.build();
BetaMessage response = client.beta().messages().create(params);
System.out.println(response);
} catch (Exception e) {
if (e.getMessage().contains("skill")) {
System.err.println("Skill error: " + e.getMessage());
} else {
throw e;
}
}
}
}
```
```php PHP hidelines={1..4} nocheck
beta->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Process data']
],
model: 'claude-opus-4-7',
betas: ['code-execution-2025-08-25', 'skills-2025-10-02'],
container: [
'skills' => [
[
'type' => 'custom',
'skill_id' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
'version' => 'latest'
]
]
],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
]
);
echo $message->content[0]->text;
} catch (Exception $e) {
if (str_contains($e->getMessage(), 'skill')) {
echo "Skill error: " . $e->getMessage();
} else {
throw $e;
}
}
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
begin
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 4096,
betas: ["code-execution-2025-08-25", "skills-2025-10-02"],
container: {
skills: [
{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: "latest"
}
]
},
messages: [{ role: "user", content: "Process data" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
)
rescue Anthropic::Errors::BadRequestError => e
if e.message.include?("skill")
puts "Skill error: #{e.message}"
else
raise
end
end
```
---
## Data retention
Agent Skills are not covered by ZDR arrangements. Skill definitions and execution data are retained according to Anthropic's standard data retention policy.
For ZDR eligibility across all features, see [API and data retention](/docs/en/manage-claude/api-and-data-retention).
## Next steps
Complete API reference with all endpoints
Best practices for writing effective Skills
Learn about the code execution environment
### MCP
---
# MCP connector
URL: https://platform.claude.com/docs/en/agents-and-tools/mcp-connector
# MCP connector
---
Claude's Model Context Protocol (MCP) connector feature enables you to connect to remote MCP servers directly from the Messages API without a separate MCP client.
**Current version:** This feature requires the beta header: `"anthropic-beta": "mcp-client-2025-11-20"`
The previous version (`mcp-client-2025-04-04`) is deprecated. See [Deprecated version: mcp-client-2025-04-04](#deprecated-version-mcp-client-2025-04-04).
This feature is **not** eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). Data is retained according to the feature's standard retention policy.
## Key features
- **Direct API integration**: Connect to MCP servers without implementing an MCP client
- **Tool calling support**: Access MCP tools through the Messages API
- **Flexible tool configuration**: Enable all tools, allowlist specific tools, or denylist unwanted tools
- **Per-tool configuration**: Configure individual tools with custom settings
- **OAuth authentication**: Support for OAuth Bearer tokens for authenticated servers
- **Multiple servers**: Connect to multiple MCP servers in a single request
## Limitations
- Of the feature set of the [MCP specification](https://modelcontextprotocol.io/introduction#explore-mcp), only [tool calls](https://modelcontextprotocol.io/docs/concepts/tools) are currently supported.
- The server must be publicly exposed through HTTP (supports both Streamable HTTP and SSE transports). Local STDIO servers cannot be connected directly.
- The MCP connector is available on the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry). It is not currently available on Amazon Bedrock or Vertex AI.
## Using the MCP connector in the Messages API
The MCP connector uses two components:
1. **MCP Server Definition** (`mcp_servers` array): Defines server connection details (URL, authentication)
2. **MCP Toolset** (`tools` array): Configures which tools to enable and how to configure them
### Basic example
This example enables all tools from an MCP server with default configuration:
```bash cURL nocheck
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" \
-H "anthropic-beta: mcp-client-2025-11-20" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1000,
"messages": [{"role": "user", "content": "What tools do you have available?"}],
"mcp_servers": [
{
"type": "url",
"url": "https://example-server.modelcontextprotocol.io/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN"
}
],
"tools": [
{
"type": "mcp_toolset",
"mcp_server_name": "example-mcp"
}
]
}'
```
```bash CLI nocheck
ant beta:messages create --beta mcp-client-2025-11-20 <<'YAML'
model: claude-opus-4-7
max_tokens: 1000
messages:
- role: user
content: What tools do you have available?
mcp_servers:
- type: url
url: https://example-server.modelcontextprotocol.io/sse
name: example-mcp
authorization_token: YOUR_TOKEN
tools:
- type: mcp_toolset
mcp_server_name: example-mcp
YAML
```
```python Python nocheck hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-opus-4-7",
max_tokens=1000,
messages=[{"role": "user", "content": "What tools do you have available?"}],
mcp_servers=[
{
"type": "url",
"url": "https://example-server.modelcontextprotocol.io/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN",
}
],
tools=[{"type": "mcp_toolset", "mcp_server_name": "example-mcp"}],
betas=["mcp-client-2025-11-20"],
)
print(response)
```
```typescript TypeScript nocheck hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const response = await anthropic.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 1000,
messages: [
{
role: "user",
content: "What tools do you have available?"
}
],
mcp_servers: [
{
type: "url",
url: "https://example-server.modelcontextprotocol.io/sse",
name: "example-mcp",
authorization_token: "YOUR_TOKEN"
}
],
tools: [
{
type: "mcp_toolset",
mcp_server_name: "example-mcp"
}
],
betas: ["mcp-client-2025-11-20"]
});
console.log(response);
```
```csharp C# nocheck hidelines={1..6}
using Anthropic;
using Anthropic.Models.Beta.Messages;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1000,
Messages = new List
{
new() { Role = Role.User, Content = "What tools do you have available?" }
},
McpServers = new List
{
new()
{
Url = "https://example-server.modelcontextprotocol.io/sse",
Name = "example-mcp",
AuthorizationToken = "YOUR_TOKEN"
}
},
Tools = new List
{
new BetaMcpToolset("example-mcp")
},
Betas = new List { "mcp-client-2025-11-20" }
};
var message = await client.Beta.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1000,
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("What tools do you have available?")),
},
MCPServers: []anthropic.BetaRequestMCPServerURLDefinitionParam{
{
URL: "https://example-server.modelcontextprotocol.io/sse",
Name: "example-mcp",
AuthorizationToken: anthropic.String("YOUR_TOKEN"),
},
},
Tools: []anthropic.BetaToolUnionParam{
{OfMCPToolset: &anthropic.BetaMCPToolsetParam{
MCPServerName: "example-mcp",
}},
},
Betas: []anthropic.AnthropicBeta{
anthropic.AnthropicBetaMCPClient2025_11_20,
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response)
}
```
```java Java nocheck hidelines={1..2,4,6..7}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.BetaMcpToolset;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaRequestMcpServerUrlDefinition;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1000L)
.addUserMessage("What tools do you have available?")
.addMcpServer(BetaRequestMcpServerUrlDefinition.builder()
.url("https://example-server.modelcontextprotocol.io/sse")
.name("example-mcp")
.authorizationToken("YOUR_TOKEN")
.build())
.addTool(BetaMcpToolset.builder()
.mcpServerName("example-mcp")
.build())
.addBeta("mcp-client-2025-11-20")
.build();
BetaMessage response = client.beta().messages().create(params);
IO.println(response);
}
```
```php PHP nocheck hidelines={1..4}
beta->messages->create(
maxTokens: 1000,
messages: [
['role' => 'user', 'content' => 'What tools do you have available?']
],
model: 'claude-opus-4-7',
mcpServers: [
[
'type' => 'url',
'url' => 'https://example-server.modelcontextprotocol.io/sse',
'name' => 'example-mcp',
'authorization_token' => 'YOUR_TOKEN',
],
],
tools: [
[
'type' => 'mcp_toolset',
'mcp_server_name' => 'example-mcp',
],
],
betas: ['mcp-client-2025-11-20'],
);
echo $message;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
client = Anthropic::Client.new
response = client.beta.messages.create(
model: "claude-opus-4-7",
max_tokens: 1000,
messages: [
{ role: "user", content: "What tools do you have available?" }
],
mcp_servers: [
{
type: "url",
url: "https://example-server.modelcontextprotocol.io/sse",
name: "example-mcp",
authorization_token: "YOUR_TOKEN"
}
],
tools: [
{
type: "mcp_toolset",
mcp_server_name: "example-mcp"
}
],
betas: ["mcp-client-2025-11-20"]
)
puts response
```
## MCP server configuration
Each MCP server in the `mcp_servers` array defines the connection details:
```json
{
"type": "url",
"url": "https://example-server.modelcontextprotocol.io/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN"
}
```
### Field descriptions
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `type` | string | Yes | Currently only "url" is supported. |
| `url` | string | Yes | The URL of the MCP server. Must start with https://. |
| `name` | string | Yes | A unique identifier for this MCP server. Must be referenced by exactly one MCPToolset in the `tools` array. |
| `authorization_token` | string | No | OAuth authorization token if required by the MCP server. See [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization). |
## MCP toolset configuration
The MCPToolset lives in the `tools` array and configures which tools from the MCP server are enabled and how they should be configured.
### Basic structure
```json
{
"type": "mcp_toolset",
"mcp_server_name": "example-mcp",
"default_config": {
"enabled": true,
"defer_loading": false
},
"configs": {
"specific_tool_name": {
"enabled": true,
"defer_loading": true
}
}
}
```
### Field descriptions
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `type` | string | Yes | Must be "mcp_toolset". |
| `mcp_server_name` | string | Yes | Must match a server name defined in the `mcp_servers` array. |
| `default_config` | object | No | Default configuration applied to all tools in this set. Individual tool configs in `configs` override these defaults. |
| `configs` | object | No | Per-tool configuration overrides. Keys are tool names, values are configuration objects. |
| `cache_control` | object | No | [Prompt caching](/docs/en/build-with-claude/prompt-caching) cache breakpoint configuration for this toolset. |
### Tool configuration options
Each tool (whether configured in `default_config` or in `configs`) supports the following fields:
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `enabled` | boolean | `true` | Whether this tool is enabled. |
| `defer_loading` | boolean | `false` | If true, tool description is not sent to the model initially. Used with [Tool search tool](/docs/en/agents-and-tools/tool-use/tool-search-tool). |
For the full directory of Anthropic-provided tools and optional properties such as `defer_loading`, see the [Tool reference](/docs/en/agents-and-tools/tool-use/tool-reference). For searching across large tool sets, see [Tool search tool](/docs/en/agents-and-tools/tool-use/tool-search-tool).
### Configuration merging
Configuration values merge with this precedence (highest to lowest):
1. Tool-specific settings in `configs`
2. Set-level `default_config`
3. System defaults
Example:
```json
{
"type": "mcp_toolset",
"mcp_server_name": "google-calendar-mcp",
"default_config": {
"defer_loading": true
},
"configs": {
"search_events": {
"enabled": false
}
}
}
```
Results in:
- `search_events`: `enabled: false` (from configs), `defer_loading: true` (from default_config)
- All other tools: `enabled: true` (system default), `defer_loading: true` (from default_config)
## Common configuration patterns
### Enable all tools with default configuration
The simplest pattern - enable all tools from a server:
```json
{
"type": "mcp_toolset",
"mcp_server_name": "google-calendar-mcp"
}
```
### Allowlist: enable only specific tools
Set `enabled: false` as the default, then explicitly enable specific tools:
```json
{
"type": "mcp_toolset",
"mcp_server_name": "google-calendar-mcp",
"default_config": {
"enabled": false
},
"configs": {
"search_events": {
"enabled": true
},
"create_event": {
"enabled": true
}
}
}
```
### Denylist: disable specific tools
Enable all tools by default, then explicitly disable unwanted tools:
```json
{
"type": "mcp_toolset",
"mcp_server_name": "google-calendar-mcp",
"configs": {
"delete_all_events": {
"enabled": false
},
"share_calendar_publicly": {
"enabled": false
}
}
}
```
### Mixed: allowlist with per-tool configuration
Combine allowlisting with custom configuration for each tool:
```json
{
"type": "mcp_toolset",
"mcp_server_name": "google-calendar-mcp",
"default_config": {
"enabled": false,
"defer_loading": true
},
"configs": {
"search_events": {
"enabled": true,
"defer_loading": false
},
"list_events": {
"enabled": true
}
}
}
```
In this example:
- `search_events` is enabled with `defer_loading: false`
- `list_events` is enabled with `defer_loading: true` (inherited from default_config)
- All other tools are disabled
## Validation rules
The API enforces these validation rules:
- **Server must exist**: The `mcp_server_name` in an MCPToolset must match a server defined in the `mcp_servers` array
- **Server must be used**: Every MCP server defined in `mcp_servers` must be referenced by exactly one MCPToolset
- **Unique toolset per server**: Each MCP server can only be referenced by one MCPToolset
- **Unknown tool names**: If a tool name in `configs` doesn't exist on the MCP server, a backend warning is logged but no error is returned (MCP servers may have dynamic tool availability)
## Response content types
When Claude uses MCP tools, the response includes two new content block types:
### MCP tool use block
```json
{
"type": "mcp_tool_use",
"id": "mcptoolu_014Q35RayjACSWkSj4X2yov1",
"name": "echo",
"server_name": "example-mcp",
"input": { "param1": "value1", "param2": "value2" }
}
```
### MCP tool result block
```json
{
"type": "mcp_tool_result",
"tool_use_id": "mcptoolu_014Q35RayjACSWkSj4X2yov1",
"is_error": false,
"content": [
{
"type": "text",
"text": "Hello"
}
]
}
```
## Multiple MCP servers
You can connect to multiple MCP servers by including multiple server definitions in `mcp_servers` and a corresponding MCPToolset for each in the `tools` array:
```json
{
"model": "claude-opus-4-7",
"max_tokens": 1000,
"messages": [
{
"role": "user",
"content": "Use tools from both mcp-server-1 and mcp-server-2 to complete this task"
}
],
"mcp_servers": [
{
"type": "url",
"url": "https://mcp.example1.com/sse",
"name": "mcp-server-1",
"authorization_token": "TOKEN1"
},
{
"type": "url",
"url": "https://mcp.example2.com/sse",
"name": "mcp-server-2",
"authorization_token": "TOKEN2"
}
],
"tools": [
{
"type": "mcp_toolset",
"mcp_server_name": "mcp-server-1"
},
{
"type": "mcp_toolset",
"mcp_server_name": "mcp-server-2",
"default_config": {
"defer_loading": true
}
}
]
}
```
## Authentication
For MCP servers that require OAuth authentication, you'll need to obtain an access token. The MCP connector beta supports passing an `authorization_token` parameter in the MCP server definition.
API consumers are expected to handle the OAuth flow and obtain the access token prior to making the API call, and to refresh the token as needed.
### Obtaining an access token for testing
The MCP inspector can guide you through the process of obtaining an access token for testing purposes.
1. Run the inspector with the following command. You need Node.js installed on your machine.
```bash
npx @modelcontextprotocol/inspector
```
2. In the sidebar on the left, for "Transport type", select either "SSE" or "Streamable HTTP".
3. Enter the URL of the MCP server.
4. In the right area, click the "Open Auth Settings" button after "Need to configure authentication?".
5. Click "Quick OAuth Flow" and authorize on the OAuth screen.
6. Follow the steps in the "OAuth Flow Progress" section of the inspector and click "Continue" until you reach "Authentication complete".
7. Copy the `access_token` value.
8. Paste it into the `authorization_token` field in your MCP server configuration.
### Using the access token
Once you've obtained an access token using either of the preceding OAuth flows, you can use it in your MCP server configuration:
```json
{
"mcp_servers": [
{
"type": "url",
"url": "https://example-server.modelcontextprotocol.io/sse",
"name": "authenticated-server",
"authorization_token": "YOUR_ACCESS_TOKEN_HERE"
}
]
}
```
For detailed explanations of the OAuth flow, refer to the [Authorization section](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) in the MCP specification.
## Client-side MCP helpers (TypeScript)
If you manage your own MCP client connection (for example, with local stdio servers, MCP prompts, or MCP resources), the TypeScript SDK provides helper functions that convert between MCP types and Claude API types. This eliminates manual conversion code when using the [MCP SDK](https://github.com/modelcontextprotocol/typescript-sdk) alongside the Anthropic SDK.
These helpers are currently available in the TypeScript SDK only.
Use the [`mcp_servers` API parameter](#using-the-mcp-connector-in-the-messages-api) when you have remote servers accessible by URL and only need tool support. Use the client-side helpers when you need local servers, prompts, resources, or more control over the connection with the base SDK.
### Installation
Install both the Anthropic SDK and the MCP SDK:
```bash
npm install @anthropic-ai/sdk @modelcontextprotocol/sdk
```
### Available helpers
Import the helpers from the beta namespace:
```typescript nocheck
import {
mcpTools,
mcpMessages,
mcpResourceToContent,
mcpResourceToFile
} from "@anthropic-ai/sdk/helpers/beta/mcp";
```
| Helper | Description |
|--------|-------------|
| `mcpTools(tools, mcpClient)` | Converts MCP tools to Claude API tools for use with `client.beta.messages.toolRunner()` |
| `mcpMessages(messages)` | Converts MCP prompt messages to Claude API message format |
| `mcpResourceToContent(resource)` | Converts an MCP resource to a Claude API content block |
| `mcpResourceToFile(resource)` | Converts an MCP resource to a file object for upload |
### Use MCP tools
Convert MCP tools for use with the SDK's [tool runner](/docs/en/agents-and-tools/tool-use/tool-runner), which handles tool execution automatically:
```typescript nocheck hidelines={1}
import Anthropic from "@anthropic-ai/sdk";
import { mcpTools } from "@anthropic-ai/sdk/helpers/beta/mcp";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const anthropic = new Anthropic();
// Connect to an MCP server
const transport = new StdioClientTransport({ command: "mcp-server", args: [] });
const mcpClient = new Client({ name: "my-client", version: "1.0.0" });
await mcpClient.connect(transport);
// List tools and convert them for the Claude API
const { tools } = await mcpClient.listTools();
const finalMessage = await anthropic.beta.messages.toolRunner({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "What tools do you have available?" }],
tools: mcpTools(tools, mcpClient)
});
console.log(finalMessage);
```
### Use MCP prompts
Convert MCP prompt messages into Claude API message format:
```typescript nocheck
import { mcpMessages } from "@anthropic-ai/sdk/helpers/beta/mcp";
const { messages } = await mcpClient.getPrompt({ name: "my-prompt" });
const response = await anthropic.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: mcpMessages(messages)
});
console.log(response);
```
### Use MCP resources
Convert MCP resources into content blocks to include in messages, or into file objects for upload:
```typescript nocheck
import { mcpResourceToContent, mcpResourceToFile } from "@anthropic-ai/sdk/helpers/beta/mcp";
// As a content block in a message
const resource = await mcpClient.readResource({ uri: "file:///path/to/doc.txt" });
await anthropic.beta.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
mcpResourceToContent(resource),
{ type: "text", text: "Summarize this document" }
]
}
]
});
// As a file upload
const fileResource = await mcpClient.readResource({ uri: "file:///path/to/data.json" });
await anthropic.beta.files.upload({ file: mcpResourceToFile(fileResource) });
```
### Error handling
The conversion functions throw `UnsupportedMCPValueError` if an MCP value isn't supported by the Claude API. This can happen with unsupported content types, MIME types, or non-HTTP resource links.
## Data retention
The MCP connector is not covered by ZDR arrangements. Data exchanged with MCP servers, including tool definitions and execution results, is retained according to Anthropic's standard data retention policy.
For ZDR eligibility across all features, see [API and data retention](/docs/en/manage-claude/api-and-data-retention).
## Migration guide
If you're using the deprecated `mcp-client-2025-04-04` beta header, follow this guide to migrate to the new version.
### Key changes
1. **New beta header**: Change from `mcp-client-2025-04-04` to `mcp-client-2025-11-20`
2. **Tool configuration moved**: Tool configuration now lives in the `tools` array as MCPToolset objects, not in the MCP server definition
3. **More flexible configuration**: New pattern supports allowlisting, denylisting, and per-tool configuration
### Migration steps
**Before (deprecated):**
```json
{
"model": "claude-opus-4-7",
"max_tokens": 1000,
"messages": [
// ...
],
"mcp_servers": [
{
"type": "url",
"url": "https://mcp.example.com/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN",
"tool_configuration": {
"enabled": true,
"allowed_tools": ["tool1", "tool2"]
}
}
]
}
```
**After (current):**
```json
{
"model": "claude-opus-4-7",
"max_tokens": 1000,
"messages": [
// ...
],
"mcp_servers": [
{
"type": "url",
"url": "https://mcp.example.com/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN"
}
],
"tools": [
{
"type": "mcp_toolset",
"mcp_server_name": "example-mcp",
"default_config": {
"enabled": false
},
"configs": {
"tool1": {
"enabled": true
},
"tool2": {
"enabled": true
}
}
}
]
}
```
### Common migration patterns
| Old pattern | New pattern |
|-------------|-------------|
| No `tool_configuration` (all tools enabled) | MCPToolset with no `default_config` or `configs` |
| `tool_configuration.enabled: false` | MCPToolset with `default_config.enabled: false` |
| `tool_configuration.allowed_tools: [...]` | MCPToolset with `default_config.enabled: false` and specific tools enabled in `configs` |
## Deprecated version: mcp-client-2025-04-04
This version is deprecated. Migrate to `mcp-client-2025-11-20` using the preceding [migration guide](#migration-guide).
The previous version of the MCP connector included tool configuration directly in the MCP server definition:
```json
{
"mcp_servers": [
{
"type": "url",
"url": "https://example-server.modelcontextprotocol.io/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN",
"tool_configuration": {
"enabled": true,
"allowed_tools": ["example_tool_1", "example_tool_2"]
}
}
]
}
```
### Deprecated field descriptions
| Property | Type | Description |
|----------|------|-------------|
| `tool_configuration` | object | **Deprecated**: Use MCPToolset in the `tools` array instead |
| `tool_configuration.enabled` | boolean | **Deprecated**: Use `default_config.enabled` in MCPToolset |
| `tool_configuration.allowed_tools` | array | **Deprecated**: Use allowlist pattern with `configs` in MCPToolset |
---
# Remote MCP servers
URL: https://platform.claude.com/docs/en/agents-and-tools/remote-mcp-servers
# Remote MCP servers
---
Several companies have deployed remote MCP servers that developers can connect to via the Anthropic MCP connector API. These servers expand the capabilities available to developers and end users by providing remote access to various services and tools through the MCP protocol.
The remote MCP servers listed below are third-party services designed to work with the Claude API. These servers
are not owned, operated, or endorsed by Anthropic. Users should only connect to remote MCP servers they trust and
should review each server's security practices and terms before connecting.
## Connecting to remote MCP servers
To connect to a remote MCP server:
1. Review the documentation for the specific server you want to use.
2. Ensure you have the necessary authentication credentials.
3. Follow the server-specific connection instructions provided by each company.
For more information about using remote MCP servers with the Claude API, see the [MCP connector docs](/docs/en/agents-and-tools/mcp-connector).
## Remote MCP server examples
**Looking for more?** [Find hundreds more MCP servers on GitHub](https://github.com/modelcontextprotocol/servers).
### Claude on cloud platforms
---
# Claude in Amazon Bedrock
URL: https://platform.claude.com/docs/en/build-with-claude/claude-in-amazon-bedrock
# Claude in Amazon Bedrock
Access Claude models through Amazon Bedrock with AWS-native authentication, billing, and security boundaries.
---
This guide walks you through setting up and making API calls to Claude in Amazon Bedrock. Claude in Amazon Bedrock runs on AWS-managed infrastructure with zero operator access (Anthropic personnel have no access to the inference infrastructure), letting you build sensitive applications entirely inside the AWS security boundary while using the same Messages API shape you use with Anthropic's first-party API.
This page covers Claude in Amazon Bedrock, which serves Claude through the Messages API at `/anthropic/v1/messages` on AWS-managed infrastructure. The previous Amazon Bedrock integration (the `InvokeModel` and `Converse` APIs with ARN-versioned model identifiers) remains available and is documented at [Claude on Amazon Bedrock (legacy)](/docs/en/build-with-claude/claude-on-amazon-bedrock-legacy). For an Anthropic-operated alternative on AWS with AWS Marketplace billing and typically same-day feature access, see [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws).
## Access
Claude Opus 4.7 and Claude Haiku 4.5 are open to all Amazon Bedrock customers. Claude Mythos Preview requires an invitation; see [Project Glasswing](https://anthropic.com/glasswing). For region availability, see [Regions](#regions).
## Prerequisites
Before you begin, ensure you have:
- An AWS account with [Amazon Bedrock model access](https://console.aws.amazon.com/bedrock/home#/modelaccess) enabled for the Claude models you intend to use.
- The [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) installed and configured (optional, for credential management).
Claude Mythos Preview additionally requires a dedicated AWS account that has been allowlisted by the Bedrock Marketplace team. Your Anthropic account executive can submit your account ID for allowlisting (typically processed within 24 hours), and AWS sends a welcome email once it's complete.
## Authentication
Claude in Amazon Bedrock supports three authentication paths. Choose the one that best fits your security requirements.
### Bedrock service role (recommended)
Use a Bedrock service role with AWS-managed keys for the most secure, long-lived access:
An AWS administrator provisions a Bedrock service role and grants developers `iam:PassRole` permission on the service role ARN.
When calling the API, Bedrock assumes the service role on your behalf. See the [Amazon Bedrock documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-mantle.html) for how to associate the role with your requests.
### IAM assumed roles
For identity-federated access with a 12-hour maximum session:
Create an IAM role scoped to your Claude models. The trust policy names your identity provider (SAML, OIDC, or AWS Identity Center). The permissions policy grants `bedrock-mantle:CreateInference` only on the allowed model ARNs.
Authenticate through your corporate identity provider, then assume the IAM role. AWS STS issues temporary credentials that the SDK or CLI uses to sign requests.
### Bearer tokens
For short-term access without IAM roles (12-hour maximum, least preferred):
Block long-term keys by attaching a policy that denies `bedrock:CallWithBearerToken` unless the `bedrock:BearerTokenType` condition matches a short-term token.
Use the `aws-bedrock-token-generator` CLI to mint a bearer token. Pass it in the `x-api-key` header on each request.
## Install an SDK
Anthropic's [client SDKs](/docs/en/api/client-sdks) support Claude in Amazon Bedrock through a Bedrock-specific package or module.
```bash
pip install -U "anthropic[bedrock]"
```
```bash
npm install @anthropic-ai/bedrock-sdk
```
```bash
dotnet add package Anthropic.Bedrock
```
```bash
go get github.com/anthropics/anthropic-sdk-go/bedrock
```
```kotlin
implementation("com.anthropic:anthropic-java-bedrock:2.30.0")
```
```xml
com.anthropicanthropic-java-bedrock2.30.0
```
```bash
composer require anthropic-ai/sdk aws/aws-sdk-php
```
```bash
# Gemfile
gem "anthropic"
gem "aws-sdk-core"
```
## Making your first request
The endpoint follows the pattern `https://bedrock-mantle.{region}.api.aws/anthropic/v1/messages`. Unlike the `InvokeModel`-based integration, this endpoint uses standard SSE streaming and the same request body shape as Anthropic's first-party API.
The SDK resolves credentials and region using the standard AWS precedence: constructor arguments, then environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_REGION`), then the AWS config file and credential chain (SSO, assumed roles, ECS task role, IMDS).
```bash nocheck
curl https://bedrock-mantle.us-east-1.api.aws/anthropic/v1/messages \
--aws-sigv4 "aws:amz:us-east-1:bedrock-mantle" \
--user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
-H "x-amz-security-token: $AWS_SESSION_TOKEN" \
-H "content-type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "anthropic.claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hello, Claude"}
]
}'
```
The `ant` CLI does not support Amazon Bedrock. Use either cURL or an SDK.
```python nocheck
from anthropic import AnthropicBedrockMantle
client = AnthropicBedrockMantle(aws_region="us-east-1")
message = client.messages.create(
model="anthropic.claude-opus-4-7",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(message.content[0].text)
```
```typescript nocheck
import { AnthropicBedrockMantle } from "@anthropic-ai/bedrock-sdk";
const client = new AnthropicBedrockMantle({
awsRegion: "us-east-1"
});
const message = await client.messages.create({
model: "anthropic.claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello, Claude" }]
});
const block = message.content[0];
if (block.type === "text") {
console.log(block.text);
}
```
```csharp nocheck
using Anthropic.Bedrock;
using Anthropic.Models.Messages;
var client = new AnthropicBedrockMantleClient(new() { AwsRegion = "us-east-1" });
var message = await client.Messages.Create(new()
{
Model = "anthropic.claude-opus-4-7",
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello, Claude" }],
});
if (message.Content[0].Value is TextBlock block)
Console.WriteLine(block.Text);
```
```go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/bedrock"
)
func main() {
client, err := bedrock.NewMantleClient(context.Background(), bedrock.MantleClientConfig{
AWSRegion: "us-east-1",
})
if err != nil {
panic(err)
}
message, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: "anthropic.claude-opus-4-7",
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, Claude")),
},
})
if err != nil {
panic(err)
}
fmt.Println(message.Content[0].Text)
}
```
```java nocheck
import com.anthropic.bedrock.backends.BedrockMantleBackend;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
void main() {
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(BedrockMantleBackend.fromEnv())
.build();
Message message = client.messages().create(
MessageCreateParams.builder()
.model("anthropic.claude-opus-4-7")
.maxTokens(1024)
.addUserMessage("Hello, Claude")
.build()
);
IO.println(message.content().getFirst().asText().text());
}
```
```php nocheck hidelines={1..2}
messages->create(
model: 'anthropic.claude-opus-4-7',
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Hello, Claude'],
],
);
echo $message->content[0]->text;
```
```ruby nocheck
require "anthropic"
client = Anthropic::BedrockMantleClient.new(aws_region: "us-east-1")
message = client.messages.create(
model: "anthropic.claude-opus-4-7",
max_tokens: 1024,
messages: [{role: "user", content: "Hello, Claude"}]
)
puts message.content[0].text
```
You can also use the standard `Anthropic` client: set `base_url` to `https://bedrock-mantle.{region}.api.aws/anthropic` and pass your bearer token as `api_key`. This path supports bearer-token authentication only. SigV4 signing requires the dedicated client.
## Supported models
Model IDs in Claude in Amazon Bedrock carry an `anthropic.` provider prefix. Model capabilities and behaviors are documented on the [Models overview](/docs/en/about-claude/models/overview) page.
| Model | Model ID | Access |
| --------------------- | --------------------------------- | -------------------------------------------------------------------------- |
| Claude Opus 4.7 | `anthropic.claude-opus-4-7` | Open |
| Claude Haiku 4.5 | `anthropic.claude-haiku-4-5` | Open |
| Claude Mythos Preview | `anthropic.claude-mythos-preview` | Invitation only ([Project Glasswing](https://anthropic.com/glasswing)) |
## Feature support
Claude in Amazon Bedrock supports features that run inside the model. Features that require Anthropic-operated infrastructure are not available.
**Supported:**
- Messages API (`/anthropic/v1/messages`)
- Prompt caching
- Extended thinking
- Tool use (client-defined tools)
- Citations
- Structured outputs
**Not supported:**
- Anthropic-defined tools (Web Search, Web Fetch, Remote MCP, Memory, Files API, Computer Use, Skills, Code Execution)
- Claude Managed Agents
- Message Batches API
- `/v1/users` endpoint
## Regions
Claude in Amazon Bedrock is available in the following AWS regions. Amazon Bedrock offers two endpoint types:
- **Global:** dynamic routing across all available regions for maximum availability. No pricing premium.
- **Regional:** the endpoint resolves to the single AWS region you specify, for data-residency requirements. Regional endpoints carry a 10% pricing premium over global endpoints. To route across multiple regions within a geography, use an [inference profile](https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html) (US, EU, JP, or AU). Regions marked **In-region only** in the table support direct single-region routing without an inference profile.
The global endpoint is available for Claude Opus 4.7 and Claude Haiku 4.5. Claude Mythos Preview is regional only and is available in `us-east-1`.
| AWS region | Location | Endpoint types |
| ---------------- | ------------------------- | -------------------- |
| `af-south-1` | Africa (Cape Town) | Global |
| `ap-northeast-1` | Asia Pacific (Tokyo) | Global, JP, In-region only |
| `ap-northeast-2` | Asia Pacific (Seoul) | Global |
| `ap-northeast-3` | Asia Pacific (Osaka) | Global, JP |
| `ap-south-1` | Asia Pacific (Mumbai) | Global |
| `ap-south-2` | Asia Pacific (Hyderabad) | Global |
| `ap-southeast-1` | Asia Pacific (Singapore) | Global |
| `ap-southeast-2` | Asia Pacific (Sydney) | Global, AU |
| `ap-southeast-3` | Asia Pacific (Jakarta) | Global |
| `ap-southeast-4` | Asia Pacific (Melbourne) | Global, AU, In-region only |
| `ca-central-1` | Canada (Central) | Global, US |
| `ca-west-1` | Canada West (Calgary) | Global |
| `eu-central-1` | Europe (Frankfurt) | Global, EU |
| `eu-central-2` | Europe (Zurich) | Global, EU |
| `eu-north-1` | Europe (Stockholm) | Global, EU, In-region only |
| `eu-south-1` | Europe (Milan) | Global, EU |
| `eu-south-2` | Europe (Spain) | Global, EU |
| `eu-west-1` | Europe (Ireland) | Global, EU, In-region only |
| `eu-west-2` | Europe (London) | Global, EU |
| `eu-west-3` | Europe (Paris) | Global, EU |
| `il-central-1` | Israel (Tel Aviv) | Global |
| `me-central-1` | Middle East (UAE) | Global |
| `sa-east-1` | South America (São Paulo) | Global |
| `us-east-1` | US East (N. Virginia) | Global, US, In-region only |
| `us-east-2` | US East (Ohio) | Global, US, In-region only |
| `us-west-1` | US West (N. California) | Global, US |
| `us-west-2` | US West (Oregon) | Global, US, In-region only |
## Quotas
Default quota is 2 million input tokens per minute (TPM). You can request up to 4 million input TPM without additional Anthropic approval. AWS enforces requests-per-minute (RPM) limits on the Bedrock side; contact AWS support for RPM adjustments.
## Data retention
Data handling for this offering is governed by Amazon Bedrock. For details, see [Data protection in Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/data-protection.html).
Zero data retention (ZDR) is available. To enable ZDR for your account, contact AWS support.
## Monitoring and logging
Claude in Amazon Bedrock emits logs to both CloudWatch and CloudTrail. Anthropic recommends retaining activity logs on at least a 30-day rolling basis to understand usage patterns and investigate potential issues.
## Support
For support, contact **bedrock-ant-eap@amazon.com**. Include your AWS account ID and the `request-id` from any failed API responses.
**Claude Mythos Preview** is a research preview model available to invited customers on Amazon Bedrock. For more information, see [Project Glasswing](https://anthropic.com/glasswing).
---
# Claude in Microsoft Foundry
URL: https://platform.claude.com/docs/en/build-with-claude/claude-in-microsoft-foundry
# Claude in Microsoft Foundry
Access Claude models through Microsoft Foundry with Azure-native endpoints and authentication.
---
This guide walks you through the process of setting up and making API calls to Claude in Foundry using one of Anthropic's client SDKs or direct HTTP requests. When you can access Claude in Foundry, you are billed for Claude usage in the Microsoft Marketplace, allowing you to access Claude's latest capabilities while managing costs through your Azure subscription.
Regional availability: At launch, Claude is available as a Global Standard deployment type in Foundry resources. Pricing for Claude in the Microsoft Marketplace uses Anthropic's standard API pricing. Visit [Pricing](https://claude.com/pricing#api) for details.
Foundry is supported by the C#, Java, PHP, Python, and TypeScript SDKs. The Go and Ruby SDKs do not currently support Microsoft Foundry. For available SDK platform integrations, see [Client SDKs](/docs/en/api/client-sdks).
## Preview
In this preview platform integration, Claude models run on Anthropic's infrastructure. This is a commercial integration for billing and access through Azure. As an independent processor for Microsoft, customers using Claude through Microsoft Foundry are subject to Anthropic's data use terms. Anthropic continues to provide its industry-leading safety and data commitments, including zero data retention availability.
## Prerequisites
Before you begin, ensure you have:
- An active Azure subscription
- Access to [Foundry](https://ai.azure.com/)
- The [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) installed (optional, for resource management)
## Install an SDK
Anthropic's [client SDKs](/docs/en/api/client-sdks) support Foundry through a platform-specific package or client class.
```bash
pip install -U "anthropic"
```
```bash
npm install @anthropic-ai/foundry-sdk
```
```bash
dotnet add package Anthropic.Foundry
```
```kotlin
implementation("com.anthropic:anthropic-java-foundry:2.30.0")
```
```xml
com.anthropicanthropic-java-foundry2.30.0
```
```bash
composer require anthropic-ai/sdk
```
## Provisioning
Foundry uses a two-level hierarchy: **resources** contain your security and billing configuration, while **deployments** are the model instances you call via API. You'll first create a Foundry resource, then create one or more Claude deployments within it.
### Provisioning Foundry resources
Create a Foundry resource, which is required to use and manage services in Azure. You can follow these instructions to create a [Foundry resource](https://learn.microsoft.com/en-us/azure/ai-services/multi-service-resource?pivots=azportal#create-a-new-azure-ai-foundry-resource). Alternatively, you can start by creating a [Foundry project](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/create-projects?tabs=ai-foundry), which involves creating a Foundry resource.
To provision your resource:
1. Navigate to the [Foundry portal](https://ai.azure.com/)
2. Create a new Foundry resource or select an existing one
3. Configure access management using Azure-issued API keys or Entra ID (formerly Azure Active Directory) for role-based access control
4. Optionally configure the resource to be part of a private network (Azure Virtual Network) for enhanced security
5. Note your resource name. You'll use this as `{resource}` in API endpoints (for example, `https://{resource}.services.ai.azure.com/anthropic/v1/*`)
### Creating Foundry deployments
After creating your resource, deploy a Claude model to make it available for API calls:
1. In the Foundry portal, navigate to your resource
2. Go to **Models + endpoints** and select **+ Deploy model** > **Deploy base model**
3. Search for and select a Claude model (for example, `claude-sonnet-4-6`)
4. Configure deployment settings:
- **Deployment name:** Defaults to the model ID, but you can customize it (for example, `my-claude-deployment`). The deployment name cannot be changed after it has been created.
- **Deployment type:** Select Global Standard (recommended for Claude)
5. Select **Deploy** and wait for provisioning to complete
6. Once deployed, you can find your endpoint URL and keys under **Keys and Endpoint**
The deployment name you choose becomes the value you pass in the `model` parameter of your API requests. You can create multiple deployments of the same model with different names to manage separate configurations or rate limits.
## Authentication
Claude in Foundry supports two authentication methods: API keys and Entra ID tokens. Both methods use Azure-hosted endpoints in the format `https://{resource}.services.ai.azure.com/anthropic/v1/*`.
### API key authentication
After provisioning your Foundry Claude resource, you can obtain an API key from the Foundry portal:
1. Navigate to your resource in the Foundry portal
2. Go to **Keys and Endpoint** section
3. Copy one of the provided API keys
4. Use either the `api-key` or `x-api-key` header in your requests, or provide it to the SDK
The Foundry SDKs require an API key and either a resource name or base URL. The C#, Java, PHP, Python, and TypeScript SDKs automatically read these from the following environment variables if they are defined:
- `ANTHROPIC_FOUNDRY_API_KEY` - Your API key
- `ANTHROPIC_FOUNDRY_RESOURCE` - Your resource name (for example, `example-resource`)
- `ANTHROPIC_FOUNDRY_BASE_URL` - Alternative to resource name; the full base URL (for example, `https://example-resource.services.ai.azure.com/anthropic/`)
The `resource` and `base_url` parameters are mutually exclusive. Provide either the resource name (which the SDK uses to construct the URL as `https://{resource}.services.ai.azure.com/anthropic/`) or the full base URL directly.
**Example using API key:**
```bash cURL nocheck
curl https://{resource}.services.ai.azure.com/anthropic/v1/messages \
-H "content-type: application/json" \
-H "api-key: YOUR_AZURE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hello!"}
]
}'
```
```bash CLI nocheck
# ant reads ANTHROPIC_API_KEY and sends it as x-api-key, which Foundry accepts
export ANTHROPIC_API_KEY="YOUR_AZURE_API_KEY"
ant messages create \
--base-url https://example-resource.services.ai.azure.com/anthropic \
--model claude-opus-4-7 \
--max-tokens 1024 \
--message '{role: user, content: "Hello!"}' \
--transform content
```
```python nocheck
import os
from anthropic import AnthropicFoundry
client = AnthropicFoundry(
api_key=os.environ.get("ANTHROPIC_FOUNDRY_API_KEY"),
resource="example-resource", # your resource name
)
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
print(message.content)
```
```typescript nocheck
import AnthropicFoundry from "@anthropic-ai/foundry-sdk";
const client = new AnthropicFoundry({
apiKey: process.env.ANTHROPIC_FOUNDRY_API_KEY,
resource: "example-resource" // your resource name
});
const message = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }]
});
console.log(message.content);
```
```csharp nocheck
using Anthropic.Foundry;
using Anthropic.Models.Messages;
var client = new AnthropicFoundryClient(
new AnthropicFoundryApiKeyCredentials(
Environment.GetEnvironmentVariable("ANTHROPIC_FOUNDRY_API_KEY")!,
"example-resource"
)
);
var response = await client.Messages.Create(new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello!" }],
});
Console.WriteLine(
string.Join("", response.Content
.Select(block => block.Value)
.OfType()
.Select(textBlock => textBlock.Text)));
```
```java Java nocheck
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.foundry.backends.FoundryBackend;
import com.anthropic.models.messages.MessageCreateParams;
void main() {
// Requires env vars: ANTHROPIC_FOUNDRY_API_KEY, ANTHROPIC_FOUNDRY_RESOURCE
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(FoundryBackend.fromEnv())
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(1024)
.addUserMessage("Hello!")
.build();
client.messages().create(params).content().stream()
.flatMap(block -> block.text().stream())
.forEach(textBlock -> System.out.println(textBlock.text()));
}
```
```php PHP nocheck
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Hello!']
],
model: 'claude-opus-4-7',
);
echo $message->content[0]->text;
```
The Anthropic Ruby SDK does not currently support Microsoft Foundry. You can use the standard `Anthropic::Client` with a custom `base_url` pointing to your Foundry endpoint, but Azure-specific authentication (Entra ID) is not built in. For full Foundry support, use the C#, Java, PHP, Python, or TypeScript SDKs.
Keep your API keys secure. Never commit them to version control or share them publicly. Anyone with access to your API key can make requests to Claude through your Foundry resource.
### Microsoft Entra authentication
For enhanced security and centralized access management, you can use Entra ID tokens:
1. Enable Entra authentication for your Foundry resource
2. Obtain an access token from Entra ID
3. Use the token in the `Authorization: Bearer {TOKEN}` header
**Example using Entra ID:**
```bash cURL nocheck
# Get Microsoft Entra ID token
ACCESS_TOKEN=$(az account get-access-token --resource https://cognitiveservices.azure.com --query accessToken -o tsv)
# Make request with token. Replace {resource} with your resource name
curl https://{resource}.services.ai.azure.com/anthropic/v1/messages \
-H "content-type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hello!"}
]
}'
```
```python nocheck
import os
from anthropic import AnthropicFoundry
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
# Get Microsoft Entra ID token using token provider pattern
token_provider = get_bearer_token_provider(
DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
)
# Create client with Entra ID authentication
client = AnthropicFoundry(
resource="example-resource", # your resource name
azure_ad_token_provider=token_provider, # Use token provider for Entra ID auth
)
# Make request
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
print(message.content)
```
```typescript nocheck
import AnthropicFoundry from "@anthropic-ai/foundry-sdk";
import { DefaultAzureCredential, getBearerTokenProvider } from "@azure/identity";
// Get Entra ID token using token provider pattern
const credential = new DefaultAzureCredential();
const tokenProvider = getBearerTokenProvider(
credential,
"https://cognitiveservices.azure.com/.default"
);
// Create client with Entra ID authentication
const client = new AnthropicFoundry({
resource: "example-resource", // your resource name
azureADTokenProvider: tokenProvider // Use token provider for Entra ID auth
});
// Make request
const message = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }]
});
console.log(message.content);
```
```csharp nocheck
using Anthropic.Foundry;
using Anthropic.Models.Messages;
using Azure.Identity;
var client = new AnthropicFoundryClient(
new AnthropicFoundryIdentityTokenCredentials(
new DefaultAzureCredential(),
"example-resource"
)
);
var response = await client.Messages.Create(new MessageCreateParams
{
Model = "claude-opus-4-7",
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello!" }],
});
Console.WriteLine(
string.Join("", response.Content
.Select(block => block.Value)
.OfType()
.Select(textBlock => textBlock.Text)));
```
```java Java nocheck hidelines={1..2,4,8}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.foundry.backends.FoundryBackend;
import com.anthropic.models.messages.MessageCreateParams;
import com.azure.identity.AuthenticationUtil;
import com.azure.identity.DefaultAzureCredentialBuilder;
import java.util.function.Supplier;
void main() {
Supplier bearerTokenSupplier = AuthenticationUtil.getBearerTokenSupplier(
new DefaultAzureCredentialBuilder().build(),
"https://cognitiveservices.azure.com/.default"
);
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(FoundryBackend.builder()
.bearerTokenSupplier(bearerTokenSupplier)
.resource("example-resource")
.build())
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(1024)
.addUserMessage("Hello!")
.build();
client.messages().create(params).content().stream()
.flatMap(block -> block.text().stream())
.forEach(textBlock -> System.out.println(textBlock.text()));
}
```
```php PHP nocheck
messages->create(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Hello!']
],
model: 'claude-opus-4-7',
);
echo $message->content[0]->text;
```
The Anthropic Ruby SDK does not currently support Microsoft Foundry. You can use the standard `Anthropic::Client` with a custom `base_url` pointing to your Foundry endpoint, but Azure-specific authentication (Entra ID) is not built in. For full Foundry support, use the C#, Java, PHP, Python, or TypeScript SDKs.
Microsoft Entra ID authentication allows you to manage access using Azure RBAC, integrate with your organization's identity management, and avoid managing API keys manually.
## Correlation request IDs
Foundry includes request identifiers in HTTP response headers for debugging and tracing. When contacting support, provide both the `request-id` and `apim-request-id` values to help teams quickly locate and investigate your request across both Anthropic and Azure systems.
## Feature support
Claude in Foundry supports most of Claude's powerful features. You can find all the features currently supported in [Features overview](/docs/en/build-with-claude/overview).
### Context window
Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6 have a [1M-token context window](/docs/en/build-with-claude/context-windows) on Microsoft Foundry. Other Claude models, including Sonnet 4.5, have a 200k-token context window.
### Features not supported
- Admin API (`/v1/organizations/*` endpoints)
- Compliance API (`/v1/compliance/*` endpoints)
- Models API (`/v1/models`)
- Message Batches API (`/v1/messages/batches`)
## API responses
API responses from Claude in Foundry follow the standard [Claude API response format](/docs/en/api/messages/create). This includes the `usage` object in response bodies, which provides detailed token consumption information for your requests. The `usage` object is consistent across all platforms (Claude API, Foundry, Claude Platform on AWS, Amazon Bedrock, and Vertex AI).
For details on response headers specific to Foundry, see [Correlation request IDs](#correlation-request-ids).
## API model IDs and deployments
The following Claude models are available through Foundry. The latest generation models (Opus 4.7, Opus 4.6, Sonnet 4.6, and Haiku 4.5) offer the most advanced capabilities:
| Model | Default deployment name |
| :---------------- | :-------------------------- |
| Claude Opus 4.7 | `claude-opus-4-7` |
| Claude Opus 4.6 | `claude-opus-4-6` |
| Claude Opus 4.5 | `claude-opus-4-5` |
| Claude Opus 4.1 | `claude-opus-4-1` |
| Claude Sonnet 4.6 | `claude-sonnet-4-6` |
| Claude Sonnet 4.5 | `claude-sonnet-4-5` |
| Claude Haiku 4.5 | `claude-haiku-4-5` |
By default, deployment names match the model IDs shown in the preceding table. However, you can create custom deployments with different names in the Foundry portal to manage different configurations, versions, or rate limits. Use the deployment name (not necessarily the model ID) in your API requests.
## Monitoring and logging
Azure provides comprehensive monitoring and logging capabilities for your Claude usage through standard Azure patterns:
- **Azure Monitor:** Track API usage, latency, and error rates
- **Azure Log Analytics:** Query and analyze request/response logs
- **Cost Management:** Monitor and forecast costs associated with Claude usage
Anthropic recommends logging your activity on at least a 30-day rolling basis to understand usage patterns and investigate any potential issues.
Azure's logging services are configured within your Azure subscription. Enabling logging does not provide Microsoft or Anthropic access to your content beyond what's necessary for billing and service operation.
## Troubleshooting
### Authentication errors
**Error:** `401 Unauthorized` or `Invalid API key`
- **Solution:** Verify your API key is correct. You can obtain a new API key from the Foundry portal under **Keys and Endpoint** for your Foundry resource.
- **Solution:** If using Microsoft Entra ID, ensure your access token is valid and hasn't expired. Tokens typically expire after 1 hour.
**Error:** `403 Forbidden`
- **Solution:** Your Azure account may lack the necessary permissions. Ensure you have the appropriate Azure RBAC role assigned (for example, "Cognitive Services OpenAI User").
### Rate limiting
**Error:** `429 Too Many Requests`
- **Solution:** You've exceeded your rate limit. Implement exponential backoff and retry logic in your application.
- **Solution:** Consider requesting rate limit increases through the Azure portal or Azure support.
#### Rate limit headers
Foundry does not include Anthropic's standard rate limit headers (`anthropic-ratelimit-tokens-limit`, `anthropic-ratelimit-tokens-remaining`, `anthropic-ratelimit-tokens-reset`, `anthropic-ratelimit-input-tokens-limit`, `anthropic-ratelimit-input-tokens-remaining`, `anthropic-ratelimit-input-tokens-reset`, `anthropic-ratelimit-output-tokens-limit`, `anthropic-ratelimit-output-tokens-remaining`, and `anthropic-ratelimit-output-tokens-reset`) in responses. Manage rate limiting through Azure's monitoring tools instead.
### Model and deployment errors
**Error:** `Model not found` or `Deployment not found`
- **Solution:** Verify you're using the correct deployment name. If you haven't created a custom deployment, use the default model ID (for example, `claude-sonnet-4-6`).
- **Solution:** Ensure the model/deployment is available in your Azure region.
**Error:** `Invalid model parameter`
- **Solution:** The model parameter should contain your deployment name, which can be customized in the Foundry portal. Verify the deployment exists and is properly configured.
[Claude Mythos Preview](https://anthropic.com/glasswing) is a research preview available to invited customers on Microsoft Foundry. For more information, see [Project Glasswing](https://anthropic.com/glasswing).
## Additional resources
- **Foundry documentation:** [ai.azure.com/catalog](https://ai.azure.com/catalog/publishers/anthropic)
- **Azure pricing:** [azure.microsoft.com/en-us/pricing/details/ai-foundry](https://azure.microsoft.com/en-us/pricing/details/ai-foundry/#pricing)
- **Anthropic pricing details:** [Model pricing](/docs/en/about-claude/pricing#model-pricing)
- **Authentication guide:** See [Authentication](#authentication)
- **Azure portal:** [portal.azure.com](https://portal.azure.com/)
---
# Claude on Amazon Bedrock (legacy)
URL: https://platform.claude.com/docs/en/build-with-claude/claude-on-amazon-bedrock-legacy
# Claude on Amazon Bedrock (legacy)
The legacy Amazon Bedrock integration for Claude models, using InvokeModel and Converse APIs with ARN-versioned model identifiers.
---
This page covers the legacy Amazon Bedrock integration: the `InvokeModel` and `Converse` APIs with ARN-versioned model identifiers and AWS event-stream encoding. For models available on the Messages-API Bedrock endpoint, see [Claude in Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock), which uses the Messages API at `/anthropic/v1/messages` with SSE streaming. For an Anthropic-operated alternative with AWS Marketplace billing and typically same-day feature access, see [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws). Existing Bedrock users can follow the [migration guide](/docs/en/build-with-claude/claude-platform-on-aws#migrating-from-amazon-bedrock).
Calling Claude through Bedrock slightly differs from how you would call Claude on the Claude API directly. This guide walks you through completing an API call to Claude on Bedrock using one of Anthropic's [client SDKs](/docs/en/api/client-sdks).
Note that this guide assumes you have already signed up for an [AWS account](https://portal.aws.amazon.com/billing/signup) and configured programmatic access.
## Install and configure the AWS CLI
1. [Install a version of the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html) at or newer than version `2.13.23`
2. Configure your AWS credentials using the AWS configure command (see [Configure the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html)) or find your credentials by navigating to "Command line or programmatic access" within your AWS dashboard and following the directions in the popup modal.
3. Verify that your credentials are working:
```bash AWS CLI
aws sts get-caller-identity
```
## Install an SDK for accessing Bedrock
Anthropic's [client SDKs](/docs/en/api/client-sdks) support Bedrock. You can also use an AWS SDK like `boto3` directly.
```bash
pip install -U "anthropic[bedrock]"
```
```bash
npm install @anthropic-ai/bedrock-sdk
```
```bash
dotnet add package Anthropic.Bedrock
```
```bash
go get github.com/anthropics/anthropic-sdk-go/bedrock
```
```groovy Gradle
implementation("com.anthropic:anthropic-java-bedrock:2.30.0")
```
```xml Maven
com.anthropicanthropic-java-bedrock2.30.0
```
```java Java nocheck hidelines={7..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.bedrock.backends.BedrockBackend;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
public class BasicMessage {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(BedrockBackend.fromEnv())
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_6)
.maxTokens(1024L)
.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()));
}
}
```
```bash
composer require anthropic-ai/sdk aws/aws-sdk-php
```
```bash
# Gemfile
gem "anthropic"
gem "aws-sdk-bedrockruntime"
```
```bash
pip install "boto3>=1.28.59"
```
## Accessing Bedrock
### Subscribe to Anthropic models
Go to the [AWS Console > Bedrock > Model Access](https://console.aws.amazon.com/bedrock/home?region=us-west-2#/modelaccess) and request access to Anthropic models. Note that Anthropic model availability varies by region. See [AWS documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/models-regions.html) for latest information.
#### API model IDs
Claude Opus 4.7 is reachable through `InvokeModel` on `bedrock-runtime`.
These requests are served by the same infrastructure as the
[Claude in Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock)
endpoint. For the native Messages API request shape and full feature
parity, use that page. Claude Opus 4.7 is omitted from the model table
on this page because it does not have an ARN-versioned model ID.
Lifecycle terms (Deprecated, Retired) are defined in [Model deprecations](/docs/en/about-claude/model-deprecations); a "Retiring" annotation gives the platform's announced retirement date. The dates in the following table are the **Amazon Bedrock** schedule, which AWS sets independently. A model's lifecycle status and dates here can differ from the Anthropic-operated schedule on the Model deprecations page.
| Model | Base Bedrock model ID | `global` | `us` | `eu` | `jp` | `apac` |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- |
| Claude Opus 4.6 | anthropic.claude-opus-4-6-v1 | Yes | Yes | Yes | Yes | Yes |
| Claude Sonnet 4.6 | anthropic.claude-sonnet-4-6 | Yes | Yes | Yes | Yes | No |
| Claude Sonnet 4.5 | anthropic.claude-sonnet-4-5-20250929-v1:0 | Yes | Yes | Yes | Yes | No |
| Claude Sonnet 4 Deprecated. Retiring October 14, 2026. | anthropic.claude-sonnet-4-20250514-v1:0 | Yes | Yes | Yes | No | Yes |
| Claude Sonnet 3.7 Retired April 28, 2026. | anthropic.claude-3-7-sonnet-20250219-v1:0 | No | No | No | No | No |
| Claude Opus 4.5 | anthropic.claude-opus-4-5-20251101-v1:0 | Yes | Yes | Yes | No | No |
| Claude Opus 4.1 | anthropic.claude-opus-4-1-20250805-v1:0 | No | Yes | No | No | No |
| Claude Opus 4 Deprecated. Retiring May 31, 2026. | anthropic.claude-opus-4-20250514-v1:0 | No | Yes | No | No | No |
| Claude Haiku 4.5 | anthropic.claude-haiku-4-5-20251001-v1:0 | Yes | Yes | Yes | No | No |
| Claude Haiku 3.5 Deprecated. Retiring June 19, 2026. | anthropic.claude-3-5-haiku-20241022-v1:0 | No | Yes | No | No | No |
For more information about regional vs global model IDs, see the [Global vs regional endpoints](#global-vs-regional-endpoints) section below.
### List available models
The following examples show how to print a list of all the Claude models available through Bedrock:
```bash AWS CLI
aws bedrock list-foundation-models --region=us-west-2 --by-provider anthropic --query "modelSummaries[*].modelId"
```
```python Boto3 (Python) nocheck
import boto3
bedrock = boto3.client(service_name="bedrock")
response = bedrock.list_foundation_models(byProvider="anthropic")
for summary in response["modelSummaries"]:
print(summary["modelId"])
```
```typescript TypeScript nocheck
import { BedrockClient, ListFoundationModelsCommand } from "@aws-sdk/client-bedrock";
const client = new BedrockClient({ region: "us-west-2" });
const command = new ListFoundationModelsCommand({ byProvider: "anthropic" });
const response = await client.send(command);
if (response.modelSummaries) {
for (const summary of response.modelSummaries) {
console.log(summary.modelId);
}
}
```
```csharp C# nocheck
using System;
using System.Threading.Tasks;
using Amazon;
using Amazon.Bedrock;
using Amazon.Bedrock.Model;
public class ListAnthropicModels
{
public static async Task Main(string[] args)
{
var client = new AmazonBedrockClient(RegionEndpoint.USWest2);
var request = new ListFoundationModelsRequest
{
ByProvider = "anthropic"
};
var response = await client.ListFoundationModelsAsync(request);
foreach (var summary in response.ModelSummaries)
{
Console.WriteLine(summary.ModelId);
}
}
}
```
```go Go nocheck hidelines={1..2,11..12,-1}
package main
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/bedrock"
)
func main() {
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-west-2"))
if err != nil {
log.Fatal(err)
}
client := bedrock.NewFromConfig(cfg)
byProvider := "anthropic"
response, err := client.ListFoundationModels(context.TODO(), &bedrock.ListFoundationModelsInput{
ByProvider: &byProvider,
})
if err != nil {
log.Fatal(err)
}
for _, summary := range response.ModelSummaries {
fmt.Println(*summary.ModelId)
}
}
```
```java Java nocheck hidelines={6..8,-2..}
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.bedrock.BedrockClient;
import software.amazon.awssdk.services.bedrock.model.ListFoundationModelsRequest;
import software.amazon.awssdk.services.bedrock.model.ListFoundationModelsResponse;
import software.amazon.awssdk.services.bedrock.model.FoundationModelSummary;
public class ListAnthropicModels {
public static void main(String[] args) {
BedrockClient client = BedrockClient.builder()
.region(Region.US_WEST_2)
.build();
ListFoundationModelsRequest request = ListFoundationModelsRequest.builder()
.byProvider("anthropic")
.build();
ListFoundationModelsResponse response = client.listFoundationModels(request);
for (FoundationModelSummary summary : response.modelSummaries()) {
System.out.println(summary.modelId());
}
client.close();
}
}
```
```php PHP nocheck
'us-west-2',
'version' => 'latest'
]);
$result = $client->listFoundationModels([
'byProvider' => 'anthropic'
]);
foreach ($result['modelSummaries'] as $summary) {
echo $summary['modelId'] . PHP_EOL;
}
```
```ruby Ruby nocheck
require "aws-sdk-bedrock"
client = Aws::Bedrock::Client.new(region: "us-west-2")
response = client.list_foundation_models({
by_provider: "anthropic"
})
response.model_summaries.each do |summary|
puts summary.model_id
end
```
### Making requests
The following examples show how to generate text from Claude on Bedrock:
```bash CLI
# The ant CLI does not support Amazon Bedrock.
```
```python Python nocheck
from anthropic import AnthropicBedrock
client = AnthropicBedrock(
# Authenticate by either providing the keys below or use the default AWS credential providers, such as
# using ~/.aws/credentials or the "AWS_SECRET_ACCESS_KEY" and "AWS_ACCESS_KEY_ID" environment variables.
aws_access_key="",
aws_secret_key="",
# Temporary credentials can be used with aws_session_token.
# Read more at https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html.
aws_session_token="",
# aws_region changes the aws region to which the request is made. By default, the SDK reads AWS_REGION,
# and if that's not present, defaults to us-east-1. Note that the SDK does not read ~/.aws/config for the region.
aws_region="us-west-2",
)
message = client.messages.create(
model="global.anthropic.claude-opus-4-6-v1",
max_tokens=256,
messages=[{"role": "user", "content": "Hello, world"}],
)
print(message.content)
```
```typescript TypeScript nocheck
import AnthropicBedrock from "@anthropic-ai/bedrock-sdk";
const client = new AnthropicBedrock({
// Authenticate by either providing the keys below or use
// the default AWS credential providers, such as
// ~/.aws/credentials or the "AWS_SECRET_ACCESS_KEY" and
// "AWS_ACCESS_KEY_ID" environment variables.
awsAccessKey: "",
awsSecretKey: "",
// Temporary credentials can be used with awsSessionToken.
// Read more at https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html.
awsSessionToken: "",
// awsRegion changes the aws region to which the request
// is made. By default, the SDK reads AWS_REGION, and if
// that's not present, defaults to us-east-1. Note that
// the SDK does not read ~/.aws/config for the region.
awsRegion: "us-west-2"
});
async function main() {
const message = await client.messages.create({
model: "global.anthropic.claude-opus-4-6-v1",
max_tokens: 256,
messages: [{ role: "user", content: "Hello, world" }]
});
console.log(message);
}
main().catch(console.error);
```
```csharp C# nocheck
using Anthropic.Bedrock;
using Anthropic.Models.Messages;
AnthropicBedrockClient client = new(
await AnthropicBedrockCredentialsHelper.FromEnv()
?? throw new InvalidOperationException("AWS credentials not configured.")
);
var response = await client.Messages.Create(new MessageCreateParams
{
Model = "global.anthropic.claude-opus-4-6-v1",
MaxTokens = 256,
Messages = [new() { Role = Role.User, Content = "Hello, world" }],
});
Console.WriteLine(
string.Join("", response.Content
.Where(c => c.Value is TextBlock)
.Select(c => (c.Value as TextBlock)!.Text)));
```
```go Go nocheck hidelines={1..2,10..11,-1}
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/bedrock"
)
func main() {
// Uses default AWS credential provider chain
client := anthropic.NewClient(
bedrock.WithLoadDefaultConfig(context.Background()),
)
message, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: "global.anthropic.claude-opus-4-6-v1",
MaxTokens: 256,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, world")),
},
})
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", message.Content)
}
```
```java Java nocheck hidelines={6..9,-2..}
import com.anthropic.bedrock.backends.BedrockBackend;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
public class BedrockExample {
public static void main(String[] args) {
// Uses default AWS credential provider chain
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(BedrockBackend.fromEnv())
.build();
Message message = client
.messages()
.create(
MessageCreateParams.builder()
.model("global.anthropic.claude-opus-4-6-v1")
.maxTokens(256)
.addUserMessage("Hello, world")
.build()
);
System.out.println(message.content());
}
}
```
```php PHP nocheck
messages->create(
maxTokens: 256,
messages: [
['role' => 'user', 'content' => 'Hello, world']
],
model: 'global.anthropic.claude-opus-4-6-v1',
);
echo $message->content[0]->text;
```
```ruby Ruby nocheck
require "anthropic"
client = Anthropic::BedrockClient.new
message = client.messages.create(
model: "global.anthropic.claude-opus-4-6-v1",
max_tokens: 256,
messages: [{role: "user", content: "Hello, world"}]
)
puts message.content.first.text
```
```python Boto3 (Python) nocheck
import boto3
import json
bedrock = boto3.client(service_name="bedrock-runtime")
body = json.dumps(
{
"max_tokens": 256,
"messages": [{"role": "user", "content": "Hello, world"}],
"anthropic_version": "bedrock-2023-05-31",
}
)
response = bedrock.invoke_model(
body=body, modelId="global.anthropic.claude-opus-4-6-v1"
)
response_body = json.loads(response.get("body").read())
print(response_body.get("content"))
```
See the [client SDKs](/docs/en/api/client-sdks) for more details, and the [official Bedrock documentation](https://docs.aws.amazon.com/bedrock/).
### Bearer token authentication
You can authenticate with Bedrock using bearer tokens instead of AWS credentials. This is useful in corporate environments where teams need access to Bedrock without managing AWS credentials, IAM roles, or account-level permissions.
Bearer token authentication is supported in the C#, Go, and Java SDKs. The PHP, Python, TypeScript, and Ruby SDKs use AWS SigV4 signing only.
The simplest approach is to set the `AWS_BEARER_TOKEN_BEDROCK` environment variable, which each SDK detects automatically when resolving credentials from the environment.
To provide a token programmatically:
```csharp C# nocheck
using Anthropic.Bedrock;
using Anthropic.Models.Messages;
var client = new AnthropicBedrockClient(
new AnthropicBedrockApiTokenCredentials
{
BearerToken = "your-bearer-token",
Region = "us-west-2",
}
);
var response = await client.Messages.Create(new MessageCreateParams
{
Model = "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello!" }],
});
```
```go Go nocheck hidelines={1..2,11..12,-1}
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/bedrock"
"github.com/aws/aws-sdk-go-v2/aws"
)
func main() {
cfg := aws.Config{
Region: "us-west-2",
BearerAuthTokenProvider: bedrock.NewStaticBearerTokenProvider("your-bearer-token"),
}
client := anthropic.NewClient(
bedrock.WithConfig(cfg),
)
message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello!")),
},
})
if err != nil {
panic(err)
}
fmt.Println(message.Content[0].Text)
}
```
```java Java nocheck
import com.anthropic.bedrock.backends.BedrockBackend;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
// Option 1: Set AWS_BEARER_TOKEN_BEDROCK environment variable and use fromEnv()
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(BedrockBackend.fromEnv())
.build();
// Option 2: Provide the token programmatically
client = AnthropicOkHttpClient.builder()
.backend(BedrockBackend.builder()
.apiKey("your-bearer-token")
.build())
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model("us.anthropic.claude-sonnet-4-5-20250929-v1:0")
.maxTokens(1024)
.addUserMessage("Hello!")
.build();
client.messages().create(params).content().stream()
.flatMap(block -> block.text().stream())
.forEach(textBlock -> System.out.println(textBlock.text()));
```
## Activity logging
Bedrock provides an [invocation logging service](https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html) that allows customers to log the prompts and completions associated with your usage.
Anthropic recommends that you log your activity on at least a 30-day rolling basis in order to understand your activity and investigate any potential misuse.
Turning on this service does not give AWS or Anthropic any access to your content.
## Feature support
For all currently supported features on Bedrock, see [Features overview](/docs/en/build-with-claude/overview).
### PDF support on Bedrock
PDF support is available on Bedrock through both the Converse API and InvokeModel API. For detailed information about PDF processing capabilities and limitations, see [Amazon Bedrock PDF support](/docs/en/build-with-claude/pdf-support#amazon-bedrock-pdf-support).
**Important considerations for Converse API users:**
- Visual PDF analysis (charts, images, layouts) requires citations to be enabled
- Without citations, only basic text extraction is available
- For full control without forced citations, use the InvokeModel API
### Context window
Claude Opus 4.6 and Claude Sonnet 4.6 have a [1M-token context window](/docs/en/build-with-claude/context-windows) on Amazon Bedrock. Other Claude models, including Sonnet 4.5 and Sonnet 4 (deprecated), have a 200k-token context window.
Bedrock limits request payloads to 20 MB. When sending large documents or many images, you may reach this limit before the token limit.
## Global vs regional endpoints
Starting with **Claude Sonnet 4.5 and all future models**, Bedrock offers two endpoint types:
- **Global endpoints:** Dynamic routing for maximum availability
- **Regional endpoints:** Guaranteed data routing through specific geographic regions
Regional endpoints include a 10% pricing premium over global endpoints.
This applies to Claude Sonnet 4.5 and future models only. Older models (Claude Sonnet 4 (deprecated), Opus 4 (deprecated), and earlier) maintain their existing pricing structures.
### When to use each option
**Global endpoints (recommended):**
- Provide maximum availability and uptime
- Dynamically route requests to regions with available capacity
- No pricing premium
- Best for applications where data residency is flexible
**Regional endpoints (CRIS):**
- Route traffic through specific geographic regions
- Required for data residency and compliance requirements
- Available for US, EU, Japan, and Asia-Pacific
- 10% pricing premium reflects infrastructure costs for dedicated regional capacity
### Implementation
**Using global endpoints (default for Opus 4.6, Sonnet 4.6, and Sonnet 4.5):**
The model IDs for Claude Opus 4.6, Sonnet 4.6, and Sonnet 4.5 already include the `global.` prefix:
```bash CLI
# The ant CLI does not support Amazon Bedrock.
```
```python Python nocheck
from anthropic import AnthropicBedrock
client = AnthropicBedrock(aws_region="us-west-2")
message = client.messages.create(
model="global.anthropic.claude-opus-4-6-v1",
max_tokens=256,
messages=[{"role": "user", "content": "Hello, world"}],
)
```
```typescript TypeScript nocheck
import AnthropicBedrock from "@anthropic-ai/bedrock-sdk";
const client = new AnthropicBedrock({
awsRegion: "us-west-2"
});
const message = await client.messages.create({
model: "global.anthropic.claude-opus-4-6-v1",
max_tokens: 256,
messages: [{ role: "user", content: "Hello, world" }]
});
```
```csharp C# nocheck
using Anthropic.Bedrock;
using Anthropic.Models.Messages;
// C# Bedrock client uses model IDs with region prefix for global routing
AnthropicBedrockClient client = new(
await AnthropicBedrockCredentialsHelper.FromEnv()
?? throw new InvalidOperationException("AWS credentials not configured.")
);
var response = await client.Messages.Create(new MessageCreateParams
{
// Use "global." prefix for global cross-region inference
Model = "global.anthropic.claude-opus-4-6-v1",
MaxTokens = 256,
Messages = [new() { Role = Role.User, Content = "Hello, world" }],
});
```
```go Go hidelines={1..2,9..10,-1}
package main
import (
"context"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/bedrock"
)
func main() {
// Uses default AWS credential provider chain
client := anthropic.NewClient(
bedrock.WithLoadDefaultConfig(context.Background()),
)
message, _ := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: "global.anthropic.claude-opus-4-6-v1",
MaxTokens: 256,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, world")),
},
})
_ = message
}
```
```java Java nocheck
import com.anthropic.bedrock.backends.BedrockBackend;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
// Uses default AWS credential provider chain
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(BedrockBackend.fromEnv())
.build();
var message = client
.messages()
.create(
MessageCreateParams.builder()
.model("global.anthropic.claude-opus-4-6-v1")
.maxTokens(256)
.addUserMessage("Hello, world")
.build()
);
```
```php PHP nocheck
messages->create(
maxTokens: 256,
messages: [
['role' => 'user', 'content' => 'Hello, world']
],
model: 'global.anthropic.claude-opus-4-6-v1',
);
```
```ruby Ruby nocheck
require "anthropic"
# Default credentials resolve region from AWS_REGION env var
client = Anthropic::BedrockClient.new
message = client.messages.create(
# Use "global." prefix for global cross-region inference
model: "global.anthropic.claude-opus-4-6-v1",
max_tokens: 256,
messages: [{role: "user", content: "Hello, world"}]
)
```
**Using regional endpoints (CRIS):**
To use regional endpoints, replace the `global.` prefix with a regional prefix such as `us.`:
```bash CLI
# The ant CLI does not support Amazon Bedrock.
```
```python Python nocheck
from anthropic import AnthropicBedrock
client = AnthropicBedrock(aws_region="us-west-2")
# Using US regional endpoint (CRIS)
message = client.messages.create(
model="us.anthropic.claude-opus-4-6-v1", # Regional prefix
max_tokens=256,
messages=[{"role": "user", "content": "Hello, world"}],
)
```
```typescript TypeScript nocheck
import AnthropicBedrock from "@anthropic-ai/bedrock-sdk";
const client = new AnthropicBedrock({
awsRegion: "us-west-2"
});
// Using US regional endpoint (CRIS)
const message = await client.messages.create({
model: "us.anthropic.claude-opus-4-6-v1", // Regional prefix
max_tokens: 256,
messages: [{ role: "user", content: "Hello, world" }]
});
```
```csharp C# nocheck
using Anthropic.Bedrock;
using Anthropic.Models.Messages;
AnthropicBedrockClient client = new(
new AnthropicBedrockPrivateKeyCredentials { Region = "us-west-2" }
);
// Using US regional endpoint (CRIS)
var response = await client.Messages.Create(new MessageCreateParams
{
Model = "us.anthropic.claude-opus-4-6-v1", // Regional prefix
MaxTokens = 256,
Messages = [new() { Role = Role.User, Content = "Hello, world" }],
});
```
```go Go hidelines={1..2,9..10,-1}
package main
import (
"context"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/bedrock"
)
func main() {
// Uses default AWS credential provider chain
client := anthropic.NewClient(
bedrock.WithLoadDefaultConfig(context.Background()),
)
// Using US regional endpoint (CRIS)
message, _ := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: "us.anthropic.claude-opus-4-6-v1", // Regional prefix
MaxTokens: 256,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, world")),
},
})
_ = message
}
```
```java Java nocheck
import com.anthropic.bedrock.backends.BedrockBackend;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
// Uses default AWS credential provider chain
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(BedrockBackend.fromEnv())
.build();
// Using US regional endpoint (CRIS)
var message = client
.messages()
.create(
MessageCreateParams.builder()
.model("us.anthropic.claude-opus-4-6-v1") // Regional prefix
.maxTokens(256)
.addUserMessage("Hello, world")
.build()
);
```
```php PHP nocheck
messages->create(
maxTokens: 256,
messages: [
['role' => 'user', 'content' => 'Hello, world']
],
model: 'us.anthropic.claude-opus-4-6-v1',
);
```
```ruby Ruby nocheck
require "anthropic"
# Using US regional endpoint (CRIS)
client = Anthropic::BedrockClient.new(aws_region: "us-west-2")
message = client.messages.create(
model: "us.anthropic.claude-opus-4-6-v1", # Regional prefix
max_tokens: 256,
messages: [{role: "user", content: "Hello, world"}]
)
```
**Claude Mythos Preview** is a research preview model available to invited customers on Amazon Bedrock. For more information, see [Project Glasswing](https://anthropic.com/glasswing).
## Additional resources
- **Bedrock pricing:** [aws.amazon.com/bedrock/pricing](https://aws.amazon.com/bedrock/pricing/)
- **AWS pricing documentation:** [Bedrock pricing guide](https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-pricing.html)
- **AWS blog post:** [Introducing Claude Sonnet 4.5 in Amazon Bedrock](https://aws.amazon.com/blogs/aws/introducing-claude-sonnet-4-5-in-amazon-bedrock-anthropics-most-intelligent-model-best-for-coding-and-complex-agents/)
- **Anthropic pricing details:** [Cloud platform pricing](/docs/en/about-claude/pricing#cloud-platform-pricing)
---
# Claude on Vertex AI
URL: https://platform.claude.com/docs/en/build-with-claude/claude-on-vertex-ai
# Claude on Vertex AI
Anthropic's Claude models are available through [Vertex AI](https://cloud.google.com/vertex-ai).
---
The Vertex API for accessing Claude is nearly-identical to the [Messages API](/docs/en/api/messages/create) and supports all of the same options, with two key differences:
* In Vertex, `model` is not passed in the request body. Instead, it is specified in the Google Cloud endpoint URL.
* In Vertex, `anthropic_version` is passed in the request body (rather than as a header), and must be set to the value `vertex-2023-10-16`.
Vertex is also supported by Anthropic's official [client SDKs](/docs/en/api/client-sdks). This guide walks you through making a request to Claude on Vertex AI using one of Anthropic's client SDKs.
Note that this guide assumes you already have a GCP project that is able to use Vertex AI. See [Anthropic Claude models on Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude) for more information on the setup required and a full walkthrough.
## Install an SDK for accessing Vertex AI
First, install Anthropic's [client SDK](/docs/en/api/client-sdks) for your language of choice.
```bash
pip install -U google-cloud-aiplatform "anthropic[vertex]"
```
```bash
npm install @anthropic-ai/vertex-sdk
```
```bash
dotnet add package Anthropic.Vertex
```
```bash
go get github.com/anthropics/anthropic-sdk-go
```
```groovy Gradle
implementation("com.anthropic:anthropic-java-vertex:2.30.0")
```
```xml Maven
com.anthropicanthropic-java-vertex2.30.0
```
```java Java nocheck hidelines={7..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.vertex.backends.VertexBackend;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
public class BasicMessage {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(VertexBackend.fromEnv())
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024L)
.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()));
}
}
```
```bash
composer require anthropic-ai/sdk google/auth
```
```bash
# Gemfile
gem "anthropic"
gem "googleauth"
```
## Accessing Vertex AI
### Model availability
Note that Anthropic model availability varies by region. Search for "Claude" in the [Vertex AI Model Garden](https://cloud.google.com/model-garden) or go to [Anthropic Claude models](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude) for the latest information.
#### API model IDs
Lifecycle terms (Deprecated, Retired) are defined in [Model deprecations](/docs/en/about-claude/model-deprecations); a "Retiring" annotation gives the platform's announced retirement date. The dates in the following table are the **Vertex AI** schedule, which Google Cloud sets independently. A model's lifecycle status and dates here can differ from the Anthropic-operated schedule on the Model deprecations page.
| Model | Vertex AI API model ID |
| ------------------------------ | ------------------------ |
| Claude Opus 4.7 | claude-opus-4-7 |
| Claude Opus 4.6 | claude-opus-4-6 |
| Claude Sonnet 4.6 | claude-sonnet-4-6 |
| Claude Sonnet 4.5 | claude-sonnet-4-5@20250929 |
| Claude Sonnet 4 Deprecated. Retiring September 14, 2026. | claude-sonnet-4@20250514 |
| Claude Sonnet 3.7 Retired May 11, 2026. | claude-3-7-sonnet@20250219 |
| Claude Opus 4.5 | claude-opus-4-5@20251101 |
| Claude Opus 4.1 | claude-opus-4-1@20250805 |
| Claude Opus 4 Deprecated. Retiring September 14, 2026. | claude-opus-4@20250514 |
| Claude Haiku 4.5 | claude-haiku-4-5@20251001 |
| Claude Haiku 3.5 Deprecated. Retiring July 5, 2026. | claude-3-5-haiku@20241022 |
### Making requests
Before running requests you may need to run `gcloud auth application-default login` to authenticate with GCP.
The following examples show how to generate text from Claude on Vertex AI:
```bash cURL nocheck
MODEL_ID=claude-opus-4-7
LOCATION=global
PROJECT_ID=MY_PROJECT_ID
curl \
-X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
https://$LOCATION-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${LOCATION}/publishers/anthropic/models/${MODEL_ID}:streamRawPredict -d \
'{
"anthropic_version": "vertex-2023-10-16",
"messages": [{
"role": "user",
"content": "Hey Claude!"
}],
"max_tokens": 100
}'
```
```bash CLI
# The ant CLI does not support Vertex AI.
```
```python Python nocheck
from anthropic import AnthropicVertex
project_id = "MY_PROJECT_ID"
region = "global"
client = AnthropicVertex(project_id=project_id, region=region)
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=100,
messages=[
{
"role": "user",
"content": "Hey Claude!",
}
],
)
print(message)
```
```typescript TypeScript nocheck
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
const projectId = "MY_PROJECT_ID";
const region = "global";
// Goes through the standard `google-auth-library` flow.
const client = new AnthropicVertex({
projectId,
region
});
async function main() {
const result = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 100,
messages: [
{
role: "user",
content: "Hey Claude!"
}
]
});
console.log(JSON.stringify(result, null, 2));
}
main();
```
```csharp C# nocheck
using Anthropic;
using Anthropic.Models.Messages;
using Anthropic.Vertex;
var projectId = "MY_PROJECT_ID";
var region = "global";
var client = new AnthropicClient
{
Backend = new VertexBackend(projectId, region)
};
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 100,
Messages = [new() { Role = Role.User, Content = "Hey Claude!" }]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go nocheck hidelines={1..2,10..11,-1}
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/vertex"
)
func main() {
// Uses default Google Cloud credentials
client := anthropic.NewClient(
vertex.WithGoogleAuth(context.Background(), "global", "MY_PROJECT_ID"),
)
message, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 100,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hey Claude!")),
},
})
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", message)
}
```
```java Java nocheck hidelines={6..9,-2..}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.vertex.backends.VertexBackend;
public class VertexExample {
public static void main(String[] args) {
// Uses default Google Cloud credentials
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(VertexBackend.fromEnv())
.build();
Message message = client
.messages()
.create(
MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(100)
.addUserMessage("Hey Claude!")
.build()
);
System.out.println(message);
}
}
```
```php PHP nocheck
messages->create(
maxTokens: 100,
messages: [
['role' => 'user', 'content' => 'Hey Claude!']
],
model: 'claude-opus-4-7',
);
echo $message->content[0]->text;
```
```ruby Ruby nocheck
require "anthropic"
client = Anthropic::VertexClient.new(
region: "global",
project_id: "MY_PROJECT_ID"
)
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 100,
messages: [{role: "user", content: "Hey Claude!"}]
)
puts message.content.first.text
```
See the [client SDKs](/docs/en/api/client-sdks) and the official [Vertex AI docs](https://cloud.google.com/vertex-ai/docs) for more details.
Claude is also available through [Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock), [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry).
## Activity logging
Vertex provides a [request-response logging service](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/request-response-logging) that allows customers to log the prompts and completions associated with your usage.
Anthropic recommends that you log your activity on at least a 30-day rolling basis in order to understand your activity and investigate any potential misuse.
Turning on this service does not give Google or Anthropic any access to your content.
## Feature support
For all currently supported features on Vertex AI, see [API features overview](/docs/en/build-with-claude/overview).
### Context window
Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6 have a [1M-token context window](/docs/en/build-with-claude/context-windows) on Vertex AI. Other Claude models, including Sonnet 4.5 and Sonnet 4 (deprecated), have a 200k-token context window.
Vertex AI limits request payloads to 30 MB. When sending large documents or many images, you may reach this limit before the token limit.
## Global, multi-region, and regional endpoints
Vertex AI offers three endpoint types:
- **Global endpoints:** Dynamic routing for maximum availability
- **Multi-region endpoints:** Dynamic routing within a geographic area (for example, the United States or the European Union) for data residency with high availability
- **Regional endpoints:** Guaranteed data routing through specific geographic regions
Regional and multi-region endpoints include a 10% pricing premium over global endpoints.
This applies to Claude Sonnet 4.5 and future models only. Older models (Claude Sonnet 4 (deprecated), Opus 4 (deprecated), and earlier) maintain their existing pricing structures.
### When to use each option
**Global endpoints (recommended):**
- Provide maximum availability and uptime
- Dynamically route requests to regions with available capacity
- No pricing premium
- Best for applications where data residency is flexible
- Only supports pay-as-you-go traffic (provisioned throughput requires regional endpoints)
**Multi-region endpoints:**
- Dynamically route requests across regions within a geographic area (currently `us` and `eu`)
- Useful when you need data residency within a broad geography but want higher availability than a single region
- 10% pricing premium over global endpoints
- Only supports pay-as-you-go traffic (provisioned throughput requires regional endpoints)
**Regional endpoints:**
- Route traffic through specific geographic regions
- Required for single-region data residency, strict compliance mandates, or provisioned throughput
- Support both pay-as-you-go and provisioned throughput
- 10% pricing premium reflects infrastructure costs for dedicated regional capacity
### Implementation
**Using global endpoints (recommended):**
Set the `region` parameter to `"global"` when initializing the client:
```bash CLI
# The ant CLI does not support Vertex AI.
```
```python Python nocheck
from anthropic import AnthropicVertex
project_id = "MY_PROJECT_ID"
region = "global"
client = AnthropicVertex(project_id=project_id, region=region)
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=100,
messages=[
{
"role": "user",
"content": "Hey Claude!",
}
],
)
print(message)
```
```typescript TypeScript nocheck
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
const projectId = "MY_PROJECT_ID";
const region = "global";
const client = new AnthropicVertex({
projectId,
region
});
const result = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 100,
messages: [
{
role: "user",
content: "Hey Claude!"
}
]
});
```
```csharp C# nocheck
using Anthropic;
using Anthropic.Models.Messages;
using Anthropic.Vertex;
var projectId = "MY_PROJECT_ID";
var region = "global";
var client = new AnthropicClient
{
Backend = new VertexBackend(projectId, region)
};
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 100,
Messages = [new() { Role = Role.User, Content = "Hey Claude!" }]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go nocheck hidelines={1..2,9..10,-1}
package main
import (
"context"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/vertex"
)
func main() {
// Uses default Google Cloud credentials
client := anthropic.NewClient(
vertex.WithGoogleAuth(context.Background(), "global", "MY_PROJECT_ID"),
)
message, _ := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 100,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hey Claude!")),
},
})
_ = message
}
```
```java Java nocheck
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.vertex.backends.VertexBackend;
void main() {
// Uses default Google Cloud credentials
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(
VertexBackend.builder()
.region("global")
.project("MY_PROJECT_ID")
.build()
)
.build();
var message = client
.messages()
.create(
MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(100)
.addUserMessage("Hey Claude!")
.build()
);
IO.println(message);
}
```
```php PHP nocheck
messages->create(
maxTokens: 100,
messages: [
['role' => 'user', 'content' => 'Hey Claude!']
],
model: 'claude-opus-4-7',
);
echo $message->content[0]->text;
```
```ruby Ruby nocheck
require "anthropic"
client = Anthropic::VertexClient.new(
region: "global",
project_id: "MY_PROJECT_ID"
)
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 100,
messages: [{role: "user", content: "Hey Claude!"}]
)
puts message.content.first.text
```
**Using multi-region endpoints:**
Set the `region` parameter to a multi-region identifier: `"us"` for the United States or `"eu"` for the European Union. The SDK routes requests to the corresponding multi-region endpoint (`https://aiplatform.us.rep.googleapis.com` or `https://aiplatform.eu.rep.googleapis.com`), which dynamically balances traffic across regions within that geography.
```bash CLI
# The ant CLI does not support Vertex AI.
```
```python Python nocheck
from anthropic import AnthropicVertex
project_id = "MY_PROJECT_ID"
region = "us" # Multi-region identifier: "us" or "eu"
client = AnthropicVertex(project_id=project_id, region=region)
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=100,
messages=[
{
"role": "user",
"content": "Hey Claude!",
}
],
)
print(message)
```
```typescript TypeScript nocheck
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
const projectId = "MY_PROJECT_ID";
const region = "us"; // Multi-region identifier: "us" or "eu"
const client = new AnthropicVertex({
projectId,
region
});
const result = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 100,
messages: [
{
role: "user",
content: "Hey Claude!"
}
]
});
```
```csharp C# nocheck
using Anthropic;
using Anthropic.Models.Messages;
using Anthropic.Vertex;
var projectId = "MY_PROJECT_ID";
var region = "us"; // Multi-region identifier: "us" or "eu"
var client = new AnthropicClient
{
Backend = new VertexBackend(projectId, region)
};
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 100,
Messages = [new() { Role = Role.User, Content = "Hey Claude!" }]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go nocheck hidelines={1..2,9..10,-1}
package main
import (
"context"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/vertex"
)
func main() {
// Multi-region identifier: "us" or "eu"
client := anthropic.NewClient(
vertex.WithGoogleAuth(context.Background(), "us", "MY_PROJECT_ID"),
)
message, _ := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 100,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hey Claude!")),
},
})
_ = message
}
```
```java Java nocheck
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.vertex.backends.VertexBackend;
void main() {
// Multi-region identifier: "us" or "eu"
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(
VertexBackend.builder()
.region("us")
.project("MY_PROJECT_ID")
.build()
)
.build();
var message = client
.messages()
.create(
MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(100)
.addUserMessage("Hey Claude!")
.build()
);
IO.println(message);
}
```
```php PHP nocheck
messages->create(
maxTokens: 100,
messages: [
['role' => 'user', 'content' => 'Hey Claude!']
],
model: 'claude-opus-4-7',
);
echo $message->content[0]->text;
```
```ruby Ruby nocheck
require "anthropic"
client = Anthropic::VertexClient.new(
region: "us", # Multi-region identifier: "us" or "eu"
project_id: "MY_PROJECT_ID"
)
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 100,
messages: [{role: "user", content: "Hey Claude!"}]
)
puts message.content.first.text
```
**Using regional endpoints:**
Specify a specific region like `"us-east1"` or `"europe-west1"`:
```bash CLI
# The ant CLI does not support Vertex AI.
```
```python Python nocheck
from anthropic import AnthropicVertex
project_id = "MY_PROJECT_ID"
region = "us-east1" # Specify a specific region
client = AnthropicVertex(project_id=project_id, region=region)
message = client.messages.create(
model="claude-opus-4-7",
max_tokens=100,
messages=[
{
"role": "user",
"content": "Hey Claude!",
}
],
)
print(message)
```
```typescript TypeScript nocheck
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
const projectId = "MY_PROJECT_ID";
const region = "us-east1"; // Specify a specific region
const client = new AnthropicVertex({
projectId,
region
});
const result = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 100,
messages: [
{
role: "user",
content: "Hey Claude!"
}
]
});
```
```csharp C# nocheck
using Anthropic;
using Anthropic.Models.Messages;
using Anthropic.Vertex;
var projectId = "MY_PROJECT_ID";
var region = "us-east1";
AnthropicClient client = new()
{
Backend = new VertexBackend(projectId, region)
};
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 100,
Messages = [new() { Role = Role.User, Content = "Hey Claude!" }]
};
var message = await client.Messages.Create(parameters);
Console.WriteLine(message);
```
```go Go nocheck hidelines={1..2,9..10,-1}
package main
import (
"context"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/vertex"
)
func main() {
// Specify a specific region
client := anthropic.NewClient(
vertex.WithGoogleAuth(context.Background(), "us-east1", "MY_PROJECT_ID"),
)
message, _ := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: "claude-opus-4-7",
MaxTokens: 100,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hey Claude!")),
},
})
_ = message
}
```
```java Java nocheck
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.vertex.backends.VertexBackend;
void main() {
// Uses default Google Cloud credentials with specific region
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(
VertexBackend.builder()
.region("us-east1") // Specify a specific region
.project("MY_PROJECT_ID")
.build()
)
.build();
var message = client
.messages()
.create(
MessageCreateParams.builder()
.model("claude-opus-4-7")
.maxTokens(100)
.addUserMessage("Hey Claude!")
.build()
);
IO.println(message);
}
```
```php PHP nocheck
messages->create(
maxTokens: 100,
messages: [
['role' => 'user', 'content' => 'Hey Claude!']
],
model: 'claude-opus-4-7',
);
echo $message->content[0]->text;
```
```ruby Ruby nocheck
require "anthropic"
client = Anthropic::VertexClient.new(
region: "us-east1", # Specify a specific region
project_id: "MY_PROJECT_ID"
)
message = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 100,
messages: [{role: "user", content: "Hey Claude!"}]
)
puts message.content.first.text
```
Claude Mythos Preview is a research preview available to invited customers on Vertex AI. For more information, see [Project Glasswing](https://anthropic.com/glasswing).
## Additional resources
- **Vertex AI pricing:** [cloud.google.com/vertex-ai/generative-ai/pricing](https://cloud.google.com/vertex-ai/generative-ai/pricing)
- **Claude models documentation:** [Claude on Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude)
- **Google blog post:** [Global endpoint for Claude models](https://cloud.google.com/blog/products/ai-machine-learning/global-endpoint-for-claude-models-generally-available-on-vertex-ai)
- **Anthropic pricing details:** [Cloud platform pricing](/docs/en/about-claude/pricing#cloud-platform-pricing)
---
# Claude Platform on AWS
URL: https://platform.claude.com/docs/en/build-with-claude/claude-platform-on-aws
# Claude Platform on AWS
Access Claude's full platform capabilities through AWS with Anthropic-managed infrastructure.
---
Claude Platform on AWS gives you the full Anthropic platform experience, including the Messages API, Agent Skills, code execution, and beta features, accessible through your AWS account. Unlike [Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock), where AWS operates the inference stack, Anthropic operates Claude Platform on AWS. AWS provides the authentication layer (SigV4 or API key), IAM-based access control, and billing integration through AWS Marketplace.
The Anthropic SDKs support Claude Platform on AWS. For per-language client availability, see [Client SDKs](/docs/en/api/client-sdks).
## How the platform integration works
Claude models run on Anthropic-managed infrastructure. This is a commercial integration for billing and access through AWS. Anthropic is the data processor for inference inputs and outputs; AWS processes billing and identity metadata under the marketplace model. Customers using Claude through Claude Platform on AWS are subject to Anthropic's [data use terms](https://www.anthropic.com/legal). Anthropic continues to provide its industry-leading safety and data commitments.
Note the following operational characteristics: data may not reside in AWS; inference may route to Anthropic's primary cloud; and subservices may move under the hood without notice. Set the [`inference_geo`](#data-residency) parameter per request to pin inference to a specific geography.
Claude Platform on AWS follows the same data retention policy as the first-party Claude API. Zero Data Retention (ZDR) is available on request. Contact your Anthropic account representative to enable it for your organization.
## Claude Platform on AWS vs Amazon Bedrock
Both offerings let you use Claude through AWS, but they differ significantly in architecture, API surface, and feature availability.
| Aspect | Claude Platform on AWS | [Claude in Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock) | [Amazon Bedrock (legacy)](/docs/en/build-with-claude/claude-on-amazon-bedrock-legacy) |
| :--- | :--- | :--- | :--- |
| **Who operates the stack** | Anthropic | AWS | AWS |
| **API surface** | Anthropic Messages API (`/v1/messages`) | Anthropic Messages API at `/anthropic/v1/messages` | Bedrock Converse / InvokeModel |
| **Feature availability** | Typically same-day as Claude API (see [feature limitations](#features-not-currently-available)) | Per Amazon Bedrock release schedule | Per Amazon Bedrock release schedule |
| **Agent Skills** | Available (beta) | Not available (requires code execution) | Not available |
| **Beta features** | Pass through with `anthropic-beta` headers (see [feature limitations](#features-not-currently-available)) | `anthropic-beta` header not supported | `anthropic-beta` header not supported |
| **Authentication** | AWS IAM / SigV4 or API key | AWS IAM / SigV4 | AWS IAM / SigV4 or bearer token (C#, Go, and Java SDKs only) |
| **Billing** | AWS Marketplace | AWS (native service) | AWS (native service) |
| **Base URL** | `aws-external-anthropic.{region}.api.aws` | `bedrock-mantle.{region}.api.aws` | `bedrock-runtime.{region}.amazonaws.com` |
| **SDK client** | Platform-specific client class (for example, `AnthropicAWS` in Python), in beta | `AnthropicBedrockMantle` | `AnthropicBedrock` / Bedrock SDK |
| **Console** | Claude Console (`platform.claude.com`, access through the AWS Console) | Bedrock Console | Bedrock Console |
| **Rate limits and quotas** | Managed by Anthropic | Managed by AWS | Managed by AWS |
| **Inference data processor** | Anthropic | AWS | AWS |
If you need AWS-operated Claude, see [Claude in Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock). Claude Platform on AWS uses a separate capacity pool from both the first-party Claude API and Amazon Bedrock. You can run workloads on more than one platform and fail over between them.
[AWS PrivateLink](https://docs.aws.amazon.com/vpc/latest/privatelink/what-is-privatelink.html) is supported for connecting your VPC to the Claude Platform on AWS endpoint.
**When to choose Bedrock:** Organizations in regulated industries that require FedRAMP High, IL4, IL5, or HIPAA-ready compliance, or that need AWS to be the sole data processor, should use [Claude in Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock). Bedrock runs entirely on AWS-controlled infrastructure with AWS as the operating party.
## Set up your account
Setting up Claude Platform on AWS happens in four phases: sign up on the AWS Console service page, complete your Anthropic organization setup, note your workspace ID, and sign in to the Claude Console.
Signing up through the AWS Console provisions a new Anthropic organization tied to your AWS account. This organization is separate from any existing organizations your company has with Anthropic, including Claude Enterprise organizations procured through AWS Marketplace. API keys, workspaces, and Claude Console settings from a first-party Anthropic organization don't carry over.
If you have an existing Amazon Bedrock private offer, contact your Anthropic or AWS account representative before signing up so your discount applies from your first request. Discounts cannot be applied retroactively to usage incurred before your private offer is accepted. See [Private offers](/docs/en/about-claude/pricing#private-offers).
1. Open the [AWS Console](https://console.aws.amazon.com/) and navigate to the **Claude Platform on AWS** service page.
2. Choose **Sign up**.
3. On the Sign-up page, review the terms (Anthropic's End User License Agreement, the AWS Privacy Notice, and the AWS Customer Agreement) and select the agreement checkbox.
4. Choose **Continue**.
The page shows a **Sign-up in progress** banner. Stay on the page. Sign-up takes a few minutes while AWS handles the AWS Marketplace subscription for you, then redirects you automatically.
If your organization has a private offer from Anthropic, the Console looks it up and prompts you to accept it in AWS Marketplace. See [Private offers](/docs/en/about-claude/pricing#private-offers) for details.
If you use Claude Platform on AWS, your content (such as prompts and completions) is processed by Anthropic outside of AWS. See Anthropic's [data use policies](https://www.anthropic.com/legal) for details on how content and metadata are processed and stored.
After sign-up completes, you're redirected to `platform.claude.com/partner-signup`.
1. Enter the email address of your organization's owner and choose **Get started**.
2. Check that email inbox for a setup link and follow it. If your browser shows a **Signed in as a different account** page, choose **Log out and continue**.
3. Complete the organization details form (organization name, entity type, country, intended use) and choose **Complete setup**.
Completing setup creates your Anthropic organization and accepts Anthropic's Commercial Terms of Service and Usage Policy. The AWS Console service page now shows a left navigation with **Home**, **API keys**, **Quickstart**, and **Workspaces**.
After you complete setup, the AWS Console prompts you to create a workspace. See [Workspaces](#workspaces) for details on region binding, IAM resource scoping, and creating additional workspaces.
Find the workspace ID under **Workspaces** on the AWS Console **Claude Platform on AWS** service page or in the [Claude Console](#using-the-claude-console). Workspace IDs use the format `wrkspc_` followed by an alphanumeric identifier.
Access to the Claude Console is federated through AWS IAM:
1. Assume an IAM role with the `aws-external-anthropic:AssumeConsole` permission. See [IAM actions for Claude Platform on AWS](/docs/en/api/claude-platform-on-aws-iam-actions#console-access).
2. From the **Claude Platform on AWS** service page, choose **Open Claude Console**. The AWS Console issues a JWT and redirects you to `platform.claude.com`.
3. On first sign-in, you're prompted for an email address. Enter your work email. The platform provisions your Claude Console user just-in-time.
When you're signed in through the AWS Console, the Claude Console scopes to your Claude Platform on AWS organization. An **Account managed by AWS** indicator appears in the bottom-left of the Claude Console sidebar.
### Troubleshooting account setup
- **"Sign-up failed: Failed to enable OutboundWebIdentityFederation":** If you see this banner on first submit, choose **Continue** again. The IAM enablement can take a moment to take effect.
- **No progress indicator during sign-up:** Sign-up takes a few minutes. The page shows a static **Sign-up in progress** banner without a progress bar while AWS provisions your account.
- **"Signed in as a different account" after following the setup link:** Choose **Log out and continue**. The page reauthenticates you with the email address you entered.
- **"Not found" message during sign-in:** This message might appear briefly during redirect. You can dismiss it.
- **Usage page shows no data after your first API call:** Usage data can take a few minutes to appear in the Claude Console.
## Before making API calls
Ensure you have:
1. An active AWS account with a subscription to Claude Platform on AWS (see [Set up your account](#set-up-your-account))
2. The [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html) installed and configured
3. **Outbound web identity federation enabled** on your AWS account (a one-time setup step; see [Enable outbound web identity federation](#enable-outbound-web-identity-federation))
4. Your workspace ID (see [Obtain your workspace ID](#obtain-your-workspace-id))
### Enable outbound web identity federation
The Claude Platform on AWS gateway calls `sts:GetWebIdentityToken` server-side to mint a JWT it forwards to Anthropic. This STS capability is **disabled by default** on every AWS account. Enable it once per account:
```bash CLI
aws iam enable-outbound-web-identity-federation
```
If the response is `[ERROR] (FeatureEnabled) ... already enabled`, the setting is already on for your account and you can move on. Verify and retrieve your account's issuer URL:
```bash CLI
aws iam get-outbound-web-identity-federation-info
```
Without this step, every request returns `"Outbound web identity federation is disabled for your account"`. This is the most common setup error.
### Obtain your workspace ID
You create a workspace from the AWS Console after completing account setup (see [Set up your account](#set-up-your-account)). Workspaces are bound to a single AWS region. You can find the workspace ID in the [Claude Console](#using-the-claude-console) under **Workspaces** or in the **Workspaces** section of the AWS Console service page.
Set the `ANTHROPIC_AWS_WORKSPACE_ID` and `AWS_REGION` environment variables so the SDK clients read them automatically:
```bash CLI
export ANTHROPIC_AWS_WORKSPACE_ID='wrkspc_01AbCdEf23GhIj'
export AWS_REGION='us-west-2' # Your workspace's AWS region
```
The region is required. The SDK client raises an error if no region is set. Pass `aws_region`/`awsRegion` to the constructor, or set `AWS_REGION` (or `AWS_DEFAULT_REGION`). All AWS commercial regions are supported.
## Authentication
Claude Platform on AWS supports two authentication methods: AWS IAM with SigV4 request signing (primary) and API key authentication. Both use the same base URL and request format.
### SigV4 authentication
SigV4 is the enterprise-native path and integrates with your existing AWS IAM policies, roles, and auditing. Configure AWS credentials using any method supported by the [AWS default credential provider chain](https://docs.aws.amazon.com/sdkref/latest/guide/standardized-credentials.html):
- Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`)
- Shared credentials file (`~/.aws/credentials`)
- Shared config file (`~/.aws/config`) including SSO and `credential_process`
- Web identity (`AWS_WEB_IDENTITY_TOKEN_FILE` and `AWS_ROLE_ARN`) for IRSA and GitHub Actions
- ECS container credentials
- EC2 instance metadata service (IMDS)
Verify that your credentials are working:
```bash CLI
aws sts get-caller-identity
```
### API key authentication
For simpler integration paths (local development and scripts), you can authenticate with an API key instead of SigV4. Set the `ANTHROPIC_AWS_API_KEY` environment variable or pass `apiKey` to the SDK constructor.
Generate API keys in the **AWS Console** under **Claude Platform on AWS → API keys**. Choose **Generate a key**, then copy the key value. Grant the `aws-external-anthropic:CallWithBearerToken` IAM action to the principals that should be allowed to use API key authentication.
API keys for Claude Platform on AWS are managed in the AWS Console, not the Claude Console. Keys created in the standard [Claude Console](https://platform.claude.com/) (for first-party API access) don't work with the Claude Platform on AWS endpoint.
#### Short-term API keys
For workloads that need to hand a credential to a separate process (such as an LLM gateway, a serverless function, or a tool that supports bearer-token authentication but not SigV4), generate a short-term API key from your AWS credentials instead of provisioning a long-lived key in the AWS Console.
AWS publishes token-generator libraries for [JavaScript](https://github.com/aws/token-generator-for-aws-external-anthropic-js), [Python](https://github.com/aws/token-generator-for-aws-external-anthropic-python), and [Java](https://github.com/aws/token-generator-for-aws-external-anthropic-java). Each library reads your AWS credentials through the standard provider chain and returns a time-limited token that works with the `x-api-key` header. Token lifetime defaults to 12 hours and is capped at the lesser of your requested duration, your AWS credentials' expiry, and 12 hours. See the linked repository READMEs for installation and full configuration options.
Pass the generated token to the SDK the same way you'd pass an AWS Console-generated API key:
```python Python nocheck
from token_generator_for_aws_external_anthropic import TokenGenerator
from anthropic import AnthropicAWS
token = TokenGenerator(region="us-west-2").get_token()
client = AnthropicAWS(api_key=token, aws_region="us-west-2")
```
```typescript TypeScript nocheck
import { getTokenProvider } from "@aws/token-generator-for-aws-external-anthropic";
import AnthropicAws from "@anthropic-ai/aws-sdk";
const tokenProvider = getTokenProvider({ region: "us-west-2" });
const token = await tokenProvider();
const client = new AnthropicAws({ apiKey: token, awsRegion: "us-west-2" });
```
```java Java nocheck
import software.amazon.awsexternalanthropic.TokenGenerator;
import software.amazon.awssdk.regions.Region;
import com.anthropic.aws.backends.AwsBackend;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
void main() {
String token = TokenGenerator.builder().region(Region.US_WEST_2).build().getToken();
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(AwsBackend.builder()
.apiKey(token)
.region(Region.US_WEST_2)
.workspaceId(System.getenv("ANTHROPIC_AWS_WORKSPACE_ID"))
.build())
.build();
}
```
If you can generate the token locally, your process already has SigV4 credentials, and SigV4 authentication is usually the simpler choice. Use short-term keys when the process making API calls is separate from the process that holds AWS credentials.
The SDK does not refresh short-term keys automatically. When a token expires, generate a new one and construct a new client. The principal that uses the token still needs the `aws-external-anthropic:CallWithBearerToken` IAM action.
### Credential precedence
The platform-specific client resolves authentication in the following order. Argument names vary by language convention (TypeScript and PHP use camelCase as shown; Python and Ruby use snake_case; Go uses PascalCase with capitalized acronyms; C# and Java use the language's property or builder idioms).
1. `apiKey` constructor argument → `x-api-key` header
2. `awsAccessKey` + `awsSecretAccessKey` constructor arguments → AWS SigV4
3. `awsProfile` constructor argument → AWS SigV4 with named profile
4. `ANTHROPIC_AWS_API_KEY` environment variable → `x-api-key` header
5. Default AWS credential provider chain → AWS SigV4
### Region resolution
The client reads `AWS_REGION` from the environment if `aws_region`/`awsRegion` is not passed to the constructor, falling back to `AWS_DEFAULT_REGION` for compatibility with the standard AWS SDKs. Region is required; there is no fallback default. Unlike `AnthropicBedrock`, which falls back to `us-east-1`, the `AnthropicAWS`/`AnthropicAws` client raises an error if neither the constructor argument nor the environment variable is set.
## Install an SDK
Anthropic's [client SDKs](/docs/en/api/client-sdks) support Claude Platform on AWS. Each SDK provides a platform-specific client class that handles SigV4 signing, region-based base URL construction, and the `anthropic-workspace-id` header.
```bash
pip install -U "anthropic[aws]"
```
On macOS with Homebrew Python or other externally managed Python environments, `pip install` can fail with a PEP 668 `externally-managed-environment` error. Create and activate a virtual environment first: `python3 -m venv .venv && source .venv/bin/activate`.
```bash
npm install @anthropic-ai/aws-sdk
```
```bash
dotnet add package Anthropic.Aws
```
```bash
go get github.com/anthropics/anthropic-sdk-go
```
```kotlin Gradle
implementation("com.anthropic:anthropic-java-aws:2.30.0")
```
```xml Maven
com.anthropicanthropic-java-aws2.30.0
```
```bash
composer require anthropic-ai/sdk aws/aws-sdk-php
```
```bash
gem install anthropic aws-sdk-core
```
SDK clients for Claude Platform on AWS are in beta.
## Available models
The following models are available on Claude Platform on AWS:
| Model | Model ID |
| :--- | :--- |
| Claude Opus 4.7 | `claude-opus-4-7` |
| Claude Opus 4.6 | `claude-opus-4-6` |
| Claude Sonnet 4.6 | `claude-sonnet-4-6` |
| Claude Opus 4.5 | `claude-opus-4-5` |
| Claude Sonnet 4.5 | `claude-sonnet-4-5` |
| Claude Haiku 4.5 | `claude-haiku-4-5` |
Model IDs are identical to the first-party Claude API. There are no Bedrock-style ARNs or `anthropic.` prefixes.
New models launch on Claude Platform on AWS simultaneously with the first-party Claude API.
## Making requests
Claude Platform on AWS uses the Anthropic Messages API (`/v1/messages`), the same API surface as the first-party Claude API. The differences are the base URL, the authentication method, and a required `anthropic-workspace-id` header that identifies which [workspace](#workspaces) the request targets.
```bash cURL nocheck
# Replace us-west-2 with your AWS region in both the URL and --aws-sigv4
curl "https://aws-external-anthropic.us-west-2.api.aws/v1/messages" \
--aws-sigv4 "aws:amz:us-west-2:aws-external-anthropic" \
--user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
-H "x-amz-security-token: $AWS_SESSION_TOKEN" \
-H "content-type: application/json" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-workspace-id: $ANTHROPIC_AWS_WORKSPACE_ID" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hello!"}
]
}'
```
```python Python nocheck
from anthropic import AnthropicAWS
client = AnthropicAWS()
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
print(message)
```
```typescript TypeScript nocheck
import AnthropicAws from "@anthropic-ai/aws-sdk";
const client = new AnthropicAws();
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }]
});
console.log(message);
```
```csharp C# nocheck
using Anthropic;
using Anthropic.Aws;
var client = new AnthropicAwsClient();
var message = await client.Messages.Create(new()
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello!" }]
});
Console.WriteLine(message);
```
```go Go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
anthropicaws "github.com/anthropics/anthropic-sdk-go/aws"
)
func main() {
client, err := anthropicaws.NewClient(context.Background(), anthropicaws.ClientConfig{})
if err != nil {
panic(err)
}
message, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello!")),
},
})
if err != nil {
panic(err)
}
fmt.Println(message)
}
```
```java Java nocheck
import com.anthropic.aws.backends.AwsBackend;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
void main() {
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(AwsBackend.fromEnv())
.build();
Message message = client.messages().create(
MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(1024)
.addUserMessage("Hello!")
.build()
);
IO.println(message);
}
```
```php PHP nocheck hidelines={1}
messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello!']],
);
echo $message;
```
```ruby Ruby nocheck
require "anthropic"
client = Anthropic::AWSClient.new
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }]
)
puts message
```
The client reads `AWS_REGION` (or `AWS_DEFAULT_REGION`) and `ANTHROPIC_AWS_WORKSPACE_ID` from the environment. You can override either by passing `aws_region` / `awsRegion` or `workspace_id` / `workspaceId` to the constructor. Both region and workspace ID are required. The constructor raises an error if the workspace ID cannot be resolved; a missing region likewise raises an error.
The `x-amz-security-token` header (cURL) is only required for temporary credentials such as IAM roles, SSO, or STS. Omit it when using long-term IAM user credentials. The SDK clients handle this automatically based on the credential source.
The `--aws-sigv4` value follows the format `aws:amz::`. The SigV4 service name is `aws-external-anthropic`, and the region must match the region in your endpoint URL. A mismatch in either produces a generic signature-rejection error rather than a specific diagnostic.
### Context window
Context-window sizes on Claude Platform on AWS are identical to the first-party Claude API. See [Context windows](/docs/en/build-with-claude/context-windows) for per-model limits.
## Feature support
Claude Platform on AWS uses the Anthropic Messages API directly, which means you get full Messages API feature parity with the first-party Claude API (except where noted in the [feature limitations](#features-not-currently-available)):
- **Feature access:** Because Anthropic operates both platforms, most new features and beta headers become available on Claude Platform on AWS without a separate integration step. See [feature limitations](#features-not-currently-available) for exceptions.
- **Beta features:** Pass the standard `anthropic-beta` header to access beta features, just as you would with the Claude API.
- **Agent Skills:** Use pre-built and custom [Agent Skills](/docs/en/agents-and-tools/agent-skills/overview) with the same `container.skills` parameter and beta headers as the Claude API. All pre-built Skills (PowerPoint, Excel, Word, PDF) work out of the box.
- **Code execution:** Run code in Anthropic's managed sandbox using the [code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool).
- **Tool use:** Computer use and all other [tool use capabilities](/docs/en/agents-and-tools/tool-use/overview) are available.
- **Extended thinking:** Enable extended thinking with the same parameters as the Claude API.
- **Streaming:** Full SSE streaming support for real-time responses.
- **Batch processing:** Submit batch requests for high-throughput workloads.
- **Prompt caching:** Cache tools, system prompts, and message history to reduce latency and cost. All prompt caching capabilities (5-minute TTL, 1-hour TTL, and automatic caching) are available.
- **Files API:** Upload and reference files across requests.
See the [comparison table](#claude-platform-on-aws-vs-amazon-bedrock) for feature-availability differences from Amazon Bedrock.
### Claude Managed Agents
[Claude Managed Agents](/docs/en/managed-agents/overview) is available on Claude Platform on AWS, including agents, environments, sessions, credential vaults, and memory stores. The [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview) is also supported.
Session behavior on Claude Platform on AWS differs from first-party Claude Managed Agents in one way:
- **Autonomous-session reauthentication:** A session can run autonomously, without any [user events](/docs/en/managed-agents/events-and-streaming#event-types), for up to 6 hours. After 6 hours, the session requires reauthentication before it continues. To reauthenticate, send any user-role event to the session (see [Events and streaming](/docs/en/managed-agents/events-and-streaming)). First-party Claude Managed Agents has no autonomous-session runtime limit.
### Features not currently available
The following capabilities are not currently available on Claude Platform on AWS:
- **HIPAA readiness:** Anthropic's HIPAA-ready program is not available. See [API and data retention](/docs/en/manage-claude/api-and-data-retention).
- **Admin API:** Workspace endpoints (create, get, list, update, and archive on `/v1/organizations/workspaces`) are available. Other Admin API endpoints (organization members, workspace members, invites, API keys, usage reports, cost reports, and rate limit reports) are not currently available. View usage and cost data in the [Claude Console](#using-the-claude-console) instead. AWS IAM manages organization membership.
- **Workspace member management:** Adding or removing users from individual workspaces is not available. AWS IAM policies on workspace ARNs control access.
- **Spend limits:** Not available. Rely on AWS billing controls instead.
- **Claude Code workspace and Analytics API:** The Claude Code workspace with automatic rate limits is not available. Claude Code usage appears in the general usage view rather than a dedicated screen.
- **OAuth authentication:** Not supported. Use SigV4 or API key authentication.
- **Fast mode:** Not available on Claude Platform on AWS.
- **OpenAI-compatible API endpoints:** Not available on Claude Platform on AWS.
- **Workspace-level inference geography controls:** `allowed_inference_geos` and `default_inference_geo` are not available. Set `inference_geo` on each request instead.
## Data residency
Claude Platform on AWS supports the following inference geographies:
- **US:** Inference stays within US data centers. A 1.1x pricing multiplier applies.
- **Global:** Inference can route to any Anthropic-operated data center worldwide. Standard pricing applies.
The AWS region your workspace is bound to controls which gateway endpoint you call and where AWS-side resources (IAM, CloudTrail, billing) are scoped. It does not pin where model inference runs. To pin inference to a specific geography, set `inference_geo` on each request.
Set the inference geography per request with the `inference_geo` parameter:
The `inference_geo` parameter is supported on Claude Opus 4.6, Claude Sonnet 4.6, and later models. Requests with `inference_geo` on Claude Opus 4.5, Claude Sonnet 4.5, or Claude Haiku 4.5 return a 400 error. See [Data residency](/docs/en/manage-claude/data-residency) for model availability details.
```bash cURL nocheck
# Replace us-west-2 with your AWS region in both the URL and --aws-sigv4
curl "https://aws-external-anthropic.us-west-2.api.aws/v1/messages" \
--aws-sigv4 "aws:amz:us-west-2:aws-external-anthropic" \
--user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
-H "x-amz-security-token: $AWS_SESSION_TOKEN" \
-H "content-type: application/json" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-workspace-id: $ANTHROPIC_AWS_WORKSPACE_ID" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"inference_geo": "us",
"messages": [
{"role": "user", "content": "Hello!"}
]
}'
```
```python Python nocheck
from anthropic import AnthropicAWS
client = AnthropicAWS()
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
inference_geo="us",
messages=[{"role": "user", "content": "Hello!"}],
)
print(message)
```
```typescript TypeScript nocheck
import AnthropicAws from "@anthropic-ai/aws-sdk";
const client = new AnthropicAws();
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
inference_geo: "us",
messages: [{ role: "user", content: "Hello!" }]
});
console.log(message);
```
```csharp C# nocheck
using Anthropic;
using Anthropic.Aws;
var client = new AnthropicAwsClient();
var message = await client.Messages.Create(new()
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 1024,
InferenceGeo = "us",
Messages = [new() { Role = Role.User, Content = "Hello!" }]
});
Console.WriteLine(message);
```
```go Go nocheck hidelines={1..11,-1}
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
anthropicaws "github.com/anthropics/anthropic-sdk-go/aws"
)
func main() {
client, err := anthropicaws.NewClient(context.Background(), anthropicaws.ClientConfig{})
if err != nil {
panic(err)
}
message, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 1024,
InferenceGeo: anthropic.String("us"),
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello!")),
},
})
if err != nil {
panic(err)
}
fmt.Println(message)
}
```
```java Java nocheck
import com.anthropic.aws.backends.AwsBackend;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
void main() {
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(AwsBackend.fromEnv())
.build();
Message message = client.messages().create(
MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(1024)
.inferenceGeo("us")
.addUserMessage("Hello!")
.build()
);
IO.println(message);
}
```
```php PHP nocheck hidelines={1}
messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
inferenceGeo: 'us',
messages: [['role' => 'user', 'content' => 'Hello!']],
);
echo $message;
```
```ruby Ruby nocheck
require "anthropic"
client = Anthropic::AWSClient.new
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
inference_geo: "us",
messages: [{ role: "user", content: "Hello!" }]
)
puts message
```
If you omit `inference_geo`, the request defaults to `global`.
Workspace-level inference geography controls (`allowed_inference_geos` and `default_inference_geo`) are not available on Claude Platform on AWS. Set `inference_geo` on each request instead.
## Workspaces
Inference and resource requests on Claude Platform on AWS target a workspace. You pass the workspace's ID in the `anthropic-workspace-id` header on these API calls. Workspace IDs use the tagged format `wrkspc_` followed by an alphanumeric identifier (for example, `wrkspc_01AbCdEf23GhIj`). See [Obtain your workspace ID](#obtain-your-workspace-id) if you don't have it yet.
### Workspace scoping
Workspaces are bound to a single AWS region. A workspace created in `us-west-2` can only be accessed through the `us-west-2` endpoint. Usage, quotas, cost, files, batches, and Skills all roll up per workspace, giving you per-region breakdowns in the Claude Console.
Workspaces also serve as the primary IAM resource for Claude Platform on AWS. You grant or deny access to specific workspaces through AWS IAM policies using the workspace ARN. The ARN's resource segment is the same `wrkspc_`-prefixed ID you pass in the `anthropic-workspace-id` header:
```text
arn:aws:aws-external-anthropic:{region}:{account-id}:workspace/{workspace-id}
```
For example:
```text
arn:aws:aws-external-anthropic:us-west-2:123456789012:workspace/wrkspc_01AbCdEf23GhIj
```
See [IAM policies](#iam-policies) for policy examples.
### Managing workspaces
Create additional workspaces, rename a workspace, or archive a workspace from the AWS Console **Workspaces** page or with the [Admin API](/docs/en/manage-claude/admin-api) workspace endpoints. A new workspace is bound to the AWS region of the endpoint you call to create it (see [Workspace scoping](#workspace-scoping)). The Claude Console Workspaces page is read-only.
## Using the Claude Console
Claude Platform on AWS uses the standard Claude Console at [platform.claude.com](https://platform.claude.com). When you sign in from the AWS Console, an **Account managed by AWS** indicator appears in the bottom-left of the Claude Console sidebar and the Console scopes to your Claude Platform on AWS organization. It provides usage analytics, cost breakdowns, rate limit visibility, workspace visibility, and pages for managing files, Agent Skills, batch jobs, and Claude Managed Agents resources (agents, sessions, environments, credential vaults, and memory stores).
### Signing in
Access to the Claude Console is federated through AWS IAM. See [Set up your account](#set-up-your-account) for the full first-time sign-in flow. In short:
1. Assume an IAM role with the `aws-external-anthropic:AssumeConsole` permission. See [IAM actions for Claude Platform on AWS](/docs/en/api/claude-platform-on-aws-iam-actions#console-access).
2. Navigate to the Claude Platform on AWS page in the [AWS Console](https://console.aws.amazon.com/).
3. Choose **Open Claude Console**. The AWS Console issues a JWT and redirects you to `platform.claude.com`.
4. On first sign-in, you're prompted for an email address; enter your work email. The platform provisions your Claude Console user just-in-time.
Two Claude Console roles are available: **Admin** and **Developer**. The Admin role grants access to all Claude Console pages and settings available for Claude Platform on AWS. The Developer role grants read access to usage, cost, rate limit, and workspace information. Contact your Anthropic account representative to assign the Admin or Developer role to a principal.
### Available pages
The **Through AWS gateway** column indicates whether the page reads and writes data through the AWS gateway (and is therefore governed by [IAM actions](/docs/en/api/claude-platform-on-aws-iam-actions)). Pages marked **No** read organization-level metadata directly through Anthropic APIs and bypass IAM action checks.
| Page | Available | Through AWS gateway | Notes |
| :--- | :--- | :--- | :--- |
| **Usage** | Yes | No | View token usage by model, workspace, and dimension. Data can take a few minutes to appear after a request. |
| **Cost** | Yes | No | View cost breakdowns by model and workspace. AWS Cost Explorer shows the aggregated [Claude Consumption Unit (CCU)](#billing) line item. |
| **Limits** | Yes | No | View rate limits (read-only). |
| **Workspaces** | Yes | No | View per-region workspaces (read-only). |
| **Files** | Yes | Yes | View and manage uploaded files. |
| **Skills** | Yes | Yes | View and manage Agent Skills. |
| **Batches** | Yes | Yes | View and manage batch processing jobs. |
| **Agents** | Yes | Yes | View and manage agent definitions. |
| **Sessions** | Yes | Yes | View agent sessions and event history. |
| **Environments** | Yes | Yes | View and manage cloud container configurations for sessions. |
| **Credential vaults** | Yes | Yes | View and manage credential vaults for session authentication. |
| **Memory stores** | Yes | Yes | View and manage persistent agent memory. |
| **API keys** | No | N/A | Manage API keys in the AWS Console (**Claude Platform on AWS → API keys**). See [API key authentication](#api-key-authentication). |
| **Members** | No | N/A | Not applicable. AWS IAM manages access. |
| **Billing** | No | N/A | Not applicable. AWS Marketplace manages billing and invoicing. View cost breakdowns on the Cost page. |
| **Claude Code** | No | N/A | View Claude Code usage on the Usage page. |
### Switching organizations
The Claude Console does not support organization switching for Claude Platform on AWS. To access a different organization, sign out and reauthenticate through the AWS Console using the IAM role for that organization's AWS account.
## Rate limits and quotas
Claude Platform on AWS assigns Tier 1 rate limits on sign-up. Anthropic manages rate limits directly, not through AWS quota systems.
Unlike the first-party Claude API, automatic tier advancement does not apply. If you need higher limits, contact your Anthropic account representative. For tier details and per-model limits, see [Rate limits](/docs/en/api/rate-limits).
## Billing
Claude Platform on AWS bills through [AWS Marketplace](https://aws.amazon.com/marketplace). Usage is denominated in Claude Consumption Units (CCUs), metered hourly, and invoiced monthly in arrears on your AWS bill. CCUs are not prepaid credits; there is no CCU balance or commitment.
For the CCU price, conversion mechanics, discount application, and per-model token rates, see [Claude Platform on AWS pricing](/docs/en/about-claude/pricing#claude-platform-on-aws-pricing).
## Monitoring and logging
AWS CloudTrail can capture all requests to Claude Platform on AWS. Workspace and vault operations are logged as Management events by default. Inference, batch, file, skill, model, user profile, and Claude Managed Agents operations (other than vaults) are classified as Data events and require explicit data event logging configuration, which incurs additional CloudTrail charges. See the [IAM actions reference](/docs/en/api/claude-platform-on-aws-iam-actions#route-to-action-mapping) for the full event type classification and the [AWS CloudTrail documentation](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/) for configuration details.
### Request IDs
Each response includes two request IDs in the response headers:
- **AWS request ID (`x-amzn-requestid`):** The primary ID, indexed in CloudTrail. Use this when investigating requests through AWS tooling or when contacting AWS support.
- **Anthropic request ID (`request-id`):** The secondary ID. Use this when contacting Anthropic support.
```python Python nocheck
from anthropic import AnthropicAWS
client = AnthropicAWS()
response = client.messages.with_raw_response.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.headers.get("x-amzn-requestid")) # AWS request ID
print(response.headers.get("request-id")) # Anthropic request ID
message = response.parse()
print(message.content)
```
```typescript TypeScript nocheck
import AnthropicAws from "@anthropic-ai/aws-sdk";
const client = new AnthropicAws();
const { data: message, response } = await client.messages
.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }]
})
.withResponse();
console.log(response.headers.get("x-amzn-requestid")); // AWS request ID
console.log(response.headers.get("request-id")); // Anthropic request ID
console.log(message.content);
```
```csharp C# nocheck
using Anthropic;
using Anthropic.Aws;
var client = new AnthropicAwsClient();
var response = await client.WithRawResponse.Messages.Create(new()
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello!" }]
});
Console.WriteLine(response.Headers.GetValues("x-amzn-requestid").First()); // AWS request ID
Console.WriteLine(response.Headers.GetValues("request-id").First()); // Anthropic request ID
Console.WriteLine(response.Value.Content);
```
```go Go nocheck hidelines={1..13,-1}
package main
import (
"context"
"fmt"
"net/http"
"github.com/anthropics/anthropic-sdk-go"
anthropicaws "github.com/anthropics/anthropic-sdk-go/aws"
"github.com/anthropics/anthropic-sdk-go/option"
)
func main() {
client, err := anthropicaws.NewClient(context.Background(), anthropicaws.ClientConfig{})
if err != nil {
panic(err)
}
var response *http.Response
message, err := client.Messages.New(
context.Background(),
anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello!")),
},
},
option.WithResponseInto(&response),
)
if err != nil {
panic(err)
}
fmt.Println(response.Header.Get("x-amzn-requestid")) // AWS request ID
fmt.Println(response.Header.Get("request-id")) // Anthropic request ID
fmt.Println(message.Content)
}
```
```java Java nocheck
import com.anthropic.aws.backends.AwsBackend;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.http.HttpResponseFor;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
void main() {
AnthropicClient client = AnthropicOkHttpClient.builder()
.backend(AwsBackend.fromEnv())
.build();
HttpResponseFor response = client.messages().withRawResponse().create(
MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(1024)
.addUserMessage("Hello!")
.build()
);
IO.println(response.headers().values("x-amzn-requestid").get(0)); // AWS request ID
IO.println(response.requestId().orElse(null)); // Anthropic request ID
IO.println(response.parse().content());
}
```
```php PHP nocheck hidelines={1}
messages->raw->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello!']],
);
echo $response->getHeaderLine('x-amzn-requestid') . "\n"; // AWS request ID
echo $response->getHeaderLine('request-id') . "\n"; // Anthropic request ID
echo $response->parse()->content;
```
```ruby Ruby nocheck
# Accessing raw response headers is not currently supported in the Ruby SDK.
# To inspect the x-amzn-requestid header, use one of the other SDK examples.
```
Anthropic recommends logging your activity on at least a 30-day rolling basis to understand usage patterns and investigate any potential issues.
AWS CloudTrail is configured within your AWS account. Enabling logging does not provide AWS or Anthropic access to your content beyond what is necessary for billing and service operation.
## Migrating from Amazon Bedrock
If you currently use Claude on Bedrock, migrating to Claude Platform on AWS requires changes throughout your integration. SigV4 signing remains supported, but the signing context, base URL, API format, model IDs, SDK client and package, streaming format, request headers, and region availability all change. The following table summarizes the differences.
### What changes
The migration delta depends on which Bedrock integration you're coming from. The following table shows both the [current Bedrock integration](/docs/en/build-with-claude/claude-in-amazon-bedrock) (Messages API at `bedrock-mantle.{region}.api.aws`) and the [legacy InvokeModel integration](/docs/en/build-with-claude/claude-on-amazon-bedrock-legacy).
| Aspect | From [Claude in Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock) | From [Amazon Bedrock (legacy)](/docs/en/build-with-claude/claude-on-amazon-bedrock-legacy) | To Claude Platform on AWS |
| :--- | :--- | :--- | :--- |
| **Base URL** | `bedrock-mantle.{region}.api.aws` | `bedrock-runtime.{region}.amazonaws.com` | `aws-external-anthropic.{region}.api.aws` |
| **API format** | Anthropic Messages API at `/anthropic/v1/messages` | Bedrock Converse / InvokeModel | Anthropic Messages API (`/v1/messages`) |
| **Model IDs** | `anthropic.claude-opus-4-6` | `anthropic.claude-opus-4-6-v1` (with optional `us.`/`global.` prefix) | `claude-opus-4-6` |
| **SDK client** | `AnthropicBedrockMantle` | `AnthropicBedrock` / Bedrock SDK | Platform-specific client (see [Install an SDK](#install-an-sdk)), in beta |
| **SDK package** | `anthropic[bedrock]`, `@anthropic-ai/bedrock-sdk`, and others | `anthropic[bedrock]`, `@anthropic-ai/bedrock-sdk`, or AWS SDK | `anthropic[aws]`, `@anthropic-ai/aws-sdk`, and others (see [Install an SDK](#install-an-sdk)) |
| **SigV4 service name** | `bedrock-mantle` | `bedrock` | `aws-external-anthropic` |
| **Streaming format** | SSE | AWS EventStream | SSE (same as Claude API) |
| **Workspace header** | Not applicable | Not applicable | `anthropic-workspace-id` required |
| **Region availability** | See [Amazon Bedrock regions](https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html) | See [Amazon Bedrock regions](https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html) | All AWS commercial regions |
If you're on the current Bedrock integration, the request body format is already the Anthropic Messages API; the changes are the base URL, SigV4 service name, model IDs, and adding the `anthropic-workspace-id` header. If you're on the legacy InvokeModel or Converse API, you'll also rewrite the request and response shapes to the Messages API format. See [Claude on Amazon Bedrock (legacy)](/docs/en/build-with-claude/claude-on-amazon-bedrock-legacy) for the request-shape mapping.
### What you gain
- Typically same-day access to new models and features (see [feature limitations](#features-not-currently-available))
- Agent Skills for document generation (PowerPoint, Excel, Word, PDF)
- Code execution in Anthropic's managed sandbox
- Beta features through the `anthropic-beta` header (see [feature limitations](#features-not-currently-available))
- Claude Console for quota visibility and usage analytics
- Direct Anthropic support
- API key authentication as an alternative to SigV4 (see [API key authentication](#api-key-authentication))
### What stays the same
- AWS IAM authentication (SigV4)
- AWS as the invoicing party (the billing channel changes from native AWS service to AWS Marketplace; see [Commercial considerations](#commercial-considerations))
- AWS commitment retirement
### Migration pitfalls
**Enable outbound web identity federation first.** If your AWS account has not previously used Claude Platform on AWS, you must [enable outbound web identity federation](#enable-outbound-web-identity-federation) once per account before making requests. Without this step, all requests fail with a federation error (see [Enable outbound web identity federation](#enable-outbound-web-identity-federation) for the exact error and remediation). This step is not required for Bedrock.
**Zero Data Retention (ZDR) is opt-in on Claude Platform on AWS.** On Bedrock, AWS is the data processor and Anthropic does not retain inference inputs or outputs; Anthropic's ZDR program does not apply there. On Claude Platform on AWS, Anthropic processes inference data as an independent data processor, and ZDR follows the first-party Claude API model: it is available on request through your Anthropic account representative. Confirm ZDR enrollment before migrating production workloads that depend on data-retention guarantees.
### Commercial considerations
- **Anthropic terms of service:** Using Claude Platform on AWS requires accepting Anthropic's Commercial Terms of Service and Usage Policy. If your organization hasn't already accepted these (for example, if you've only used Claude through Bedrock), you're prompted during account setup. See [Set up your account](#set-up-your-account).
- **Discounts and private offers:** Negotiated discounts and AWS Marketplace private offers don't transfer automatically between Bedrock and Claude Platform on AWS. Work with your Anthropic account representative to set up commercial terms for Claude Platform on AWS.
## IAM policies
Claude Platform on AWS integrates with AWS IAM for access control. You grant or deny access to specific API actions on specific workspaces using standard IAM policy syntax.
The SigV4 service name and IAM action namespace is `aws-external-anthropic`. Actions follow the pattern `aws-external-anthropic:` (for example, `aws-external-anthropic:CreateInference`).
### Example: deny batch inference
The following policy allows real-time inference while blocking batch processing:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"aws-external-anthropic:CreateInference",
"aws-external-anthropic:CountTokens",
"aws-external-anthropic:GetModel",
"aws-external-anthropic:ListModels",
"aws-external-anthropic:GetWorkspace"
],
"Resource": "arn:aws:aws-external-anthropic:*:*:workspace/*"
},
{
"Effect": "Allow",
"Action": "aws-external-anthropic:ListWorkspaces",
"Resource": "*"
},
{
"Effect": "Deny",
"Action": [
"aws-external-anthropic:CreateBatchInference",
"aws-external-anthropic:GetBatchInference",
"aws-external-anthropic:ListBatchInferences"
],
"Resource": "*"
}
]
}
```
The `GetBatchInference` action authorizes both the batch metadata route and the batch results route. Denying it blocks both reads. For a Deny-only policy suitable for ZDR-sensitive workloads, see [Feature lockdown for a ZDR-sensitive workspace](/docs/en/api/claude-platform-on-aws-iam-actions#feature-lockdown-for-a-zdr-sensitive-workspace).
`ListWorkspaces` is account-scoped, so it appears in a separate Allow statement with `"Resource": "*"`. Specifying a workspace ARN on an account-scoped action has no effect (see [Provisioning automation](/docs/en/api/claude-platform-on-aws-iam-actions#provisioning-automation)).
This policy assumes AWS SigV4 authentication. If the principal authenticates with an API key, also add `aws-external-anthropic:CallWithBearerToken` to the `"Resource": "*"` Allow statement. `CallWithBearerToken` is a route-less authentication-layer action that does not bind to a workspace ARN. See [Per-customer workspace isolation](/docs/en/api/claude-platform-on-aws-iam-actions#per-customer-workspace-isolation) for the two-statement pattern.
### Managed policies
AWS provides four managed policies (`AnthropicFullAccess`, `AnthropicReadOnlyAccess`, `AnthropicInferenceAccess`, and `AnthropicLimitedAccess`) for common access patterns. For the actions each policy grants, the complete list of IAM actions, the route-to-action mapping, and additional policy examples, see [IAM actions for Claude Platform on AWS](/docs/en/api/claude-platform-on-aws-iam-actions#managed-policies).
## Additional resources
- **Claude Console for Claude Platform on AWS:** [platform.claude.com](https://platform.claude.com) (access through the AWS Console)
- **Pricing details:** [Pricing](/docs/en/about-claude/pricing#claude-platform-on-aws-pricing)
- **Bedrock (AWS-operated Claude):** [Claude in Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock)
- **AWS Marketplace:** [aws.amazon.com/marketplace](https://aws.amazon.com/marketplace)
## Managed Agents
### First steps
---
# Claude Managed Agents overview
URL: https://platform.claude.com/docs/en/managed-agents/overview
# Claude Managed Agents overview
Pre-built, configurable agent harness that runs in managed infrastructure. Best for long-running tasks and asynchronous work.
---
Anthropic offers two ways to build with Claude, each suited to different use cases:
| | Messages API | Claude Managed Agents |
|---|---|---|
| **What it is** | Direct model prompting access | Pre-built, configurable agent harness that runs in managed infrastructure |
| **Best for** | Custom agent loops and fine-grained control | Long-running tasks and asynchronous work |
| **Learn more** | [Messages API docs](/docs/en/build-with-claude/working-with-messages) | [Claude Managed Agents docs](/docs/en/managed-agents/overview) |
Claude Managed Agents provides the harness and infrastructure for running Claude as an autonomous agent. Instead of building your own agent loop, tool execution, and runtime, you get a fully managed environment where Claude can read files, run commands, browse the web, and execute code securely. The harness supports built-in prompt caching, compaction, and other performance optimizations for high-quality, efficient agent outputs.
Claude Managed Agents is also available on Claude Platform on AWS, with some differences in feature availability and session behavior. See [Claude Managed Agents](/docs/en/build-with-claude/claude-platform-on-aws#claude-managed-agents) in the Claude Platform on AWS guide.
Create your first agent session
Full endpoint documentation
## Core concepts
Claude Managed Agents is built around four concepts:
| Concept | Description |
|---------|-------------|
| **Agent** | The model, system prompt, tools, MCP servers, and skills |
| **Environment** | A configured container template (packages, network access) |
| **Session** | A running agent instance within an environment, performing a specific task and generating outputs |
| **Events** | Messages exchanged between your application and the agent (user turns, tool results, status updates) |
## How it works
Define the model, system prompt, tools, MCP servers, and skills. Create the agent once and reference it by ID across sessions.
Configure a cloud container with pre-installed packages (Python, Node.js, Go, etc.), network access rules, and mounted files.
Launch a session that references your agent and environment configuration.
Send user messages as events. Claude autonomously executes tools and streams back results via server-sent events (SSE). Event history is persisted server-side and can be fetched in full.
Send additional user events to guide the agent mid-execution, or interrupt it to change direction.
## When to use Claude Managed Agents
Claude Managed Agents is best for workloads that need:
- **Long-running execution:** Tasks that run for minutes or hours with multiple tool calls
- **Cloud infrastructure:** Secure containers with pre-installed packages and network access
- **Minimal infrastructure:** No need to build your own agent loop, sandbox, or tool execution layer
- **Stateful sessions:** Persistent filesystems and conversation history across multiple interactions
## Supported tools
Claude Managed Agents gives Claude access to a set of built-in tools:
- **Bash:** Run shell commands in the container
- **File operations:** Read, write, edit, glob, and grep files in the container
- **Web search and fetch:** Search the web and retrieve content from URLs
- **MCP servers:** Connect to external tool providers
See [Tools](/docs/en/managed-agents/tools) for the full list and configuration options.
## Beta access
Claude Managed Agents is currently in beta. All Managed Agents endpoints require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically. Behaviors may be refined between releases to improve outputs.
To get started, you need:
1. A [Claude API key](/settings/keys)
2. The `managed-agents-2026-04-01` beta header on all requests
3. Access to Claude Managed Agents (enabled by default for all API accounts)
Certain features ([outcomes](/docs/en/managed-agents/define-outcomes) and [multiagent](/docs/en/managed-agents/multi-agent)) are in beta (research preview). [Request access](https://claude.com/form/claude-managed-agents) to try them.
## Rate limits
Managed Agents endpoints are rate-limited per organization:
| Operation | Limit |
| --- | --- |
| Create endpoints (agents, sessions, environments, etc.) | 300 requests per minute |
| Read endpoints (retrieve, list, stream, etc.) | 600 requests per minute |
Organization-level [spend limits and tier-based rate limits](/docs/en/api/rate-limits) also apply.
## Branding guidelines
For partners integrating Claude Managed Agents, use of Claude branding is optional. When referencing Claude in your product:
**Allowed:**
- "Claude Agent" (preferred for dropdown menus)
- "Claude" (when within a menu already labeled "Agents")
- "{YourAgentName} Powered by Claude" (if you have an existing agent name)
**Not permitted:**
- "Claude Code" or "Claude Code Agent"
- "Claude Cowork" or "Claude Cowork Agent"
- Claude Code-branded ASCII art or visual elements that mimic Claude Code
Your product should maintain its own branding and not appear to be Claude Code, Claude Cowork, or any other Anthropic product. For questions about branding compliance, contact the Anthropic [sales team](https://www.anthropic.com/contact-sales).
---
# Get started with Claude Managed Agents
URL: https://platform.claude.com/docs/en/managed-agents/quickstart
# Get started with Claude Managed Agents
Create your first autonomous agent.
---
This guide walks you through creating an agent, setting up an environment, starting a session, and streaming agent responses.
**Prefer an interactive walkthrough?** Run `/claude-api managed-agents-onboard` in the latest version of [Claude Code](https://claude.com/product/claude-code) for a guided setup and interactive question-answering.
## Core concepts
| Concept | Description |
|---------|-------------|
| **Agent** | The model, system prompt, tools, MCP servers, and skills |
| **Environment** | A configured container template (packages, network access) |
| **Session** | A running agent instance within an environment, performing a specific task and generating outputs |
| **Events** | Messages exchanged between your application and the agent (user turns, tool results, status updates) |
## Prerequisites
- An Anthropic [Console account](/)
- An [API key](/settings/keys)
## Install the CLI
```bash
brew install anthropics/tap/ant
```
For Linux environments, download the release binary directly.
```bash nocheck
VERSION=1.7.0
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/')
curl -fsSL "https://github.com/anthropics/anthropic-cli/releases/download/v${VERSION}/ant_${VERSION}_${OS}_${ARCH}.tar.gz" \
| sudo tar -xz -C /usr/local/bin ant
```
You can find all releases on the [GitHub releases page](https://github.com/anthropics/anthropic-cli/releases).
You may also install the CLI from source using `go install`. Requires Go 1.22 or later.
```bash
go install github.com/anthropics/anthropic-cli/cmd/ant@latest
```
The binary is placed in `$(go env GOPATH)/bin`. Add it to your `PATH` if it isn't already:
```bash
export PATH="$PATH:$(go env GOPATH)/bin"
```
Check the installation:
```bash
ant --version
```
## Install the SDK
```bash
pip install anthropic
```
```bash
npm install @anthropic-ai/sdk
```
```groovy Gradle
implementation("com.anthropic:anthropic-java:2.30.0")
```
```bash
go get github.com/anthropics/anthropic-sdk-go
```
```bash
dotnet add package Anthropic
```
```bash
bundle add anthropic
```
```bash
composer require anthropic-ai/sdk
```
Set your API key as an environment variable:
```bash
export ANTHROPIC_API_KEY="your-api-key-here"
```
## Create your first session
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
Create an agent that defines the model, system prompt, and available tools.
````bash
set -euo pipefail
agent=$(
curl -sS --fail-with-body https://api.anthropic.com/v1/agents \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <<'EOF'
{
"name": "Coding Assistant",
"model": "claude-opus-4-7",
"system": "You are a helpful coding assistant. Write clean, well-documented code.",
"tools": [
{"type": "agent_toolset_20260401"}
]
}
EOF
)
AGENT_ID=$(jq -er '.id' <<<"$agent")
AGENT_VERSION=$(jq -er '.version' <<<"$agent")
echo "Agent ID: $AGENT_ID, version: $AGENT_VERSION"
````
````bash
ant beta:agents create \
--name "Coding Assistant" \
--model '{id: claude-opus-4-7}' \
--system "You are a helpful coding assistant. Write clean, well-documented code." \
--tool '{type: agent_toolset_20260401}'
````
````python
from anthropic import Anthropic
client = Anthropic()
agent = client.beta.agents.create(
name="Coding Assistant",
model="claude-opus-4-7",
system="You are a helpful coding assistant. Write clean, well-documented code.",
tools=[
{"type": "agent_toolset_20260401"},
],
)
print(f"Agent ID: {agent.id}, version: {agent.version}")
````
````typescript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const agent = await client.beta.agents.create({
name: "Coding Assistant",
model: "claude-opus-4-7",
system: "You are a helpful coding assistant. Write clean, well-documented code.",
tools: [
{ type: "agent_toolset_20260401" },
],
});
console.log(`Agent ID: ${agent.id}, version: ${agent.version}`);
````
````csharp
using Anthropic;
using Anthropic.Models.Beta.Agents;
using Anthropic.Models.Beta.Environments;
using Anthropic.Models.Beta.Sessions;
using Anthropic.Models.Beta.Sessions.Events;
var client = new AnthropicClient();
var agent = await client.Beta.Agents.Create(new()
{
Name = "Coding Assistant",
Model = BetaManagedAgentsModel.ClaudeOpus4_7,
System = "You are a helpful coding assistant. Write clean, well-documented code.",
Tools =
[
new BetaManagedAgentsAgentToolset20260401Params
{
Type = "agent_toolset_20260401",
},
],
});
Console.WriteLine($"Agent ID: {agent.ID}, version: {agent.Version}");
````
````go
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
)
func main() {
client := anthropic.NewClient()
ctx := context.Background()
agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
Name: "Coding Assistant",
Model: anthropic.BetaManagedAgentsModelConfigParams{
ID: anthropic.BetaManagedAgentsModelClaudeOpus4_7,
Type: anthropic.BetaManagedAgentsModelConfigParamsTypeModelConfig,
},
System: anthropic.String("You are a helpful coding assistant. Write clean, well-documented code."),
Tools: []anthropic.BetaAgentNewParamsToolUnion{{
OfAgentToolset20260401: &anthropic.BetaManagedAgentsAgentToolset20260401Params{
Type: anthropic.BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401,
},
}},
})
if err != nil {
panic(err)
}
fmt.Printf("Agent ID: %s, version: %d\n", agent.ID, agent.Version)
````
````java
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.agents.AgentCreateParams;
import com.anthropic.models.beta.agents.BetaManagedAgentsAgentToolset20260401Params;
import com.anthropic.models.beta.agents.BetaManagedAgentsModel;
import com.anthropic.models.beta.environments.BetaCloudConfigParams;
import com.anthropic.models.beta.environments.EnvironmentCreateParams;
import com.anthropic.models.beta.environments.UnrestrictedNetwork;
import com.anthropic.models.beta.sessions.SessionCreateParams;
import com.anthropic.models.beta.sessions.events.BetaManagedAgentsUserMessageEventParams;
import com.anthropic.models.beta.sessions.events.EventSendParams;
import com.anthropic.models.beta.sessions.events.StreamEvents;
void main() {
var client = AnthropicOkHttpClient.fromEnv();
var agent = client.beta().agents().create(AgentCreateParams.builder()
.name("Coding Assistant")
.model(BetaManagedAgentsModel.CLAUDE_OPUS_4_7)
.system("You are a helpful coding assistant. Write clean, well-documented code.")
.addTool(BetaManagedAgentsAgentToolset20260401Params.builder()
.type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
.build())
.build());
IO.println("Agent ID: " + agent.id() + ", version: " + agent.version());
````
````php
use Anthropic\Client;
$client = new Client();
$agent = $client->beta->agents->create(
name: 'Coding Assistant',
model: 'claude-opus-4-7',
system: 'You are a helpful coding assistant. Write clean, well-documented code.',
tools: [
['type' => 'agent_toolset_20260401'],
],
);
echo "Agent ID: {$agent->id}, version: {$agent->version}\n";
````
````ruby
require "anthropic"
client = Anthropic::Client.new
agent = client.beta.agents.create(
name: "Coding Assistant",
model: "claude-opus-4-7",
system_: "You are a helpful coding assistant. Write clean, well-documented code.",
tools: [{type: "agent_toolset_20260401"}]
)
puts "Agent ID: #{agent.id}, version: #{agent.version}"
````
The `agent_toolset_20260401` tool type enables the full set of pre-built agent tools (bash, file operations, web search, and more). See [Tools](/docs/en/managed-agents/tools) for the complete list and per-tool configuration options.
Save the returned `agent.id`. You'll reference it in every session you create.
An environment defines the container where your agent runs.
````bash
environment=$(
curl -sS --fail-with-body https://api.anthropic.com/v1/environments \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <<'EOF'
{
"name": "quickstart-env",
"config": {
"type": "cloud",
"networking": {"type": "unrestricted"}
}
}
EOF
)
ENVIRONMENT_ID=$(jq -er '.id' <<<"$environment")
echo "Environment ID: $ENVIRONMENT_ID"
````
````bash
ant beta:environments create \
--name "quickstart-env" \
--config '{type: cloud, networking: {type: unrestricted}}'
````
````python
environment = client.beta.environments.create(
name="quickstart-env",
config={
"type": "cloud",
"networking": {"type": "unrestricted"},
},
)
print(f"Environment ID: {environment.id}")
````
````typescript
const environment = await client.beta.environments.create({
name: "quickstart-env",
config: {
type: "cloud",
networking: { type: "unrestricted" },
},
});
console.log(`Environment ID: ${environment.id}`);
````
````csharp
var environment = await client.Beta.Environments.Create(new()
{
Name = "quickstart-env",
Config = new() { Networking = new UnrestrictedNetwork() },
});
Console.WriteLine($"Environment ID: {environment.ID}");
````
````go
environment, err := client.Beta.Environments.New(ctx, anthropic.BetaEnvironmentNewParams{
Name: "quickstart-env",
Config: anthropic.BetaCloudConfigParams{
Networking: anthropic.BetaCloudConfigParamsNetworkingUnion{
OfUnrestricted: &anthropic.UnrestrictedNetworkParam{},
},
},
})
if err != nil {
panic(err)
}
fmt.Printf("Environment ID: %s\n", environment.ID)
````
````java
var environment = client.beta().environments().create(EnvironmentCreateParams.builder()
.name("quickstart-env")
.config(BetaCloudConfigParams.builder()
.networking(UnrestrictedNetwork.builder().build())
.build())
.build());
IO.println("Environment ID: " + environment.id());
````
````php
$environment = $client->beta->environments->create(
name: 'quickstart-env',
config: ['type' => 'cloud', 'networking' => ['type' => 'unrestricted']],
);
echo "Environment ID: {$environment->id}\n";
````
````ruby
environment = client.beta.environments.create(
name: "quickstart-env",
config: {type: "cloud", networking: {type: "unrestricted"}}
)
puts "Environment ID: #{environment.id}"
````
Save the returned `environment.id`. You'll reference it in every session you create.
Create a session that references your agent and environment.
````bash
session=$(
curl -sS --fail-with-body https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
title: 'Quickstart session',
);
echo "Session ID: {$session->id}\n";
````
````ruby
session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id,
title: "Quickstart session"
)
puts "Session ID: #{session.id}"
````
Open a stream, send a user event, then process events as they arrive:
````bash
# Send the user message first; the API buffers events until the stream attaches
curl -sS --fail-with-body \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/events" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- >/dev/null <<'EOF'
{
"events": [
{
"type": "user.message",
"content": [
{
"type": "text",
"text": "Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt"
}
]
}
]
}
EOF
# Open the SSE stream and process events as they arrive
while IFS= read -r line; do
[[ $line == data:* ]] || continue
json=${line#data: }
case $(jq -r '.type' <<<"$json") in
agent.message)
jq -j '.content[] | select(.type == "text") | .text' <<<"$json"
;;
agent.tool_use)
printf '\n[Using tool: %s]\n' "$(jq -r '.name' <<<"$json")"
;;
session.status_idle)
printf '\n\nAgent finished.\n'
break
;;
esac
done < <(
curl -sS -N --fail-with-body \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/stream" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "Accept: text/event-stream"
)
````
````python
with client.beta.sessions.events.stream(session.id) as stream:
# Send the user message after the stream opens
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [
{
"type": "text",
"text": "Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt",
},
],
},
],
)
# Process streaming events
for event in stream:
match event.type:
case "agent.message":
for block in event.content:
print(block.text, end="")
case "agent.tool_use":
print(f"\n[Using tool: {event.name}]")
case "session.status_idle":
print("\n\nAgent finished.")
break
````
````typescript
const stream = await client.beta.sessions.events.stream(session.id);
// Send the user message after the stream opens
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [
{
type: "text",
text: "Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt",
},
],
},
],
});
// Process streaming events
for await (const event of stream) {
if (event.type === "agent.message") {
for (const block of event.content) {
process.stdout.write(block.text);
}
} else if (event.type === "agent.tool_use") {
console.log(`\n[Using tool: ${event.name}]`);
} else if (event.type === "session.status_idle") {
console.log("\n\nAgent finished.");
break;
}
}
````
````csharp
var stream = client.Beta.Sessions.Events.StreamStreaming(session.ID);
// Send the user message after the stream opens
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserMessageEventParams
{
Type = "user.message",
Content =
[
new BetaManagedAgentsTextBlock
{
Type = "text",
Text = "Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt",
},
],
},
],
});
// Process streaming events
await foreach (var ev in stream)
{
if (ev.Value is BetaManagedAgentsAgentMessageEvent message)
{
foreach (var block in message.Content)
{
Console.Write(block.Text);
}
}
else if (ev.Value is BetaManagedAgentsAgentToolUseEvent toolUse)
{
Console.WriteLine($"\n[Using tool: {toolUse.Name}]");
}
else if (ev.Value is BetaManagedAgentsSessionStatusIdleEvent)
{
Console.WriteLine("\n\nAgent finished.");
break;
}
}
````
````go
stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, anthropic.BetaSessionEventStreamParams{})
defer stream.Close()
// Send the user message after the stream opens
_, err = client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.SendEventsParamsUnion{{
OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{{
OfText: &anthropic.BetaManagedAgentsTextBlockParam{
Type: anthropic.BetaManagedAgentsTextBlockTypeText,
Text: "Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt",
},
}},
},
}},
})
if err != nil {
panic(err)
}
// Process streaming events
loop:
for stream.Next() {
switch event := stream.Current().AsAny().(type) {
case anthropic.BetaManagedAgentsAgentMessageEvent:
for _, block := range event.Content {
fmt.Print(block.Text)
}
case anthropic.BetaManagedAgentsAgentToolUseEvent:
fmt.Printf("\n[Using tool: %s]\n", event.Name)
case anthropic.BetaManagedAgentsSessionStatusIdleEvent:
fmt.Print("\n\nAgent finished.\n")
break loop
}
}
if err := stream.Err(); err != nil {
panic(err)
}
````
````java
try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
// Send the user message after the stream opens
client.beta().sessions().events().send(session.id(), EventSendParams.builder()
.addEvent(BetaManagedAgentsUserMessageEventParams.builder()
.type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
.addTextContent("Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt")
.build())
.build());
// Process streaming events
for (var event : (Iterable) stream.stream()::iterator) {
if (event.isAgentMessage()) {
event.asAgentMessage().content().forEach(block -> IO.print(block.text()));
} else if (event.isAgentToolUse()) {
IO.println("\n[Using tool: " + event.asAgentToolUse().name() + "]");
} else if (event.isSessionStatusIdle()) {
IO.println("\n\nAgent finished.");
break;
}
}
}
````
````php
$stream = $client->beta->sessions->events->streamStream($session->id);
// Send the user message after the stream opens
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.message',
'content' => [
['type' => 'text', 'text' => 'Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt'],
],
],
],
);
// Process streaming events
foreach ($stream as $event) {
match ($event->type) {
'agent.message' => print(implode('', array_map(fn($block) => $block->text, $event->content))),
'agent.tool_use' => print("\n[Using tool: {$event->name}]\n"),
'session.status_idle' => print("\n\nAgent finished.\n"),
default => null,
};
if ($event->type === 'session.status_idle') {
break;
}
}
````
````ruby
stream = client.beta.sessions.events.stream_events(session.id)
# Send the user message after the stream opens
client.beta.sessions.events.send_(
session.id,
events: [{
type: "user.message",
content: [{type: "text", text: "Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt"}]
}]
)
# Process streaming events
stream.each do |event|
case event.type
in :"agent.message"
event.content.each { print it.text }
in :"agent.tool_use"
puts "\n[Using tool: #{event.name}]"
in :"session.status_idle"
puts "\n\nAgent finished."
break
else
# ignore other event types
end
end
````
The agent will write a Python script, execute it in the container, and verify the output file was created. Your output will look similar to this:
```text
I'll create a Python script that generates the first 20 Fibonacci numbers and saves them to a file.
[Using tool: write]
[Using tool: bash]
The script ran successfully. Let me verify the output file.
[Using tool: bash]
fibonacci.txt contains the first 20 Fibonacci numbers (0 through 4181).
Agent finished.
```
## What's happening
When you send a user event, Claude Managed Agents:
1. **Provisions a container:** Your environment configuration determines how it's built.
2. **Runs the agent loop:** Claude decides which tools to use based on your message
3. **Executes tools:** File writes, bash commands, and other tool calls run inside the container
4. **Streams events:** You receive real-time updates as the agent works
5. **Goes idle:** The agent emits a `session.status_idle` event when it has nothing more to do
## Next steps
Create reusable, versioned agent configurations
Customize networking and container settings
Enable specific tools for your agent
Handle events and steer the agent mid-execution
---
# Prototype in Console
URL: https://platform.claude.com/docs/en/managed-agents/onboarding
# Prototype in Console
Create and test agents visually in Console without writing API calls.
---
[Console](https://platform.claude.com/workspaces/default/agent-quickstart/) provides a visual interface for creating and configuring agents. It lets you iterate on configuration interactively before writing code.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## How to build an agent
The [visual interface](https://platform.claude.com/workspaces/default/agent-quickstart/) walks you through each field of an agent definition:
- **Model and system prompt:** Pick a model and write the system prompt in a full-width editor.
- **MCP servers:** Add remote MCP servers by URL and authenticate your agent to take action on your behalf.
- **Tools:** Extend your agent's capabilities using a pre-built agent toolset and MCP tools.
- **Skills:** Attach Anthropic or custom skills from your organization's library.
As you configure, Console shows the equivalent API request so you can copy it into your code once you're satisfied.
## Testing an agent
Console includes an inline session runner. After configuring your agent, you can start a test session directly, send messages, and watch the event stream without leaving the page. This is the fastest way to check that your system prompt and tool selection produce the behavior you expect.
## From prototype to code
Once your agent works as expected:
1. Copy the agent ID and [environment ID](/docs/en/managed-agents/environments) from Console.
2. Reference them in your code when [creating sessions](/docs/en/managed-agents/sessions):
```bash curl nocheck
session=$(curl -fsSL https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{
"agent": "agent_01J8XkN5uT3vHpLqRfWdY2",
"environment_id": "env_01K2mPsT7hNwR4jXuLvCqD8",
"title": "My first session"
}')
```
```bash CLI nocheck
ant beta:sessions create \
--agent agent_01J8XkN5uT3vHpLqRfWdY2 \
--environment-id env_01K2mPsT7hNwR4jXuLvCqD8 \
--title "My first session"
```
```python Python nocheck
session = client.beta.sessions.create(
agent="agent_01J8XkN5uT3vHpLqRfWdY2",
environment_id="env_01K2mPsT7hNwR4jXuLvCqD8",
title="My first session",
)
```
```typescript TypeScript nocheck
const session = await client.beta.sessions.create({
agent: "agent_01J8XkN5uT3vHpLqRfWdY2",
environment_id: "env_01K2mPsT7hNwR4jXuLvCqD8",
title: "My first session"
});
```
```csharp C# nocheck
var session = await client.Beta.Sessions.Create(new()
{
Agent = "agent_01J8XkN5uT3vHpLqRfWdY2",
EnvironmentID = "env_01K2mPsT7hNwR4jXuLvCqD8",
Title = "My first session",
});
```
```go Go nocheck hidelines={-1}
session, err := client.Beta.Sessions.New(ctx, anthropic.BetaSessionNewParams{
Agent: anthropic.BetaSessionNewParamsAgentUnion{
OfString: anthropic.String("agent_01J8XkN5uT3vHpLqRfWdY2"),
},
EnvironmentID: "env_01K2mPsT7hNwR4jXuLvCqD8",
Title: anthropic.String("My first session"),
})
if err != nil {
panic(err)
}
_ = session
```
```java Java nocheck
var session = client.beta().sessions().create(
SessionCreateParams.builder()
.agent("agent_01J8XkN5uT3vHpLqRfWdY2")
.environmentId("env_01K2mPsT7hNwR4jXuLvCqD8")
.title("My first session")
.build()
);
```
```php PHP nocheck
$session = $client->beta->sessions->create(
agent: 'agent_01J8XkN5uT3vHpLqRfWdY2',
environmentID: 'env_01K2mPsT7hNwR4jXuLvCqD8',
title: 'My first session',
);
```
```ruby Ruby nocheck
session = client.beta.sessions.create(
agent: "agent_01J8XkN5uT3vHpLqRfWdY2",
environment_id: "env_01K2mPsT7hNwR4jXuLvCqD8",
title: "My first session"
)
```
### Define your agent
---
# Define your agent
URL: https://platform.claude.com/docs/en/managed-agents/agent-setup
# Define your agent
Create a reusable, versioned agent configuration.
---
An agent is a reusable, versioned configuration that defines persona and capabilities. It bundles the model, system prompt, tools, MCP servers, and skills that shape how Claude behaves during a session.
Create the agent once as a reusable resource and reference it by ID each time you [start a session](/docs/en/managed-agents/sessions). Agents are versioned and easier to manage across many sessions.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## Agent configuration fields
| Field | Description |
| --- | --- |
| `name` | Required. A human-readable name for the agent. |
| `model` | Required. The Claude [model](/docs/en/about-claude/models/overview) that powers the agent. All Claude 4.5 and later models are supported. |
| `system` | A [system prompt](/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#give-claude-a-role) that defines the agent's behavior and persona. The system prompt is distinct from [user messages](/docs/en/managed-agents/events-and-streaming#user-events), which should describe the work to be done. |
| `tools` | The tools available to the agent. Combines [pre-built agent tools](/docs/en/managed-agents/tools), [MCP tools](/docs/en/managed-agents/mcp-connector), and [custom tools](/docs/en/managed-agents/tools#custom-tools). |
| `mcp_servers` | MCP servers that provide standardized third-party capabilities. |
| `skills` | [Skills](/docs/en/managed-agents/skills) that supply domain-specific context with progressive disclosure. |
| `multiagent` | A coordinator declaration listing the agents this agent can delegate to. See [Multiagent sessions](/docs/en/managed-agents/multi-agent). |
| `description` | A description of what the agent does. |
| `metadata` | Arbitrary key-value pairs for your own tracking. |
## Create an agent
The following example defines a coding agent that uses Claude Opus 4.7 with access to the pre-built agent toolset. The toolset lets the agent write code, read files, search the web, and more. See the [agent tools reference](/docs/en/managed-agents/tools) for the full list of supported tools.
````bash
agent=$(curl -fsSL https://api.anthropic.com/v1/agents \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{
"name": "Coding Assistant",
"model": "claude-opus-4-7",
"system": "You are a helpful coding agent.",
"tools": [{"type": "agent_toolset_20260401"}]
}')
AGENT_ID=$(jq -r '.id' <<< "$agent")
AGENT_VERSION=$(jq -r '.version' <<< "$agent")
````
````bash
ant beta:agents create \
--name "Coding Assistant" \
--model '{id: claude-opus-4-7}' \
--system "You are a helpful coding agent." \
--tool '{type: agent_toolset_20260401}'
````
````python
agent = client.beta.agents.create(
name="Coding Assistant",
model="claude-opus-4-7",
system="You are a helpful coding agent.",
tools=[
{"type": "agent_toolset_20260401"},
],
)
````
````typescript
const agent = await client.beta.agents.create({
name: "Coding Assistant",
model: "claude-opus-4-7",
system: "You are a helpful coding agent.",
tools: [{ type: "agent_toolset_20260401" }],
});
````
````csharp
var agent = await client.Beta.Agents.Create(new()
{
Name = "Coding Assistant",
Model = new("claude-opus-4-7"),
System = "You are a helpful coding agent.",
Tools =
[
new BetaManagedAgentsAgentToolset20260401Params
{
Type = "agent_toolset_20260401",
},
],
});
````
````go
agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
Name: "Coding Assistant",
Model: anthropic.BetaManagedAgentsModelConfigParams{
ID: "claude-opus-4-7",
Type: anthropic.BetaManagedAgentsModelConfigParamsTypeModelConfig,
},
System: anthropic.String("You are a helpful coding agent."),
Tools: []anthropic.BetaAgentNewParamsToolUnion{{
OfAgentToolset20260401: &anthropic.BetaManagedAgentsAgentToolset20260401Params{
Type: anthropic.BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401,
},
}},
})
if err != nil {
panic(err)
}
````
````java
var agent = client.beta().agents().create(
AgentCreateParams.builder()
.name("Coding Assistant")
.model(BetaManagedAgentsModel.CLAUDE_OPUS_4_7)
.system("You are a helpful coding agent.")
.addTool(
BetaManagedAgentsAgentToolset20260401Params.builder()
.type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
.build()
)
.build()
);
````
````php
$agent = $client->beta->agents->create(
name: 'Coding Assistant',
model: 'claude-opus-4-7',
system: 'You are a helpful coding agent.',
tools: [
BetaManagedAgentsAgentToolset20260401Params::with(
type: 'agent_toolset_20260401',
),
],
);
````
````ruby
agent = client.beta.agents.create(
name: "Coding Assistant",
model: "claude-opus-4-7",
system_: "You are a helpful coding agent.",
tools: [{type: "agent_toolset_20260401"}]
)
````
To use Claude Opus 4.6 or Claude Opus 4.7 with [fast mode](/docs/en/build-with-claude/fast-mode), pass `model` as an object: `{"id": "claude-opus-4-7", "speed": "fast"}`.
The response echoes your configuration and adds `id`, `type`, `version`, `created_at`, `updated_at`, and `archived_at` fields. The `version` starts at 1 and increments each time an update changes the agent.
```json
{
"id": "agent_01HqR2k7vXbZ9mNpL3wYcT8f",
"type": "agent",
"name": "Coding Assistant",
"model": {
"id": "claude-opus-4-7",
"speed": "standard"
},
"system": "You are a helpful coding agent.",
"description": null,
"tools": [
{
"type": "agent_toolset_20260401",
"default_config": {
"permission_policy": { "type": "always_allow" }
}
}
],
"skills": [],
"mcp_servers": [],
"metadata": {},
"version": 1,
"created_at": "2026-04-03T18:24:10.412Z",
"updated_at": "2026-04-03T18:24:10.412Z",
"archived_at": null
}
```
## Update an agent
Updating an agent generates a new version when the configuration changes. Pass the current `version` to ensure you're updating from a known state.
````bash
updated_agent=$(curl -fsSL "https://api.anthropic.com/v1/agents/$AGENT_ID" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <beta->agents->update(
$agent->id,
version: $agent->version,
system: 'You are a helpful coding agent. Always write tests.',
);
echo "New version: {$updatedAgent->version}\n";
````
````ruby
updated_agent = client.beta.agents.update(
agent.id,
version: agent.version,
system_: "You are a helpful coding agent. Always write tests."
)
puts "New version: #{updated_agent.version}"
````
### Update semantics
- **Omitted fields are preserved.** You only need to include the fields you want to change.
- **Scalar fields** (`model`, `system`, `name`, `description`) are replaced with the new value. `system` and `description` can be cleared by passing `null`. `model` and `name` are mandatory and cannot be cleared.
- **Array fields** (`tools`, `mcp_servers`, `skills`) are fully replaced by the new array. To clear an array field entirely, pass `null` or an empty array.
- **`multiagent`** is replaced as a whole, including its `agents` roster. Pass `null` to clear it.
- **Metadata** is merged at the key level. Keys you provide are added or updated. Keys you omit are preserved. To delete a specific key, set its value to an empty string.
- **No-op detection.** If the update produces no change relative to the current version, no new version is created and the existing version is returned.
## Agent lifecycle
| Operation | Behavior |
| --- | --- |
| **Update** | Generates a new agent version when the configuration changes. |
| **List versions** | Returns the full version history so you can track changes over time. |
| **Archive** | Makes the agent read-only. New sessions cannot reference it, but existing sessions continue to run. |
### List versions
Fetch the full version history to track how an agent has changed over time.
````bash
curl -fsSL "https://api.anthropic.com/v1/agents/$AGENT_ID/versions" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
| jq -r '.data[] | "Version \(.version): \(.updated_at)"'
````
````bash
ant beta:agents:versions list --agent-id "$AGENT_ID"
````
````python
for version in client.beta.agents.versions.list(agent.id):
print(f"Version {version.version}: {version.updated_at.isoformat()}")
````
````typescript
for await (const version of client.beta.agents.versions.list(agent.id)) {
console.log(`Version ${version.version}: ${version.updated_at}`);
}
````
````csharp
var versions = await client.Beta.Agents.Versions.List(agent.ID);
await foreach (var version in versions.Paginate())
{
Console.WriteLine($"Version {version.Version}: {version.UpdatedAt:O}");
}
````
````go
iter := client.Beta.Agents.Versions.ListAutoPaging(ctx, agent.ID, anthropic.BetaAgentVersionListParams{})
for iter.Next() {
version := iter.Current()
fmt.Printf("Version %d: %s\n", version.Version, version.UpdatedAt.Format(time.RFC3339))
}
if err := iter.Err(); err != nil {
panic(err)
}
````
````java
for (var version : client.beta().agents().versions().list(agent.id()).autoPager()) {
IO.println("Version " + version.version() + ": " + version.updatedAt());
}
````
````php
foreach ($client->beta->agents->versions->list($agent->id)->pagingEachItem() as $version) {
echo "Version {$version->version}: {$version->updatedAt->format(DateTimeInterface::ATOM)}\n";
}
````
````ruby
client.beta.agents.versions.list(agent.id).auto_paging_each do
puts "Version #{it.version}: #{it.updated_at.iso8601}"
end
````
### Archive an agent
Archiving makes the agent read-only. Existing sessions continue to run, but new sessions cannot reference the agent. The response sets `archived_at` to the archive timestamp.
````bash
archived=$(curl -fsSL -X POST "https://api.anthropic.com/v1/agents/$AGENT_ID/archive" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01")
echo "Archived at: $(jq -r '.archived_at' <<< "$archived")"
````
````bash
ant beta:agents archive --agent-id "$AGENT_ID"
````
````python
archived = client.beta.agents.archive(agent.id)
print(f"Archived at: {archived.archived_at.isoformat()}")
````
````typescript
const archived = await client.beta.agents.archive(agent.id);
console.log(`Archived at: ${archived.archived_at}`);
````
````csharp
var archived = await client.Beta.Agents.Archive(agent.ID);
Console.WriteLine($"Archived at: {archived.ArchivedAt:O}");
````
````go
archived, err := client.Beta.Agents.Archive(ctx, agent.ID, anthropic.BetaAgentArchiveParams{})
if err != nil {
panic(err)
}
fmt.Printf("Archived at: %s\n", archived.ArchivedAt.Format(time.RFC3339))
````
````java
var archived = client.beta().agents().archive(agent.id());
IO.println("Archived at: " + archived.archivedAt().orElseThrow());
````
````php
$archived = $client->beta->agents->archive($agent->id);
echo "Archived at: {$archived->archivedAt->format(DateTimeInterface::ATOM)}\n";
````
````ruby
archived = client.beta.agents.archive(agent.id)
puts "Archived at: #{archived.archived_at.iso8601}"
````
## Next steps
- [Configure tools](/docs/en/managed-agents/tools) to customize which capabilities the agent can use.
- [Attach skills](/docs/en/managed-agents/skills) for domain-specific expertise.
- [Start a session](/docs/en/managed-agents/sessions) that references your agent.
---
# MCP connector
URL: https://platform.claude.com/docs/en/managed-agents/mcp-connector
# MCP connector
Connect MCP servers to your agents for access to external tools and data sources.
---
Claude Managed Agents supports connecting [Model Context Protocol (MCP)](https://modelcontextprotocol.io) servers to your agents. This gives the agent access to external tools, data sources, and services through a standardized protocol.
MCP configuration is split across two steps:
1. **Agent creation** declares which MCP servers the agent connects to, by name and URL.
2. **Session creation** supplies auth for those servers by referencing a pre-registered [vault](/docs/en/managed-agents/vaults).
This separation keeps secrets out of reusable agent definitions while letting each session authenticate with its own credentials.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## Declare MCP servers on the agent
Specify MCP servers in the `mcp_servers` array when creating an agent. Each server needs a `type`, a unique `name`, and a `url`. No auth tokens are provided at this stage.
The `name` you assign in the MCP server array is used to reference the `mcp_toolset` entries in the tools array.
````bash
agent_response=$(curl -sS --fail-with-body https://api.anthropic.com/v1/agents \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <<'EOF'
{
"name": "GitHub Assistant",
"model": "claude-opus-4-7",
"mcp_servers": [
{
"type": "url",
"name": "github",
"url": "https://api.githubcopilot.com/mcp/"
}
],
"tools": [
{"type": "agent_toolset_20260401"},
{"type": "mcp_toolset", "mcp_server_name": "github"}
]
}
EOF
)
agent_id=$(jq -r '.id' <<<"$agent_response")
````
````bash
AGENT_ID=$(ant beta:agents create \
--name "GitHub Assistant" \
--model claude-opus-4-7 \
--mcp-server '{type: url, name: github, url: "https://api.githubcopilot.com/mcp/"}' \
--tool '{type: agent_toolset_20260401}' \
--tool '{type: mcp_toolset, mcp_server_name: github}' \
--transform id --raw-output)
````
````python
agent = client.beta.agents.create(
name="GitHub Assistant",
model="claude-opus-4-7",
mcp_servers=[
{
"type": "url",
"name": "github",
"url": "https://api.githubcopilot.com/mcp/",
},
],
tools=[
{"type": "agent_toolset_20260401"},
{"type": "mcp_toolset", "mcp_server_name": "github"},
],
)
````
````typescript
const agent = await client.beta.agents.create({
name: "GitHub Assistant",
model: "claude-opus-4-7",
mcp_servers: [
{
type: "url",
name: "github",
url: "https://api.githubcopilot.com/mcp/",
},
],
tools: [
{ type: "agent_toolset_20260401" },
{ type: "mcp_toolset", mcp_server_name: "github" },
],
});
````
````csharp
var agent = await client.Beta.Agents.Create(new()
{
Name = "GitHub Assistant",
Model = BetaManagedAgentsModel.ClaudeOpus4_7,
McpServers =
[
new() { Type = "url", Name = "github", Url = "https://api.githubcopilot.com/mcp/" },
],
Tools =
[
new BetaManagedAgentsAgentToolset20260401Params
{
Type = "agent_toolset_20260401",
},
new BetaManagedAgentsMcpToolsetParams { Type = "mcp_toolset", McpServerName = "github" },
],
});
````
````go
agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
Name: "GitHub Assistant",
Model: anthropic.BetaManagedAgentsModelConfigParams{
ID: anthropic.BetaManagedAgentsModelClaudeOpus4_7,
Type: anthropic.BetaManagedAgentsModelConfigParamsTypeModelConfig,
},
MCPServers: []anthropic.BetaManagedAgentsUrlmcpServerParams{{
Type: anthropic.BetaManagedAgentsUrlmcpServerParamsTypeURL,
Name: "github",
URL: "https://api.githubcopilot.com/mcp/",
}},
Tools: []anthropic.BetaAgentNewParamsToolUnion{
{
OfAgentToolset20260401: &anthropic.BetaManagedAgentsAgentToolset20260401Params{
Type: anthropic.BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401,
},
},
{
OfMCPToolset: &anthropic.BetaManagedAgentsMCPToolsetParams{
Type: anthropic.BetaManagedAgentsMCPToolsetParamsTypeMCPToolset,
MCPServerName: "github",
},
},
},
})
if err != nil {
panic(err)
}
````
````java
var agent = client.beta().agents().create(
AgentCreateParams.builder()
.name("GitHub Assistant")
.model(BetaManagedAgentsModel.CLAUDE_OPUS_4_7)
.addMcpServer(
BetaManagedAgentsUrlmcpServerParams.builder()
.type(BetaManagedAgentsUrlmcpServerParams.Type.URL)
.name("github")
.url("https://api.githubcopilot.com/mcp/")
.build()
)
.addTool(
BetaManagedAgentsAgentToolset20260401Params.builder()
.type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
.build()
)
.addTool(
BetaManagedAgentsMcpToolsetParams.builder()
.type(BetaManagedAgentsMcpToolsetParams.Type.MCP_TOOLSET)
.mcpServerName("github")
.build()
)
.build()
);
````
````php
$agent = $client->beta->agents->create(
name: 'GitHub Assistant',
model: 'claude-opus-4-7',
mcpServers: [
BetaManagedAgentsUrlmcpServerParams::with(
type: 'url',
name: 'github',
url: 'https://api.githubcopilot.com/mcp/',
),
],
tools: [
BetaManagedAgentsAgentToolset20260401Params::with(
type: 'agent_toolset_20260401',
),
BetaManagedAgentsMCPToolsetParams::with(
type: 'mcp_toolset',
mcpServerName: 'github',
),
],
);
````
````ruby
agent = client.beta.agents.create(
name: "GitHub Assistant",
model: "claude-opus-4-7",
mcp_servers: [
{
type: "url",
name: "github",
url: "https://api.githubcopilot.com/mcp/"
}
],
tools: [
{type: "agent_toolset_20260401"},
{type: "mcp_toolset", mcp_server_name: "github"}
]
)
````
The MCP toolset defaults to a permission policy of `always_ask`, which requires user approval before each tool call. See [permission policies](/docs/en/managed-agents/permission-policies) to configure this behavior.
## Provide auth at session creation
When starting a session, pass `vault_ids` to provide credentials for your MCP servers. Vaults are collections of credentials that you register once and reference by ID. See [Authenticate with vaults](/docs/en/managed-agents/vaults) for how to create vaults and manage credentials.
````bash
session_response=$(curl -sS --fail-with-body https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
vaultIDs: [$vault->id],
);
````
````ruby
session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id,
vault_ids: [vault.id]
)
````
If the authorization credentials supplied in the vault are invalid, session creation will succeed and interaction is still possible. A `session.error` event is emitted describing the MCP auth failure. You can decide whether to block further interactions on this error, trigger a credential update, or allow the session to continue without the MCP. Authentication retries will happen on the following `session.status_idle` to `session.status_running` transition. See [Session event stream](/docs/en/managed-agents/events-and-streaming) for details on consuming `session.error` and other events.
## Supported MCP server types
Claude Managed Agents connects to [remote MCP servers](/docs/en/agents-and-tools/remote-mcp-servers) that expose an HTTP endpoint. The server must support the MCP protocol's streamable HTTP transport.
For more information on MCP and building MCP servers, see the [MCP documentation](https://modelcontextprotocol.io).
---
# Permission policies
URL: https://platform.claude.com/docs/en/managed-agents/permission-policies
# Permission policies
Control when agent and MCP tools execute.
---
Permission policies control whether server-executed tools (the pre-built agent toolset and MCP toolset) run automatically or wait for your approval. Custom tools are executed by your application and controlled by you, so they are not governed by permission policies.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## Permission policy types
| Policy | Behavior |
| --- | --- |
| `always_allow` | The tool executes automatically with no confirmation. |
| `always_ask` | The session pauses and waits for your approval before executing. See [Respond to confirmation requests](#respond-to-confirmation-requests) for the event flow. |
## Set a policy for a toolset
### Agent toolset permissions
When creating an agent, you may optionally apply a policy to every tool in `agent_toolset_20260401` using `default_config.permission_policy`:
```bash curl
agent=$(curl -fsSL https://api.anthropic.com/v1/agents \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{
"name": "Coding Assistant",
"model": "claude-opus-4-7",
"tools": [
{
"type": "agent_toolset_20260401",
"default_config": {
"permission_policy": {"type": "always_ask"}
}
}
]
}')
```
```bash CLI
ant beta:agents create <<'YAML'
name: Coding Assistant
model: claude-opus-4-7
tools:
- type: agent_toolset_20260401
default_config:
permission_policy:
type: always_ask
YAML
```
```python Python
agent = client.beta.agents.create(
name="Coding Assistant",
model="claude-opus-4-7",
tools=[
{
"type": "agent_toolset_20260401",
"default_config": {
"permission_policy": {"type": "always_ask"},
},
},
],
)
```
```typescript TypeScript
const agent = await client.beta.agents.create({
name: "Coding Assistant",
model: "claude-opus-4-7",
tools: [
{
type: "agent_toolset_20260401",
default_config: {
permission_policy: { type: "always_ask" }
}
}
]
});
```
```csharp C#
var agent = await client.Beta.Agents.Create(new()
{
Name = "Coding Assistant",
Model = new("claude-opus-4-7"),
Tools =
[
new BetaManagedAgentsAgentToolset20260401Params
{
Type = "agent_toolset_20260401",
DefaultConfig = new()
{
PermissionPolicy = new BetaManagedAgentsAlwaysAskPolicy { Type = "always_ask" },
},
},
],
});
```
```go Go
agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
Name: "Coding Assistant",
Model: anthropic.BetaManagedAgentsModelConfigParams{
ID: "claude-opus-4-7",
Type: anthropic.BetaManagedAgentsModelConfigParamsTypeModelConfig,
},
Tools: []anthropic.BetaAgentNewParamsToolUnion{{
OfAgentToolset20260401: &anthropic.BetaManagedAgentsAgentToolset20260401Params{
Type: anthropic.BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401,
DefaultConfig: anthropic.BetaManagedAgentsAgentToolsetDefaultConfigParams{
PermissionPolicy: anthropic.BetaManagedAgentsAgentToolsetDefaultConfigParamsPermissionPolicyUnion{
OfAlwaysAsk: &anthropic.BetaManagedAgentsAlwaysAskPolicyParam{
Type: anthropic.BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk,
},
},
},
},
}},
})
if err != nil {
panic(err)
}
```
```java Java
var agent = client.beta().agents().create(
AgentCreateParams.builder()
.name("Coding Assistant")
.model(BetaManagedAgentsModel.CLAUDE_OPUS_4_7)
.addTool(
BetaManagedAgentsAgentToolset20260401Params.builder()
.type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
.defaultConfig(
BetaManagedAgentsAgentToolsetDefaultConfigParams.builder()
.permissionPolicy(
BetaManagedAgentsAlwaysAskPolicy.builder()
.type(BetaManagedAgentsAlwaysAskPolicy.Type.ALWAYS_ASK)
.build()
)
.build()
)
.build()
)
.build()
);
```
```php PHP
$agent = $client->beta->agents->create(
name: 'Coding Assistant',
model: 'claude-opus-4-7',
tools: [
BetaManagedAgentsAgentToolset20260401Params::with(
type: 'agent_toolset_20260401',
defaultConfig: BetaManagedAgentsAgentToolsetDefaultConfigParams::with(
permissionPolicy: BetaManagedAgentsAlwaysAskPolicy::with(type: 'always_ask'),
),
),
],
);
```
```ruby Ruby
agent = client.beta.agents.create(
name: "Coding Assistant",
model: "claude-opus-4-7",
tools: [
{
type: "agent_toolset_20260401",
default_config: {
permission_policy: {type: "always_ask"}
}
}
]
)
```
`default_config` is an optional setting. If you omit it, the agent toolset will be enabled with the default permission policy, `always_allow`.
### MCP toolset permissions
MCP toolsets default to `always_ask`. This ensures that new tools that are added to an MCP server do not execute in your application without approval. To auto-approve tools from a trusted MCP server, set `default_config.permission_policy` on the `mcp_toolset` entry.
The `mcp_server_name` must match the `name` referenced in the `mcp_servers` array.
This example connects a GitHub MCP server and allows its tools to run without confirmation:
```bash curl
agent=$(curl -fsSL https://api.anthropic.com/v1/agents \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{
"name": "Dev Assistant",
"model": "claude-opus-4-7",
"mcp_servers": [
{"type": "url", "name": "github", "url": "https://mcp.example.com/github"}
],
"tools": [
{"type": "agent_toolset_20260401"},
{
"type": "mcp_toolset",
"mcp_server_name": "github",
"default_config": {
"permission_policy": {"type": "always_allow"}
}
}
]
}')
```
```bash CLI
ant beta:agents create <<'YAML'
name: Dev Assistant
model: claude-opus-4-7
mcp_servers:
- type: url
name: github
url: https://mcp.example.com/github
tools:
- type: agent_toolset_20260401
- type: mcp_toolset
mcp_server_name: github
default_config:
permission_policy:
type: always_allow
YAML
```
```python Python
agent = client.beta.agents.create(
name="Dev Assistant",
model="claude-opus-4-7",
mcp_servers=[
{"type": "url", "name": "github", "url": "https://mcp.example.com/github"},
],
tools=[
{"type": "agent_toolset_20260401"},
{
"type": "mcp_toolset",
"mcp_server_name": "github",
"default_config": {
"permission_policy": {"type": "always_allow"},
},
},
],
)
```
```typescript TypeScript
const agent = await client.beta.agents.create({
name: "Dev Assistant",
model: "claude-opus-4-7",
mcp_servers: [{ type: "url", name: "github", url: "https://mcp.example.com/github" }],
tools: [
{ type: "agent_toolset_20260401" },
{
type: "mcp_toolset",
mcp_server_name: "github",
default_config: {
permission_policy: { type: "always_allow" }
}
}
]
});
```
```csharp C#
var agent = await client.Beta.Agents.Create(new()
{
Name = "Dev Assistant",
Model = new("claude-opus-4-7"),
McpServers =
[
new() { Type = "url", Name = "github", Url = "https://mcp.example.com/github" },
],
Tools =
[
new BetaManagedAgentsAgentToolset20260401Params
{
Type = "agent_toolset_20260401",
},
new BetaManagedAgentsMcpToolsetParams
{
Type = "mcp_toolset",
McpServerName = "github",
DefaultConfig = new()
{
PermissionPolicy = new BetaManagedAgentsAlwaysAllowPolicy { Type = "always_allow" },
},
},
],
});
```
```go Go
agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
Name: "Dev Assistant",
Model: anthropic.BetaManagedAgentsModelConfigParams{
ID: "claude-opus-4-7",
Type: anthropic.BetaManagedAgentsModelConfigParamsTypeModelConfig,
},
MCPServers: []anthropic.BetaManagedAgentsUrlmcpServerParams{{
Type: anthropic.BetaManagedAgentsUrlmcpServerParamsTypeURL,
Name: "github",
URL: "https://mcp.example.com/github",
}},
Tools: []anthropic.BetaAgentNewParamsToolUnion{
{
OfAgentToolset20260401: &anthropic.BetaManagedAgentsAgentToolset20260401Params{
Type: anthropic.BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401,
},
},
{
OfMCPToolset: &anthropic.BetaManagedAgentsMCPToolsetParams{
Type: anthropic.BetaManagedAgentsMCPToolsetParamsTypeMCPToolset,
MCPServerName: "github",
DefaultConfig: anthropic.BetaManagedAgentsMCPToolsetDefaultConfigParams{
PermissionPolicy: anthropic.BetaManagedAgentsMCPToolsetDefaultConfigParamsPermissionPolicyUnion{
OfAlwaysAllow: &anthropic.BetaManagedAgentsAlwaysAllowPolicyParam{
Type: anthropic.BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow,
},
},
},
},
},
},
})
if err != nil {
panic(err)
}
```
```java Java
var agent = client.beta().agents().create(
AgentCreateParams.builder()
.name("Dev Assistant")
.model(BetaManagedAgentsModel.CLAUDE_OPUS_4_7)
.addMcpServer(
BetaManagedAgentsUrlmcpServerParams.builder()
.type(BetaManagedAgentsUrlmcpServerParams.Type.URL)
.name("github")
.url("https://mcp.example.com/github")
.build()
)
.addTool(
BetaManagedAgentsAgentToolset20260401Params.builder()
.type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
.build()
)
.addTool(
BetaManagedAgentsMcpToolsetParams.builder()
.type(BetaManagedAgentsMcpToolsetParams.Type.MCP_TOOLSET)
.mcpServerName("github")
.defaultConfig(
BetaManagedAgentsMcpToolsetDefaultConfigParams.builder()
.permissionPolicy(
BetaManagedAgentsAlwaysAllowPolicy.builder()
.type(BetaManagedAgentsAlwaysAllowPolicy.Type.ALWAYS_ALLOW)
.build()
)
.build()
)
.build()
)
.build()
);
```
```php PHP
use Anthropic\Beta\Agents\BetaManagedAgentsMCPToolsetDefaultConfigParams;
use Anthropic\Beta\Agents\BetaManagedAgentsMCPToolsetParams;
use Anthropic\Beta\Agents\BetaManagedAgentsUrlmcpServerParams;
$agent = $client->beta->agents->create(
name: 'Dev Assistant',
model: 'claude-opus-4-7',
mcpServers: [
BetaManagedAgentsUrlmcpServerParams::with(
type: 'url',
name: 'github',
url: 'https://mcp.example.com/github',
),
],
tools: [
BetaManagedAgentsAgentToolset20260401Params::with(
type: 'agent_toolset_20260401',
),
BetaManagedAgentsMCPToolsetParams::with(
type: 'mcp_toolset',
mcpServerName: 'github',
defaultConfig: BetaManagedAgentsMCPToolsetDefaultConfigParams::with(
permissionPolicy: BetaManagedAgentsAlwaysAllowPolicy::with(type: 'always_allow'),
),
),
],
);
```
```ruby Ruby
agent = client.beta.agents.create(
name: "Dev Assistant",
model: "claude-opus-4-7",
mcp_servers: [
{type: "url", name: "github", url: "https://mcp.example.com/github"}
],
tools: [
{type: "agent_toolset_20260401"},
{
type: "mcp_toolset",
mcp_server_name: "github",
default_config: {
permission_policy: {type: "always_allow"}
}
}
]
)
```
## Override an individual tool policy
Use the `configs` array to override the default for individual tools. This example allows the full agent toolset by default but requires confirmation before any bash command runs:
```bash curl
tools='[
{
"type": "agent_toolset_20260401",
"default_config": {
"permission_policy": {"type": "always_allow"}
},
"configs": [
{
"name": "bash",
"permission_policy": {"type": "always_ask"}
}
]
}
]'
```
```bash CLI
tools=$(cat <<'YAML'
- type: agent_toolset_20260401
default_config:
permission_policy:
type: always_allow
configs:
- name: bash
permission_policy:
type: always_ask
YAML
)
```
```python Python
tools = [
{
"type": "agent_toolset_20260401",
"default_config": {
"permission_policy": {"type": "always_allow"},
},
"configs": [
{
"name": "bash",
"permission_policy": {"type": "always_ask"},
},
],
},
]
```
```typescript TypeScript
const tools = [
{
type: "agent_toolset_20260401",
default_config: {
permission_policy: { type: "always_allow" }
},
configs: [
{
name: "bash",
permission_policy: { type: "always_ask" }
}
]
}
] satisfies Anthropic.Beta.AgentCreateParams["tools"];
```
```csharp C#
Tool[] tools =
[
new BetaManagedAgentsAgentToolset20260401Params
{
Type = "agent_toolset_20260401",
DefaultConfig = new()
{
PermissionPolicy = new BetaManagedAgentsAlwaysAllowPolicy { Type = "always_allow" },
},
Configs =
[
new()
{
Name = "bash",
PermissionPolicy = new BetaManagedAgentsAlwaysAskPolicy { Type = "always_ask" },
},
],
},
];
```
```go Go
tools := []anthropic.BetaAgentNewParamsToolUnion{{
OfAgentToolset20260401: &anthropic.BetaManagedAgentsAgentToolset20260401Params{
Type: anthropic.BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401,
DefaultConfig: anthropic.BetaManagedAgentsAgentToolsetDefaultConfigParams{
PermissionPolicy: anthropic.BetaManagedAgentsAgentToolsetDefaultConfigParamsPermissionPolicyUnion{
OfAlwaysAllow: &anthropic.BetaManagedAgentsAlwaysAllowPolicyParam{
Type: anthropic.BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow,
},
},
},
Configs: []anthropic.BetaManagedAgentsAgentToolConfigParams{{
Name: anthropic.BetaManagedAgentsAgentToolConfigParamsNameBash,
PermissionPolicy: anthropic.BetaManagedAgentsAgentToolConfigParamsPermissionPolicyUnion{
OfAlwaysAsk: &anthropic.BetaManagedAgentsAlwaysAskPolicyParam{
Type: anthropic.BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk,
},
},
}},
},
}}
```
```java Java
var tools = List.of(
AgentCreateParams.Tool.ofAgentToolset20260401(
BetaManagedAgentsAgentToolset20260401Params.builder()
.type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
.defaultConfig(
BetaManagedAgentsAgentToolsetDefaultConfigParams.builder()
.permissionPolicy(
BetaManagedAgentsAlwaysAllowPolicy.builder()
.type(BetaManagedAgentsAlwaysAllowPolicy.Type.ALWAYS_ALLOW)
.build()
)
.build()
)
.addConfig(
BetaManagedAgentsAgentToolConfigParams.builder()
.name(BetaManagedAgentsAgentToolConfigParams.Name.BASH)
.permissionPolicy(
BetaManagedAgentsAlwaysAskPolicy.builder()
.type(BetaManagedAgentsAlwaysAskPolicy.Type.ALWAYS_ASK)
.build()
)
.build()
)
.build()
)
);
```
```php PHP
use Anthropic\Beta\Agents\BetaManagedAgentsAlwaysAskPolicy;
$tools = [
BetaManagedAgentsAgentToolset20260401Params::with(
type: 'agent_toolset_20260401',
defaultConfig: BetaManagedAgentsAgentToolsetDefaultConfigParams::with(
permissionPolicy: BetaManagedAgentsAlwaysAllowPolicy::with(type: 'always_allow'),
),
configs: [
BetaManagedAgentsAgentToolConfigParams::with(
name: 'bash',
permissionPolicy: BetaManagedAgentsAlwaysAskPolicy::with(type: 'always_ask'),
),
],
),
];
```
```ruby Ruby
tools = [
{
type: "agent_toolset_20260401",
default_config: {
permission_policy: {type: "always_allow"}
},
configs: [
{
name: "bash",
permission_policy: {type: "always_ask"}
}
]
}
]
```
## Respond to confirmation requests
When the agent invokes a tool with an `always_ask` policy:
1. The session emits an `agent.tool_use` or `agent.mcp_tool_use` event.
2. The session pauses with a `session.status_idle` event containing `stop_reason: requires_action`. The blocking event IDs are in the `stop_reason.event_ids` array.
3. Send a `user.tool_confirmation` event for each, passing the event ID in the `tool_use_id` parameter. Set `result` to `"allow"` or `"deny"`. Use `deny_message` to explain a denial.
4. Once all blocking events are resolved, the session transitions back to `running`.
Learn more about event handling in the [Session event stream](/docs/en/managed-agents/events-and-streaming) guide.
```bash curl
# Allow the tool to execute
curl -fsSL "https://api.anthropic.com/v1/sessions/$SESSION_ID/events" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{
"events": [
{
"type": "user.tool_confirmation",
"tool_use_id": "'$AGENT_TOOL_USE_EVENT_ID'",
"result": "allow"
}
]
}'
# Or deny it with an explanation
curl -fsSL "https://api.anthropic.com/v1/sessions/$SESSION_ID/events" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{
"events": [
{
"type": "user.tool_confirmation",
"tool_use_id": "'$MCP_TOOL_USE_EVENT_ID'",
"result": "deny",
"deny_message": "Don'\''t create issues in the production project. Use the staging project."
}
]
}'
```
```bash CLI nocheck
# Allow the tool to execute
ant beta:sessions:events send \
--session-id "$SESSION_ID" \
--event "{type: user.tool_confirmation, tool_use_id: $AGENT_TOOL_USE_EVENT_ID, result: allow}"
# Or deny it with an explanation
ant beta:sessions:events send \
--session-id "$SESSION_ID" \
--event "{type: user.tool_confirmation, tool_use_id: $MCP_TOOL_USE_EVENT_ID, result: deny,
deny_message: Don't create issues in the production project. Use the staging project.}"
```
```python Python
# Allow the tool to execute
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.tool_confirmation",
"tool_use_id": agent_tool_use_event.id,
"result": "allow",
},
],
)
# Or deny it with an explanation
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.tool_confirmation",
"tool_use_id": mcp_tool_use_event.id,
"result": "deny",
"deny_message": "Don't create issues in the production project. Use the staging project.",
},
],
)
```
```typescript TypeScript
// Allow the tool to execute
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.tool_confirmation",
tool_use_id: agent_tool_use_event.id,
result: "allow"
}
]
});
// Or deny it with an explanation
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.tool_confirmation",
tool_use_id: mcp_tool_use_event.id,
result: "deny",
deny_message: "Don't create issues in the production project. Use the staging project."
}
]
});
```
```csharp C#
// Allow the tool to execute
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserToolConfirmationEventParams
{
Type = "user.tool_confirmation",
ToolUseID = agentToolUseEvent.ID,
Result = "allow",
},
],
});
// Or deny it with an explanation
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserToolConfirmationEventParams
{
Type = "user.tool_confirmation",
ToolUseID = mcpToolUseEvent.ID,
Result = "deny",
DenyMessage = "Don't create issues in the production project. Use the staging project.",
},
],
});
```
```go Go
// Allow the tool to execute
_, err = client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.SendEventsParamsUnion{{
OfUserToolConfirmation: &anthropic.BetaManagedAgentsUserToolConfirmationEventParams{
Type: anthropic.BetaManagedAgentsUserToolConfirmationEventParamsTypeUserToolConfirmation,
ToolUseID: agentToolUseEvent.ID,
Result: anthropic.BetaManagedAgentsUserToolConfirmationEventParamsResultAllow,
},
}},
})
if err != nil {
panic(err)
}
// Or deny it with an explanation
_, err = client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.SendEventsParamsUnion{{
OfUserToolConfirmation: &anthropic.BetaManagedAgentsUserToolConfirmationEventParams{
Type: anthropic.BetaManagedAgentsUserToolConfirmationEventParamsTypeUserToolConfirmation,
ToolUseID: mcpToolUseEvent.ID,
Result: anthropic.BetaManagedAgentsUserToolConfirmationEventParamsResultDeny,
DenyMessage: anthropic.String("Don't create issues in the production project. Use the staging project."),
},
}},
})
if err != nil {
panic(err)
}
```
```java Java
// Allow the tool to execute
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(
BetaManagedAgentsUserToolConfirmationEventParams.builder()
.type(BetaManagedAgentsUserToolConfirmationEventParams.Type.USER_TOOL_CONFIRMATION)
.toolUseId(agentToolUseEvent.id())
.result(BetaManagedAgentsUserToolConfirmationEventParams.Result.ALLOW)
.build()
)
.build()
);
// Or deny it with an explanation
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(
BetaManagedAgentsUserToolConfirmationEventParams.builder()
.type(BetaManagedAgentsUserToolConfirmationEventParams.Type.USER_TOOL_CONFIRMATION)
.toolUseId(mcpToolUseEvent.id())
.result(BetaManagedAgentsUserToolConfirmationEventParams.Result.DENY)
.denyMessage("Don't create issues in the production project. Use the staging project.")
.build()
)
.build()
);
```
```php PHP
use Anthropic\Beta\Sessions\Events\ManagedAgentsUserToolConfirmationEventParams;
// Allow the tool to execute
$client->beta->sessions->events->send(
$session->id,
events: [
ManagedAgentsUserToolConfirmationEventParams::with(
type: 'user.tool_confirmation',
toolUseID: $agentToolUseEvent->id,
result: 'allow',
),
],
);
// Or deny it with an explanation
$client->beta->sessions->events->send(
$session->id,
events: [
ManagedAgentsUserToolConfirmationEventParams::with(
type: 'user.tool_confirmation',
toolUseID: $mcpToolUseEvent->id,
result: 'deny',
denyMessage: "Don't create issues in the production project. Use the staging project.",
),
],
);
```
```ruby Ruby
# Allow the tool to execute
client.beta.sessions.events.send_(
session.id,
events: [
{
type: "user.tool_confirmation",
tool_use_id: agent_tool_use_event.id,
result: "allow"
}
]
)
# Or deny it with an explanation
client.beta.sessions.events.send_(
session.id,
events: [
{
type: "user.tool_confirmation",
tool_use_id: mcp_tool_use_event.id,
result: "deny",
deny_message: "Don't create issues in the production project. Use the staging project."
}
]
)
```
## Custom tools
Permission policies do not apply to custom tools. When the agent invokes a custom tool, your application receives an `agent.custom_tool_use` event and is responsible for deciding whether to execute it before sending back a `user.custom_tool_result`. See [Session event stream](/docs/en/managed-agents/events-and-streaming#handling-custom-tool-calls) for the full flow.
---
# Skills
URL: https://platform.claude.com/docs/en/managed-agents/skills
# Skills
Attach reusable, filesystem-based expertise to your agent for domain-specific workflows.
---
Skills are reusable, filesystem-based resources that give your agent domain-specific expertise: workflows, context, and best practices that turn a general-purpose agent into a specialist. Unlike prompts (conversation-level instructions for one-off tasks), skills load on demand, only impacting the context window when needed.
You can attach two types of skill. Both work the same way: your agent invokes them automatically when they are relevant to the task.
- **Pre-built Anthropic skills:** Common document tasks such as PowerPoint, Excel, Word, and PDF handling.
- **Custom skills:** Skills you author and upload to your workspace.
To learn how to author custom skills, see [Agent Skills](/docs/en/agents-and-tools/agent-skills/overview) and [Skill authoring best practices](/docs/en/agents-and-tools/agent-skills/best-practices). This page assumes you already have skills available in your workspace or are using Anthropic pre-built skills.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## Attach skills to an agent
Attach skills when creating an agent. Each session supports up to 20 skills total, counted across every agent in the session (see [Multiagent sessions](/docs/en/managed-agents/multi-agent)).
Each entry in the `skills` array uses the following fields:
| Field | Description |
| --- | --- |
| `type` | Either `anthropic` for pre-built skills or `custom` for workspace-authored skills. |
| `skill_id` | The skill identifier. For Anthropic skills, use the short name (for example, `xlsx`). For custom skills, use the `skill_*` ID returned at creation. |
| `version` | Custom skills only. Pin to a specific version or use `latest`. |
```bash curl
agent=$(curl -sS https://api.anthropic.com/v1/agents \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
--json @- <<'EOF'
{
"name": "Financial Analyst",
"model": "claude-opus-4-7",
"system": "You are a financial analysis agent.",
"skills": [
{"type": "anthropic", "skill_id": "xlsx"},
{"type": "custom", "skill_id": "skill_abc123", "version": "latest"}
]
}
EOF
)
```
```bash CLI nocheck
ant beta:agents create <<'YAML'
name: Financial Analyst
model: claude-opus-4-7
system: You are a financial analysis agent.
skills:
- type: anthropic
skill_id: xlsx
- type: custom
skill_id: skill_abc123
version: latest
YAML
```
```python Python
agent = client.beta.agents.create(
name="Financial Analyst",
model="claude-opus-4-7",
system="You are a financial analysis agent.",
skills=[
{
"type": "anthropic",
"skill_id": "xlsx",
},
{
"type": "custom",
"skill_id": "skill_abc123",
"version": "latest",
},
],
)
```
```typescript TypeScript
const agent = await client.beta.agents.create({
name: "Financial Analyst",
model: "claude-opus-4-7",
system: "You are a financial analysis agent.",
skills: [
{
type: "anthropic",
skill_id: "xlsx"
},
{
type: "custom",
skill_id: "skill_abc123",
version: "latest"
}
]
});
```
```csharp C#
var agent = await client.Beta.Agents.Create(new()
{
Name = "Financial Analyst",
Model = BetaManagedAgentsModel.ClaudeOpus4_7,
System = "You are a financial analysis agent.",
Skills =
[
new BetaManagedAgentsAnthropicSkillParams { Type = BetaManagedAgentsAnthropicSkillParamsType.Anthropic, SkillID = "xlsx" },
new BetaManagedAgentsCustomSkillParams { Type = BetaManagedAgentsCustomSkillParamsType.Custom, SkillID = "skill_abc123", Version = "latest" },
],
});
```
```go Go
agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
Name: "Financial Analyst",
Model: anthropic.BetaManagedAgentsModelConfigParams{
ID: "claude-opus-4-7",
Type: anthropic.BetaManagedAgentsModelConfigParamsTypeModelConfig,
},
System: anthropic.String("You are a financial analysis agent."),
Skills: []anthropic.ManagedAgentsSkillParamUnion{
{OfAnthropic: &anthropic.BetaManagedAgentsAnthropicSkillParams{
SkillID: "xlsx",
Type: anthropic.BetaManagedAgentsAnthropicSkillParamsTypeAnthropic,
}},
{OfCustom: &anthropic.BetaManagedAgentsCustomSkillParams{
SkillID: "skill_abc123",
Type: anthropic.BetaManagedAgentsCustomSkillParamsTypeCustom,
Version: anthropic.String("latest"),
}},
},
})
if err != nil {
panic(err)
}
```
```java Java
var agent = client.beta().agents().create(
AgentCreateParams.builder()
.name("Financial Analyst")
.model(BetaManagedAgentsModel.CLAUDE_OPUS_4_7)
.system("You are a financial analysis agent.")
.addSkill(
BetaManagedAgentsAnthropicSkillParams.builder()
.type(BetaManagedAgentsAnthropicSkillParams.Type.ANTHROPIC)
.skillId("xlsx")
.build()
)
.addSkill(
BetaManagedAgentsCustomSkillParams.builder()
.type(BetaManagedAgentsCustomSkillParams.Type.CUSTOM)
.skillId("skill_abc123")
.version("latest")
.build()
)
.build()
);
```
```php PHP
$agent = $client->beta->agents->create(
name: 'Financial Analyst',
model: 'claude-opus-4-7',
system: 'You are a financial analysis agent.',
skills: [
['type' => 'anthropic', 'skill_id' => 'xlsx'],
['type' => 'custom', 'skill_id' => 'skill_abc123', 'version' => 'latest'],
],
);
```
```ruby Ruby
agent = client.beta.agents.create(
name: "Financial Analyst",
model: "claude-opus-4-7",
system_: "You are a financial analysis agent.",
skills: [
{type: "anthropic", skill_id: "xlsx"},
{type: "custom", skill_id: "skill_abc123", version: "latest"}
]
)
```
---
# Tools
URL: https://platform.claude.com/docs/en/managed-agents/tools
# Tools
Configure tools available to your agent.
---
Claude Managed Agents provides a set of built-in tools that Claude can use autonomously within a session. You control which tools are available by specifying them in the agent configuration.
Custom, user-defined tools are also supported. Your application executes these tools separately and sends the tool results back to Claude; Claude can use the results to continue the task at hand.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## Available tools
The agent toolset includes the following tools. All are enabled by default when you include the toolset in your agent configuration.
| Tool | Name | Description |
|---|---|---|
| Bash | `bash` | Execute bash commands in a shell session |
| Read | `read` | Read a file from the local filesystem |
| Write | `write` | Write a file to the local filesystem |
| Edit | `edit` | Perform string replacement in a file |
| Glob | `glob` | Fast file pattern matching using glob patterns |
| Grep | `grep` | Text search using regex patterns |
| Web fetch | `web_fetch` | Fetch content from a URL |
| Web search | `web_search` | Search the web for information |
## Configuring the toolset
Enable the full toolset with `agent_toolset_20260401` when creating an agent. Use the `configs` array to disable specific tools or override their settings.
```bash curl
agent=$(curl -fsSL https://api.anthropic.com/v1/agents \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <<'EOF'
{
"name": "Coding Assistant",
"model": "claude-opus-4-7",
"tools": [
{
"type": "agent_toolset_20260401",
"configs": [
{"name": "web_fetch", "enabled": false}
]
}
]
}
EOF
)
```
```bash CLI
ant beta:agents create <<'YAML'
name: Coding Assistant
model: claude-opus-4-7
tools:
- type: agent_toolset_20260401
configs:
- name: web_fetch
enabled: false
YAML
```
```python Python
agent = client.beta.agents.create(
name="Coding Assistant",
model="claude-opus-4-7",
tools=[
{
"type": "agent_toolset_20260401",
"configs": [
{"name": "web_fetch", "enabled": False},
],
},
],
)
```
```typescript TypeScript
const agent = await client.beta.agents.create({
name: "Coding Assistant",
model: "claude-opus-4-7",
tools: [
{
type: "agent_toolset_20260401",
configs: [{ name: "web_fetch", enabled: false }]
}
]
});
```
```csharp C#
var agent = await client.Beta.Agents.Create(new()
{
Name = "Coding Assistant",
Model = new("claude-opus-4-7"),
Tools =
[
new BetaManagedAgentsAgentToolset20260401Params
{
Type = "agent_toolset_20260401",
Configs =
[
new() { Name = "web_fetch", Enabled = false },
],
},
],
});
```
```go Go
agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
Name: "Coding Assistant",
Model: anthropic.BetaManagedAgentsModelConfigParams{
ID: "claude-opus-4-7",
Type: anthropic.BetaManagedAgentsModelConfigParamsTypeModelConfig,
},
Tools: []anthropic.BetaAgentNewParamsToolUnion{{
OfAgentToolset20260401: &anthropic.BetaManagedAgentsAgentToolset20260401Params{
Type: anthropic.BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401,
Configs: []anthropic.BetaManagedAgentsAgentToolConfigParams{{
Name: anthropic.BetaManagedAgentsAgentToolConfigParamsNameWebFetch,
Enabled: anthropic.Bool(false),
}},
},
}},
})
if err != nil {
panic(err)
}
```
```java Java
var agent = client.beta().agents().create(AgentCreateParams.builder()
.name("Coding Assistant")
.model(BetaManagedAgentsModel.CLAUDE_OPUS_4_7)
.addTool(BetaManagedAgentsAgentToolset20260401Params.builder()
.type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
.addConfig(BetaManagedAgentsAgentToolConfigParams.builder()
.name(BetaManagedAgentsAgentToolConfigParams.Name.WEB_FETCH)
.enabled(false)
.build())
.build())
.build());
```
```php PHP
$agent = $client->beta->agents->create(
name: 'Coding Assistant',
model: 'claude-opus-4-7',
tools: [
BetaManagedAgentsAgentToolset20260401Params::with(
type: 'agent_toolset_20260401',
configs: [
BetaManagedAgentsAgentToolConfigParams::with(name: 'web_fetch', enabled: false),
],
),
],
);
```
```ruby Ruby
agent = client.beta.agents.create(
name: "Coding Assistant",
model: "claude-opus-4-7",
tools: [
{
type: :agent_toolset_20260401,
configs: [
{name: :web_fetch, enabled: false}
]
}
]
)
```
### Disabling specific tools
To disable a tool, set `enabled: false` in its config entry:
```json
{
"type": "agent_toolset_20260401",
"configs": [
{ "name": "web_fetch", "enabled": false },
{ "name": "web_search", "enabled": false }
]
}
```
### Enabling only specific tools
To start with everything off and enable only what you need, set `default_config.enabled` to `false`:
```json
{
"type": "agent_toolset_20260401",
"default_config": { "enabled": false },
"configs": [
{ "name": "bash", "enabled": true },
{ "name": "read", "enabled": true },
{ "name": "write", "enabled": true }
]
}
```
## Custom tools
In addition to built-in tools, you can define custom tools. Custom tools are analogous to [user-defined client tools](/docs/en/agents-and-tools/tool-use/how-tool-use-works#user-defined-tools-client-executed) in the Messages API.
Custom tools allow you to extend Claude's capabilities to perform a wider variety of tasks. Each tool defines a contract: you specify what operations are available and what they return; Claude decides when and how to call them. The model never executes anything on its own. It emits a structured request, your code runs the operation, and the result flows back into the conversation.
```bash curl
agent=$(curl -fsSL https://api.anthropic.com/v1/agents \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <<'EOF'
{
"name": "Weather Agent",
"model": "claude-opus-4-7",
"tools": [
{
"type": "agent_toolset_20260401"
},
{
"type": "custom",
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"}
},
"required": ["location"]
}
}
]
}
EOF
)
```
```bash CLI
ant beta:agents create <<'YAML'
name: Weather Agent
model: claude-opus-4-7
tools:
- type: agent_toolset_20260401
- type: custom
name: get_weather
description: Get current weather for a location
input_schema:
type: object
properties:
location:
type: string
description: City name
required:
- location
YAML
```
```python Python
agent = client.beta.agents.create(
name="Weather Agent",
model="claude-opus-4-7",
tools=[
{
"type": "agent_toolset_20260401",
},
{
"type": "custom",
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"},
},
"required": ["location"],
},
},
],
)
```
```typescript TypeScript
const agent = await client.beta.agents.create({
name: "Weather Agent",
model: "claude-opus-4-7",
tools: [
{ type: "agent_toolset_20260401" },
{
type: "custom",
name: "get_weather",
description: "Get current weather for a location",
input_schema: {
type: "object",
properties: { location: { type: "string", description: "City name" } },
required: ["location"]
}
}
]
});
```
```csharp C#
var agent = await client.Beta.Agents.Create(new()
{
Name = "Weather Agent",
Model = new("claude-opus-4-7"),
Tools =
[
new BetaManagedAgentsAgentToolset20260401Params
{
Type = "agent_toolset_20260401",
},
new BetaManagedAgentsCustomToolParams
{
Type = "custom",
Name = "get_weather",
Description = "Get current weather for a location",
InputSchema = new()
{
Type = "object",
Properties = new Dictionary
{
["location"] = JsonSerializer.SerializeToElement(
new { type = "string", description = "City name" }
),
},
Required = ["location"],
},
},
],
});
```
```go Go
agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
Name: "Weather Agent",
Model: anthropic.BetaManagedAgentsModelConfigParams{
ID: "claude-opus-4-7",
Type: anthropic.BetaManagedAgentsModelConfigParamsTypeModelConfig,
},
Tools: []anthropic.BetaAgentNewParamsToolUnion{{
OfAgentToolset20260401: &anthropic.BetaManagedAgentsAgentToolset20260401Params{
Type: anthropic.BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401,
},
}, {
OfCustom: &anthropic.BetaManagedAgentsCustomToolParams{
Type: anthropic.BetaManagedAgentsCustomToolParamsTypeCustom,
Name: "get_weather",
Description: "Get current weather for a location",
InputSchema: anthropic.BetaManagedAgentsCustomToolInputSchemaParam{
Type: anthropic.BetaManagedAgentsCustomToolInputSchemaTypeObject,
Properties: map[string]any{
"location": map[string]any{
"type": "string",
"description": "City name",
},
},
Required: []string{"location"},
},
},
}},
})
if err != nil {
panic(err)
}
```
```java Java
var agent = client.beta().agents().create(AgentCreateParams.builder()
.name("Weather Agent")
.model(BetaManagedAgentsModel.CLAUDE_OPUS_4_7)
.addTool(BetaManagedAgentsAgentToolset20260401Params.builder()
.type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
.build())
.addTool(BetaManagedAgentsCustomToolParams.builder()
.type(BetaManagedAgentsCustomToolParams.Type.CUSTOM)
.name("get_weather")
.description("Get current weather for a location")
.inputSchema(BetaManagedAgentsCustomToolInputSchema.builder()
.type(BetaManagedAgentsCustomToolInputSchema.Type.OBJECT)
.properties(BetaManagedAgentsCustomToolInputSchema.Properties.builder()
.putAdditionalProperty("location", JsonValue.from(Map.of(
"type", "string",
"description", "City name")))
.build())
.addRequired("location")
.build())
.build())
.build());
```
```php PHP
use Anthropic\Beta\Agents\BetaManagedAgentsCustomToolInputSchema;
use Anthropic\Beta\Agents\BetaManagedAgentsCustomToolParams;
$agent = $client->beta->agents->create(
name: 'Weather Agent',
model: 'claude-opus-4-7',
tools: [
BetaManagedAgentsAgentToolset20260401Params::with(
type: 'agent_toolset_20260401',
),
BetaManagedAgentsCustomToolParams::with(
type: 'custom',
name: 'get_weather',
description: 'Get current weather for a location',
inputSchema: BetaManagedAgentsCustomToolInputSchema::with(
type: 'object',
properties: ['location' => ['type' => 'string', 'description' => 'City name']],
required: ['location'],
),
),
],
);
```
```ruby Ruby
agent = client.beta.agents.create(
name: "Weather Agent",
model: "claude-opus-4-7",
tools: [
{type: :agent_toolset_20260401},
{
type: :custom,
name: "get_weather",
description: "Get current weather for a location",
input_schema: {
type: :object,
properties: {location: {type: "string", description: "City name"}},
required: ["location"]
}
}
]
)
```
Once you've defined the tool at the agent level, the agent will invoke the tools through the course of a session. See [Session event stream](/docs/en/managed-agents/events-and-streaming#handling-custom-tool-calls) for the full flow.
### Best practices for custom tool definitions
- **Provide extremely detailed descriptions.** This is by far the most important factor in tool performance. Your descriptions should explain what the tool does, when it should be used (and when it shouldn't), what each parameter means and how it affects the tool's behavior, and any important caveats or limitations. The more context you can give Claude about your tools, the better it will be at deciding when and how to use them. Aim for at least 3-4 sentences per tool description, more if the tool is complex.
- **Consolidate related operations into fewer tools.** Rather than creating a separate tool for every action (`create_pr`, `review_pr`, `merge_pr`), group them into a single tool with an `action` parameter. Fewer, more capable tools reduce selection ambiguity and make your tool surface easier for Claude to navigate.
- **Use meaningful namespacing in tool names.** When your tools span multiple services or resources, prefix names with the resource (e.g., `db_query`, `storage_read`). This makes tool selection unambiguous as your library grows.
- **Design tool responses to return only high-signal information.** Return semantic, stable identifiers (e.g., slugs or UUIDs) rather than opaque internal references, and include only the fields Claude needs to reason about its next step. Bloated responses waste context and make it harder for Claude to extract what matters.
### Configure agent environment
---
# Cloud environment setup
URL: https://platform.claude.com/docs/en/managed-agents/environments
# Cloud environment setup
Customize cloud containers for your sessions.
---
Environments define the container configuration where your agent runs. You create an environment once, then reference its ID each time you start a session. Multiple sessions can share the same environment, but each session gets its own isolated container instance.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## Create an environment
````bash
environment=$(curl -fsS https://api.anthropic.com/v1/environments \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF'
{
"name": "python-dev",
"config": {
"type": "cloud",
"networking": {"type": "unrestricted"}
}
}
EOF
)
environment_id=$(jq -r '.id' <<< "$environment")
echo "Environment ID: $environment_id"
````
````bash
ant beta:environments create \
--name "python-dev" \
--config '{type: cloud, networking: {type: unrestricted}}'
````
````python
environment = client.beta.environments.create(
name="python-dev",
config={
"type": "cloud",
"networking": {"type": "unrestricted"},
},
)
print(f"Environment ID: {environment.id}")
````
````typescript
const environment = await client.beta.environments.create({
name: "python-dev",
config: {
type: "cloud",
networking: { type: "unrestricted" },
},
});
console.log(`Environment ID: ${environment.id}`);
````
````csharp
var environment = await client.Beta.Environments.Create(new()
{
Name = "python-dev",
Config = new()
{
Networking = new UnrestrictedNetwork(),
},
});
Console.WriteLine($"Environment ID: {environment.ID}");
````
````go
environment, err := client.Beta.Environments.New(ctx, anthropic.BetaEnvironmentNewParams{
Name: "python-dev",
Config: anthropic.BetaCloudConfigParams{
Networking: anthropic.BetaCloudConfigParamsNetworkingUnion{
OfUnrestricted: &anthropic.UnrestrictedNetworkParam{},
},
},
})
if err != nil {
panic(err)
}
fmt.Printf("Environment ID: %s\n", environment.ID)
````
````java
var environment = client.beta().environments().create(EnvironmentCreateParams.builder()
.name("python-dev")
.config(BetaCloudConfigParams.builder()
.networking(UnrestrictedNetwork.builder().build())
.build())
.build());
IO.println("Environment ID: " + environment.id());
````
````php
$environment = $client->beta->environments->create(
name: 'python-dev',
config: ['type' => 'cloud', 'networking' => ['type' => 'unrestricted']],
);
echo "Environment ID: {$environment->id}\n";
````
````ruby
environment = client.beta.environments.create(
name: "python-dev",
config: {
type: "cloud",
networking: {type: "unrestricted"}
}
)
puts "Environment ID: #{environment.id}"
````
The `name` must be unique within your organization and workspace.
## Use the environment in a session
Pass the environment ID as a string when creating a session.
````bash
session=$(curl -fsS https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
);
````
````ruby
session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id
)
````
## Configuration options
### Packages
The `packages` field pre-installs packages into the container before the agent starts. Packages are installed by their respective package managers and cached across sessions that share the same environment. When multiple package managers are specified, they run in alphabetical order (apt, cargo, gem, go, npm, pip). You can optionally pin specific versions; the default is latest.
```bash curl
environment=$(curl -fsS https://api.anthropic.com/v1/environments \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF'
{
"name": "data-analysis",
"config": {
"type": "cloud",
"packages": {
"pip": ["pandas", "numpy", "scikit-learn"],
"npm": ["express"]
},
"networking": {"type": "unrestricted"}
}
}
EOF
)
```
```bash CLI
ant beta:environments create <<'YAML'
name: data-analysis
config:
type: cloud
packages:
pip:
- pandas
- numpy
- scikit-learn
npm:
- express
networking:
type: unrestricted
YAML
```
```python Python
environment = client.beta.environments.create(
name="data-analysis",
config={
"type": "cloud",
"packages": {
"pip": ["pandas", "numpy", "scikit-learn"],
"npm": ["express"],
},
"networking": {"type": "unrestricted"},
},
)
```
```typescript TypeScript
const environment = await client.beta.environments.create({
name: "data-analysis",
config: {
type: "cloud",
packages: {
pip: ["pandas", "numpy", "scikit-learn"],
npm: ["express"]
},
networking: { type: "unrestricted" }
}
});
```
```csharp C#
var environment = await client.Beta.Environments.Create(new()
{
Name = "data-analysis",
Config = new()
{
Packages = new()
{
Pip = ["pandas", "numpy", "scikit-learn"],
Npm = ["express"],
},
Networking = new UnrestrictedNetwork(),
},
});
```
```go Go
environment, err := client.Beta.Environments.New(ctx, anthropic.BetaEnvironmentNewParams{
Name: "data-analysis",
Config: anthropic.BetaCloudConfigParams{
Packages: anthropic.BetaPackagesParams{
Pip: []string{"pandas", "numpy", "scikit-learn"},
Npm: []string{"express"},
},
Networking: anthropic.BetaCloudConfigParamsNetworkingUnion{
OfUnrestricted: &anthropic.UnrestrictedNetworkParam{},
},
},
})
if err != nil {
panic(err)
}
```
```java Java
var environment = client.beta().environments().create(EnvironmentCreateParams.builder()
.name("data-analysis")
.config(BetaCloudConfigParams.builder()
.packages(BetaPackagesParams.builder()
.pip(List.of("pandas", "numpy", "scikit-learn"))
.npm(List.of("express"))
.build())
.networking(UnrestrictedNetwork.builder().build())
.build())
.build());
```
```php PHP
$environment = $client->beta->environments->create(
name: 'data-analysis',
config: [
'type' => 'cloud',
'packages' => [
'pip' => ['pandas', 'numpy', 'scikit-learn'],
'npm' => ['express'],
],
'networking' => ['type' => 'unrestricted'],
],
);
```
```ruby Ruby
environment = client.beta.environments.create(
name: "data-analysis",
config: {
type: "cloud",
packages: {
pip: %w[pandas numpy scikit-learn],
npm: %w[express]
},
networking: {type: "unrestricted"}
}
)
```
Supported package managers:
| Field | Package manager | Example |
| --- | --- | --- |
| `apt` | System packages (apt-get) | `"ffmpeg"` |
| `cargo` | Rust (cargo) | `"ripgrep@14.0.0"` |
| `gem` | Ruby (gem) | `"rails:7.1.0"` |
| `go` | Go modules | `"golang.org/x/tools/cmd/goimports@latest"` |
| `npm` | Node.js (npm) | `"express@4.18.0"` |
| `pip` | Python (pip) | `"pandas==2.2.0"` |
### Networking
The `networking` field controls the container's outbound network access. It does not impact the `web_search` or `web_fetch` tools' allowed domains.
| Mode | Description |
| --- | --- |
| `unrestricted` | Full outbound network access, except for a general safety blocklist. This is the default. |
| `limited` | Restricts container network access to the `allowed_hosts` list. Further access is enabled via the `allow_package_managers` and `allow_mcp_servers` bool.|
```bash curl
config=$(cat <<'EOF'
{
"type": "cloud",
"networking": {
"type": "limited",
"allowed_hosts": ["api.example.com"],
"allow_mcp_servers": true,
"allow_package_managers": true
}
}
EOF
)
```
```python Python
config = {
"type": "cloud",
"networking": {
"type": "limited",
"allowed_hosts": ["api.example.com"],
"allow_mcp_servers": True,
"allow_package_managers": True,
},
}
```
```typescript TypeScript
const config = {
type: "cloud",
networking: {
type: "limited",
allowed_hosts: ["api.example.com"],
allow_mcp_servers: true,
allow_package_managers: true
}
};
```
```csharp C#
var config = new BetaCloudConfigParams
{
Networking = new BetaLimitedNetworkParams
{
AllowedHosts = ["api.example.com"],
AllowMcpServers = true,
AllowPackageManagers = true,
},
};
```
```go Go
config := anthropic.BetaCloudConfigParams{
Networking: anthropic.BetaCloudConfigParamsNetworkingUnion{
OfLimited: &anthropic.BetaLimitedNetworkParams{
AllowedHosts: []string{"api.example.com"},
AllowMCPServers: anthropic.Bool(true),
AllowPackageManagers: anthropic.Bool(true),
},
},
}
```
```java Java
var config = BetaCloudConfigParams.builder()
.networking(BetaLimitedNetworkParams.builder()
.allowedHosts(List.of("api.example.com"))
.allowMcpServers(true)
.allowPackageManagers(true)
.build())
.build();
```
```php PHP
$config = [
'type' => 'cloud',
'networking' => [
'type' => 'limited',
'allowed_hosts' => ['api.example.com'],
'allow_mcp_servers' => true,
'allow_package_managers' => true,
],
];
```
```ruby Ruby
config = {
type: "cloud",
networking: {
type: "limited",
allowed_hosts: %w[api.example.com],
allow_mcp_servers: true,
allow_package_managers: true
}
}
```
For production deployments, use `limited` networking with an explicit `allowed_hosts` list. Follow the principle of least privilege by granting only the minimum network access your agent requires, and regularly audit your allowed domains.
When using `limited` networking:
- `allowed_hosts` specifies domains the container can reach. These must be HTTPS-prefixed.
- `allow_mcp_servers` permits outbound access to MCP server endpoints configured on the agent, beyond those listed in the `allowed_hosts` array. Defaults to `false`.
- `allow_package_managers` permits outbound access to public package registries (PyPI, npm, etc.) beyond those listed in the `allowed_hosts` array. Defaults to `false`.
## Environment lifecycle
- Environments persist until explicitly archived or deleted.
- Multiple sessions can reference the same environment.
- Each session gets its own container instance. Sessions do not share file system state.
- Environments are not versioned. If you frequently update your environments, you may want to log these updates on your side, to map environment state with sessions.
## Manage environments
````bash
# List environments
environments=$(curl -fsS https://api.anthropic.com/v1/environments \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01")
# Retrieve a specific environment
env=$(curl -fsS "https://api.anthropic.com/v1/environments/$environment_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01")
# Archive an environment (read-only, existing sessions continue)
curl -fsS -X POST "https://api.anthropic.com/v1/environments/$environment_id/archive" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01"
# Delete an environment (only if no sessions reference it)
curl -fsS -X DELETE "https://api.anthropic.com/v1/environments/$environment_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01"
````
````bash
# List environments
ant beta:environments list
# Retrieve a specific environment
ant beta:environments retrieve --environment-id "$ENVIRONMENT_ID"
# Archive an environment (read-only, existing sessions continue)
ant beta:environments archive --environment-id "$ENVIRONMENT_ID"
# Delete an environment (only if no sessions reference it)
ant beta:environments delete --environment-id "$ENVIRONMENT_ID"
````
````python
# List environments
environments = client.beta.environments.list()
# Retrieve a specific environment
env = client.beta.environments.retrieve(environment.id)
# Archive an environment (read-only, existing sessions continue)
client.beta.environments.archive(environment.id)
# Delete an environment (only if no sessions reference it)
client.beta.environments.delete(environment.id)
````
````typescript
// List environments
const environments = await client.beta.environments.list();
// Retrieve a specific environment
const env = await client.beta.environments.retrieve(environment.id);
// Archive an environment (read-only, existing sessions continue)
await client.beta.environments.archive(environment.id);
// Delete an environment (only if no sessions reference it)
await client.beta.environments.delete(environment.id);
````
````csharp
// List environments
var environments = await client.Beta.Environments.List();
// Retrieve a specific environment
var env = await client.Beta.Environments.Retrieve(environment.ID);
// Archive an environment (read-only, existing sessions continue)
await client.Beta.Environments.Archive(environment.ID);
// Delete an environment (only if no sessions reference it)
await client.Beta.Environments.Delete(environment.ID);
````
````go
// List environments
environments, err := client.Beta.Environments.List(ctx, anthropic.BetaEnvironmentListParams{})
if err != nil {
panic(err)
}
// Retrieve a specific environment
env, err := client.Beta.Environments.Get(ctx, environment.ID, anthropic.BetaEnvironmentGetParams{})
if err != nil {
panic(err)
}
// Archive an environment (read-only, existing sessions continue)
_, err = client.Beta.Environments.Archive(ctx, environment.ID, anthropic.BetaEnvironmentArchiveParams{})
if err != nil {
panic(err)
}
// Delete an environment (only if no sessions reference it)
_, err = client.Beta.Environments.Delete(ctx, environment.ID, anthropic.BetaEnvironmentDeleteParams{})
if err != nil {
panic(err)
}
````
````java
// List environments
var environments = client.beta().environments().list();
// Retrieve a specific environment
var env = client.beta().environments().retrieve(environment.id());
// Archive an environment (read-only, existing sessions continue)
client.beta().environments().archive(environment.id());
// Delete an environment (only if no sessions reference it)
client.beta().environments().delete(environment.id());
````
````php
// List environments
$environments = $client->beta->environments->list();
// Retrieve a specific environment
$env = $client->beta->environments->retrieve($environment->id);
// Archive an environment (read-only, existing sessions continue)
$client->beta->environments->archive($environment->id);
// Delete an environment (only if no sessions reference it)
$client->beta->environments->delete($environment->id);
````
````ruby
# List environments
environments = client.beta.environments.list
# Retrieve a specific environment
env = client.beta.environments.retrieve(environment.id)
# Archive an environment (read-only, existing sessions continue)
client.beta.environments.archive(environment.id)
# Delete an environment (only if no sessions reference it)
client.beta.environments.delete(environment.id)
````
## Pre-installed runtimes
Cloud containers include common runtimes out of the box. See [Container reference](/docs/en/managed-agents/cloud-containers) for the full list of pre-installed languages, databases, and utilities.
---
# Container reference
URL: https://platform.claude.com/docs/en/managed-agents/cloud-containers
# Container reference
Pre-installed packages, databases, and utilities available in cloud containers.
---
Cloud containers in Claude Managed Agents come pre-installed with a comprehensive set of programming languages, databases, and utilities. The agent can use these immediately without any installation steps.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## Programming languages
| Language | Version | Package manager |
|----------|---------|-----------------|
| Python | 3.12+ | pip, uv |
| Node.js | 20+ | npm, yarn, pnpm |
| Go | 1.22+ | go modules |
| Rust | 1.77+ | cargo |
| Java | 21+ | maven, gradle |
| Ruby | 3.3+ | bundler, gem |
| PHP | 8.3+ | composer |
| C/C++ | GCC 13+ | make, cmake |
## Databases
| Database | Description |
|----------|-------------|
| SQLite | Pre-installed, available immediately |
| PostgreSQL client | `psql` client for connecting to external databases |
| Redis client | `redis-cli` for connecting to external instances |
Database servers (PostgreSQL, Redis, etc.) are not running in the container by default. The container includes client tools for connecting to external database instances. SQLite is fully available for local use.
## Utilities
### System tools
- `git` - Version control
- `curl`, `wget` - HTTP clients
- `jq` - JSON processing
- `tar`, `zip`, `unzip` - Archive tools
- `ssh`, `scp` - Remote access (requires network enabled)
- `tmux`, `screen` - Terminal multiplexers
### Development tools
- `make`, `cmake` - Build systems
- `docker` - Container management (limited availability)
- `ripgrep` (`rg`) - Fast file search
- `tree` - Directory visualization
- `htop` - Process monitoring
### Text processing
- `sed`, `awk`, `grep` - Stream editors
- `vim`, `nano` - Text editors
- `diff`, `patch` - File comparison
## Container specifications
| Property | Value |
|----------|-------|
| Operating system | Ubuntu 22.04 LTS |
| Architecture | x86_64 (amd64) |
| Memory | Up to 8 GB |
| Disk space | Up to 10 GB |
| Network | Disabled by default (enable in environment config) |
### Delegate work to your agent
---
# Authenticate with vaults
URL: https://platform.claude.com/docs/en/managed-agents/vaults
# Authenticate with vaults
Register per-user credentials when creating sessions.
---
Vaults and credentials are authentication primitives that let you register credentials for third-party services once and reference them by ID at session creation. This means you don't need to run your own secret store, transmit tokens on every call, or lose track of which end user an agent acted on behalf of.
The vault reference is a per-session parameter, so you can manage your product at the agent level and your users at the session level.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## Create a vault
Vaults and credentials are workspace-scoped, meaning anyone with API key access can use them for authorizing an agent to complete a task. To revoke access, delete the vault or credential.
A vault is the collection of `credentials` associated with an end-user. Give it a `display_name` and optionally tag it with `metadata` so you can map it back to your own user records.
````bash
vault_id=$(curl --fail-with-body -sS https://api.anthropic.com/v1/vaults \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF' | jq -r '.id'
{
"display_name": "Alice",
"metadata": {"external_user_id": "usr_abc123"}
}
EOF
)
echo "$vault_id" # "vlt_01ABC..."
````
````bash
VAULT_ID=$(ant beta:vaults create \
--display-name "Alice" \
--metadata '{external_user_id: usr_abc123}' \
--transform id --raw-output)
````
````python
vault = client.beta.vaults.create(
display_name="Alice",
metadata={"external_user_id": "usr_abc123"},
)
print(vault.id) # "vlt_01ABC..."
````
````typescript
const vault = await client.beta.vaults.create({
display_name: "Alice",
metadata: { external_user_id: "usr_abc123" },
});
console.log(vault.id); // "vlt_01ABC..."
````
````csharp
var vault = await client.Beta.Vaults.Create(new()
{
DisplayName = "Alice",
Metadata = new Dictionary { ["external_user_id"] = "usr_abc123" },
});
Console.WriteLine(vault.ID); // "vlt_01ABC..."
````
````go
vault, err := client.Beta.Vaults.New(ctx, anthropic.BetaVaultNewParams{
DisplayName: "Alice",
Metadata: map[string]string{"external_user_id": "usr_abc123"},
})
if err != nil {
panic(err)
}
fmt.Println(vault.ID) // "vlt_01ABC..."
````
````java
var vault = client.beta().vaults().create(VaultCreateParams.builder()
.displayName("Alice")
.metadata(VaultCreateParams.Metadata.builder()
.putAdditionalProperty("external_user_id", JsonValue.from("usr_abc123"))
.build())
.build());
IO.println(vault.id()); // "vlt_01ABC..."
````
````php
$vault = $client->beta->vaults->create(
displayName: 'Alice',
metadata: ['external_user_id' => 'usr_abc123'],
);
echo $vault->id . "\n"; // "vlt_01ABC..."
````
````ruby
vault = client.beta.vaults.create(
display_name: "Alice",
metadata: {external_user_id: "usr_abc123"}
)
puts vault.id # "vlt_01ABC..."
````
The response is the full vault record:
```json
{
"type": "vault",
"id": "vlt_01ABC...",
"display_name": "Alice",
"metadata": { "external_user_id": "usr_abc123" },
"created_at": "2026-03-18T10:00:00Z",
"updated_at": "2026-03-18T10:00:00Z",
"archived_at": null
}
```
## Add a credential
Each credential binds to a single `mcp_server_url`. When the agent connects to an MCP server at session runtime, the API matches the server URL against active credentials on the referenced vault and injects the token.
Use `mcp_oauth` when the MCP server uses OAuth 2.0. If you supply a `refresh` block, Anthropic refreshes the access token on your behalf when it expires.
The `refresh.token_endpoint_auth.type` field indicates how to authenticate the refresh call:
- `none`: public client
- `client_secret_basic`: HTTP Basic auth with the client secret
- `client_secret_post`: client secret in the POST body
````bash
credential_id=$(curl --fail-with-body -sS "https://api.anthropic.com/v1/vaults/$vault_id/credentials" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF' | jq -r '.id'
{
"display_name": "Alice's Slack",
"auth": {
"type": "mcp_oauth",
"mcp_server_url": "https://mcp.slack.com/mcp",
"access_token": "xoxp-...",
"expires_at": "2099-12-31T23:59:59Z",
"refresh": {
"token_endpoint": "https://slack.com/api/oauth.v2.access",
"client_id": "1234567890.0987654321",
"scope": "channels:read chat:write",
"refresh_token": "xoxe-1-...",
"token_endpoint_auth": {"type": "client_secret_post", "client_secret": "abc123..."}
}
}
}
EOF
)
````
````bash
CREDENTIAL_ID=$(ant beta:vaults:credentials create \
--vault-id "$VAULT_ID" \
--display-name "Alice's Slack" \
--transform id --raw-output <<'EOF'
auth:
type: mcp_oauth
mcp_server_url: https://mcp.slack.com/mcp
access_token: xoxp-...
expires_at: "2099-12-31T23:59:59Z"
refresh:
token_endpoint: https://slack.com/api/oauth.v2.access
client_id: "1234567890.0987654321"
scope: channels:read chat:write
refresh_token: xoxe-1-...
token_endpoint_auth:
type: client_secret_post
client_secret: abc123...
EOF
)
````
````python
credential = client.beta.vaults.credentials.create(
vault_id=vault.id,
display_name="Alice's Slack",
auth={
"type": "mcp_oauth",
"mcp_server_url": "https://mcp.slack.com/mcp",
"access_token": "xoxp-...",
"expires_at": "2099-12-31T23:59:59Z",
"refresh": {
"token_endpoint": "https://slack.com/api/oauth.v2.access",
"client_id": "1234567890.0987654321",
"scope": "channels:read chat:write",
"refresh_token": "xoxe-1-...",
"token_endpoint_auth": {"type": "client_secret_post", "client_secret": "abc123..."},
},
},
)
````
````typescript
const credential = await client.beta.vaults.credentials.create(vault.id, {
display_name: "Alice's Slack",
auth: {
type: "mcp_oauth",
mcp_server_url: "https://mcp.slack.com/mcp",
access_token: "xoxp-...",
expires_at: "2099-12-31T23:59:59Z",
refresh: {
token_endpoint: "https://slack.com/api/oauth.v2.access",
client_id: "1234567890.0987654321",
scope: "channels:read chat:write",
refresh_token: "xoxe-1-...",
token_endpoint_auth: {
type: "client_secret_post",
client_secret: "abc123...",
},
},
},
});
````
````csharp
var credential = await client.Beta.Vaults.Credentials.Create(vault.ID, new()
{
DisplayName = "Alice's Slack",
Auth = new BetaManagedAgentsMcpOAuthCreateParams
{
Type = "mcp_oauth",
McpServerUrl = "https://mcp.slack.com/mcp",
AccessToken = "xoxp-...",
ExpiresAt = DateTimeOffset.Parse("2099-12-31T23:59:59Z"),
Refresh = new()
{
TokenEndpoint = "https://slack.com/api/oauth.v2.access",
ClientID = "1234567890.0987654321",
Scope = "channels:read chat:write",
RefreshToken = "xoxe-1-...",
TokenEndpointAuth = new BetaManagedAgentsTokenEndpointAuthPostParam
{
Type = "client_secret_post",
ClientSecret = "abc123...",
},
},
},
});
````
````go
credential, err := client.Beta.Vaults.Credentials.New(ctx, vault.ID, anthropic.BetaVaultCredentialNewParams{
DisplayName: anthropic.String("Alice's Slack"),
Auth: anthropic.BetaVaultCredentialNewParamsAuthUnion{
OfMCPOAuth: &anthropic.BetaManagedAgentsMCPOAuthCreateParams{
Type: anthropic.BetaManagedAgentsMCPOAuthCreateParamsTypeMCPOAuth,
MCPServerURL: "https://mcp.slack.com/mcp",
AccessToken: "xoxp-...",
ExpiresAt: anthropic.Time(time.Date(2099, time.December, 31, 23, 59, 59, 0, time.UTC)),
Refresh: anthropic.BetaManagedAgentsMCPOAuthRefreshParams{
TokenEndpoint: "https://slack.com/api/oauth.v2.access",
ClientID: "1234567890.0987654321",
Scope: anthropic.String("channels:read chat:write"),
RefreshToken: "xoxe-1-...",
TokenEndpointAuth: anthropic.BetaManagedAgentsMCPOAuthRefreshParamsTokenEndpointAuthUnion{
OfClientSecretPost: &anthropic.BetaManagedAgentsTokenEndpointAuthPostParam{
Type: anthropic.BetaManagedAgentsTokenEndpointAuthPostParamTypeClientSecretPost,
ClientSecret: "abc123...",
},
},
},
},
},
})
if err != nil {
panic(err)
}
````
````java
var credential = client.beta().vaults().credentials().create(vault.id(),
CredentialCreateParams.builder()
.displayName("Alice's Slack")
.auth(BetaManagedAgentsMcpOAuthCreateParams.builder()
.type(BetaManagedAgentsMcpOAuthCreateParams.Type.MCP_OAUTH)
.mcpServerUrl("https://mcp.slack.com/mcp")
.accessToken("xoxp-...")
.expiresAt(OffsetDateTime.parse("2099-12-31T23:59:59Z"))
.refresh(BetaManagedAgentsMcpOAuthRefreshParams.builder()
.tokenEndpoint("https://slack.com/api/oauth.v2.access")
.clientId("1234567890.0987654321")
.scope("channels:read chat:write")
.refreshToken("xoxe-1-...")
.clientSecretPostTokenEndpointAuth("abc123...")
.build())
.build())
.build());
````
````php
$credential = $client->beta->vaults->credentials->create(
vaultID: $vault->id,
displayName: "Alice's Slack",
auth: [
'type' => 'mcp_oauth',
'mcp_server_url' => 'https://mcp.slack.com/mcp',
'access_token' => 'xoxp-...',
'expires_at' => '2099-12-31T23:59:59Z',
'refresh' => [
'token_endpoint' => 'https://slack.com/api/oauth.v2.access',
'client_id' => '1234567890.0987654321',
'scope' => 'channels:read chat:write',
'refresh_token' => 'xoxe-1-...',
'token_endpoint_auth' => [
'type' => 'client_secret_post',
'client_secret' => 'abc123...',
],
],
],
);
````
````ruby
credential = client.beta.vaults.credentials.create(
vault.id,
display_name: "Alice's Slack",
auth: {
type: "mcp_oauth",
mcp_server_url: "https://mcp.slack.com/mcp",
access_token: "xoxp-...",
expires_at: "2099-12-31T23:59:59Z",
refresh: {
token_endpoint: "https://slack.com/api/oauth.v2.access",
client_id: "1234567890.0987654321",
scope: "channels:read chat:write",
refresh_token: "xoxe-1-...",
token_endpoint_auth: {
type: "client_secret_post",
client_secret: "abc123..."
}
}
}
)
````
Use `static_bearer` when the MCP server accepts a fixed bearer token (API key, personal access token, or similar). No refresh flow is needed.
````bash
curl --fail-with-body -sS "https://api.anthropic.com/v1/vaults/$vault_id/credentials" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{
"display_name": "Linear API key",
"auth": {
"type": "static_bearer",
"mcp_server_url": "https://mcp.linear.app/mcp",
"token": "lin_api_your_linear_key"
}
}'
````
````bash
ant beta:vaults:credentials create --vault-id "$VAULT_ID" <<'YAML'
display_name: Linear API key
auth:
type: static_bearer
mcp_server_url: https://mcp.linear.app/mcp
token: lin_api_your_linear_key
YAML
````
````python
bearer_credential = client.beta.vaults.credentials.create(
vault_id=vault.id,
display_name="Linear API key",
auth={
"type": "static_bearer",
"mcp_server_url": "https://mcp.linear.app/mcp",
"token": "lin_api_your_linear_key",
},
)
````
````typescript
const bearerCredential = await client.beta.vaults.credentials.create(vault.id, {
display_name: "Linear API key",
auth: {
type: "static_bearer",
mcp_server_url: "https://mcp.linear.app/mcp",
token: "lin_api_your_linear_key"
}
});
````
````csharp
var bearerCredential = await client.Beta.Vaults.Credentials.Create(vault.ID, new()
{
DisplayName = "Linear API key",
Auth = new BetaManagedAgentsStaticBearerCreateParams
{
Type = "static_bearer",
McpServerUrl = "https://mcp.linear.app/mcp",
Token = "lin_api_your_linear_key",
},
});
````
````go
bearerCredential, err := client.Beta.Vaults.Credentials.New(ctx, vault.ID, anthropic.BetaVaultCredentialNewParams{
DisplayName: anthropic.String("Linear API key"),
Auth: anthropic.BetaVaultCredentialNewParamsAuthUnion{
OfStaticBearer: &anthropic.BetaManagedAgentsStaticBearerCreateParams{
Type: anthropic.BetaManagedAgentsStaticBearerCreateParamsTypeStaticBearer,
MCPServerURL: "https://mcp.linear.app/mcp",
Token: "lin_api_your_linear_key",
},
},
})
if err != nil {
panic(err)
}
_ = bearerCredential
````
````java
var bearerCredential = client.beta().vaults().credentials().create(vault.id(),
CredentialCreateParams.builder()
.displayName("Linear API key")
.auth(BetaManagedAgentsStaticBearerCreateParams.builder()
.type(BetaManagedAgentsStaticBearerCreateParams.Type.STATIC_BEARER)
.mcpServerUrl("https://mcp.linear.app/mcp")
.token("lin_api_your_linear_key")
.build())
.build());
````
````php
$bearerCredential = $client->beta->vaults->credentials->create(
vaultID: $vault->id,
displayName: 'Linear API key',
auth: [
'type' => 'static_bearer',
'mcp_server_url' => 'https://mcp.linear.app/mcp',
'token' => 'lin_api_your_linear_key',
],
);
````
````ruby
bearer_credential = client.beta.vaults.credentials.create(
vault.id,
display_name: "Linear API key",
auth: {
type: "static_bearer",
mcp_server_url: "https://mcp.linear.app/mcp",
token: "lin_api_your_linear_key"
}
)
````
Secret fields (`token`, `access_token`, `refresh_token`, `client_secret`) are write-only. They are never returned in API responses.
Credentials are stored as provided and are not validated until session runtime. A bad token surfaces as an MCP auth error during the session, which is emitted but does not block the session from continuing.
Constraints:
- **One active credential per `mcp_server_url` per vault.** Creating a second credential for the same URL returns a 409.
- **`mcp_server_url` is immutable.** To point at a different server, archive this credential and create a new one.
- **Maximum 20 credentials per vault.** This matches the maximum amount of MCP servers per agent.
## Reference the vault at session creation
Pass `vault_ids` when creating a session:
````bash
session_id=$(curl --fail-with-body -sS https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
vaultIDs: [$vault->id],
title: "Alice's Slack digest",
);
````
````ruby
session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id,
vault_ids: [vault.id],
title: "Alice's Slack digest"
)
````
Runtime behavior:
- When a vault has no credential for the MCP server, the connection is attempted unauthenticated and produces an error if the server requires authentication.
- When multiple vaults cover the MCP server, the first vault with a match wins.
## Credential refresh
Credentials are re-resolved periodically, both during a session and during the vault lifecycle. This ensures that credential rotation, archival, or deletion propagates to running sessions without a restart.
To be notified if a credential is archived, deleted, or fails to refresh you can subscribe to the vault and credential [webhooks](/docs/en/managed-agents/webhooks) associated with those lifecycle changes.
| Event | Trigger |
| ----- | ------- |
| `vault.archived` | Vault archived. A `vault_credential.archived` event is also emitted for each underlying credential. |
| `vault.deleted` | Vault deleted. A `vault_credential.deleted` event is also emitted for each underlying credential. |
| `vault_credential.archived` | Credential archived, either directly or as a result of vault archival. |
| `vault_credential.deleted` | Credential deleted, either directly or as a result of vault deletion. |
| `vault_credential.refresh_failed` | A `mcp_oauth` credential cannot be refreshed (invalid refresh token, or irrecoverable error from the OAuth server). |
This is a non-exhaustive list of webhooks; visit the [webhooks documentation](/docs/en/managed-agents/webhooks) for a complete list.
### Diagnose an OAuth refresh failure
To diagnose why a refresh failed, use the `/mcp_oauth_validate` endpoint. This enables you to determine how to handle the failure, which is distinct by error type.
The top-level `status` tells you what to do next:
- `valid`: the token works; no action needed.
- `invalid`: the grant is gone or the OAuth server rejected the refresh with a 4xx. Prompt the end user to re-authorize.
- `unknown`: a transient error (5xx, 429, or network failure). Wait and retry.
````bash
curl --fail-with-body -sS -X POST \
"https://api.anthropic.com/v1/vaults/$vault_id/credentials/$credential_id/mcp_oauth_validate?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01"
````
The response is a `vault_credential_validation` object. `mcp_probe` includes the failed MCP handshake step; `refresh` includes the outcome of the attempted refresh.
```json
{
"type": "vault_credential_validation",
"credential_id": "vcrd_01ABC...",
"vault_id": "vlt_01XYZ...",
"validated_at": "2026-04-29T17:12:00Z",
"has_refresh_token": false,
"status": "invalid",
"mcp_probe": {
"method": "initialize",
"http_response": {
"status_code": 401,
"content_type": "application/json",
"body": "{\"error\":\"invalid_token\"}",
"body_truncated": false
}
},
"refresh": {
"status": "no_refresh_token",
"http_response": null
}
}
```
## Rotate a credential
Only the secret payload and a handful of metadata fields are mutable. `mcp_server_url`, `token_endpoint`, and `client_id` are locked after creation.
````bash
curl --fail-with-body -sS \
"https://api.anthropic.com/v1/vaults/$vault_id/credentials/$credential_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF' > /dev/null
{
"auth": {
"type": "mcp_oauth",
"access_token": "xoxp-new-...",
"expires_at": "2099-12-31T23:59:59Z",
"refresh": {"refresh_token": "xoxe-1-new-..."}
}
}
EOF
````
````bash
ant beta:vaults:credentials update \
--vault-id "$VAULT_ID" \
--credential-id "$CREDENTIAL_ID" <<'EOF'
auth:
type: mcp_oauth
access_token: xoxp-new-...
expires_at: "2099-12-31T23:59:59Z"
refresh:
refresh_token: xoxe-1-new-...
EOF
````
````python
client.beta.vaults.credentials.update(
credential.id,
vault_id=vault.id,
auth={
"type": "mcp_oauth",
"access_token": "xoxp-new-...",
"expires_at": "2099-12-31T23:59:59Z",
"refresh": {"refresh_token": "xoxe-1-new-..."},
},
)
````
````typescript
await client.beta.vaults.credentials.update(credential.id, {
vault_id: vault.id,
auth: {
type: "mcp_oauth",
access_token: "xoxp-new-...",
expires_at: "2099-12-31T23:59:59Z",
refresh: {
refresh_token: "xoxe-1-new-...",
},
},
});
````
````csharp
await client.Beta.Vaults.Credentials.Update(credential.ID, new()
{
VaultID = vault.ID,
Auth = new BetaManagedAgentsMcpOAuthUpdateParams
{
Type = "mcp_oauth",
AccessToken = "xoxp-new-...",
ExpiresAt = DateTimeOffset.Parse("2099-12-31T23:59:59Z"),
Refresh = new() { RefreshToken = "xoxe-1-new-..." },
},
});
````
````go
_, err = client.Beta.Vaults.Credentials.Update(ctx, credential.ID, anthropic.BetaVaultCredentialUpdateParams{
VaultID: vault.ID,
Auth: anthropic.BetaVaultCredentialUpdateParamsAuthUnion{
OfMCPOAuth: &anthropic.BetaManagedAgentsMCPOAuthUpdateParams{
Type: anthropic.BetaManagedAgentsMCPOAuthUpdateParamsTypeMCPOAuth,
AccessToken: anthropic.String("xoxp-new-..."),
ExpiresAt: anthropic.Time(time.Date(2099, time.December, 31, 23, 59, 59, 0, time.UTC)),
Refresh: anthropic.BetaManagedAgentsMCPOAuthRefreshUpdateParams{
RefreshToken: anthropic.String("xoxe-1-new-..."),
},
},
},
})
if err != nil {
panic(err)
}
````
````java
client.beta().vaults().credentials().update(credential.id(),
CredentialUpdateParams.builder()
.vaultId(vault.id())
.auth(BetaManagedAgentsMcpOAuthUpdateParams.builder()
.type(BetaManagedAgentsMcpOAuthUpdateParams.Type.MCP_OAUTH)
.accessToken("xoxp-new-...")
.expiresAt(OffsetDateTime.parse("2099-12-31T23:59:59Z"))
.refresh(BetaManagedAgentsMcpOAuthRefreshUpdateParams.builder()
.refreshToken("xoxe-1-new-...")
.build())
.build())
.build());
````
````php
$client->beta->vaults->credentials->update(
$credential->id,
vaultID: $vault->id,
auth: [
'type' => 'mcp_oauth',
'access_token' => 'xoxp-new-...',
'expires_at' => '2099-12-31T23:59:59Z',
'refresh' => ['refresh_token' => 'xoxe-1-new-...'],
],
);
````
````ruby
client.beta.vaults.credentials.update(
credential.id,
vault_id: vault.id,
auth: {
type: "mcp_oauth",
access_token: "xoxp-new-...",
expires_at: "2099-12-31T23:59:59Z",
refresh: {refresh_token: "xoxe-1-new-..."}
}
)
````
## Other operations
- **List vaults or credentials:** Paginated, newest first. Archived records are excluded by default (pass `include_archived=true` to include them).
- **Archive a vault:** `POST /v1/vaults/{id}/archive`. Cascades to all credentials. Secrets are purged; records are retained for auditing. Future sessions referencing this vault fail; running sessions continue.
- **Archive a credential:** `POST /v1/vaults/{id}/credentials/{cred_id}/archive`. Purges the secret payload; `mcp_server_url` remains visible. Frees the `mcp_server_url` for a replacement credential.
- **Delete a vault or credential:** Hard delete. The record is not retained. Use archive if you need an audit trail.
---
# Define outcomes
URL: https://platform.claude.com/docs/en/managed-agents/define-outcomes
# Define outcomes
Tell the agent what 'done' looks like, and let it iterate until it gets there.
---
The `outcome` elevates a session from *conversation* to *work*. You define what the end result should look like and how to measure quality. The agent works toward that target, self-evaluating and iterating until the outcome is met.
When you define an outcome, the harness automatically provisions a *grader* to evaluate the artifact against a rubric. It leverages a separate context window to avoid being influenced by the main agent's implementation choices.
The grader returns a per-criterion breakdown: either confirmation that the artifact satisfies the rubric, or the specific gaps between the current work and the requirements. That feedback is handed back to the agent for the next iteration.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets this beta header automatically.
## Create a rubric
A rubric is a markdown document describing per-criterion scoring. The rubric is required.
Structure the rubric as explicit, gradeable criteria, such as "The CSV contains a price column with numeric values" rather than "The data looks good." The grader scores each criterion independently, so vague criteria produce noisy evaluations.
If you don't have a rubric on hand, try giving Claude an example of a known-good artifact and asking it to analyze what makes that content good, then turn that analysis into a rubric. This middle-ground approach often produces better results than writing criteria from scratch.
Example rubric:
```markdown
# DCF Model Rubric
## Revenue Projections
- Uses historical revenue data from the last 5 fiscal years
- Projects revenue for at least 5 years forward
- Growth rate assumptions are explicitly stated and reasonable
## Cost Structure
- COGS and operating expenses are modeled separately
- Margins are consistent with historical trends or deviations are justified
## Discount Rate
- WACC is calculated with stated assumptions for cost of equity and cost of debt
- Beta, risk-free rate, and equity risk premium are sourced or justified
## Terminal Value
- Uses either perpetuity growth or exit multiple method (stated which)
- Terminal growth rate does not exceed long-term GDP growth
## Output Quality
- All figures are in a single .xlsx file with clearly labeled sheets
- Key assumptions are on a separate "Assumptions" sheet
- Sensitivity analysis on WACC and terminal growth rate is included
```
Pass the rubric as inline text on `user.define_outcome` (see the next section), or upload it via the Files API for reuse across sessions:
**Requires beta header `files-api-2025-04-14`.**
````bash
rubric=$(curl -fsSL https://api.anthropic.com/v1/files \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01,files-api-2025-04-14" \
-F file=@/tmp/rubric.md)
rubric_id=$(jq -r '.id' <<<"$rubric")
printf 'Uploaded rubric: %s\n' "$rubric_id"
````
````bash
RUBRIC_ID=$(ant beta:files upload \
--file /tmp/rubric.md \
--transform id --raw-output)
````
````python
rubric = client.beta.files.upload(file=Path("/tmp/rubric.md"))
print(f"Uploaded rubric: {rubric.id}")
````
````typescript
const rubric = await client.beta.files.upload({
file: await toFile(readFile("/tmp/rubric.md"), "/tmp/rubric.md"),
});
console.log(`Uploaded rubric: ${rubric.id}`);
````
````csharp
var rubric = await client.Beta.Files.Upload(new()
{
File = File.OpenRead("/tmp/rubric.md"),
});
Console.WriteLine($"Uploaded rubric: {rubric.ID}");
````
````go
f, err := os.Open("/tmp/rubric.md")
if err != nil {
panic(err)
}
uploaded, err := client.Beta.Files.Upload(ctx, anthropic.BetaFileUploadParams{
File: anthropic.File(f, "/tmp/rubric.md", "text/markdown"),
})
if err != nil {
panic(err)
}
fmt.Printf("Uploaded rubric: %s\n", uploaded.ID)
````
````java
var rubric = client.beta().files().upload(
FileUploadParams.builder()
.file(Path.of("/tmp/rubric.md"))
.build());
IO.println("Uploaded rubric: " + rubric.id());
````
````php
$rubric = $client->beta->files->upload(
file: fopen('/tmp/rubric.md', 'r'),
);
echo "Uploaded rubric: {$rubric->id}\n";
````
````ruby
rubric = client.beta.files.upload(file: Pathname.new("/tmp/rubric.md"))
puts "Uploaded rubric: #{rubric.id}"
````
## Create a session with an outcome
After creating a session, send a `user.define_outcome` event. The agent begins work immediately; no additional user message event is required.
````bash
# Create a session
session=$(curl -fsSL https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
--json @- </dev/null <beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
title: 'Financial analysis on Costco',
);
// Define the outcome — agent starts working on receipt
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.define_outcome',
'description' => 'Build a DCF model for Costco in .xlsx',
'rubric' => ['type' => 'text', 'content' => $rubricText],
// or: 'rubric' => ['type' => 'file', 'file_id' => $rubric->id],
'max_iterations' => 5, // optional; default 3, max 20
],
],
);
````
````ruby
# Create a session
session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id,
title: "Financial analysis on Costco"
)
# Define the outcome — agent starts working on receipt
client.beta.sessions.events.send_(
session.id,
events: [
{
type: "user.define_outcome",
description: "Build a DCF model for Costco in .xlsx",
rubric: {type: "text", content: RUBRIC},
# or: rubric: {type: "file", file_id: rubric.id},
max_iterations: 5 # optional; default 3, max 20
}
]
)
````
## Outcome events
Progress on an outcome-oriented session is surfaced on the events [stream](/docs/en/managed-agents/events-and-streaming).
- `agent.*` events (messages, tool use, etc.) show progress towards the outcome.
- `span.outcome_evaluation_*` events are only emitted for outcome-oriented sessions and show the number of iteration loops and the grader's feedback process.
- You can also send `user.message` [events](/docs/en/managed-agents/events-and-streaming#user-events) to an outcome-oriented session, to direct the agent's work as it progresses, but these are not as necessary; the agent knows to work until it has exhausted its iterations or achieved the outcome.
- A `user.interrupt` event will pause work on the current outcome and mark the `span.outcome_evaluation_end.result` as `interrupted`, allowing you to kick off a new outcome.
- After the final outcome evaluation, the session can be continued as a conversational session, or a new outcome can be kicked off. The session will retain history of the prior outcome.
### Define outcome user event
Only one outcome supported at a time, but you may chain together outcomes in sequence. To do this, send a new `user.define_outcome` event after the terminal event of the previous outcome.
This is the event you send to initiate an outcome. It is echoed back on receipt, including a `processed_at` timestamp and `outcome_id`.
```json
{
"type": "user.define_outcome",
"description": "Build a DCF model for Costco in .xlsx",
"rubric": { "type": "file", "file_id": "file_01..." },
"max_iterations": 5
}
```
### Outcome evaluation start
Emitted once the grader starts an evaluation over one iteration loop. The `iteration` field is a 0-indexed revision counter: `0` is the first evaluation, `1` is the re-evaluation after the first revision, and so on.
```json
{
"type": "span.outcome_evaluation_start",
"id": "sevt_01def...",
"outcome_id": "outc_01a...",
"iteration": 0,
"processed_at": "2026-03-25T14:01:45Z"
}
```
### Outcome evaluation ongoing
Heartbeat emitted while the grader runs. The grader's internal reasoning is opaque: you see that it's working, not what it's thinking.
```json
{
"type": "span.outcome_evaluation_ongoing",
"id": "sevt_01ghi...",
"outcome_id": "outc_01a...",
"processed_at": "2026-03-25T14:02:10Z"
}
```
### Outcome evaluation end
Emitted after the grader finishes evaluating one iteration. The `result` field indicates what happens next.
| Result | Next |
| --- | --- |
| `satisfied` | Session transitions to `idle`. |
| `needs_revision` | Agent starts a new iteration cycle. |
| `max_iterations_reached` | No further evaluation cycles. The agent may run one final revision before the session transitions to `idle`. |
| `failed` | Session transitions to `idle`. Returned when the rubric fundamentally does not match the task, for example if the description and rubric contradict each other. |
| `interrupted` | Only emitted if `outcome_evaluation_start` already fired before the interrupt. |
```json
{
"type": "span.outcome_evaluation_end",
"id": "sevt_01jkl...",
"outcome_evaluation_start_id": "sevt_01def...",
"outcome_id": "outc_01a...",
"result": "satisfied",
"explanation": "All 12 criteria met: revenue projections use 5 years of historical data, WACC assumptions are stated, sensitivity table is included...",
"iteration": 0,
"usage": {
"input_tokens": 2400,
"output_tokens": 350,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 1800
},
"processed_at": "2026-03-25T14:03:00Z"
}
```
## Checking on outcome status
You can either listen on the [event stream](/docs/en/managed-agents/events-and-streaming) for `span.outcome_evaluation_end`, or poll `GET /v1/sessions/:id` and read `outcome_evaluations[].result`:
````bash
session=$(curl -fsSL "https://api.anthropic.com/v1/sessions/$session_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01")
jq -r '.outcome_evaluations[] | "\(.outcome_id): \(.result)"' <<<"$session"
# outc_01a...: satisfied
````
````bash
ant beta:sessions retrieve --session-id "$SESSION_ID" \
--transform 'outcome_evaluations' --format yaml
````
````python
session = client.beta.sessions.retrieve(session.id)
for outcome in session.outcome_evaluations:
print(f"{outcome.outcome_id}: {outcome.result}")
# outc_01a...: satisfied
````
````typescript
const retrieved = await client.beta.sessions.retrieve(session.id);
for (const outcome of retrieved.outcome_evaluations) {
console.log(`${outcome.outcome_id}: ${outcome.result}`);
// outc_01a...: satisfied
}
````
````csharp
session = await client.Beta.Sessions.Retrieve(session.ID);
foreach (var outcome in session.OutcomeEvaluations)
{
Console.WriteLine($"{outcome.OutcomeID}: {outcome.Result}");
// outc_01a...: satisfied
}
````
````go
session, err = client.Beta.Sessions.Get(ctx, session.ID, anthropic.BetaSessionGetParams{})
if err != nil {
panic(err)
}
for _, outcome := range session.OutcomeEvaluations {
fmt.Printf("%s: %s\n", outcome.OutcomeID, outcome.Result)
// outc_01a...: satisfied
}
````
````java
var retrieved = client.beta().sessions().retrieve(session.id());
for (var outcome : retrieved.outcomeEvaluations()) {
IO.println(outcome.outcomeId() + ": " + outcome.result());
// outc_01a...: satisfied
}
````
````php
$session = $client->beta->sessions->retrieve($session->id);
foreach ($session->outcomeEvaluations as $outcome) {
echo "{$outcome->outcomeID}: {$outcome->result}\n";
// outc_01a...: satisfied
}
````
````ruby
session = client.beta.sessions.retrieve(session.id)
session.outcome_evaluations.each do
puts "#{it.outcome_id}: #{it.result}"
# outc_01a...: satisfied
end
````
## Retrieving deliverables
The agent writes output files to `/mnt/session/outputs/` inside the container. Once the session is idle, fetch them via the [Files API](/docs/en/build-with-claude/files) scoped to the session:
````bash
# List files produced by this session
files=$(curl -fsSL "https://api.anthropic.com/v1/files?scope_id=$session_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01")
jq -r '.data[] | "\(.id) \(.filename)"' <<<"$files"
# Download a file
file_id=$(jq -r '.data[0].id // empty' <<<"$files")
if [[ -n $file_id ]]; then
curl -fsSL "https://api.anthropic.com/v1/files/$file_id/content" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-o /tmp/output.txt
fi
````
````bash
# List files produced by this session
ant beta:files list --scope-id "$SESSION_ID"
# Download a file
FILE_ID=$(ant beta:files list --scope-id "$SESSION_ID" \
--transform 'data[0].id' --raw-output)
if [[ -n $FILE_ID ]]; then
ant beta:files download --file-id "$FILE_ID" --output /tmp/output.txt
fi
````
````python
# List files produced by this session
files = client.beta.files.list(scope_id=session.id)
for f in files:
print(f.id, f.filename)
# Download a file
if files.data:
content = client.beta.files.download(files.data[0].id)
content.write_to_file("/tmp/output.txt")
````
````typescript
// List files produced by this session
const files = await client.beta.files.list({ scope_id: session.id });
for (const f of files.data) {
console.log(f.id, f.filename);
}
// Download a file
if (files.data.length > 0) {
const content = await client.beta.files.download(files.data[0].id);
await writeFile("/tmp/output.txt", new Uint8Array(await content.arrayBuffer()));
}
````
````csharp
// List files produced by this session
var files = await client.Beta.Files.List(new() { ScopeID = session.ID });
foreach (var file in files.Data)
{
Console.WriteLine($"{file.ID} {file.Filename}");
}
// Download a file
if (files.Data.Count > 0)
{
var content = await client.Beta.Files.Download(files.Data[0].ID);
await File.WriteAllBytesAsync("/tmp/output.txt", content);
}
````
````go
// List files produced by this session
files, err := client.Beta.Files.List(ctx, anthropic.BetaFileListParams{
// pass ScopeID: anthropic.String(session.ID) to filter
})
if err != nil {
panic(err)
}
for _, file := range files.Data {
fmt.Println(file.ID, file.Filename)
}
// Download a file
if len(files.Data) > 0 {
resp, err := client.Beta.Files.Download(ctx, files.Data[0].ID, anthropic.BetaFileDownloadParams{})
if err != nil {
panic(err)
}
defer resp.Body.Close()
fileContent, _ := io.ReadAll(resp.Body)
os.WriteFile("/tmp/output.txt", fileContent, 0o644)
}
````
````java
// List files produced by this session
var files = client.beta().files().list(
FileListParams.builder()/* pass .scopeId(session.id()) to filter */.build());
for (var file : files.data()) {
IO.println(file.id() + " " + file.filename());
}
// Download a file
if (!files.data().isEmpty()) {
try (HttpResponse response = client.beta().files().download(files.data().getFirst().id())) {
try (InputStream body = response.body()) {
Files.copy(body, Path.of("/tmp/output.txt"), StandardCopyOption.REPLACE_EXISTING);
}
}
}
````
````php
// List files produced by this session
$files = $client->beta->files->list(/* pass scopeID: $session->id to filter */);
foreach ($files->data as $file) {
echo "{$file->id} {$file->filename}\n";
}
// Download a file
if (count($files->data) > 0) {
$content = $client->beta->files->download($files->data[0]->id);
file_put_contents('/tmp/output.txt', $content);
}
````
````ruby
# List files produced by this session
files = client.beta.files.list(scope_id: session.id)
files.data.each { puts "#{it.id} #{it.filename}" }
# Download a file
if (first = files.data.first)
content = client.beta.files.download(first.id)
File.binwrite("/tmp/output.txt", content.read)
end
````
---
# Session event stream
URL: https://platform.claude.com/docs/en/managed-agents/events-and-streaming
# Session event stream
Send events, stream responses, and interrupt or redirect your session mid-execution.
---
Communication with Claude Managed Agents is event-based. You send user events to the agent, and receive agent and session events back to track status.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## Event types
Events flow in two directions.
- **User events** are what you send to the agent to kick off a session and steer it as it progresses.
- **Session events**, **span events**, and **agent events** are sent to you for observability into your session state and agent progress.
Event type strings follow a `{domain}.{action}` naming convention.
| Type | Description |
| --- | --- |
| `user.message` | A user message with text content. |
| `user.interrupt` | Stop the agent mid-execution. |
| `user.custom_tool_result` | Response to a custom tool call from the agent. |
| `user.tool_confirmation` | Approve or deny an agent or MCP tool call when a permission policy requires confirmation. |
| `user.define_outcome` | Define an [outcome](/docs/en/managed-agents/define-outcomes) for the agent to work toward. |
| Type | Description |
| --- | --- |
| `agent.message` | Agent response containing text content blocks. |
| `agent.thinking` | Agent thinking content, emitted separately from messages. |
| `agent.tool_use` | Agent invokes a pre-built agent tool (bash, file operations, and so on). |
| `agent.tool_result` | Result of a pre-built agent tool execution. |
| `agent.mcp_tool_use` | Agent invokes an MCP server tool. |
| `agent.mcp_tool_result` | Result of an MCP tool execution. |
| `agent.custom_tool_use` | Agent invokes one of your custom tools. Respond with a `user.custom_tool_result` event. |
| `agent.thread_context_compacted` | Conversation history was compacted to fit the context window. |
| `agent.thread_message_received` | In a [multiagent](/docs/en/managed-agents/multi-agent) session, an agent delivered its result to the coordinator. |
| `agent.thread_message_sent` | In a [multiagent](/docs/en/managed-agents/multi-agent) session, the coordinator sent a follow-up to another agent. |
| Type | Description |
| --- | --- |
| `session.status_running` | Agent is actively processing. |
| `session.status_idle` | Agent finished its current task and is waiting for input. Includes a `stop_reason` indicating why the agent stopped. |
| `session.status_rescheduled` | A transient error occurred and the session is retrying automatically. |
| `session.status_terminated` | Session ended due to an unrecoverable error. |
| `session.error` | An error occurred during processing. Includes a typed `error` object with a `retry_status`. |
| `session.thread_created` | A [multiagent](/docs/en/managed-agents/multi-agent) thread was created. |
| `session.thread_status_running` | A [multiagent](/docs/en/managed-agents/multi-agent) thread started activity. |
| `session.thread_status_idle` | A [multiagent](/docs/en/managed-agents/multi-agent) thread finished its turn and is awaiting input. Includes `stop_reason`. |
| `session.thread_status_terminated` | A [multiagent](/docs/en/managed-agents/multi-agent) thread was archived or reached a terminal error. |
Span events are observability markers that wrap activity for timing and usage tracking.
| Type | Description |
| --- | --- |
| `span.model_request_start` | A model inference call has started. |
| `span.model_request_end` | A model inference call has completed. Includes `model_usage` with token counts. |
| `span.outcome_evaluation_start` | [Outcome](/docs/en/managed-agents/define-outcomes) evaluation has started. |
| `span.outcome_evaluation_ongoing` | Heartbeat during an ongoing [outcome](/docs/en/managed-agents/define-outcomes) evaluation. |
| `span.outcome_evaluation_end` | [Outcome](/docs/en/managed-agents/define-outcomes) evaluation has completed. |
Every event includes a `processed_at` timestamp indicating when the event was recorded server-side. If `processed_at` is null, it means the event has been queued by the harness and will be handled after preceding events finish processing.
## Integrating events
Send a `user.message` event to start or continue the agent's work:
````bash
curl -sS --fail-with-body "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <<'EOF'
{
"events": [
{
"type": "user.message",
"content": [
{"type": "text", "text": "Analyze the performance of the sort function in utils.py"}
]
}
]
}
EOF
````
````bash
ant beta:sessions:events send --session-id "$SESSION_ID" <<'YAML'
events:
- type: user.message
content:
- type: text
text: Analyze the performance of the sort function in utils.py
YAML
````
````python
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [
{
"type": "text",
"text": "Analyze the performance of the sort function in utils.py",
},
],
},
],
)
````
````typescript
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [
{
type: "text",
text: "Analyze the performance of the sort function in utils.py",
},
],
},
],
});
````
````csharp
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserMessageEventParams
{
Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
Content =
[
new BetaManagedAgentsTextBlock
{
Type = BetaManagedAgentsTextBlockType.Text,
Text = "Analyze the performance of the sort function in utils.py",
},
],
},
],
});
````
````go
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{{
OfText: &anthropic.BetaManagedAgentsTextBlockParam{
Type: anthropic.BetaManagedAgentsTextBlockTypeText,
Text: "Analyze the performance of the sort function in utils.py",
},
}},
},
}},
}); err != nil {
panic(err)
}
````
````java
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserMessageEventParams.builder()
.type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
.addTextContent("Analyze the performance of the sort function in utils.py")
.build())
.build());
````
````php
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.message',
'content' => [
[
'type' => 'text',
'text' => 'Analyze the performance of the sort function in utils.py',
],
],
],
],
);
````
````ruby
client.beta.sessions.events.send_(
session.id,
events: [
{
type: "user.message",
content: [
{
type: "text",
text: "Analyze the performance of the sort function in utils.py"
}
]
}
]
)
````
Send a `user.interrupt` event to stop the agent mid-execution, then follow up with a `user.message` event to redirect it:
````bash
# Agent is currently analyzing a file...
# Interrupt with a new direction:
curl -sS --fail-with-body "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <<'EOF'
{
"events": [
{"type": "user.interrupt"},
{
"type": "user.message",
"content": [
{"type": "text", "text": "Instead, focus on fixing the bug in line 42."}
]
}
]
}
EOF
````
````bash
# Agent is currently analyzing a file...
# Interrupt with a new direction:
ant beta:sessions:events send --session-id "$SESSION_ID" <<'YAML'
events:
- type: user.interrupt
- type: user.message
content:
- type: text
text: Instead, focus on fixing the bug in line 42.
YAML
````
````python
# Agent is currently analyzing a file...
# Interrupt with a new direction:
client.beta.sessions.events.send(
session.id,
events=[
{"type": "user.interrupt"},
{
"type": "user.message",
"content": [
{
"type": "text",
"text": "Instead, focus on fixing the bug in line 42.",
},
],
},
],
)
````
````typescript
// Agent is currently analyzing a file...
// Interrupt with a new direction:
await client.beta.sessions.events.send(session.id, {
events: [
{ type: "user.interrupt" },
{
type: "user.message",
content: [
{
type: "text",
text: "Instead, focus on fixing the bug in line 42.",
},
],
},
],
});
````
````csharp
// Agent is currently analyzing a file...
// Interrupt with a new direction:
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserInterruptEventParams
{
Type = BetaManagedAgentsUserInterruptEventParamsType.UserInterrupt,
},
new BetaManagedAgentsUserMessageEventParams
{
Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
Content =
[
new BetaManagedAgentsTextBlock
{
Type = BetaManagedAgentsTextBlockType.Text,
Text = "Instead, focus on fixing the bug in line 42.",
},
],
},
],
});
````
````go
// Agent is currently analyzing a file...
// Interrupt with a new direction:
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.BetaManagedAgentsEventParamsUnion{
{
OfUserInterrupt: &anthropic.BetaManagedAgentsUserInterruptEventParams{
Type: anthropic.BetaManagedAgentsUserInterruptEventParamsTypeUserInterrupt,
},
},
{
OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{{
OfText: &anthropic.BetaManagedAgentsTextBlockParam{
Type: anthropic.BetaManagedAgentsTextBlockTypeText,
Text: "Instead, focus on fixing the bug in line 42.",
},
}},
},
},
},
}); err != nil {
panic(err)
}
````
````java
// Agent is currently analyzing a file...
// Interrupt with a new direction:
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserInterruptEventParams.builder()
.type(BetaManagedAgentsUserInterruptEventParams.Type.USER_INTERRUPT)
.build())
.addEvent(BetaManagedAgentsUserMessageEventParams.builder()
.type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
.addTextContent("Instead, focus on fixing the bug in line 42.")
.build())
.build());
````
````php
// Agent is currently analyzing a file...
// Interrupt with a new direction:
$client->beta->sessions->events->send(
$session->id,
events: [
['type' => 'user.interrupt'],
[
'type' => 'user.message',
'content' => [
[
'type' => 'text',
'text' => 'Instead, focus on fixing the bug in line 42.',
],
],
],
],
);
````
````ruby
# Agent is currently analyzing a file...
# Interrupt with a new direction:
client.beta.sessions.events.send_(
session.id,
events: [
{type: "user.interrupt"},
{
type: "user.message",
content: [
{type: "text", text: "Instead, focus on fixing the bug in line 42."}
]
}
]
)
````
The agent will acknowledge the interruption and switch to the new task.
Stream events from the session to receive real-time updates as the agent works. Only events emitted after the stream is opened are delivered, so open the stream before sending events to avoid a race condition.
````bash
# Open the stream first, then send the user message
exec {stream}< <(
curl -sS -N --fail-with-body \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/events/stream?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-H "Accept: text/event-stream"
)
curl -sS --fail-with-body \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- >/dev/null <<'EOF'
{
"events": [
{
"type": "user.message",
"content": [{"type": "text", "text": "Summarize the repo README"}]
}
]
}
EOF
while IFS= read -r -u "$stream" line; do
[[ $line == data:* ]] || continue
json=${line#data: }
case $(jq -r '.type' <<<"$json") in
agent.message)
jq -j '.content[] | select(.type == "text") | .text' <<<"$json"
;;
session.status_idle)
break
;;
session.error)
printf '\n[Error: %s]\n' "$(jq -r '.error.message // "unknown"' <<<"$json")"
break
;;
esac
done
exec {stream}<&-
````
````bash
# This workflow does not translate well to a one-off shell command.
# Use one of the SDK examples in this code group instead.
````
````python
# Open the stream first, then send the user message
with client.beta.sessions.events.stream(session.id) as stream:
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [{"type": "text", "text": "Summarize the repo README"}],
},
],
)
for event in stream:
match event.type:
case "agent.message":
for block in event.content:
if block.type == "text":
print(block.text, end="")
case "session.status_idle":
break
case "session.error":
msg = event.error.message if event.error else "unknown"
print(f"\n[Error: {msg}]")
break
````
````typescript
// Open the stream first, then send the user message
const stream = await client.beta.sessions.events.stream(session.id);
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [{ type: "text", text: "Summarize the repo README" }]
}
]
});
for await (const event of stream) {
if (event.type === "agent.message") {
for (const block of event.content) {
if (block.type === "text") {
process.stdout.write(block.text);
}
}
} else if (event.type === "session.status_idle") {
break;
} else if (event.type === "session.error") {
console.log(`\n[Error: ${event.error?.message ?? "unknown"}]`);
break;
}
}
````
````csharp
// Open the stream first, then send the user message
using var stream = await client.Beta.Sessions.Events.WithRawResponse.StreamStreaming(session.ID);
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserMessageEventParams
{
Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
Content =
[
new BetaManagedAgentsTextBlock
{
Type = BetaManagedAgentsTextBlockType.Text,
Text = "Summarize the repo README",
},
],
},
],
});
await foreach (var streamEvent in stream.Enumerate())
{
if (streamEvent.Value is BetaManagedAgentsAgentMessageEvent message)
{
foreach (var block in message.Content)
{
Console.Write(block.Text);
}
}
else if (streamEvent.Value is BetaManagedAgentsSessionStatusIdleEvent)
{
break;
}
else if (streamEvent.Value is BetaManagedAgentsSessionErrorEvent error)
{
Console.WriteLine($"\n[Error: {error.Error?.Message ?? "unknown"}]");
break;
}
}
````
````go
// Open the stream first, then send the user message
stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, anthropic.BetaSessionEventStreamParams{})
defer stream.Close()
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.SendEventsParamsUnion{{
OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{{
OfText: &anthropic.BetaManagedAgentsTextBlockParam{
Type: anthropic.BetaManagedAgentsTextBlockTypeText,
Text: "Summarize the repo README",
},
}},
},
}},
}); err != nil {
panic(err)
}
events:
for stream.Next() {
switch event := stream.Current().AsAny().(type) {
case anthropic.BetaManagedAgentsAgentMessageEvent:
for _, block := range event.Content {
fmt.Print(block.Text)
}
case anthropic.BetaManagedAgentsSessionStatusIdleEvent:
break events
case anthropic.BetaManagedAgentsSessionErrorEvent:
fmt.Printf("\n[Error: %s]\n", cmp.Or(event.Error.Message, "unknown"))
break events
}
}
if err := stream.Err(); err != nil {
panic(err)
}
````
````java
// Open the stream first, then send the user message
try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserMessageEventParams.builder()
.type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
.addTextContent("Summarize the repo README")
.build())
.build()
);
for (var event : (Iterable) stream.stream()::iterator) {
if (event.isAgentMessage()) {
event.asAgentMessage().content().forEach(block -> IO.print(block.text()));
} else if (event.isSessionStatusIdle()) {
break;
} else if (event.isSessionError()) {
var msg = event.asSessionError().error()
.flatMap(err -> err._json())
.map(json -> {
Optional
To reconnect to an existing session without missing events, open a new stream and then list the full history to seed a set of seen event IDs. Tail the live stream while skipping any events already returned by the history list.
````bash
exec {stream}< <(
curl -sS -N --fail-with-body \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/events/stream?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-H "Accept: text/event-stream"
)
# Stream is open and buffering. List history before tailing live.
declare -A seen_event_ids
while IFS= read -r id; do
seen_event_ids[$id]=1
done < <(
curl -sS --fail-with-body \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" | jq -r '.data[].id'
)
# Tail live events, skipping anything already seen
while IFS= read -r -u "$stream" line; do
[[ $line == data:* ]] || continue
json=${line#data: }
id=$(jq -r '.id' <<<"$json")
[[ -n ${seen_event_ids[$id]+seen} ]] && continue
seen_event_ids[$id]=1
case $(jq -r '.type' <<<"$json") in
agent.message)
jq -j '.content[] | select(.type == "text") | .text' <<<"$json"
;;
session.status_idle)
break
;;
esac
done
exec {stream}<&-
````
````bash
# This workflow does not translate well to a one-off shell command.
# Use one of the SDK examples in this code group instead.
````
````python
with client.beta.sessions.events.stream(session.id) as stream:
# Stream is open and buffering. List history before tailing live.
seen_event_ids = {event.id for event in client.beta.sessions.events.list(session.id)}
# Tail live events, skipping anything already seen
for event in stream:
if event.id in seen_event_ids:
continue
seen_event_ids.add(event.id)
match event.type:
case "agent.message":
for block in event.content:
if block.type == "text":
print(block.text, end="")
case "session.status_idle":
break
````
````typescript
const seenEventIds = new Set();
const stream = await client.beta.sessions.events.stream(session.id);
// Stream is open and buffering. List history before tailing live.
for await (const event of client.beta.sessions.events.list(session.id)) {
seenEventIds.add(event.id);
}
// Tail live events, skipping anything already seen
for await (const event of stream) {
if (seenEventIds.has(event.id)) continue;
seenEventIds.add(event.id);
if (event.type === "agent.message") {
for (const block of event.content) {
if (block.type === "text") {
process.stdout.write(block.text);
}
}
} else if (event.type === "session.status_idle") {
break;
}
}
````
````csharp
using var stream = await client.Beta.Sessions.Events.WithRawResponse.StreamStreaming(session.ID);
// Stream is open and buffering. List history before tailing live.
HashSet seenEventIds = [];
var history = await client.Beta.Sessions.Events.List(session.ID);
await foreach (var pastEvent in history.Paginate())
{
seenEventIds.Add(pastEvent.ID);
}
// Tail live events, skipping anything already seen
await foreach (var streamEvent in stream.Enumerate())
{
if (!seenEventIds.Add(streamEvent.ID))
{
continue;
}
if (streamEvent.Value is BetaManagedAgentsAgentMessageEvent message)
{
foreach (var block in message.Content)
{
Console.Write(block.Text);
}
}
else if (streamEvent.Value is BetaManagedAgentsSessionStatusIdleEvent)
{
break;
}
}
````
````go
stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, anthropic.BetaSessionEventStreamParams{})
defer stream.Close()
// Stream is open and buffering. List history before tailing live.
seenEventIDs := map[string]struct{}{}
history := client.Beta.Sessions.Events.ListAutoPaging(ctx, session.ID, anthropic.BetaSessionEventListParams{})
for history.Next() {
seenEventIDs[history.Current().ID] = struct{}{}
}
if err := history.Err(); err != nil {
panic(err)
}
// Tail live events, skipping anything already seen
tail:
for stream.Next() {
event := stream.Current()
if _, seen := seenEventIDs[event.ID]; seen {
continue
}
seenEventIDs[event.ID] = struct{}{}
switch event := event.AsAny().(type) {
case anthropic.BetaManagedAgentsAgentMessageEvent:
for _, block := range event.Content {
fmt.Print(block.Text)
}
case anthropic.BetaManagedAgentsSessionStatusIdleEvent:
break tail
}
}
if err := stream.Err(); err != nil {
panic(err)
}
````
````java
try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
// Stream is open and buffering. List history before tailing live.
// _json() exposes the raw event so we can read the cross-variant `id` field.
var seenEventIds = new HashSet();
for (var past : client.beta().sessions().events().list(session.id()).autoPager()) {
Optional
Retrieve the full event history for a session:
````bash
curl -sS --fail-with-body "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
| jq -r '.data[] | "[\(.type)] \(.processed_at)"'
````
````bash
ant beta:sessions:events list \
--session-id "$SESSION_ID" \
--format jsonl \
--transform '{type,processed_at}'
````
````python
events = client.beta.sessions.events.list(session.id)
for event in events.data:
print(f"[{event.type}] {event.processed_at}")
````
````typescript
const events = await client.beta.sessions.events.list(session.id);
for (const event of events.data) {
console.log(`[${event.type}] ${event.processed_at}`);
}
````
````csharp
var events = await client.Beta.Sessions.Events.List(session.ID);
foreach (var evt in events.Items)
{
Console.WriteLine($"[{evt.Json.GetProperty("type").GetString()}] {evt.ProcessedAt}");
}
````
````go
events, err := client.Beta.Sessions.Events.List(ctx, session.ID, anthropic.BetaSessionEventListParams{})
if err != nil {
panic(err)
}
for _, event := range events.Data {
fmt.Printf("[%s] %s\n", event.Type, event.ProcessedAt)
}
````
````java
var events = client.beta().sessions().events().list(session.id());
for (var event : events.data()) {
var json = (Map) event._json().orElseThrow().asObject().orElseThrow();
var type = json.get("type").asStringOrThrow();
var processedAt = json.containsKey("processed_at")
? json.get("processed_at").asStringOrThrow()
: "pending";
IO.println("[" + type + "] " + processedAt);
}
````
````php
$events = $client->beta->sessions->events->list($session->id);
foreach ($events->data as $event) {
$processedAt = ($event->processedAt ?? null)?->format(DATE_RFC3339) ?? 'pending';
echo "[{$event->type}] {$processedAt}\n";
}
````
````ruby
events = client.beta.sessions.events.list(session.id)
events.data.each { puts "[#{it.type}] #{it.processed_at}" }
````
Pass a `types` filter to return only specific event types:
````bash
curl -sS --fail-with-body "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true&types[]=agent.tool_use&types[]=agent.tool_result" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
| jq -r '.data[] | "[\(.type)] \(.processed_at)"'
````
````bash
ant beta:sessions:events list \
--session-id "$SESSION_ID" \
--type agent.tool_use \
--type agent.tool_result \
--format jsonl \
--transform '{type,processed_at}'
````
````python
events = client.beta.sessions.events.list(
session.id,
types=["agent.tool_use", "agent.tool_result"],
)
for event in events.data:
print(f"[{event.type}] {event.processed_at}")
````
````typescript
const events = await client.beta.sessions.events.list(session.id, {
types: ["agent.tool_use", "agent.tool_result"],
});
for (const event of events.data) {
console.log(`[${event.type}] ${event.processed_at}`);
}
````
````csharp
var events = await client.Beta.Sessions.Events.List(session.ID, new()
{
Types = ["agent.tool_use", "agent.tool_result"],
});
foreach (var evt in events.Items)
{
Console.WriteLine($"[{evt.Json.GetProperty("type").GetString()}] {evt.ProcessedAt}");
}
````
````go
events, err := client.Beta.Sessions.Events.List(ctx, session.ID, anthropic.BetaSessionEventListParams{
Types: []string{"agent.tool_use", "agent.tool_result"},
})
if err != nil {
panic(err)
}
for _, event := range events.Data {
fmt.Printf("[%s] %s\n", event.Type, event.ProcessedAt)
}
````
````java
var events = client.beta().sessions().events().list(
session.id(),
EventListParams.builder()
.addType("agent.tool_use")
.addType("agent.tool_result")
.build());
for (var event : events.data()) {
event.agentToolUse().ifPresent(toolUse ->
IO.println("[" + toolUse.type() + "] " + toolUse.processedAt()));
event.agentToolResult().ifPresent(toolResult ->
IO.println("[" + toolResult.type() + "] " + toolResult.processedAt()));
}
````
````php
$events = $client->beta->sessions->events->list(
$session->id,
types: ['agent.tool_use', 'agent.tool_result'],
);
foreach ($events->data as $event) {
$processedAt = ($event->processedAt ?? null)?->format(DATE_RFC3339) ?? 'pending';
echo "[{$event->type}] {$processedAt}\n";
}
````
````ruby
events = client.beta.sessions.events.list(
session.id,
types: ["agent.tool_use", "agent.tool_result"]
)
events.data.each { puts "[#{it.type}] #{it.processed_at}" }
````
## Additional scenarios
### Handling custom tool calls
When the agent invokes a [custom tool](/docs/en/managed-agents/tools#custom-tools):
1. The session emits an `agent.custom_tool_use` event containing the tool name and input.
2. The session pauses with a `session.status_idle` event containing `stop_reason: requires_action`. The blocking event IDs are in the `stop_reason.event_ids` array.
3. Execute the tool in your system and send a `user.custom_tool_result` event for each, passing the event ID in the `custom_tool_use_id` param along with the result content.
4. Once all blocking events are resolved, the session transitions back to `running`.
````bash
exec {fd}< <(curl -sS -N --fail-with-body \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/events/stream?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-H "Accept: text/event-stream")
while IFS= read -r -u "$fd" line; do
[[ $line == data:* ]] || continue
data="${line#data: }"
[[ $(jq -r '.type' <<<"$data") == "session.status_idle" ]] || continue
case $(jq -r '.stop_reason.type // empty' <<<"$data") in
requires_action)
while IFS= read -r event_id; do
# Look up the custom tool use event and execute it
result=$(call_tool "$event_id")
# Send the result back
jq -n --arg id "$event_id" --arg result "$result" \
'{events: [{type: "user.custom_tool_result", custom_tool_use_id: $id, content: [{type: "text", text: $result}]}]}' |
curl -sS --fail-with-body \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @-
done < <(jq -r '.stop_reason.event_ids[]' <<<"$data")
;;
end_turn)
break
;;
esac
done
exec {fd}<&-
````
````bash
# This workflow does not translate well to a one-off shell command.
# Use one of the SDK examples in this code group instead.
````
````python
with client.beta.sessions.events.stream(session.id) as stream:
for event in stream:
if event.type == "session.status_idle" and (stop := event.stop_reason):
match stop.type:
case "requires_action":
for event_id in stop.event_ids:
# Look up the custom tool use event and execute it
tool_event = events_by_id[event_id]
result = call_tool(tool_event.name, tool_event.input)
# Send the result back
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.custom_tool_result",
"custom_tool_use_id": event_id,
"content": [{"type": "text", "text": result}],
},
],
)
case "end_turn":
break
````
````typescript
const stream = await client.beta.sessions.events.stream(session.id);
for await (const event of stream) {
if (event.type === "session.status_idle") {
if (event.stop_reason?.type === "requires_action") {
for (const eventId of event.stop_reason.event_ids) {
// Look up the custom tool use event and execute it
const toolEvent = eventsById[eventId];
const result = await callTool(toolEvent.name, toolEvent.input);
// Send the result back
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.custom_tool_result",
custom_tool_use_id: eventId,
content: [{ type: "text", text: result }],
},
],
});
}
} else if (event.stop_reason?.type === "end_turn") {
break;
}
}
}
````
````csharp
await foreach (var streamEvent in client.Beta.Sessions.Events.StreamStreaming(session.ID))
{
if (streamEvent.Value is BetaManagedAgentsSessionStatusIdleEvent idle)
{
if (idle.StopReason?.Value is BetaManagedAgentsSessionRequiresAction requiresAction)
{
foreach (var eventId in requiresAction.EventIds)
{
// Look up the custom tool use event and execute it
var toolEvent = eventsById[eventId];
var result = await CallTool(toolEvent.Name, toolEvent.Input);
// Send the result back
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserCustomToolResultEventParams
{
Type = BetaManagedAgentsUserCustomToolResultEventParamsType.UserCustomToolResult,
CustomToolUseID = eventId,
Content =
[
new BetaManagedAgentsTextBlock
{
Type = BetaManagedAgentsTextBlockType.Text,
Text = result,
},
],
},
],
});
}
}
else if (idle.StopReason?.Value is BetaManagedAgentsSessionEndTurn)
{
break;
}
}
}
````
````go
stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, anthropic.BetaSessionEventStreamParams{})
defer stream.Close()
loop:
for stream.Next() {
event, ok := stream.Current().AsAny().(anthropic.BetaManagedAgentsSessionStatusIdleEvent)
if !ok {
continue
}
switch stopReason := event.StopReason.AsAny().(type) {
case anthropic.BetaManagedAgentsSessionRequiresAction:
for _, eventID := range stopReason.EventIDs {
// Look up the custom tool use event and execute it
toolEvent := eventsByID[eventID]
result := callTool(toolEvent.Name, toolEvent.Input)
// Send the result back
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
OfUserCustomToolResult: &anthropic.BetaManagedAgentsUserCustomToolResultEventParams{
Type: anthropic.BetaManagedAgentsUserCustomToolResultEventParamsTypeUserCustomToolResult,
CustomToolUseID: eventID,
Content: []anthropic.BetaManagedAgentsUserCustomToolResultEventParamsContentUnion{{
OfText: &anthropic.BetaManagedAgentsTextBlockParam{
Type: anthropic.BetaManagedAgentsTextBlockTypeText,
Text: result,
},
}},
},
}},
}); err != nil {
panic(err)
}
}
case anthropic.BetaManagedAgentsSessionEndTurn:
break loop
}
}
if err := stream.Err(); err != nil {
panic(err)
}
````
````java
try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
for (var event : (Iterable) stream.stream()::iterator) {
if (!event.isSessionStatusIdle()) continue;
var stopReason = event.asSessionStatusIdle().stopReason().orElseThrow();
if (stopReason.isRequiresAction()) {
for (var eventId : stopReason.asRequiresAction().eventIds()) {
// Look up the custom tool use event and execute it
var toolEvent = eventsById.get(eventId);
var result = callTool(toolEvent.name(), toolEvent.input());
// Send the result back
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserCustomToolResultEventParams.builder()
.type(BetaManagedAgentsUserCustomToolResultEventParams.Type.USER_CUSTOM_TOOL_RESULT)
.customToolUseId(eventId)
.addTextContent(result)
.build())
.build());
}
} else if (stopReason.isEndTurn()) {
break;
}
}
}
````
````php
$stream = $client->beta->sessions->events->streamStream(
$session->id,
requestOptions: ['transporter' => $streamingClient],
);
foreach ($stream as $event) {
if ($event->type === 'session.status_idle' && $event->stopReason) {
if ($event->stopReason->type === 'requires_action') {
foreach ($event->stopReason->eventIDs as $eventId) {
// Look up the custom tool use event and execute it
$toolEvent = $eventsById[$eventId];
$result = callTool($toolEvent->name, $toolEvent->input);
// Send the result back
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.custom_tool_result',
'custom_tool_use_id' => $eventId,
'content' => [['type' => 'text', 'text' => $result]],
],
],
);
}
} elseif ($event->stopReason->type === 'end_turn') {
break;
}
}
}
````
````ruby
client.beta.sessions.events.stream_events(session.id).each do |event|
case event
in {type: :"session.status_idle", stop_reason: {type: :requires_action, event_ids:}}
event_ids.each do |event_id|
# Look up the custom tool use event and execute it
tool_event = events_by_id[event_id]
result = call_tool.call(tool_event.name, tool_event.input)
# Send the result back
client.beta.sessions.events.send_(
session.id,
events: [
{
type: "user.custom_tool_result",
custom_tool_use_id: event_id,
content: [{type: "text", text: result}]
}
]
)
end
in {type: :"session.status_idle", stop_reason: {type: :end_turn}}
break
else
end
end
````
### Tool confirmation
When a [permission policy](/docs/en/managed-agents/permission-policies) requires confirmation before a tool executes:
1. The session emits an `agent.tool_use` or `agent.mcp_tool_use` event.
2. The session pauses with a `session.status_idle` event containing `stop_reason: requires_action`. The blocking event IDs are in the `stop_reason.event_ids` array.
3. Send a `user.tool_confirmation` event for each, passing the event ID in the `tool_use_id` param. Set `result` to `"allow"` or `"deny"`. Use `deny_message` to explain a denial.
4. Once all blocking events are resolved, the session transitions back to `running`.
````bash
exec {fd}< <(curl -sS -N --fail-with-body \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/events/stream?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-H "Accept: text/event-stream")
while IFS= read -r -u "$fd" line; do
[[ $line == data:* ]] || continue
data="${line#data: }"
[[ $(jq -r '.type' <<<"$data") == "session.status_idle" ]] || continue
case $(jq -r '.stop_reason.type // empty' <<<"$data") in
requires_action)
while IFS= read -r event_id; do
# Approve the pending tool call
jq -n --arg id "$event_id" \
'{events: [{type: "user.tool_confirmation", tool_use_id: $id, result: "allow"}]}' |
curl -sS --fail-with-body \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @-
done < <(jq -r '.stop_reason.event_ids[]' <<<"$data")
;;
end_turn)
break
;;
esac
done
exec {fd}<&-
````
````bash
# This workflow does not translate well to a one-off shell command.
# Use one of the SDK examples in this code group instead.
````
````python
with client.beta.sessions.events.stream(session.id) as stream:
for event in stream:
if event.type == "session.status_idle" and (stop := event.stop_reason):
match stop.type:
case "requires_action":
for event_id in stop.event_ids:
# Approve the pending tool call
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.tool_confirmation",
"tool_use_id": event_id,
"result": "allow",
},
],
)
case "end_turn":
break
````
````typescript
const stream = await client.beta.sessions.events.stream(session.id);
for await (const event of stream) {
if (event.type === "session.status_idle") {
if (event.stop_reason?.type === "requires_action") {
for (const eventId of event.stop_reason.event_ids) {
// Approve the pending tool call
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.tool_confirmation",
tool_use_id: eventId,
result: "allow",
},
],
});
}
} else if (event.stop_reason?.type === "end_turn") {
break;
}
}
}
````
````csharp
await foreach (var streamEvent in client.Beta.Sessions.Events.StreamStreaming(session.ID))
{
if (streamEvent.Value is BetaManagedAgentsSessionStatusIdleEvent idle)
{
if (idle.StopReason?.Value is BetaManagedAgentsSessionRequiresAction requiresAction)
{
foreach (var eventId in requiresAction.EventIds)
{
// Approve the pending tool call
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserToolConfirmationEventParams
{
Type = BetaManagedAgentsUserToolConfirmationEventParamsType.UserToolConfirmation,
ToolUseID = eventId,
Result = BetaManagedAgentsUserToolConfirmationEventParamsResult.Allow,
},
],
});
}
}
else if (idle.StopReason?.Value is BetaManagedAgentsSessionEndTurn)
{
break;
}
}
}
````
````go
stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, anthropic.BetaSessionEventStreamParams{})
defer stream.Close()
loop:
for stream.Next() {
event, ok := stream.Current().AsAny().(anthropic.BetaManagedAgentsSessionStatusIdleEvent)
if !ok {
continue
}
switch stopReason := event.StopReason.AsAny().(type) {
case anthropic.BetaManagedAgentsSessionRequiresAction:
for _, eventID := range stopReason.EventIDs {
// Approve the pending tool call
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
OfUserToolConfirmation: &anthropic.BetaManagedAgentsUserToolConfirmationEventParams{
Type: anthropic.BetaManagedAgentsUserToolConfirmationEventParamsTypeUserToolConfirmation,
ToolUseID: eventID,
Result: anthropic.BetaManagedAgentsUserToolConfirmationEventParamsResultAllow,
},
}},
}); err != nil {
panic(err)
}
}
case anthropic.BetaManagedAgentsSessionEndTurn:
break loop
}
}
if err := stream.Err(); err != nil {
panic(err)
}
````
````java
try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
for (var event : (Iterable) stream.stream()::iterator) {
if (!event.isSessionStatusIdle()) continue;
var stopReason = event.asSessionStatusIdle().stopReason().orElseThrow();
if (stopReason.isRequiresAction()) {
for (var eventId : stopReason.asRequiresAction().eventIds()) {
// Approve the pending tool call
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserToolConfirmationEventParams.builder()
.type(BetaManagedAgentsUserToolConfirmationEventParams.Type.USER_TOOL_CONFIRMATION)
.toolUseId(eventId)
.result(BetaManagedAgentsUserToolConfirmationEventParams.Result.ALLOW)
.build())
.build());
}
} else if (stopReason.isEndTurn()) {
break;
}
}
}
````
````php
$stream = $client->beta->sessions->events->streamStream(
$session->id,
requestOptions: ['transporter' => $streamingClient],
);
foreach ($stream as $event) {
if ($event->type === 'session.status_idle' && $event->stopReason) {
if ($event->stopReason->type === 'requires_action') {
foreach ($event->stopReason->eventIDs as $eventId) {
// Approve the pending tool call
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.tool_confirmation',
'tool_use_id' => $eventId,
'result' => 'allow',
],
],
);
}
} elseif ($event->stopReason->type === 'end_turn') {
break;
}
}
}
````
````ruby
client.beta.sessions.events.stream_events(session.id).each do |event|
case event
in {type: :"session.status_idle", stop_reason: {type: :requires_action, event_ids:}}
event_ids.each do |event_id|
# Approve the pending tool call
client.beta.sessions.events.send_(
session.id,
events: [
{type: "user.tool_confirmation", tool_use_id: event_id, result: "allow"}
]
)
end
in {type: :"session.status_idle", stop_reason: {type: :end_turn}}
break
else
end
end
````
### Resuming an idle session
Sessions persist between interactions. Conversation history is preserved unless the session is explicitly deleted. When a session goes idle, its container is checkpointed, preserving the full container state, including the filesystem, installed packages, and any files the agent created. This allows you to resume cleanly from inactivity.
While session history is persisted until deleted, checkpoints are only preserved for 30 days after the session's last activity. If your workflow requires the full container state (files, installed tools, and so on) to persist beyond 30 days, send periodic `user.message` events to reset the inactivity timer before the checkpoint expires.
To resume a session, send a `user.message` event to it as usual:
```python nocheck
# Resume a previously created session by ID
client.beta.sessions.events.send(
"sesn_01...",
events=[
{
"type": "user.message",
"content": [
{
"type": "text",
"text": "Now run the tests against the changes you made earlier.",
},
],
},
],
)
```
### Tracking usage
The session object includes a `usage` field with cumulative token statistics. Fetch the session after it goes idle to read the latest totals, and use them to track costs, enforce budgets, or monitor consumption.
```json
{
"id": "sesn_01...",
"status": "idle",
"usage": {
"input_tokens": 5000,
"output_tokens": 3200,
"cache_creation_input_tokens": 2000,
"cache_read_input_tokens": 20000
}
}
```
`input_tokens` reports uncached input tokens and `output_tokens` reports total output tokens across all model calls in the session. The `cache_creation_input_tokens` and `cache_read_input_tokens` fields reflect prompt caching activity. Cache entries use a 5-minute TTL, so back-to-back turns within that window benefit from cache reads, which reduce per-token cost.
## Console observability
The Console provides a visual timeline view of your agent sessions. Navigate to the Claude Managed Agents section in the Console to see:
- **Session list** - All sessions with their status, creation time, and model
- **Tracing view** - A chronological view of events (content, timestamps, token usage) within a session. These are only accessible to Developers and Admins.
- **Tool execution** - Details of each tool call and its result
## Debugging tips
- **Check session events** - Session errors are conveyed through the `session.error` event
- **Review tool results** - Tool execution failures often explain unexpected agent behavior
- **Track token usage** - Monitor token consumption to optimize prompts and reduce costs
- **Use system prompts** - Add logging instructions to the system prompt to make the agent explain its reasoning
---
# Start a session
URL: https://platform.claude.com/docs/en/managed-agents/sessions
# Start a session
Create a session to run your agent and begin executing tasks.
---
A session is an agent instance within an environment. Each session references an [agent](/docs/en/managed-agents/agent-setup) and an [environment](/docs/en/managed-agents/environments) (both created separately), and maintains conversation history across multiple interactions. Sessions follow a two-step lifecycle: first [create the session](#creating-a-session) to provision its container, then [send a user event](#starting-the-session) to start work.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## Creating a session
A session requires an `agent` ID and an `environment` ID. Agents are versioned resources; passing in the `agent` ID as a string starts the session with the latest agent version.
```bash curl nocheck
session=$(curl -fsSL https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
);
```
```ruby Ruby
session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id
)
```
To pin a session to a specific agent version, pass an object. This lets you control exactly which version runs and stage rollouts of new versions independently.
```bash curl nocheck
pinned_session=$(curl -fsSL https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <beta->sessions->create(
agent: ['type' => 'agent', 'id' => $agent->id, 'version' => 1],
environmentID: $environment->id,
);
```
```ruby Ruby
pinned_session = client.beta.sessions.create(
agent: {type: "agent", id: agent.id, version: 1},
environment_id: environment.id
)
```
The agent defines how Claude behaves within the session, including the model, system prompt, tools, and MCP servers. See [Define your agent](/docs/en/managed-agents/agent-setup) for details.
## MCP authentication through vaults
If your agent uses MCP tools that require authentication, pass `vault_ids` at session creation to reference a vault containing stored OAuth credentials. Anthropic manages token refresh on your behalf. See [Authenticate with vaults](/docs/en/managed-agents/vaults) for how to create vaults and register credentials.
```bash curl nocheck
vault_session=$(curl -fsSL https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
vaultIDs: [$vault->id],
);
```
```ruby Ruby
vault_session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id,
vault_ids: [vault.id]
)
```
## Starting the session
Creating a session provisions the environment's container but does not start any work. To delegate a task, send events to the session using a [user event](/docs/en/managed-agents/events-and-streaming#event-types). The session acts as a state machine that tracks progress while events drive the actual execution.
```bash curl nocheck
curl -fsSL "https://api.anthropic.com/v1/sessions/$SESSION_ID/events" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <<'EOF'
{
"events": [
{
"type": "user.message",
"content": [{"type": "text", "text": "List the files in the working directory."}]
}
]
}
EOF
```
```bash CLI nocheck
ant beta:sessions:events send \
--session-id "$SESSION_ID" <<'YAML'
events:
- type: user.message
content:
- type: text
text: List the files in the working directory.
YAML
```
```python Python nocheck
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [
{"type": "text", "text": "List the files in the working directory."}
],
},
],
)
```
```typescript TypeScript nocheck
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [{ type: "text", text: "List the files in the working directory." }]
}
]
});
```
```csharp C# nocheck
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserMessageEventParams
{
Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
Content =
[
new BetaManagedAgentsTextBlock
{
Type = BetaManagedAgentsTextBlockType.Text,
Text = "List the files in the working directory.",
},
],
},
],
});
```
```go Go nocheck
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.SendEventsParamsUnion{{
OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{{
OfText: &anthropic.BetaManagedAgentsTextBlockParam{
Type: anthropic.BetaManagedAgentsTextBlockTypeText,
Text: "List the files in the working directory.",
},
}},
},
}},
}); err != nil {
panic(err)
}
```
```java Java nocheck
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserMessageEventParams.builder()
.type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
.addTextContent("List the files in the working directory.")
.build())
.build());
```
```php PHP nocheck
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.message',
'content' => [['type' => 'text', 'text' => 'List the files in the working directory.']],
],
],
);
```
```ruby Ruby nocheck
client.beta.sessions.events.send_(
session.id,
events: [
{
type: "user.message",
content: [{type: "text", text: "List the files in the working directory."}]
}
]
)
```
See [Session event stream](/docs/en/managed-agents/events-and-streaming) for how to stream the agent's responses and handle tool confirmations.
## Session statuses
Sessions progress through these statuses:
| Status | Description |
|--------|-------------|
| `idle` | Agent is waiting for input, including user messages or tool confirmations. Sessions start in `idle`. |
| `running` | Agent is actively executing. |
| `rescheduling` | Transient error occurred, retrying automatically. |
| `terminated` | Session has ended due to an unrecoverable error. |
## Other session operations
### Retrieving a session
```bash curl nocheck
retrieved=$(curl -fsSL "https://api.anthropic.com/v1/sessions/$SESSION_ID" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01")
echo "Status: $(jq -r '.status' <<< "$retrieved")"
```
```bash CLI nocheck
ant beta:sessions retrieve --session-id "$SESSION_ID"
```
```python Python
retrieved = client.beta.sessions.retrieve(session.id)
print(f"Status: {retrieved.status}")
```
```typescript TypeScript
const retrieved = await client.beta.sessions.retrieve(session.id);
console.log(`Status: ${retrieved.status}`);
```
```csharp C#
var retrieved = await client.Beta.Sessions.Retrieve(session.ID);
Console.WriteLine($"Status: {retrieved.Status.Raw()}");
```
```go Go
retrieved, err := client.Beta.Sessions.Get(ctx, session.ID, anthropic.BetaSessionGetParams{})
if err != nil {
panic(err)
}
fmt.Printf("Status: %s\n", retrieved.Status)
```
```java Java
var retrieved = client.beta().sessions().retrieve(session.id());
IO.println("Status: " + retrieved.status());
```
```php PHP
$retrieved = $client->beta->sessions->retrieve($session->id);
echo "Status: {$retrieved->status}\n";
```
```ruby Ruby
retrieved = client.beta.sessions.retrieve(session.id)
puts "Status: #{retrieved.status}"
```
### Listing sessions
```bash curl
curl -fsSL https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
| jq -r '.data[] | "\(.id): \(.status)"'
```
```bash CLI
ant beta:sessions list
```
```python Python
for session in client.beta.sessions.list():
print(f"{session.id}: {session.status}")
```
```typescript TypeScript
for await (const session of client.beta.sessions.list()) {
console.log(`${session.id}: ${session.status}`);
}
```
```csharp C#
var sessions = await client.Beta.Sessions.List();
await foreach (var listedSession in sessions.Paginate())
{
Console.WriteLine($"{listedSession.ID}: {listedSession.Status.Raw()}");
}
```
```go Go
page := client.Beta.Sessions.ListAutoPaging(ctx, anthropic.BetaSessionListParams{})
for page.Next() {
session := page.Current()
fmt.Printf("%s: %s\n", session.ID, session.Status)
}
if err := page.Err(); err != nil {
panic(err)
}
```
```java Java
for (var listed : client.beta().sessions().list().autoPager()) {
IO.println(listed.id() + ": " + listed.status());
}
```
```php PHP
foreach ($client->beta->sessions->list()->pagingEachItem() as $session) {
echo "{$session->id}: {$session->status}\n";
}
```
```ruby Ruby
client.beta.sessions.list.auto_paging_each do |session|
puts "#{session.id}: #{session.status}"
end
```
### Archiving a session
Archive a session to prevent new events from being sent while preserving its history:
```bash curl nocheck
curl -fsSL -X POST "https://api.anthropic.com/v1/sessions/$SESSION_ID/archive" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01"
```
```bash CLI nocheck
ant beta:sessions archive \
--session-id "$SESSION_ID"
```
```python Python
client.beta.sessions.archive(session.id)
```
```typescript TypeScript
await client.beta.sessions.archive(session.id);
```
```csharp C#
await client.Beta.Sessions.Archive(session.ID);
```
```go Go
_, err = client.Beta.Sessions.Archive(ctx, session.ID, anthropic.BetaSessionArchiveParams{})
if err != nil {
panic(err)
}
```
```java Java
client.beta().sessions().archive(session.id());
```
```php PHP
$client->beta->sessions->archive($session->id);
```
```ruby Ruby
client.beta.sessions.archive(session.id)
```
### Deleting a session
Delete a session to permanently remove its record, events, and associated container. A `running` session cannot be deleted; send an [interrupt event](/docs/en/managed-agents/events-and-streaming#event-types) if you need to delete it immediately.
Files, memory stores, vaults, skills, environments, and agents are independent resources and are not affected by session deletion.
```bash curl nocheck
curl -fsSL -X DELETE "https://api.anthropic.com/v1/sessions/$SESSION_ID" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01"
```
```bash CLI nocheck
ant beta:sessions delete \
--session-id "$SESSION_ID"
```
```python Python
client.beta.sessions.delete(session.id)
```
```typescript TypeScript
await client.beta.sessions.delete(session.id);
```
```csharp C#
await client.Beta.Sessions.Delete(session.ID);
```
```go Go
_, err = client.Beta.Sessions.Delete(ctx, session.ID, anthropic.BetaSessionDeleteParams{})
if err != nil {
panic(err)
}
```
```java Java
client.beta().sessions().delete(session.id());
```
```php PHP
$client->beta->sessions->delete($session->id);
```
```ruby Ruby
client.beta.sessions.delete(session.id)
```
---
# Subscribe to webhooks
URL: https://platform.claude.com/docs/en/managed-agents/webhooks
# Subscribe to webhooks
Get notified when major events happen without polling.
---
Sessions are long-running interactions. While most real-time interactions happen through the [SSE event stream](/docs/en/managed-agents/events-and-streaming), webhooks notify you of major state changes.
Webhook events return the event `type` and `id`, not the full object. When you receive a webhook event, you need to fetch the object directly with a `GET` call. This avoids delivering stale data on retries and keeps every delivery small.
## Supported event types
| Event | Trigger |
| ----- | ------- |
| `session.status_run_started` | Agent execution kicked off. This triggers at every session status transition to `running`. |
| `session.status_idled` | Agent awaiting input, for example a tool permission approval or a new user message. |
| `session.status_rescheduled` | A transient error occurred and the session is retrying automatically. |
| `session.status_terminated` | The session hit a terminal error. |
| `session.thread_created` | New [multiagent thread](/docs/en/managed-agents/multi-agent) opened, meaning an additional agent called by the coordinator is kicking off work. |
| `session.thread_idled` | An agent in a [multiagent interaction](/docs/en/managed-agents/multi-agent) is waiting for input. |
| `session.thread_terminated` | A [multiagent thread](/docs/en/managed-agents/multi-agent) was archived. |
| `session.outcome_evaluation_ended` | [Outcome evaluation](/docs/en/managed-agents/define-outcomes) for a single iteration completed. |
| Event | Trigger |
| ----- | ------- |
| `vault.created` | Vault successfully created. |
| `vault.archived` | Vault archived. A `vault_credential.archived` event is also emitted for each underlying credential. |
| `vault.deleted` | Vault deleted. A `vault_credential.deleted` event is also emitted for each underlying credential. |
| `vault_credential.created` | Credential successfully created. |
| `vault_credential.archived` | Credential archived, either directly or as a result of vault archival. |
| `vault_credential.deleted` | Credential deleted, either directly or as a result of vault deletion. |
| `vault_credential.refresh_failed` | A `mcp_oauth` credential cannot be refreshed (invalid refresh token, or irrecoverable error from the OAuth server). |
## Register an endpoint
Visit **Manage > Webhooks** in [Console](https://platform.claude.com/settings/workspaces/default/webhooks).
A webhook endpoint consists of:
- **URL:** Must be HTTPS on port 443 with a publicly resolvable hostname.
- **Event types:** The list of `data.type` values this endpoint receives. An endpoint only receives events it's subscribed to, plus test events (see [Delivery behavior](#delivery-behavior)).
- **Signing secret:** A 32-byte `whsec_`-prefixed secret generated at creation. It's shown only once, so store it securely to verify webhook deliveries.
## Verify the signature
Every delivery carries an `X-Webhook-Signature` header. Use the SDK's `unwrap()` helper to verify the signature and parse the event in one step. It throws if the signature is invalid or the payload is more than five minutes old.
Set `ANTHROPIC_WEBHOOK_SIGNING_KEY` to the `whsec_`-prefixed secret shown at endpoint creation.
```python Python nocheck
from flask import Flask, request
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
app = Flask(__name__)
@app.route("/webhook", methods=["POST"])
def webhook():
try:
# unwrap() raises if the signature is invalid or the payload is stale
event = client.beta.webhooks.unwrap(
request.get_data(as_text=True),
headers=dict(request.headers),
)
except Exception:
return "invalid signature", 400
if event.data.type == "session.status_idled":
print("session idled:", event.data.id)
# handle other event types
return "", 200
```
```typescript TypeScript nocheck
import express from "express";
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
const app = express();
// IMPORTANT: use express.raw(), not express.json(). The signature is computed over raw bytes.
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
let event;
try {
// unwrap() throws if the signature is invalid or the payload is stale
event = client.beta.webhooks.unwrap(req.body.toString("utf8"), {
headers: req.headers as Record
});
} catch {
return res.status(400).send("invalid signature");
}
switch (event.data.type) {
case "session.status_idled":
console.log("session idled:", event.data.id);
break;
// handle other event types
}
res.sendStatus(200);
});
```
```csharp C# nocheck
using Anthropic;
var client = new AnthropicClient(); // reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
var app = WebApplication.Create(args);
app.MapPost("/webhook", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var body = await reader.ReadToEndAsync();
var headers = request.Headers.ToDictionary(header => header.Key, header => header.Value.ToString());
UnwrapWebhookEvent webhookEvent;
try
{
// Unwrap() throws if the signature is invalid or the payload is stale
webhookEvent = client.Beta.Webhooks.Unwrap(body, headers);
}
catch
{
return Results.BadRequest("invalid signature");
}
if (webhookEvent.Data.TryPickSessionStatusIdled(out var idled))
{
Console.WriteLine($"session idled: {idled.ID}");
}
// handle other event types
return Results.Ok();
});
```
```go Go nocheck
package main
import (
"fmt"
"io"
"net/http"
"github.com/anthropics/anthropic-sdk-go"
)
var client = anthropic.NewClient() // reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
func webhook(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "could not read body", http.StatusBadRequest)
return
}
// Unwrap returns an error if the signature is invalid or the payload is stale
event, err := client.Beta.Webhooks.Unwrap(body, r.Header)
if err != nil {
http.Error(w, "invalid signature", http.StatusBadRequest)
return
}
switch event.Data.Type {
case "session.status_idled":
fmt.Println("session idled:", event.Data.ID)
// handle other event types
}
w.WriteHeader(http.StatusOK)
}
func main() {
http.HandleFunc("/webhook", webhook)
}
```
```java Java nocheck
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.UnwrapWebhookParams;
import com.anthropic.core.http.Headers;
import com.sun.net.httpserver.HttpServer;
// reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
void main() throws Exception {
var server = HttpServer.create(new InetSocketAddress(8000), 0);
server.createContext("/webhook", exchange -> {
var body = new String(exchange.getRequestBody().readAllBytes());
var headers = Headers.builder();
exchange.getRequestHeaders().forEach(headers::put);
try {
// unwrap() throws if the signature is invalid or the payload is stale
var event = client.beta().webhooks().unwrap(
UnwrapWebhookParams.builder()
.body(body)
.headers(headers.build())
.build());
event.data().sessionStatusIdled().ifPresent(idled ->
IO.println("session idled: " + idled.id()));
// handle other event types
exchange.sendResponseHeaders(200, -1);
} catch (Exception _) {
exchange.sendResponseHeaders(400, -1);
}
exchange.close();
});
}
```
```php PHP nocheck
use Anthropic\Client;
use Anthropic\Core\Exceptions\WebhookException;
$client = new Client(); // reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
$body = file_get_contents('php://input');
$headers = getallheaders();
try {
// unwrap() throws if the signature is invalid or the payload is stale
$event = $client->beta->webhooks->unwrap($body, headers: $headers);
} catch (WebhookException) {
http_response_code(400);
exit('invalid signature');
}
match ($event->data->type) {
'session.status_idled' => print "session idled: {$event->data->id}\n",
// handle other event types
default => null,
};
http_response_code(200);
```
```ruby Ruby nocheck
require "sinatra"
require "anthropic"
client = Anthropic::Client.new # reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env
post "/webhook" do
headers = request.env
.select { |key, _| key.start_with?("HTTP_") }
.transform_keys { it.delete_prefix("HTTP_").downcase.tr("_", "-") }
begin
# unwrap raises if the signature is invalid or the payload is stale
event = client.beta.webhooks.unwrap(request.body.read, headers: headers)
rescue StandardError
halt 400, "invalid signature"
end
if event.data.type == "session.status_idled"
puts "session idled: #{event.data.id}"
end
# handle other event types
status 200
end
```
## Handle an event
Parse the body, switch on `data.type`, and fetch the resource by ID. Return any `2xx` to acknowledge. Anything else (including `3xx`) counts as a failure and triggers a retry.
Every event payload has the same structure, including the event type, identifier, and timestamp of when the object was created.
```json
{
"type": "event",
"id": "event_01ABC...",
"created_at": "2026-03-18T14:05:22Z",
"data": {
"type": "session.status_idled",
"id": "sesn_01XYZ...",
"organization_id": "8a3d2f1e-...",
"workspace_id": "c7b0e4d9-..."
}
}
```
```python Python nocheck
if event.data.type == "session.status_idled":
session = client.beta.sessions.retrieve(event.data.id)
notify_user(session)
return "", 204
```
```typescript TypeScript nocheck
if (event.data.type === "session.status_idled") {
const session = await client.beta.sessions.retrieve(event.data.id);
notifyUser(session);
}
res.sendStatus(204);
```
```csharp C# nocheck
if (webhookEvent.Data.TryPickSessionStatusIdled(out var idled))
{
var session = await client.Beta.Sessions.Retrieve(idled.ID);
NotifyUser(session);
}
return Results.StatusCode(204);
```
```go Go nocheck
if event.Data.Type == "session.status_idled" {
session, err := client.Beta.Sessions.Get(r.Context(), event.Data.ID, anthropic.BetaSessionGetParams{})
if err != nil {
panic(err)
}
notifyUser(session)
}
w.WriteHeader(http.StatusNoContent)
```
```java Java nocheck
event.data().sessionStatusIdled().ifPresent(idled -> {
var session = client.beta().sessions().retrieve(idled.id());
notifyUser(session);
});
exchange.sendResponseHeaders(204, -1);
```
```php PHP nocheck
if ($event->data->type === 'session.status_idled') {
$session = $client->beta->sessions->retrieve($event->data->id);
notifyUser($session);
}
http_response_code(204);
```
```ruby Ruby nocheck
if event.data.type == "session.status_idled"
session = client.beta.sessions.retrieve(event.data.id)
notify_user(session)
end
status 204
```
The top-level `event.id` is unique per event, not per delivery. If you receive the same `event.id` twice, it's a retry and you can discard it.
## Delivery behavior
- **Ordering is not guaranteed.** `session.status_idled` may arrive before `session.outcome_evaluation_ended` even if the outcome was produced first. Use the `created_at` timestamp to sort if ordering matters.
- **Retries:** Anthropic retries at least once. The retry delivers the same `event.id`.
- **Redirects are not followed.** A `3xx` is treated as a failure. If your endpoint moves, update the URL in Console.
- **Auto-disable:** An endpoint is automatically set to `disabled` with a machine-readable `disabled_reason` after roughly 20 consecutive failed deliveries, or immediately if the hostname resolves to a private IP or the endpoint returns a redirect. Re-enable manually in Console after resolving the issue.
### Manage agent context
---
# Accessing GitHub
URL: https://platform.claude.com/docs/en/managed-agents/github
# Accessing GitHub
Connect your agent to GitHub repositories for cloning, reading, and creating pull requests.
---
You can mount a GitHub repository to your session container and connect to the GitHub MCP for making pull requests.
GitHub repositories are cached, so future sessions that use the same repository start faster.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## GitHub MCP and Session Resources
First, create an agent that declares the GitHub MCP server. The agent definition holds the server URL but no auth token:
````bash
agent_id=$(curl -fsS https://api.anthropic.com/v1/agents \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <beta->agents->create(
name: 'Code Reviewer',
model: 'claude-opus-4-7',
system: 'You are a code review assistant with access to GitHub.',
mcpServers: [
[
'type' => 'url',
'name' => 'github',
'url' => 'https://api.githubcopilot.com/mcp/',
],
],
tools: [
['type' => 'agent_toolset_20260401'],
[
'type' => 'mcp_toolset',
'mcpServerName' => 'github',
],
],
);
````
````ruby
agent = client.beta.agents.create(
name: "Code Reviewer",
model: "claude-opus-4-7",
system_: "You are a code review assistant with access to GitHub.",
mcp_servers: [
{
type: "url",
name: "github",
url: "https://api.githubcopilot.com/mcp/"
}
],
tools: [
{type: "agent_toolset_20260401"},
{
type: "mcp_toolset",
mcp_server_name: "github"
}
]
)
````
Then create a session that mounts the GitHub repository:
````bash
session_id=$(curl -fsS https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
resources: [
[
'type' => 'github_repository',
'url' => 'https://github.com/org/repo',
'mountPath' => '/workspace/repo',
'authorizationToken' => 'ghp_your_github_token',
],
],
);
````
````ruby
session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id,
resources: [
{
type: "github_repository",
url: "https://github.com/org/repo",
mount_path: "/workspace/repo",
authorization_token: "ghp_your_github_token"
}
]
)
````
The `resources[].authorization_token` authenticates the repository clone operation and is not echoed in API responses.
## Token permissions
When providing a GitHub token, use the minimum required permissions:
| Action | Required scopes |
|--------|----------------|
| Clone private repos | `repo` |
| Create PRs | `repo` |
| Read issues | `repo` (private) or `public_repo` |
| Create issues | `repo` (private) or `public_repo` |
Use fine-grained personal access tokens with minimum required permissions. Avoid using tokens with broad access to your GitHub account.
## Multiple repositories
Mount multiple repositories by adding entries to the `resources` array:
````bash
resources='[
{
"type": "github_repository",
"url": "https://github.com/org/frontend",
"mount_path": "/workspace/frontend",
"authorization_token": "ghp_your_github_token"
},
{
"type": "github_repository",
"url": "https://github.com/org/backend",
"mount_path": "/workspace/backend",
"authorization_token": "ghp_your_github_token"
}
]'
````
````bash
RESOURCES_BODY=$(cat <<'EOF'
resources:
- type: github_repository
url: https://github.com/org/frontend
mount_path: /workspace/frontend
authorization_token: ghp_your_github_token
- type: github_repository
url: https://github.com/org/backend
mount_path: /workspace/backend
authorization_token: ghp_your_github_token
EOF
)
````
````python
resources = [
{
"type": "github_repository",
"url": "https://github.com/org/frontend",
"mount_path": "/workspace/frontend",
"authorization_token": "ghp_your_github_token",
},
{
"type": "github_repository",
"url": "https://github.com/org/backend",
"mount_path": "/workspace/backend",
"authorization_token": "ghp_your_github_token",
},
]
````
````typescript
const resources = [
{
type: "github_repository",
url: "https://github.com/org/frontend",
mount_path: "/workspace/frontend",
authorization_token: "ghp_your_github_token",
},
{
type: "github_repository",
url: "https://github.com/org/backend",
mount_path: "/workspace/backend",
authorization_token: "ghp_your_github_token",
},
];
````
````csharp
BetaManagedAgentsGitHubRepositoryResourceParams[] resources =
[
new()
{
Type = "github_repository",
Url = "https://github.com/org/frontend",
MountPath = "/workspace/frontend",
AuthorizationToken = "ghp_your_github_token",
},
new()
{
Type = "github_repository",
Url = "https://github.com/org/backend",
MountPath = "/workspace/backend",
AuthorizationToken = "ghp_your_github_token",
},
];
````
````go
resources := []anthropic.BetaSessionNewParamsResourceUnion{
{
OfGitHubRepository: &anthropic.BetaManagedAgentsGitHubRepositoryResourceParams{
Type: anthropic.BetaManagedAgentsGitHubRepositoryResourceParamsTypeGitHubRepository,
URL: "https://github.com/org/frontend",
MountPath: anthropic.String("/workspace/frontend"),
AuthorizationToken: "ghp_your_github_token",
},
},
{
OfGitHubRepository: &anthropic.BetaManagedAgentsGitHubRepositoryResourceParams{
Type: anthropic.BetaManagedAgentsGitHubRepositoryResourceParamsTypeGitHubRepository,
URL: "https://github.com/org/backend",
MountPath: anthropic.String("/workspace/backend"),
AuthorizationToken: "ghp_your_github_token",
},
},
}
````
````java
var resources = List.of(
BetaManagedAgentsGitHubRepositoryResourceParams.builder()
.type(BetaManagedAgentsGitHubRepositoryResourceParams.Type.GITHUB_REPOSITORY)
.url("https://github.com/org/frontend")
.mountPath("/workspace/frontend")
.authorizationToken("ghp_your_github_token")
.build(),
BetaManagedAgentsGitHubRepositoryResourceParams.builder()
.type(BetaManagedAgentsGitHubRepositoryResourceParams.Type.GITHUB_REPOSITORY)
.url("https://github.com/org/backend")
.mountPath("/workspace/backend")
.authorizationToken("ghp_your_github_token")
.build());
````
````php
$resources = [
[
'type' => 'github_repository',
'url' => 'https://github.com/org/frontend',
'mountPath' => '/workspace/frontend',
'authorizationToken' => 'ghp_your_github_token',
],
[
'type' => 'github_repository',
'url' => 'https://github.com/org/backend',
'mountPath' => '/workspace/backend',
'authorizationToken' => 'ghp_your_github_token',
],
];
````
````ruby
resources = [
{
type: "github_repository",
url: "https://github.com/org/frontend",
mount_path: "/workspace/frontend",
authorization_token: "ghp_your_github_token"
},
{
type: "github_repository",
url: "https://github.com/org/backend",
mount_path: "/workspace/backend",
authorization_token: "ghp_your_github_token"
}
]
````
## Managing repositories on a running session
After a session is created, you can list its repository resources and rotate their authorization tokens. Each resource has an `id` returned at session creation time (or via `resources.list`) that you use for updates. Repositories are attached for the lifetime of the session; to change which repositories are mounted, create a new session.
````bash
# List resources on the session
repo_resource_id=$(curl -fsS "https://api.anthropic.com/v1/sessions/$session_id/resources" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" | jq -r '.data[0].id')
echo "$repo_resource_id" # "sesrsc_01ABC..."
# Rotate the authorization token
curl -fsS "https://api.anthropic.com/v1/sessions/$session_id/resources/$repo_resource_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-o /dev/null \
--data @- <beta->sessions->resources->list($session->id);
$repoResourceId = $listed->data[0]->id;
echo $repoResourceId, PHP_EOL; // "sesrsc_01ABC..."
// Rotate the authorization token
$client->beta->sessions->resources->update(
$repoResourceId,
sessionID: $session->id,
authorizationToken: 'ghp_your_new_github_token',
);
````
````ruby
# List resources on the session
listed = client.beta.sessions.resources.list(session.id)
repo_resource_id = listed.data.first.id
puts repo_resource_id # "sesrsc_01ABC..."
# Rotate the authorization token
client.beta.sessions.resources.update(
repo_resource_id,
session_id: session.id,
authorization_token: "ghp_your_new_github_token"
)
````
## Creating pull requests
With the GitHub MCP server, the agent can create branches, commit changes, and push them:
````bash
curl -fsS "https://api.anthropic.com/v1/sessions/$session_id/events" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-o /dev/null \
--data @- < /dev/null <<'EOF'
events:
- type: user.message
content:
- type: text
text: Fix the type error in src/utils.ts, commit it to a new branch, and push it.
EOF
````
````python
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [
{
"type": "text",
"text": "Fix the type error in src/utils.ts, commit it to a new branch, and push it.",
},
],
},
],
)
````
````typescript
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [
{
type: "text",
text: "Fix the type error in src/utils.ts, commit it to a new branch, and push it.",
},
],
},
],
});
````
````csharp
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserMessageEventParams
{
Type = "user.message",
Content =
[
new BetaManagedAgentsTextBlock
{
Type = "text",
Text = "Fix the type error in src/utils.ts, commit it to a new branch, and push it.",
},
],
},
],
});
````
````go
_, err = client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.SendEventsParamsUnion{
{
OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{
{
OfText: &anthropic.BetaManagedAgentsTextBlockParam{
Type: anthropic.BetaManagedAgentsTextBlockTypeText,
Text: "Fix the type error in src/utils.ts, commit it to a new branch, and push it.",
},
},
},
},
},
},
})
if err != nil {
panic(err)
}
````
````java
client.beta().sessions().events().send(session.id(), EventSendParams.builder()
.addEvent(BetaManagedAgentsUserMessageEventParams.builder()
.type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
.addContent(BetaManagedAgentsTextBlock.builder()
.type(BetaManagedAgentsTextBlock.Type.TEXT)
.text("Fix the type error in src/utils.ts, commit it to a new branch, and push it.")
.build())
.build())
.build());
````
````php
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.message',
'content' => [
[
'type' => 'text',
'text' => 'Fix the type error in src/utils.ts, commit it to a new branch, and push it.',
],
],
],
],
);
````
````ruby
client.beta.sessions.events.send_(
session.id,
events: [
{
type: "user.message",
content: [
{
type: "text",
text: "Fix the type error in src/utils.ts, commit it to a new branch, and push it."
}
]
}
]
)
````
---
# Adding files
URL: https://platform.claude.com/docs/en/managed-agents/files
# Adding files
Upload files and mount them in your container for reading and processing.
---
You can provide files to your agent by uploading them via the Files API and mounting them in the session's container.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## Uploading files
First, upload a file using the [Files API](/docs/en/build-with-claude/files):
````bash
file=$(curl --fail-with-body -sS "${auth[@]}" \
"${base_url}/files" \
-F file=@data.csv)
file_id=$(jq -er '.id' <<<"${file}")
printf 'File ID: %s\n' "${file_id}"
````
````bash
FILE_ID=$(ant beta:files upload \
--file data.csv \
--transform id --raw-output)
````
````python
file = client.beta.files.upload(file=Path("data.csv"))
print(f"File ID: {file.id}")
````
````typescript
const file = await client.beta.files.upload({
file: await toFile(readFile("data.csv"), "data.csv", { type: "text/csv" }),
});
console.log(`File ID: ${file.id}`);
````
````csharp
await using var stream = File.OpenRead(csvPath);
var file = await client.Beta.Files.Upload(new() { File = stream });
Console.WriteLine($"File ID: {file.ID}");
````
````go
csvFile, err := os.Open("data.csv")
if err != nil {
panic(err)
}
defer csvFile.Close()
file, err := client.Beta.Files.Upload(ctx, anthropic.BetaFileUploadParams{
File: csvFile,
})
if err != nil {
panic(err)
}
fmt.Printf("File ID: %s\n", file.ID)
````
````java
var file = client.beta().files().upload(
FileUploadParams.builder().file(dataCsv).build()
);
IO.println("File ID: " + file.id());
````
````php
$file = $client->beta->files->upload(
FileParam::fromResource(fopen($csvPath, 'r'), filename: 'data.csv', contentType: 'text/csv'),
);
echo "File ID: {$file->id}\n";
````
````ruby
file = client.beta.files.upload(file: Pathname(csv_path))
puts "File ID: #{file.id}"
````
## Mounting files in a session
Mount uploaded files into the container by adding them to the `resources` array when creating a session:
The `mount_path` is optional, but make sure the uploaded file has a descriptive name so the agent knows what it is looking for.
````bash
session=$(
jq -n \
--arg agent_id "${agent_id}" \
--arg environment_id "${environment_id}" \
--arg file_id "${file_id}" \
'{
agent: $agent_id,
environment_id: $environment_id,
resources: [
{
type: "file",
file_id: $file_id,
mount_path: "/workspace/data.csv"
}
]
}' | curl --fail-with-body -sS "${auth[@]}" "${base_url}/sessions" --json @-
)
session_id=$(jq -er '.id' <<<"${session}")
````
````bash
SESSION_ID=$(ant beta:sessions create \
--agent "$AGENT_ID" \
--environment-id "$ENVIRONMENT_ID" \
--transform id --raw-output <beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
resources: [
BetaManagedAgentsFileResourceParams::with(
type: 'file',
fileID: $file->id,
mountPath: '/workspace/data.csv',
),
],
);
````
````ruby
session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id,
resources: [
{
type: "file",
file_id: file.id,
mount_path: "/workspace/data.csv"
}
]
)
````
A new `file_id` will be created that references the instance of the file in the session. These copies do not count against your [storage limits](/docs/en/build-with-claude/files).
## Multiple files
Mount multiple files by adding entries to the `resources` array:
```json curl hidelines={1,-1}
{
"resources": [
{ "type": "file", "file_id": "file_abc123", "mount_path": "/workspace/data.csv" },
{ "type": "file", "file_id": "file_def456", "mount_path": "/workspace/config.json" },
{ "type": "file", "file_id": "file_ghi789", "mount_path": "/workspace/src/main.py" }
]
}
```
```yaml CLI
resources:
- type: file
file_id: file_abc123
mount_path: /workspace/data.csv
- type: file
file_id: file_def456
mount_path: /workspace/config.json
- type: file
file_id: file_ghi789
mount_path: /workspace/src/main.py
```
```python Python
resources = [
{"type": "file", "file_id": "file_abc123", "mount_path": "/workspace/data.csv"},
{"type": "file", "file_id": "file_def456", "mount_path": "/workspace/config.json"},
{"type": "file", "file_id": "file_ghi789", "mount_path": "/workspace/src/main.py"},
]
```
```typescript TypeScript hidelines={1,-1}
const _ = {
resources: [
{ type: "file", file_id: "file_abc123", mount_path: "/workspace/data.csv" },
{ type: "file", file_id: "file_def456", mount_path: "/workspace/config.json" },
{ type: "file", file_id: "file_ghi789", mount_path: "/workspace/src/main.py" }
]
};
```
```csharp C#
var resources = new[]
{
new BetaManagedAgentsFileResourceParams { Type = BetaManagedAgentsFileResourceParamsType.File, FileID = "file_abc123", MountPath = "/workspace/data.csv" },
new BetaManagedAgentsFileResourceParams { Type = BetaManagedAgentsFileResourceParamsType.File, FileID = "file_def456", MountPath = "/workspace/config.json" },
new BetaManagedAgentsFileResourceParams { Type = BetaManagedAgentsFileResourceParamsType.File, FileID = "file_ghi789", MountPath = "/workspace/src/main.py" },
};
```
```go Go
resources := []anthropic.BetaSessionNewParamsResourceUnion{
{OfFile: &anthropic.BetaManagedAgentsFileResourceParams{Type: "file", FileID: "file_abc123", MountPath: anthropic.String("/workspace/data.csv")}},
{OfFile: &anthropic.BetaManagedAgentsFileResourceParams{Type: "file", FileID: "file_def456", MountPath: anthropic.String("/workspace/config.json")}},
{OfFile: &anthropic.BetaManagedAgentsFileResourceParams{Type: "file", FileID: "file_ghi789", MountPath: anthropic.String("/workspace/src/main.py")}},
}
```
```java Java
var resources = List.of(
BetaManagedAgentsFileResourceParams.builder()
.type(BetaManagedAgentsFileResourceParams.Type.FILE).fileId("file_abc123").mountPath("/workspace/data.csv").build(),
BetaManagedAgentsFileResourceParams.builder()
.type(BetaManagedAgentsFileResourceParams.Type.FILE).fileId("file_def456").mountPath("/workspace/config.json").build(),
BetaManagedAgentsFileResourceParams.builder()
.type(BetaManagedAgentsFileResourceParams.Type.FILE).fileId("file_ghi789").mountPath("/workspace/src/main.py").build()
);
```
```php PHP
$resources = [
['type' => 'file', 'file_id' => 'file_abc123', 'mount_path' => '/workspace/data.csv'],
['type' => 'file', 'file_id' => 'file_def456', 'mount_path' => '/workspace/config.json'],
['type' => 'file', 'file_id' => 'file_ghi789', 'mount_path' => '/workspace/src/main.py'],
];
```
```ruby Ruby
resources = [
{type: "file", file_id: "file_abc123", mount_path: "/workspace/data.csv"},
{type: "file", file_id: "file_def456", mount_path: "/workspace/config.json"},
{type: "file", file_id: "file_ghi789", mount_path: "/workspace/src/main.py"}
]
```
A maximum of 100 files is supported per session.
## Managing files on a running session
You can add or remove files from a session after creation using the session resources API. Each resource has an `id` returned when it is added (or listed), which you use for deletes.
````bash
resource=$(
jq -n --arg file_id "${file_id}" '{type: "file", file_id: $file_id}' \
| curl --fail-with-body -sS "${auth[@]}" \
"${base_url}/sessions/${session_id}/resources" --json @-
)
resource_id=$(jq -er '.id' <<<"${resource}")
printf '%s\n' "${resource_id}" # "sesrsc_01ABC..."
````
````bash
RESOURCE_ID=$(ant beta:sessions:resources create \
--session-id "$SESSION_ID" \
--type file \
--file-id "$FILE_ID" \
--transform id --raw-output)
````
````python
resource = client.beta.sessions.resources.add(
session.id,
type="file",
file_id=file.id,
)
print(resource.id) # "sesrsc_01ABC..."
````
````typescript
const resource = await client.beta.sessions.resources.add(session.id, {
type: "file",
file_id: file.id,
});
console.log(resource.id); // "sesrsc_01ABC..."
````
````csharp
var resource = await client.Beta.Sessions.Resources.Add(session.ID, new()
{
Type = "file",
FileID = file.ID,
});
Console.WriteLine(resource.ID); // "sesrsc_01ABC..."
````
````go
resource, err := client.Beta.Sessions.Resources.Add(ctx, session.ID, anthropic.BetaSessionResourceAddParams{
BetaManagedAgentsFileResourceParams: anthropic.BetaManagedAgentsFileResourceParams{
Type: anthropic.BetaManagedAgentsFileResourceParamsTypeFile,
FileID: file.ID,
},
})
if err != nil {
panic(err)
}
fmt.Println(resource.ID) // "sesrsc_01ABC..."
````
````java
var resource = client.beta().sessions().resources().add(
session.id(),
ResourceAddParams.builder()
.betaManagedAgentsFileResourceParams(
BetaManagedAgentsFileResourceParams.builder()
.type(BetaManagedAgentsFileResourceParams.Type.FILE)
.fileId(file.id())
.build()
)
.build()
);
IO.println(resource.id()); // "sesrsc_01ABC..."
````
````php
$resource = $client->beta->sessions->resources->add(
$session->id,
type: 'file',
fileID: $file->id,
);
echo "{$resource->id}\n"; // "sesrsc_01ABC..."
````
````ruby
resource = client.beta.sessions.resources.add(
session.id,
type: "file",
file_id: file.id
)
puts resource.id # "sesrsc_01ABC..."
````
List all resources on a session with `resources.list`. To remove a file, call `resources.delete` with the resource ID:
````bash
curl --fail-with-body -sS "${auth[@]}" \
"${base_url}/sessions/${session_id}/resources" \
| jq -r '.data[] | "\(.id) \(.type)"'
curl --fail-with-body -sS "${auth[@]}" -X DELETE \
"${base_url}/sessions/${session_id}/resources/${resource_id}" >/dev/null
````
````bash
ant beta:sessions:resources list --session-id "$SESSION_ID"
ant beta:sessions:resources delete \
--session-id "$SESSION_ID" \
--resource-id "$RESOURCE_ID"
````
````python
listed = client.beta.sessions.resources.list(session.id)
for entry in listed.data:
print(entry.id, entry.type)
client.beta.sessions.resources.delete(resource.id, session_id=session.id)
````
````typescript
const listed = await client.beta.sessions.resources.list(session.id);
for (const entry of listed.data) {
console.log(entry.id, entry.type);
}
await client.beta.sessions.resources.delete(resource.id, {
session_id: session.id,
});
````
````csharp
var listed = await client.Beta.Sessions.Resources.List(session.ID);
foreach (var entry in listed.Data)
{
var type = entry.Match(repository => repository.Type, fileResource => fileResource.Type);
Console.WriteLine($"{entry.ID} {type}");
}
await client.Beta.Sessions.Resources.Delete(resource.ID, new() { SessionID = session.ID });
````
````go
listed, err := client.Beta.Sessions.Resources.List(ctx, session.ID, anthropic.BetaSessionResourceListParams{})
if err != nil {
panic(err)
}
for _, entry := range listed.Data {
fmt.Println(entry.ID, entry.Type)
}
if _, err := client.Beta.Sessions.Resources.Delete(ctx, resource.ID, anthropic.BetaSessionResourceDeleteParams{
SessionID: session.ID,
}); err != nil {
panic(err)
}
````
````java
var listed = client.beta().sessions().resources().list(session.id());
for (var entry : listed.data()) {
if (entry.isFile()) {
var fileResource = entry.asFile();
IO.println(fileResource.id() + " " + fileResource.type());
} else if (entry.isGitHubRepository()) {
var repoResource = entry.asGitHubRepository();
IO.println(repoResource.id() + " " + repoResource.type());
}
}
client.beta().sessions().resources().delete(
resource.id(),
ResourceDeleteParams.builder().sessionId(session.id()).build()
);
````
````php
$listed = $client->beta->sessions->resources->list($session->id);
foreach ($listed->data as $entry) {
echo "{$entry->id} {$entry->type}\n";
}
$client->beta->sessions->resources->delete($resource->id, sessionID: $session->id);
````
````ruby
listed = client.beta.sessions.resources.list(session.id)
listed.data.each { puts "#{it.id} #{it.type}" }
client.beta.sessions.resources.delete(resource.id, session_id: session.id)
````
## Listing and downloading session files
Use the [Files API](/docs/en/build-with-claude/files) to list files scoped to a session and download them.
```bash curl
# List files associated with a session
curl -fsSL "https://api.anthropic.com/v1/files?scope_id=sesn_abc123" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01"
# Download a file
curl -fsSL "https://api.anthropic.com/v1/files/$FILE_ID/content" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-o output.txt
```
```bash CLI
# List files associated with a session
ant beta:files list --scope-id sesn_abc123 \
--beta files-api-2025-04-14 \
--beta managed-agents-2026-04-01
# Download a file
ant beta:files download --file-id "$FILE_ID" --output output.txt
```
```python Python
# List files associated with a session
files = client.beta.files.list(
scope_id="sesn_abc123",
betas=["managed-agents-2026-04-01"],
)
for f in files:
print(f.id, f.filename)
# Download a file
content = client.beta.files.download(files.data[0].id)
content.write_to_file("output.txt")
```
```typescript TypeScript
// List files associated with a session
const files = await client.beta.files.list({
scope_id: "sesn_abc123",
betas: ["managed-agents-2026-04-01"]
});
for (const f of files.data) {
console.log(f.id, f.filename);
}
// Download a file
const content = await client.beta.files.download(files.data[0].id);
await content.writeToFile("output.txt");
```
```csharp C#
// List files associated with a session
var files = await client.Beta.Files.List(new FileListParams
{
ScopeID = "sesn_abc123",
Betas = ["managed-agents-2026-04-01"],
});
// Download a file
byte[] content = await client.Beta.Files.Download(files.Data[0].ID);
await File.WriteAllBytesAsync("output.txt", content);
```
```go Go
// List files associated with a session
files, err := client.Beta.Files.List(ctx, anthropic.BetaFileListParams{
ScopeID: anthropic.String("sesn_abc123"),
Betas: []anthropic.AnthropicBeta{"managed-agents-2026-04-01"},
})
if err != nil {
panic(err)
}
// Download a file
resp, err := client.Beta.Files.Download(ctx, files.Data[0].ID, anthropic.BetaFileDownloadParams{})
if err != nil {
panic(err)
}
defer resp.Body.Close()
fileContent, _ := io.ReadAll(resp.Body)
os.WriteFile("output.txt", fileContent, 0644)
```
```java Java
// List files associated with a session
var files = client.beta().files().list(FileListParams.builder()
.scopeId("sesn_abc123")
.addBeta(AnthropicBeta.of("managed-agents-2026-04-01"))
.build());
// Download a file
try (HttpResponse response = client.beta().files().download(files.data().get(0).id())) {
try (InputStream body = response.body()) {
Files.copy(body, Path.of("output.txt"), StandardCopyOption.REPLACE_EXISTING);
}
}
```
```php PHP
// List files associated with a session
$files = $client->beta->files->list(
scopeID: 'sesn_abc123',
betas: ['managed-agents-2026-04-01'],
);
// Download a file
$content = $client->beta->files->download($files->data[0]->id);
file_put_contents('output.txt', $content);
```
```ruby Ruby
# List files associated with a session
files = client.beta.files.list(
scope_id: "sesn_abc123",
betas: ["managed-agents-2026-04-01"]
)
# Download a file
content = client.beta.files.download(files.data[0].id)
File.binwrite("output.txt", content.read)
```
## Supported file types
The agent can work with any file type, including:
- Source code (`.py`, `.js`, `.ts`, `.go`, `.rs`, etc.)
- Data files (`.csv`, `.json`, `.xml`, `.yaml`)
- Documents (`.txt`, `.md`)
- Archives (`.zip`, `.tar.gz`) - the agent can extract these using bash
- Binary files - the agent can process these with appropriate tools
## File paths
Files mounted in the container are read-only copies. The agent can read them but cannot modify the original uploaded file. To work with modified versions, the agent writes to new paths within the container.
- Files are mounted at the exact path you specify
- Parent directories are created automatically
- Paths should be absolute (starting with `/`)
### Manage agent context > Build persistent memory
---
# Dreams
URL: https://platform.claude.com/docs/en/managed-agents/dreams
# Dreams
Let Claude reflect on past sessions to curate an agent's memory and surface new insights.
---
Dreaming is a Research Preview feature. [Request access](https://claude.com/form/claude-managed-agents) to try it.
Agents write to their [memory stores](/docs/en/managed-agents/memory) as they work, but these writes are local and incremental: over many sessions a memory store accumulates duplicates, contradictions, and stale entries.
**Dreams** let Claude clean that up. A dream reads an existing memory store alongside past session transcripts, then produces a new, reorganized memory store: duplicates merged, stale or contradicted entries replaced with the latest value, and new insights surfaced.
The input store is never modified, so you can review the output and discard it if you don't like the result.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. Dreams additionally require the `dreaming-2026-04-21` beta header. The SDK sets these automatically.
## How it works
A **dream** is an asynchronous job that takes:
- a pre-existing **memory store**: the store Claude verifies, deduplicates, and reorganizes, and
- 1 to 100 **sessions**: past transcripts Claude mines for patterns and insights to fold into the output.
The dream produces another **output memory store**, separate from the input. The output store ID appears in the dream's `outputs[]` once it starts `running`.
## Create a dream
````bash
dream=$(curl -s https://api.anthropic.com/v1/dreams \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01,dreaming-2026-04-21" \
-H "content-type: application/json" \
--data @- <beta->dreams->create(
inputs: [
['type' => 'memory_store', 'memory_store_id' => $storeId],
['type' => 'sessions', 'session_ids' => [$sessionA, $sessionB]],
],
model: 'claude-opus-4-7',
instructions: 'Focus on coding-style preferences; ignore one-off debugging notes.',
);
echo "{$dream->id}\n"; // drm_01...
````
````ruby
dream = client.beta.dreams.create(
inputs: [
{type: "memory_store", memory_store_id: store_id},
{type: "sessions", session_ids: [session_a, session_b]}
],
model: "claude-opus-4-7",
instructions: "Focus on coding-style preferences; ignore one-off debugging notes."
)
puts dream.id # drm_01...
````
Dreaming inputs include the pre-existing memory store and an array of sessions. The model selected will run the dreaming pipeline; during the research preview `claude-opus-4-7` and `claude-sonnet-4-6` are supported. You can also provide additional guidance on dreaming run execution in `instructions`.
The response is the full `dream` resource with `status: "pending"`:
```json
{
"type": "dream",
"id": "drm_01AbCDefGhIjKlMnOpQrStUv",
"status": "pending",
"inputs": [
{ "type": "memory_store", "memory_store_id": "memstore_01Hx..." },
{ "type": "sessions", "session_ids": ["sesn_01...", "sesn_02..."] }
],
"outputs": [],
"model": { "id": "claude-opus-4-7" },
"instructions": "Focus on coding-style preferences; ignore one-off debugging notes.",
"session_id": null,
"created_at": "2026-04-29T17:04:10Z",
"ended_at": null,
"archived_at": null,
"usage": {
"input_tokens": 0,
"output_tokens": 0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0
},
"error": null
}
```
If you only have session transcripts and no existing store, [create an empty memory store](/docs/en/managed-agents/memory#create-a-memory-store) first and pass it as the `memory_store` input.
## Track progress
Dreams run asynchronously and typically take minutes to tens of minutes depending on input size. Poll the dream by ID to check status:
````bash
while true; do
dream=$(curl -s "https://api.anthropic.com/v1/dreams/$dream_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01,dreaming-2026-04-21")
status=$(jq -r '.status' <<< "$dream")
echo "status=$status input_tokens=$(jq -r '.usage.input_tokens' <<< "$dream")"
[[ "$status" == "pending" || "$status" == "running" ]] || break
sleep 10
done
````
````bash
ant beta:dreams retrieve --dream-id "$dream_id"
````
````python
while dream.status in ("pending", "running"):
time.sleep(10)
dream = client.beta.dreams.retrieve(dream.id)
print(f"status={dream.status} input_tokens={dream.usage.input_tokens}")
````
````typescript
while (dream.status === "pending" || dream.status === "running") {
await sleep(10_000);
dream = await client.beta.dreams.retrieve(dream.id);
console.log(`status=${dream.status} input_tokens=${dream.usage.input_tokens}`);
}
````
````csharp
while (dream.Status.Value() is BetaDreamStatus.Pending or BetaDreamStatus.Running)
{
await Task.Delay(TimeSpan.FromSeconds(10));
dream = await client.Beta.Dreams.Retrieve(dream.ID);
Console.WriteLine($"status={dream.Status.Raw()} input_tokens={dream.Usage.InputTokens}");
}
````
````go
for dream.Status == anthropic.BetaDreamStatusPending || dream.Status == anthropic.BetaDreamStatusRunning {
time.Sleep(10 * time.Second)
dream, err = client.Beta.Dreams.Get(ctx, dream.ID, anthropic.BetaDreamGetParams{})
if err != nil {
panic(err)
}
fmt.Printf("status=%s input_tokens=%d\n", dream.Status, dream.Usage.InputTokens)
}
````
````java
while (dream.status().equals(BetaDreamStatus.PENDING)
|| dream.status().equals(BetaDreamStatus.RUNNING)) {
Thread.sleep(10_000);
dream = client.beta().dreams().retrieve(dream.id());
IO.println("status=" + dream.status() + " input_tokens=" + dream.usage().inputTokens());
}
````
````php
while (in_array($dream->status, [BetaDreamStatus::PENDING->value, BetaDreamStatus::RUNNING->value], true)) {
sleep(10);
$dream = $client->beta->dreams->retrieve($dream->id);
echo "status={$dream->status} input_tokens={$dream->usage->inputTokens}\n";
}
````
````ruby
while %i[pending running].include?(dream.status)
sleep 10
dream = client.beta.dreams.retrieve(dream.id)
puts "status=#{dream.status} input_tokens=#{dream.usage.input_tokens}"
end
````
### Lifecycle
| `status` | Meaning |
| --- | --- |
| `pending` | Dream successfully created and queued. |
| `running` | The pipeline is processing. `usage` updates as work progresses. |
| `completed` | Finished successfully. The `outputs[]` value is the new memory store. |
| `failed` | Dreaming run terminated with an error. The output memory store is left as-is with whatever was written before failure. |
| `canceled` | Dreaming run canceled. The output memory store is left as-is. |
### Watch the pipeline run
Once a dream is `running`, its `session_id` field points at the underlying [session](/docs/en/managed-agents/sessions) executing the pipeline. You can stream that session's [events](/docs/en/managed-agents/events-and-streaming) to observe what the dream is reading and writing in real time. The session is archived (not deleted) when the dream reaches a terminal state, so the transcript remains available afterward.
## Use the output
When `status` reaches `completed`, the `memory_store` entry in `outputs[]` references a fully populated store. It's an ordinary memory store in your workspace. Review it with the [Memory Stores API](/docs/en/managed-agents/memory#view-and-edit-memories) or in the Console, then either:
- **Leverage it:** attach it to future sessions as a `memory_store` resource in place of (or alongside) the input memory store, or
- **Discard it:** [delete](/docs/en/api/beta/memory_stores/delete) or [archive](/docs/en/api/beta/memory_stores/archive) it.
````bash
# After the dream ends, the memory_store output holds the rebuilt store
output_store_id=$(jq -r 'first(.outputs[] | select(.type == "memory_store")).memory_store_id' <<< "$dream")
curl -s https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- < entry.type === "memory_store");
const outputStoreId = output!.memory_store_id;
await client.beta.sessions.create({
agent: agentId,
environment_id: environmentId,
resources: [
{ type: "memory_store", memory_store_id: outputStoreId },
],
});
````
````csharp
var output = dream.Outputs.FirstOrDefault(entry => entry.Type == "memory_store");
if (output is { MemoryStoreID: var outputStoreID })
{
await client.Beta.Sessions.Create(new()
{
Agent = agentID,
EnvironmentID = environmentID,
Resources =
[
new BetaManagedAgentsMemoryStoreResourceParam
{
Type = BetaManagedAgentsMemoryStoreResourceParamType.MemoryStore,
MemoryStoreID = outputStoreID,
},
],
});
}
````
````go
for _, output := range dream.Outputs {
if output.Type != "memory_store" {
continue
}
outputStoreID := output.MemoryStoreID
session, err := client.Beta.Sessions.New(ctx, anthropic.BetaSessionNewParams{
Agent: anthropic.BetaSessionNewParamsAgentUnion{
OfString: anthropic.String(agentID),
},
EnvironmentID: environmentID,
Resources: []anthropic.BetaSessionNewParamsResourceUnion{{
OfMemoryStore: &anthropic.BetaManagedAgentsMemoryStoreResourceParam{
MemoryStoreID: outputStoreID,
},
}},
})
if err != nil {
panic(err)
}
fmt.Println(session.ID)
break
}
````
````java
var output = dream.outputs().stream()
.filter(entry -> entry.type().equals(BetaDreamOutput.Type.MEMORY_STORE))
.findFirst();
if (output.isPresent()) {
var outputStoreId = output.get().memoryStoreId();
var session = client.beta().sessions().create(
SessionCreateParams.builder()
.agent(agentId)
.environmentId(environmentId)
.addMemoryStoreResource(outputStoreId)
.build()
);
}
````
````php
$matches = array_filter($dream->outputs, fn($output) => $output->type === 'memory_store');
$output = $matches ? reset($matches) : null;
if ($output !== null) {
$session = $client->beta->sessions->create(
agent: $agentId,
environmentID: $environmentId,
resources: [
['type' => 'memory_store', 'memory_store_id' => $output->memoryStoreID],
],
);
}
````
````ruby
output = dream.outputs.find { it.type == :memory_store }
if output
client.beta.sessions.create(
agent: agent_id,
environment_id: environment_id,
resources: [
{type: "memory_store", memory_store_id: output.memory_store_id}
]
)
end
````
The dream itself never deletes or modifies its inputs. On `failed` or `canceled` the output store persists with partial contents so you can inspect what was produced before stopping; clean it up via the Memory Stores API if you don't need it.
While a dream is `pending` or `running`, archiving or deleting its output store is rejected with a 400. Archiving or deleting an *input* store or session mid-run will cause the dream to fail with `input_memory_store_unavailable` or `input_session_unavailable`.
## Cancel a dream
Cancel moves a `pending` or `running` dream to `canceled` immediately. Canceling an already-`canceled` dream is an idempotent no-op; canceling a `completed` or `failed` dream returns 400.
````bash
curl -s -X POST "https://api.anthropic.com/v1/dreams/$dream_id/cancel" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01,dreaming-2026-04-21"
````
````bash
ant beta:dreams cancel --dream-id "$dream_id"
````
````python
client.beta.dreams.cancel(dream.id)
````
````typescript
await client.beta.dreams.cancel(dream.id);
````
````csharp
await client.Beta.Dreams.Cancel(dream.ID);
````
````go
dream, err = client.Beta.Dreams.Cancel(ctx, dream.ID, anthropic.BetaDreamCancelParams{})
if err != nil {
panic(err)
}
````
````java
client.beta().dreams().cancel(dream.id());
````
````php
$client->beta->dreams->cancel($dream->id);
````
````ruby
client.beta.dreams.cancel(dream.id)
````
## Archive a dream
Archive sets `archived_at` on a dream that has reached a terminal state (`completed`, `failed`, or `canceled`); `status` is left unchanged. Archived dreams are excluded from default list responses but remain readable by ID. Archiving an already-archived dream is an idempotent no-op. Archiving a `pending` or `running` dream returns 400; cancel it first. There is no unarchive.
````bash
curl -s -X POST "https://api.anthropic.com/v1/dreams/$dream_id/archive" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01,dreaming-2026-04-21"
````
````bash
ant beta:dreams archive --dream-id "$dream_id"
````
````python
client.beta.dreams.archive(dream.id)
````
````typescript
await client.beta.dreams.archive(dream.id);
````
````csharp
await client.Beta.Dreams.Archive(dream.ID);
````
````go
dream, err = client.Beta.Dreams.Archive(ctx, dream.ID, anthropic.BetaDreamArchiveParams{})
if err != nil {
panic(err)
}
````
````java
client.beta().dreams().archive(dream.id());
````
````php
$client->beta->dreams->archive($dream->id);
````
````ruby
client.beta.dreams.archive(dream.id)
````
Archiving a dream does not touch its output memory store; manage that separately via the [Memory Stores API](/docs/en/managed-agents/memory).
## List dreams
Returns all non-archived dreams in the workspace, newest first. Use `limit` (default 20, max 100) and the `page` cursor to paginate. Pass `include_archived=true` to include archived dreams.
````bash
curl -s "https://api.anthropic.com/v1/dreams?limit=20" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01,dreaming-2026-04-21"
````
````bash
ant beta:dreams list --limit 20
````
````python
for listed_dream in client.beta.dreams.list(limit=20):
print(listed_dream.id, listed_dream.status)
````
````typescript
for await (const listedDream of client.beta.dreams.list({ limit: 20 })) {
console.log(listedDream.id, listedDream.status);
}
````
````csharp
var page = await client.Beta.Dreams.List(new() { Limit = 20 });
await foreach (var listed in page.Paginate())
{
Console.WriteLine($"{listed.ID} {listed.Status.Raw()}");
}
````
````go
dreams := client.Beta.Dreams.ListAutoPaging(ctx, anthropic.BetaDreamListParams{
Limit: anthropic.Int(20),
})
for dreams.Next() {
listed := dreams.Current()
fmt.Println(listed.ID, listed.Status)
}
if err := dreams.Err(); err != nil {
panic(err)
}
````
````java
for (var listedDream : client.beta().dreams().list(
DreamListParams.builder().limit(20).build()
).autoPager()) {
IO.println(listedDream.id() + " " + listedDream.status());
}
````
````php
foreach ($client->beta->dreams->list(limit: 20)->pagingEachItem() as $dream) {
echo "{$dream->id} {$dream->status}\n";
}
````
````ruby
client.beta.dreams.list(limit: 20).auto_paging_each do
puts "#{it.id} #{it.status}"
end
````
## Errors
A non-exhaustive list of possible dreaming errors is below.
| `error.type` | When |
| --- | --- |
| `timeout` | The pipeline exceeded its runtime budget. |
| `internal_error` | Unclassified pipeline failure. |
| `memory_store_org_limit_exceeded` | Your organization hit its memory-store cap while the pipeline was provisioning working storage. |
| `input_memory_store_too_large` | The input memory store exceeds the pipeline's size limit. |
| `input_memory_store_unavailable` | The input memory store was archived or deleted after the dream was created. |
| `input_session_unavailable` | An input session was archived or deleted after the dream was created. |
## Billing
Dreams are billed at standard API token rates for the model you select; `usage` on the resource reports the exact totals. Cost scales roughly linearly with the number and length of input sessions. Start with a small batch of sessions and scale up once you're satisfied with the curation quality.
## Limits
| Limit | Value |
| --- | --- |
| Sessions per dream | 100 |
| `instructions` length | 4,096 characters |
| Supported models | `claude-opus-4-7`, `claude-sonnet-4-6` |
Default rate limits apply to dream creation while this feature is in beta. [Contact support](https://support.claude.com) if you need higher limits.
---
# Using agent memory
URL: https://platform.claude.com/docs/en/managed-agents/memory
# Using agent memory
Give your agents persistent memory that survives across sessions using memory stores.
---
Each Managed Agents session starts with a fresh context by default. When a session ends, any state the agent built up is gone. Memory stores let the agent carry information across sessions: user preferences, project conventions, prior mistakes, and domain context.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets the beta header automatically.
## Overview
A **memory store** is a workspace-scoped collection of text documents optimized for Claude. When you attach a store to a session, it is mounted as a directory inside the session's container. The agent reads and writes it with the same file tools it uses for the rest of the filesystem, and a note describing each mount is automatically added to the system prompt, telling the agent where to look. The [agent toolset](/docs/en/managed-agents/tools) is required for these interactions; make sure to enable it during [agent creation](/docs/en/managed-agents/agent-setup).
Each **memory** in a store is addressed by a path and can be read and edited directly via the API or Console, allowing for tuning, importing, and exporting.
Every change to a memory creates an immutable **memory version**, giving you an audit trail and point-in-time recovery for everything the agent writes.
## Create a memory store
Give the store a `name` and a `description`. The description is passed to the agent, telling it what the store contains.
````bash
store=$(curl -s https://api.anthropic.com/v1/memory_stores \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{"name": "User Preferences", "description": "Per-user preferences and project context."}')
store_id=$(jq -r '.id' <<< "$store")
echo "$store_id" # memstore_01Hx...
````
````bash
store_id=$(ant beta:memory-stores create \
--name "User Preferences" \
--description "Per-user preferences and project context." \
--transform id --raw-output)
````
````python
store = client.beta.memory_stores.create(
name="User Preferences",
description="Per-user preferences and project context.",
)
print(store.id) # memstore_01Hx...
````
````typescript
const store = await client.beta.memoryStores.create({
name: "User Preferences",
description: "Per-user preferences and project context."
});
console.log(store.id); // memstore_01Hx...
````
````csharp
var store = await client.Beta.MemoryStores.Create(new()
{
Name = "User Preferences",
Description = "Per-user preferences and project context.",
});
Console.WriteLine(store.ID); // memstore_01Hx...
````
````go
store, err := client.Beta.MemoryStores.New(ctx, anthropic.BetaMemoryStoreNewParams{
Name: "User Preferences",
Description: anthropic.String("Per-user preferences and project context."),
})
if err != nil {
panic(err)
}
fmt.Println(store.ID) // memstore_01Hx...
````
````java
var store = client.beta().memoryStores().create(
MemoryStoreCreateParams.builder()
.name("User Preferences")
.description("Per-user preferences and project context.")
.build()
);
IO.println(store.id()); // memstore_01Hx...
````
````php
use Anthropic\Client;
$client = new Client();
$store = $client->beta->memoryStores->create(
name: 'User Preferences',
description: 'Per-user preferences and project context.',
);
echo "{$store->id}\n"; // memstore_01Hx...
````
````ruby
require "anthropic"
client = Anthropic::Client.new
store = client.beta.memory_stores.create(
name: "User Preferences",
description: "Per-user preferences and project context."
)
puts store.id # memstore_01Hx...
````
The memory store `id` (`memstore_...`) is what you pass when attaching the store to a session.
### Seed it with content (optional)
Pre-load a store with reference material before any agent runs:
````bash
curl -s "https://api.anthropic.com/v1/memory_stores/$store_id/memories" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{"path": "/formatting_standards.md", "content": "All reports use GAAP formatting. Dates are ISO-8601..."}' > /dev/null
````
````bash
ant beta:memory-stores:memories create \
--memory-store-id "$store_id" \
--path "/formatting_standards.md" \
--content "All reports use GAAP formatting. Dates are ISO-8601..." \
> /dev/null
````
````python
client.beta.memory_stores.memories.create(
store.id,
path="/formatting_standards.md",
content="All reports use GAAP formatting. Dates are ISO-8601...",
)
````
````typescript
await client.beta.memoryStores.memories.create(store.id, {
path: "/formatting_standards.md",
content: "All reports use GAAP formatting. Dates are ISO-8601..."
});
````
````csharp
await client.Beta.MemoryStores.Memories.Create(store.ID, new()
{
Path = "/formatting_standards.md",
Content = "All reports use GAAP formatting. Dates are ISO-8601...",
});
````
````go
_, err = client.Beta.MemoryStores.Memories.New(ctx, store.ID, anthropic.BetaMemoryStoreMemoryNewParams{
Path: "/formatting_standards.md",
Content: "All reports use GAAP formatting. Dates are ISO-8601...",
})
if err != nil {
panic(err)
}
````
````java
client.beta().memoryStores().memories().create(
store.id(),
MemoryCreateParams.builder()
.path("/formatting_standards.md")
.content("All reports use GAAP formatting. Dates are ISO-8601...")
.build()
);
````
````php
$client->beta->memoryStores->memories->create(
$store->id,
path: '/formatting_standards.md',
content: 'All reports use GAAP formatting. Dates are ISO-8601...',
);
````
````ruby
client.beta.memory_stores.memories.create(
store.id,
path: "/formatting_standards.md",
content: "All reports use GAAP formatting. Dates are ISO-8601..."
)
````
Individual memories within the store are capped at 100 kB (~25k tokens). Structure memory as many small focused files, not a few large ones.
## Attach a memory store to a session
Memory stores are attached in the session's `resources[]` array when the session is created. Unlike file and repository resources, memory stores can only be attached at session creation time; adding or removing one from a running session is not supported.
Optionally include `instructions` to provide session-specific guidance for how the agent should use this store. It is shown to the agent alongside the store's `name` and `description`, and is capped at 4,096 characters.
You can configure `access` as well. It defaults to `read_write` (shown explicitly in the following example), but `read_only` is also supported.
````bash
curl -s https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
resources: [
[
'type' => 'memory_store',
'memory_store_id' => $store->id,
'access' => 'read_write',
'instructions' => 'User preferences and project context. Check before starting any task.',
],
],
);
````
````ruby
session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id,
resources: [
{
type: "memory_store",
memory_store_id: store.id,
access: "read_write",
instructions: "User preferences and project context. Check before starting any task."
}
]
)
````
Memory stores attach with `read_write` access by default. If the agent processes untrusted input (user-supplied prompts, fetched web content, or third-party tool output), a successful prompt injection could write malicious content into the store. Later sessions then read that content as trusted memory. Use `read_only` for reference material, shared lookups, and any store the agent does not need to modify.
A maximum of **8 memory stores** are supported per session. Attach multiple stores when different parts of memory have different owners or access rules. Common reasons:
- **Shared reference material:** one read-only store attached to many sessions (standards, conventions, domain knowledge), kept separate from each session's own read-write store.
- **Mapping to your product's structure:** one store per end user, per team, or per project, while sharing a single agent configuration.
- **Different lifecycles:** a store that outlives any single session, or one you want to archive on its own schedule.
### How the agent accesses memory
Each attached store is mounted inside the session's container as a directory under `/mnt/memory/`, and the agent reads and writes it with the standard [agent toolset](/docs/en/managed-agents/tools). Writes are persisted back to the store and stay in sync across sessions that share it. A short description of each mount (path, access mode, store `description`, and any `instructions`) is automatically added to the system prompt.
`access` is enforced at the filesystem level: a `read_only` mount rejects writes, while writes to a `read_write` mount produce [memory versions](#audit-memory-changes) attributed to the session.
The agent's reads and writes appear in the [event stream](/docs/en/managed-agents/events-and-streaming) as ordinary `agent.tool_use` and `agent.tool_result` events for whichever tool touched the mount.
## View and edit memories
Memory stores can be managed directly via the API. Use this for building review workflows, correcting bad memories, or seeding stores before any session runs.
### List memories
List the memories in a store, optionally filtered by `path_prefix` to browse a path like a directory:
````bash
curl -s "https://api.anthropic.com/v1/memory_stores/$store_id/memories?path_prefix=/&order_by=path&depth=2" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" | jq -r '.data[] | "\(.type) \(.path)"'
````
````bash
ant beta:memory-stores:memories list \
--memory-store-id "$store_id" \
--path-prefix "/" --order-by path --depth 2
````
````python
page = client.beta.memory_stores.memories.list(
store.id,
path_prefix="/",
order_by="path",
depth=2,
)
for item in page.data:
print(item.type, item.path)
````
````typescript
const page = await client.beta.memoryStores.memories.list(store.id, {
path_prefix: "/",
order_by: "path",
depth: 2
});
for (const item of page.data) {
console.log(item.type, item.path);
}
````
````csharp
var page = await client.Beta.MemoryStores.Memories.List(store.ID, new()
{
PathPrefix = "/",
OrderBy = "path",
Depth = 2,
});
foreach (var item in page.Data)
{
Console.WriteLine($"{item.Type.Raw()} {item.Path}");
}
````
````go
page, err := client.Beta.MemoryStores.Memories.List(ctx, store.ID, anthropic.BetaMemoryStoreMemoryListParams{
PathPrefix: anthropic.String("/"),
OrderBy: anthropic.BetaMemoryStoreMemoryListParamsOrderByPath,
Depth: anthropic.Int(2),
})
if err != nil {
panic(err)
}
for _, item := range page.Data {
fmt.Println(item.Type, item.Path)
}
````
````java
var page = client.beta().memoryStores().memories().list(
store.id(),
MemoryListParams.builder()
.pathPrefix("/")
.orderBy(MemoryListParams.OrderBy.PATH)
.depth(2L)
.build()
);
for (var item : page.data()) {
IO.println(item.type() + " " + item.path());
}
````
````php
$page = $client->beta->memoryStores->memories->list(
$store->id,
pathPrefix: '/',
orderBy: 'path',
depth: 2,
);
foreach ($page->data as $item) {
echo "{$item->type} {$item->path}\n";
}
````
````ruby
page = client.beta.memory_stores.memories.list(
store.id,
path_prefix: "/",
order_by: "path",
depth: 2
)
page.data.each do |entry|
puts "#{entry.type} #{entry.path}"
end
````
See the [List memories reference](/docs/en/api/beta/memory_stores/memories/list) for full parameters and response schema.
### Read a memory
Fetching an individual memory returns the full content.
````bash
curl -s "https://api.anthropic.com/v1/memory_stores/$store_id/memories/$mem_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" | jq -r '.content'
````
````bash
ant beta:memory-stores:memories retrieve \
--memory-store-id "$store_id" \
--memory-id "$mem_id"
````
````python
retrieved = client.beta.memory_stores.memories.retrieve(
mem.id,
memory_store_id=store.id,
)
print(retrieved.content)
````
````typescript
const retrieved = await client.beta.memoryStores.memories.retrieve(mem.id, {
memory_store_id: store.id
});
console.log(retrieved.content);
````
````csharp
var retrieved = await client.Beta.MemoryStores.Memories.Retrieve(mem.ID, new()
{
MemoryStoreID = store.ID,
});
Console.WriteLine(retrieved.Content);
````
````go
retrieved, err := client.Beta.MemoryStores.Memories.Get(ctx, mem.ID, anthropic.BetaMemoryStoreMemoryGetParams{
MemoryStoreID: store.ID,
})
if err != nil {
panic(err)
}
fmt.Println(retrieved.Content)
````
````java
var retrieved = client.beta().memoryStores().memories().retrieve(
mem.id(),
MemoryRetrieveParams.builder().memoryStoreId(store.id()).build()
);
IO.println(retrieved.content());
````
````php
$retrieved = $client->beta->memoryStores->memories->retrieve($mem->id, memoryStoreID: $store->id);
echo "{$retrieved->content}\n";
````
````ruby
retrieved = client.beta.memory_stores.memories.retrieve(
mem.id,
memory_store_id: store.id
)
puts retrieved.content
````
See the [Retrieve a memory reference](/docs/en/api/beta/memory_stores/memories/retrieve) for full parameters and response schema.
### Create a memory
`memories.create` creates a memory at a given `path`. Create does not overwrite; to change an existing memory, use [`memories.update`](#update-a-memory).
````bash
mem=$(curl -s "https://api.anthropic.com/v1/memory_stores/$store_id/memories" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{"path": "/preferences/formatting.md", "content": "Always use tabs, not spaces."}')
mem_id=$(jq -r '.id' <<< "$mem")
mem_sha=$(jq -r '.content_sha256' <<< "$mem")
````
````bash
mem=$(ant beta:memory-stores:memories create \
--memory-store-id "$store_id" \
--path "/preferences/formatting.md" \
--content "Always use tabs, not spaces." \
--format json)
mem_id=$(jq -r '.id' <<< "$mem")
mem_sha=$(jq -r '.content_sha256' <<< "$mem")
````
````python
mem = client.beta.memory_stores.memories.create(
store.id,
path="/preferences/formatting.md",
content="Always use tabs, not spaces.",
)
````
````typescript
const mem = await client.beta.memoryStores.memories.create(store.id, {
path: "/preferences/formatting.md",
content: "Always use tabs, not spaces."
});
````
````csharp
var mem = await client.Beta.MemoryStores.Memories.Create(store.ID, new()
{
Path = "/preferences/formatting.md",
Content = "Always use tabs, not spaces.",
});
````
````go
mem, err := client.Beta.MemoryStores.Memories.New(ctx, store.ID, anthropic.BetaMemoryStoreMemoryNewParams{
Path: "/preferences/formatting.md",
Content: "Always use tabs, not spaces.",
})
if err != nil {
panic(err)
}
````
````java
var mem = client.beta().memoryStores().memories().create(
store.id(),
MemoryCreateParams.builder()
.path("/preferences/formatting.md")
.content("Always use tabs, not spaces.")
.build()
);
````
````php
$mem = $client->beta->memoryStores->memories->create(
$store->id,
path: '/preferences/formatting.md',
content: 'Always use tabs, not spaces.',
);
````
````ruby
mem = client.beta.memory_stores.memories.create(
store.id,
path: "/preferences/formatting.md",
content: "Always use tabs, not spaces."
)
````
See the [Create a memory reference](/docs/en/api/beta/memory_stores/memories/create) for full parameters and response schema.
### Update a memory
`memories.update` modifies an existing memory by ID. You can change `content`, `path` (a rename), or both. The example renames a memory to an archive path:
````bash
curl -s -X POST "https://api.anthropic.com/v1/memory_stores/$store_id/memories/$mem_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{"path": "/archive/2026_q1_formatting.md"}' > /dev/null
````
````bash
ant beta:memory-stores:memories update \
--memory-store-id "$store_id" \
--memory-id "$mem_id" \
--path "/archive/2026_q1_formatting.md" \
> /dev/null
````
````python
client.beta.memory_stores.memories.update(
mem.id,
memory_store_id=store.id,
path="/archive/2026_q1_formatting.md",
)
````
````typescript
await client.beta.memoryStores.memories.update(mem.id, {
memory_store_id: store.id,
path: "/archive/2026_q1_formatting.md"
});
````
````csharp
await client.Beta.MemoryStores.Memories.Update(mem.ID, new()
{
MemoryStoreID = store.ID,
Path = "/archive/2026_q1_formatting.md",
});
````
````go
_, err = client.Beta.MemoryStores.Memories.Update(ctx, mem.ID, anthropic.BetaMemoryStoreMemoryUpdateParams{
MemoryStoreID: store.ID,
Path: anthropic.String("/archive/2026_q1_formatting.md"),
})
if err != nil {
panic(err)
}
````
````java
client.beta().memoryStores().memories().update(
mem.id(),
MemoryUpdateParams.builder()
.memoryStoreId(store.id())
.path("/archive/2026_q1_formatting.md")
.build()
);
````
````php
$client->beta->memoryStores->memories->update(
$mem->id,
memoryStoreID: $store->id,
path: '/archive/2026_q1_formatting.md',
);
````
````ruby
client.beta.memory_stores.memories.update(
mem.id,
memory_store_id: store.id,
path: "/archive/2026_q1_formatting.md"
)
````
See the [Update a memory reference](/docs/en/api/beta/memory_stores/memories/update) for full parameters and response schema.
#### Safe content edits (optimistic concurrency)
To avoid clobbering a concurrent write, pass a `content_sha256` precondition. The update only applies if the stored content hash still matches the one you read; on mismatch, re-read the memory and retry against the fresh state.
````bash
curl -s -X POST "https://api.anthropic.com/v1/memory_stores/$store_id/memories/$mem_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- > /dev/null < /dev/null
````
````python
client.beta.memory_stores.memories.update(
memory_id=mem.id,
memory_store_id=store.id,
content="CORRECTED: Always use 2-space indentation.",
precondition={"type": "content_sha256", "content_sha256": mem.content_sha256},
)
````
````typescript
await client.beta.memoryStores.memories.update(mem.id, {
memory_store_id: store.id,
content: "CORRECTED: Always use 2-space indentation.",
precondition: { type: "content_sha256", content_sha256: mem.content_sha256 }
});
````
````csharp
await client.Beta.MemoryStores.Memories.Update(mem.ID, new()
{
MemoryStoreID = store.ID,
Content = "CORRECTED: Always use 2-space indentation.",
Precondition = new ContentSha256Precondition
{
Type = "content_sha256",
ContentSha256 = mem.ContentSha256,
},
});
````
````go
_, err = client.Beta.MemoryStores.Memories.Update(ctx, mem.ID, anthropic.BetaMemoryStoreMemoryUpdateParams{
MemoryStoreID: store.ID,
Content: anthropic.String("CORRECTED: Always use 2-space indentation."),
Precondition: anthropic.BetaMemoryStoreMemoryUpdateParamsPreconditionUnion{
OfContentSha256: &anthropic.BetaManagedAgentsContentSha256PreconditionParams{
ContentSha256: mem.ContentSha256,
},
},
})
if err != nil {
panic(err)
}
````
````java
client.beta().memoryStores().memories().update(
mem.id(),
MemoryUpdateParams.builder()
.memoryStoreId(store.id())
.content("CORRECTED: Always use 2-space indentation.")
.precondition(
MemoryUpdateParams.Precondition.builder()
.type(MemoryUpdateParams.Precondition.Type.CONTENT_SHA256)
.contentSha256(mem.contentSha256())
.build()
)
.build()
);
````
````php
$client->beta->memoryStores->memories->update(
$mem->id,
memoryStoreID: $store->id,
content: 'CORRECTED: Always use 2-space indentation.',
precondition: ['type' => 'content_sha256', 'content_sha256' => $mem->contentSha256],
);
````
````ruby
client.beta.memory_stores.memories.update(
mem.id,
memory_store_id: store.id,
content: "CORRECTED: Always use 2-space indentation.",
precondition: {type: "content_sha256", content_sha256: mem.content_sha256}
)
````
### Delete a memory
````bash
curl -s -X DELETE "https://api.anthropic.com/v1/memory_stores/$store_id/memories/$mem_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" > /dev/null
````
````bash
ant beta:memory-stores:memories delete \
--memory-store-id "$store_id" \
--memory-id "$mem_id" \
> /dev/null
````
````python
client.beta.memory_stores.memories.delete(
mem.id,
memory_store_id=store.id,
)
````
````typescript
await client.beta.memoryStores.memories.delete(mem.id, {
memory_store_id: store.id
});
````
````csharp
await client.Beta.MemoryStores.Memories.Delete(mem.ID, new()
{
MemoryStoreID = store.ID,
});
````
````go
_, err = client.Beta.MemoryStores.Memories.Delete(ctx, mem.ID, anthropic.BetaMemoryStoreMemoryDeleteParams{
MemoryStoreID: store.ID,
})
if err != nil {
panic(err)
}
````
````java
client.beta().memoryStores().memories().delete(
mem.id(),
MemoryDeleteParams.builder().memoryStoreId(store.id()).build()
);
````
````php
$client->beta->memoryStores->memories->delete($mem->id, memoryStoreID: $store->id);
````
````ruby
client.beta.memory_stores.memories.delete(
mem.id,
memory_store_id: store.id
)
````
See the [Delete a memory reference](/docs/en/api/beta/memory_stores/memories/delete) for full parameters and response schema.
## Audit memory changes
Every mutation to a memory creates an immutable **memory version** (`memver_...`). Use the version endpoints to audit who changed what and when, to inspect or restore a prior snapshot, and to scrub sensitive content out of history with redact.
Versions belong to the store (not the individual memory) and survive even after the memory itself is deleted, so the audit trail stays complete. Versions are retained for 30 days; however, the recent versions are always kept regardless of age, so memories that change infrequently might retain history beyond 30 days. The live `memories.retrieve` call always returns the latest version; the version endpoints give you the retained history.
There is no dedicated restore endpoint; to roll back, retrieve the version you want and write its `content` back with `memories.update` (or `memories.create` if the parent memory has been deleted, because versions outlive their parent).
Past memory versions might be deleted after 30 days. To preserve memory history for longer, export versions via the API.
### List versions
List version history for a store, newest first. The example filters to a single memory's history:
````bash
versions=$(curl -s "https://api.anthropic.com/v1/memory_stores/$store_id/memory_versions?memory_id=$mem_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01")
jq -r '.data[] | "\(.id): \(.operation)"' <<< "$versions"
version_id=$(jq -r '.data[1].id' <<< "$versions")
````
````bash
versions=$(ant beta:memory-stores:memory-versions list \
--memory-store-id "$store_id" \
--memory-id "$mem_id" \
--format json)
jq -r '.data[] | "\(.id): \(.operation)"' <<< "$versions"
version_id=$(jq -r '.data[1].id' <<< "$versions")
````
````python
versions = client.beta.memory_stores.memory_versions.list(
store.id,
memory_id=mem.id,
)
for v in versions:
print(f"{v.id}: {v.operation}")
version_id = versions.data[1].id
````
````typescript
const versions = await client.beta.memoryStores.memoryVersions.list(store.id, {
memory_id: mem.id
});
for await (const v of versions) {
console.log(`${v.id}: ${v.operation}`);
}
const versionId = versions.data[1].id;
````
````csharp
var versions = await client.Beta.MemoryStores.MemoryVersions.List(store.ID, new()
{
MemoryID = mem.ID,
});
await foreach (var v in versions.Paginate())
{
Console.WriteLine($"{v.ID}: {v.Operation.Raw()}");
}
var versionId = versions.Data[1].ID;
````
````go
versions := client.Beta.MemoryStores.MemoryVersions.ListAutoPaging(ctx, store.ID, anthropic.BetaMemoryStoreMemoryVersionListParams{
MemoryID: anthropic.String(mem.ID),
})
for versions.Next() {
v := versions.Current()
fmt.Printf("%s: %s\n", v.ID, v.Operation)
}
if err := versions.Err(); err != nil {
panic(err)
}
vpage, err := client.Beta.MemoryStores.MemoryVersions.List(ctx, store.ID, anthropic.BetaMemoryStoreMemoryVersionListParams{
MemoryID: anthropic.String(mem.ID),
})
if err != nil {
panic(err)
}
versionID := vpage.Data[1].ID
````
````java
var versions = client.beta().memoryStores().memoryVersions().list(
store.id(),
MemoryVersionListParams.builder().memoryId(mem.id()).build()
);
for (var v : versions.autoPager()) {
IO.println(v.id() + ": " + v.operation());
}
var versionId = versions.data().get(1).id();
````
````php
$versions = $client->beta->memoryStores->memoryVersions->list(
$store->id,
memoryID: $mem->id,
);
foreach ($versions->pagingEachItem() as $v) {
echo "{$v->id}: {$v->operation}\n";
}
$versionId = $versions->data[1]->id;
````
````ruby
versions = client.beta.memory_stores.memory_versions.list(
store.id,
memory_id: mem.id
)
versions.auto_paging_each do |v|
puts "#{v.id}: #{v.operation}"
end
version_id = versions.data[1].id
````
See the [List memory versions reference](/docs/en/api/beta/memory_stores/memory_versions/list) for full parameters and response schema.
### Retrieve a version
Fetching an individual version returns the same fields as the list response plus the full `content` body.
````bash
curl -s "https://api.anthropic.com/v1/memory_stores/$store_id/memory_versions/$version_id" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01"
````
````bash
ant beta:memory-stores:memory-versions retrieve \
--memory-store-id "$store_id" \
--memory-version-id "$version_id"
````
````python
version = client.beta.memory_stores.memory_versions.retrieve(
version_id,
memory_store_id=store.id,
)
print(version.content)
````
````typescript
const version = await client.beta.memoryStores.memoryVersions.retrieve(versionId, {
memory_store_id: store.id
});
console.log(version.content);
````
````csharp
var version = await client.Beta.MemoryStores.MemoryVersions.Retrieve(versionId, new()
{
MemoryStoreID = store.ID,
});
Console.WriteLine(version.Content);
````
````go
version, err := client.Beta.MemoryStores.MemoryVersions.Get(ctx, versionID, anthropic.BetaMemoryStoreMemoryVersionGetParams{
MemoryStoreID: store.ID,
})
if err != nil {
panic(err)
}
fmt.Println(version.Content)
````
````java
var version = client.beta().memoryStores().memoryVersions().retrieve(
versionId,
MemoryVersionRetrieveParams.builder().memoryStoreId(store.id()).build()
);
IO.println(version.content());
````
````php
$version = $client->beta->memoryStores->memoryVersions->retrieve(
$versionId,
memoryStoreID: $store->id,
);
echo "{$version->content}\n";
````
````ruby
version = client.beta.memory_stores.memory_versions.retrieve(
version_id,
memory_store_id: store.id
)
puts version.content
````
See the [Retrieve a memory version reference](/docs/en/api/beta/memory_stores/memory_versions/retrieve) for full parameters and response schema.
### Redact a version
Redact scrubs content out of a historical version while preserving the audit trail (who did what, when). Use it for compliance workflows such as removing leaked secrets, PII, or user deletion requests.
A version that is the current head of a live memory cannot be redacted. Write a new version first (or delete the memory), then redact the old one.
````bash
curl -s -X POST "https://api.anthropic.com/v1/memory_stores/$store_id/memory_versions/$version_id/redact" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{}'
````
````bash
ant beta:memory-stores:memory-versions redact \
--memory-store-id "$store_id" \
--memory-version-id "$version_id"
````
````python
client.beta.memory_stores.memory_versions.redact(
version_id,
memory_store_id=store.id,
)
````
````typescript
await client.beta.memoryStores.memoryVersions.redact(versionId, {
memory_store_id: store.id
});
````
````csharp
await client.Beta.MemoryStores.MemoryVersions.Redact(versionId, new()
{
MemoryStoreID = store.ID,
});
````
````go
_, err = client.Beta.MemoryStores.MemoryVersions.Redact(ctx, versionID, anthropic.BetaMemoryStoreMemoryVersionRedactParams{
MemoryStoreID: store.ID,
})
if err != nil {
panic(err)
}
````
````java
client.beta().memoryStores().memoryVersions().redact(
versionId,
MemoryVersionRedactParams.builder().memoryStoreId(store.id()).build()
);
````
````php
$client->beta->memoryStores->memoryVersions->redact(
$versionId,
memoryStoreID: $store->id,
);
````
````ruby
client.beta.memory_stores.memory_versions.redact(
version_id,
memory_store_id: store.id
)
````
See the [Redact a memory version reference](/docs/en/api/beta/memory_stores/memory_versions/redact) for full parameters and response schema.
## Manage memory stores
In addition to [`create`](/docs/en/api/beta/memory_stores/create), memory stores support [`retrieve`](/docs/en/api/beta/memory_stores/retrieve), [`update`](/docs/en/api/beta/memory_stores/update), [`list`](/docs/en/api/beta/memory_stores/list), [`archive`](/docs/en/api/beta/memory_stores/archive), and [`delete`](/docs/en/api/beta/memory_stores/delete).
### List stores
List stores in the workspace. Archived stores are excluded by default; pass `include_archived: true` to include them.
````bash
curl -s "https://api.anthropic.com/v1/memory_stores?include_archived=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" | jq '.data[] | {id, name, archived_at}'
````
````bash
ant beta:memory-stores list --include-archived
````
````python
for s in client.beta.memory_stores.list(include_archived=True):
print(s.id, s.name, s.archived_at)
````
````typescript
for await (const s of client.beta.memoryStores.list({ include_archived: true })) {
console.log(s.id, s.name, s.archived_at);
}
````
````csharp
var stores = await client.Beta.MemoryStores.List(new() { IncludeArchived = true });
await foreach (var s in stores.Paginate())
{
Console.WriteLine($"{s.ID} {s.Name} {s.ArchivedAt}");
}
````
````go
stores := client.Beta.MemoryStores.ListAutoPaging(ctx, anthropic.BetaMemoryStoreListParams{
IncludeArchived: anthropic.Bool(true),
})
for stores.Next() {
s := stores.Current()
fmt.Println(s.ID, s.Name, s.ArchivedAt)
}
if err := stores.Err(); err != nil {
panic(err)
}
````
````java
for (var s : client.beta().memoryStores().list(
MemoryStoreListParams.builder().includeArchived(true).build()
).autoPager()) {
IO.println(s.id() + " " + s.name() + " " + s.archivedAt());
}
````
````php
foreach ($client->beta->memoryStores->list(includeArchived: true)->pagingEachItem() as $s) {
echo "{$s->id} {$s->name} {$s->archivedAt}\n";
}
````
````ruby
client.beta.memory_stores.list(include_archived: true).auto_paging_each do |s|
puts "#{s.id} #{s.name} #{s.archived_at}"
end
````
See the [List memory stores reference](/docs/en/api/beta/memory_stores/list) for full parameters and response schema.
### Archive a store
Archiving makes a store read-only and prevents it from being attached to new sessions. Archiving is one-way; there is no unarchive.
````bash
curl -s -X POST "https://api.anthropic.com/v1/memory_stores/$store_id/archive" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" > /dev/null
````
````bash
ant beta:memory-stores archive --memory-store-id "$store_id"
````
````python
client.beta.memory_stores.archive(store.id)
````
````typescript
await client.beta.memoryStores.archive(store.id);
````
````csharp
await client.Beta.MemoryStores.Archive(store.ID);
````
````go
_, err = client.Beta.MemoryStores.Archive(ctx, store.ID)
if err != nil {
panic(err)
}
````
````java
client.beta().memoryStores().archive(store.id());
````
````php
$client->beta->memoryStores->archive($store->id);
````
````ruby
client.beta.memory_stores.archive(store.id)
````
See the [Archive a memory store reference](/docs/en/api/beta/memory_stores/archive) for full parameters and response schema.
To permanently remove a store along with all of its memories and versions, use [`memory_stores.delete`](/docs/en/api/beta/memory_stores/delete).
## Limits
Default capacity and rate limits apply to memory stores while this feature is in beta. [Contact support](https://support.claude.com) if you need higher limits.
### Advanced orchestration
---
# Multiagent sessions
URL: https://platform.claude.com/docs/en/managed-agents/multi-agent
# Multiagent sessions
Coordinate multiple agents within a single session.
---
Multi-agent orchestration lets one agent coordinate with others to complete complex work. Agents can act in parallel with their own isolated context, which helps improve output quality and can also improve time to completion.
All Managed Agents API requests require the `managed-agents-2026-04-01` beta header. The SDK sets this beta header automatically.
## How it works
All agents share the same container and filesystem, but each agent runs in its own **session thread**, a context-isolated event stream with its own conversation history. The coordinator reports activity in the **primary thread** (which is the same as the session-level [event stream](/docs/en/managed-agents/events-and-streaming)); additional threads are spawned at runtime when the coordinator decides to delegate.
Threads are persistent: the coordinator can send a follow-up to an agent it called earlier, and that agent retains everything from its previous turns.
Each agent uses its own configuration (model, system prompt, tools, MCP servers, and skills) as defined when that agent was created. Tools and context are not shared.
### What to delegate
Multiagent coordination is best-suited for complex tasks that either require work across a variety of surfaces, or where multiple well-scoped tasks contribute to an overall goal.
Patterns that work well:
- **Parallelization:** Fan out independent subtasks simultaneously (searching multiple sources, analyzing separate files) and have the coordinator synthesize the results.
- **Specialization:** Route to agents with domain-focused system prompts and tools, such as a security agent or a documentation agent, rather than loading a single agent with every capability.
- **Escalation:** Consult a more capable agent / model for a subset of complex subtasks.
## Configure the coordinator
When [defining your agent](/docs/en/managed-agents/agent-setup), set `multiagent` to declare the roster of agents the coordinator can delegate to:
````bash
coordinator=$(curl -fsS https://api.anthropic.com/v1/agents \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <beta->agents->create(
name: 'Engineering Lead',
model: 'claude-opus-4-7',
system: 'You coordinate engineering work. Delegate code review to the reviewer agent and test writing to the test agent.',
tools: [
['type' => 'agent_toolset_20260401'],
],
multiagent: [
'type' => 'coordinator',
'agents' => [
['type' => 'agent', 'id' => $reviewerAgent->id],
['type' => 'agent', 'id' => $testWriterAgent->id],
],
],
);
````
````ruby
coordinator = client.beta.agents.create(
name: "Engineering Lead",
model: "claude-opus-4-7",
system: "You coordinate engineering work. Delegate code review to the reviewer agent and test writing to the test agent.",
tools: [
{type: "agent_toolset_20260401"}
],
multiagent: {
type: "coordinator",
agents: [
{type: "agent", id: reviewer_agent.id},
{type: "agent", id: test_writer_agent.id}
]
}
)
````
`multiagent.agents` can accept any of the following:
* `{"type": "agent", "id": agent.id}` references a previously created `agent` by ID. Defaults to pinning the latest agent version if no `version` is specified.
* `{"type": "agent", "id": agent.id, "version": agent.version}` pins a specific agent version.
* `{"type": "self"}`: allows the coordinator to spawn copies of itself.
The coordinator can only delegate to one level of agents; depth > 1 is ignored. A maximum of 20 unique agents can be listed in `multiagent.agents`, but the coordinator can call multiple copies of each agent.
## Create the session
Create a session referencing the coordinator. The coordinator will delegate to the agents in its roster as needed.
````bash
session=$(curl -fsSL https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <beta->sessions->create(
agent: $coordinator->id,
environmentID: $environment->id,
);
````
````ruby
session = client.beta.sessions.create(
agent: coordinator.id,
environment_id: environment.id
)
````
## Threads
The **session-level event stream** (`/v1/sessions/:id/events/stream`) is considered the **primary thread**, containing a condensed view of all activity across all threads. You won't see the full activity from non-coordinator agents, but you will see the start and end of their work, as well as blocking events like tool permission requests.
**Session threads** are where you drill into a specific agent's activity.
The session `status` is an aggregation of all agent activity; if at least one thread is `running`, then the overall session status will be `running` as well.
A maximum of 25 concurrent threads are supported. The coordinator can call multiple copies of a single agent in the roster, creating multiple threads associated with one `agent`.
List all threads associated with a session as follows:
````bash
curl -fsS "https://api.anthropic.com/v1/sessions/$SESSION_ID/threads" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
| jq -r '.data[] | "[\(.agent.name)] \(.status)"'
````
````bash
ant beta:sessions:threads list --session-id "$SESSION_ID"
````
````python
for thread in client.beta.sessions.threads.list(session.id):
print(f"[{thread.agent.name}] {thread.status}")
````
````typescript
for await (const thread of client.beta.sessions.threads.list(session.id)) {
console.log(`[${thread.agent.name}] ${thread.status}`);
}
````
````csharp
await foreach (var thread in (await client.Beta.Sessions.Threads.List(session.ID)).Paginate())
{
Console.WriteLine($"[{thread.Agent.Name}] {thread.Status}");
}
````
````go
threads := client.Beta.Sessions.Threads.ListAutoPaging(ctx, session.ID, anthropic.BetaSessionThreadListParams{})
for threads.Next() {
thread := threads.Current()
fmt.Printf("[%s] %s\n", thread.Agent.Name, thread.Status)
}
if err := threads.Err(); err != nil {
panic(err)
}
````
````java
for (var thread : client.beta().sessions().threads().list(session.id()).autoPager()) {
IO.println("[" + thread.agent().name() + "] " + thread.status());
}
````
````php
foreach ($client->beta->sessions->threads->list($session->id)->pagingEachItem() as $thread) {
echo "[{$thread->agent->name}] {$thread->status}\n";
}
````
````ruby
client.beta.sessions.threads.list(session.id).auto_paging_each do |thread|
puts "[#{thread.agent.name}] #{thread.status}"
end
````
The full list includes the primary thread. `parent_thread_id` will be null for the primary thread.
Send `user.interrupt` with `session_thread_id` to stop a specific thread. Omitting `session_thread_id` targets the primary thread.
````bash
curl -fsS "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d "{\"events\": [{\"type\": \"user.interrupt\", \"session_thread_id\": \"$THREAD_ID\"}]}"
````
````bash
ant beta:sessions:events send \
--session-id "$SESSION_ID" \
--event "{type: user.interrupt, session_thread_id: $THREAD_ID}"
````
````python
client.beta.sessions.events.send(
session.id,
events=[{"type": "user.interrupt", "session_thread_id": thread.id}],
)
````
````typescript
await client.beta.sessions.events.send(session.id, {
events: [{ type: "user.interrupt", session_thread_id: thread.id }],
});
````
````csharp
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserInterruptEventParams
{
Type = BetaManagedAgentsUserInterruptEventParamsType.UserInterrupt,
SessionThreadID = thread.ID,
},
],
});
````
````go
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
OfUserInterrupt: &anthropic.BetaManagedAgentsUserInterruptEventParams{
Type: anthropic.BetaManagedAgentsUserInterruptEventParamsTypeUserInterrupt,
SessionThreadID: anthropic.String(thread.ID),
},
}},
}); err != nil {
panic(err)
}
````
````java
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserInterruptEventParams.builder()
.type(BetaManagedAgentsUserInterruptEventParams.Type.USER_INTERRUPT)
.sessionThreadId(thread.id())
.build())
.build());
````
````php
$client->beta->sessions->events->send(
$session->id,
events: [
['type' => 'user.interrupt', 'session_thread_id' => $thread->id],
],
);
````
````ruby
client.beta.sessions.events.send_(
session.id,
events: [{type: "user.interrupt", session_thread_id: thread.id}]
)
````
Against a child thread blocked on `requires_action`, the interrupt marks each pending tool call denied and re-emits `session.thread_status_idle` with `stop_reason: end_turn` directly; the model is not sampled. Against a thread already at `idle`, the interrupt is a no-op.
Optionally archive a session thread when it has finished its work. This frees up a thread against the 25 thread limit.
````bash
curl -fsS -X POST "https://api.anthropic.com/v1/sessions/$SESSION_ID/threads/$THREAD_ID/archive" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01"
````
````bash
ant beta:sessions:threads archive \
--session-id "$SESSION_ID" \
--thread-id "$THREAD_ID"
````
````python
archived = client.beta.sessions.threads.archive(thread.id, session_id=session.id)
print(archived.status, archived.archived_at)
````
````typescript
const archived = await client.beta.sessions.threads.archive(thread.id, {
session_id: session.id,
});
console.log(archived.status, archived.archived_at);
````
````csharp
var archived = await client.Beta.Sessions.Threads.Archive(thread.ID, new() { SessionID = session.ID });
Console.WriteLine($"{archived.Status} {archived.ArchivedAt}");
````
````go
archived, err := client.Beta.Sessions.Threads.Archive(ctx, thread.ID, anthropic.BetaSessionThreadArchiveParams{
SessionID: session.ID,
})
if err != nil {
panic(err)
}
fmt.Println(archived.Status, archived.ArchivedAt)
````
````java
var archived = client.beta().sessions().threads().archive(
thread.id(),
ThreadArchiveParams.builder()
.sessionId(session.id())
.build());
IO.println(archived.status() + " " + archived.archivedAt());
````
````php
$archived = $client->beta->sessions->threads->archive($thread->id, sessionID: $session->id);
echo "{$archived->status} {$archived->archivedAt->format(DATE_ATOM)}\n";
````
````ruby
archived = client.beta.sessions.threads.archive(thread.id, session_id: session.id)
puts "#{archived.status} #{archived.archived_at}"
````
Archive only succeeds if the thread is `idle`. If the thread is running or blocked on `requires_action`, interrupt it first:
````bash
# Interrupt the thread, then archive it
curl -fsS "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d "{\"events\": [{\"type\": \"user.interrupt\", \"session_thread_id\": \"$THREAD_ID\"}]}"
curl -fsS -X POST "https://api.anthropic.com/v1/sessions/$SESSION_ID/threads/$THREAD_ID/archive" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01"
````
````bash
ant beta:sessions:events send \
--session-id "$SESSION_ID" \
--event "{type: user.interrupt, session_thread_id: $THREAD_ID}"
ant beta:sessions:threads archive \
--session-id "$SESSION_ID" \
--thread-id "$THREAD_ID"
````
````python
client.beta.sessions.events.send(
session.id,
events=[{"type": "user.interrupt", "session_thread_id": thread.id}],
)
archived = client.beta.sessions.threads.archive(thread.id, session_id=session.id)
print(archived.status, archived.archived_at)
````
````typescript
await client.beta.sessions.events.send(session.id, {
events: [{ type: "user.interrupt", session_thread_id: thread.id }],
});
const archived = await client.beta.sessions.threads.archive(thread.id, {
session_id: session.id,
});
console.log(archived.status, archived.archived_at);
````
````csharp
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserInterruptEventParams
{
Type = BetaManagedAgentsUserInterruptEventParamsType.UserInterrupt,
SessionThreadID = thread.ID,
},
],
});
archived = await client.Beta.Sessions.Threads.Archive(thread.ID, new() { SessionID = session.ID });
Console.WriteLine($"{archived.Status} {archived.ArchivedAt}");
````
````go
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
OfUserInterrupt: &anthropic.BetaManagedAgentsUserInterruptEventParams{
Type: anthropic.BetaManagedAgentsUserInterruptEventParamsTypeUserInterrupt,
SessionThreadID: anthropic.String(thread.ID),
},
}},
}); err != nil {
panic(err)
}
archived, err := client.Beta.Sessions.Threads.Archive(ctx, thread.ID, anthropic.BetaSessionThreadArchiveParams{
SessionID: session.ID,
})
if err != nil {
panic(err)
}
fmt.Println(archived.Status, archived.ArchivedAt)
````
````java
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserInterruptEventParams.builder()
.type(BetaManagedAgentsUserInterruptEventParams.Type.USER_INTERRUPT)
.sessionThreadId(thread.id())
.build())
.build());
archived = client.beta().sessions().threads().archive(
thread.id(),
ThreadArchiveParams.builder()
.sessionId(session.id())
.build());
IO.println(archived.status() + " " + archived.archivedAt());
````
````php
$client->beta->sessions->events->send(
$session->id,
events: [['type' => 'user.interrupt', 'session_thread_id' => $thread->id]],
);
$archived = $client->beta->sessions->threads->archive($thread->id, sessionID: $session->id);
echo "{$archived->status} {$archived->archivedAt->format(DATE_ATOM)}\n";
````
````ruby
client.beta.sessions.events.send_(
session.id,
events: [{type: "user.interrupt", session_thread_id: thread.id}]
)
archived = client.beta.sessions.threads.archive(thread.id, session_id: session.id)
puts "#{archived.status} #{archived.archived_at}"
````
### Primary thread events
These events surface multiagent activity on the primary thread at `/v1/sessions/:id/events/stream`.
| Type | Description |
| --- | --- |
| `session.thread_created` | A thread was created. Includes `session_thread_id` and `agent_name`. |
| `session.thread_status_running` | A thread started activity. |
| `session.thread_status_idle` | The agent associated with the thread is awaiting input. Includes a `stop_reason` indicating why the agent stopped. |
| `session.thread_status_terminated` | A thread was archived or encountered a terminal error. |
| `agent.thread_message_received` | An agent delivered its result to the coordinator. Includes `from_session_thread_id`, `from_agent_name`, and `content`. |
| `agent.thread_message_sent` | The coordinator sent a follow-up to another agent. Includes `to_session_thread_id`, `to_agent_name`, and `content`. |
### Session thread events
Critical events are proxied to the primary thread. However, you may still want to investigate a specific agent's reasoning and tool calls. To do so, stream or list the events from the associated session thread.
````bash
curl -fsSN "https://api.anthropic.com/v1/sessions/$SESSION_ID/threads/$THREAD_ID/stream?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" |
while IFS= read -r line; do
[[ $line == data:* ]] || continue
json=${line#data: }
case $(jq -r '.type' <<<"$json") in
agent.message)
printf '%s' "$(jq -j '.content[] | select(.type == "text") | .text' <<<"$json")"
;;
session.thread_status_idle)
break
;;
esac
done
````
````bash
ant beta:sessions:threads:events stream \
--session-id "$SESSION_ID" \
--thread-id "$THREAD_ID"
````
````python
with client.beta.sessions.threads.events.stream(
thread.id,
session_id=session.id,
) as stream:
for event in stream:
match event.type:
case "agent.message":
for block in event.content:
if block.type == "text":
print(block.text, end="")
case "session.thread_status_idle":
break
````
````typescript
const stream = await client.beta.sessions.threads.events.stream(thread.id, {
session_id: session.id,
});
for await (const event of stream) {
if (event.type === "agent.message") {
for (const block of event.content) {
if (block.type === "text") {
process.stdout.write(block.text);
}
}
} else if (event.type === "session.thread_status_idle") {
break;
}
}
````
````csharp
await foreach (var evt in client.Beta.Sessions.Threads.Events.StreamStreaming(thread.ID, new() { SessionID = session.ID }))
{
if (evt.Value is BetaManagedAgentsAgentMessageEvent message)
{
foreach (var block in message.Content)
{
if (block.Type == "text")
{
Console.Write(block.Text);
}
}
}
else if (evt.Value is BetaManagedAgentsSessionThreadStatusIdleEvent)
{
break;
}
}
````
````go
stream := client.Beta.Sessions.Threads.Events.StreamEvents(ctx, thread.ID, anthropic.BetaSessionThreadEventStreamParams{
SessionID: session.ID,
})
defer stream.Close()
loop:
for stream.Next() {
event := stream.Current()
switch event.Type {
case "agent.message":
for _, block := range event.AsAgentMessage().Content {
if block.Type == "text" {
fmt.Print(block.Text)
}
}
case "session.thread_status_idle":
break loop
}
}
if err := stream.Err(); err != nil {
panic(err)
}
````
````java
try (var streamResponse = client.beta().sessions().threads().events().streamStreaming(
thread.id(),
ThreadStreamParams.builder().sessionId(session.id()).build()
)) {
for (var event : (Iterable) streamResponse.stream()::iterator) {
if (event.isAgentMessage()) {
for (var block : event.asAgentMessage().content()) {
IO.print(block.text());
}
} else if (event.isSessionThreadStatusIdle()) {
break;
}
}
}
````
````php
$stream = $client->beta->sessions->threads->events->streamStream(
$thread->id,
sessionID: $session->id,
);
foreach ($stream as $event) {
if ($event->type === 'agent.message') {
foreach ($event->content as $block) {
if ($block->type === 'text') {
echo $block->text;
}
}
} elseif ($event->type === 'session.thread_status_idle') {
break;
}
}
````
````ruby
client.beta.sessions.threads.events.stream_events(thread.id, session_id: session.id).each do |event|
case event.type
when :"agent.message"
event.content.each do |block|
print block.text if block.type == :text
end
when :"session.thread_status_idle"
break
end
end
````
List all past session thread events to pull a complete history.
````bash
curl -fsS "https://api.anthropic.com/v1/sessions/$SESSION_ID/threads/$THREAD_ID/events" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
| jq -r '.data[] | "[\(.type)] \(.processed_at)"'
````
````bash
ant beta:sessions:threads:events list \
--session-id "$SESSION_ID" \
--thread-id "$THREAD_ID"
````
````python
for event in client.beta.sessions.threads.events.list(
thread.id,
session_id=session.id,
):
print(f"[{event.type}] {event.processed_at}")
````
````typescript
for await (const event of client.beta.sessions.threads.events.list(thread.id, {
session_id: session.id,
})) {
console.log(`[${event.type}] ${event.processed_at}`);
}
````
````csharp
var page = await client.Beta.Sessions.Threads.Events.List(thread.ID, new() { SessionID = session.ID });
await foreach (var evt in page.Paginate())
{
Console.WriteLine($"[{evt.Type}] {evt.ProcessedAt}");
}
````
````go
pager := client.Beta.Sessions.Threads.Events.ListAutoPaging(ctx, thread.ID, anthropic.BetaSessionThreadEventListParams{
SessionID: session.ID,
})
for pager.Next() {
event := pager.Current()
fmt.Printf("[%s] %s\n", event.Type, event.ProcessedAt)
}
if err := pager.Err(); err != nil {
panic(err)
}
````
````java
for (var event : client.beta().sessions().threads().events().list(
thread.id(),
EventListParams.builder().sessionId(session.id()).build()
).autoPager()) {
IO.println("[" + event.type() + "] " + event.processedAt());
}
````
````php
foreach (
$client->beta->sessions->threads->events->list(
$thread->id,
sessionID: $session->id,
)->pagingEachItem() as $event
) {
echo "[{$event->type}] {$event->processedAt->format(DATE_RFC3339)}\n";
}
````
````ruby
client.beta.sessions.threads.events.list(
thread.id,
session_id: session.id
).auto_paging_each do |event|
puts "[#{event.type}] #{event.processed_at}"
end
````
### Tool permissions and custom tools
If a non-coordinator agent needs something from your client, such as [permission](/docs/en/managed-agents/events-and-streaming#tool-confirmation) to run an `always_ask` tool, or the [result of a custom tool](/docs/en/managed-agents/events-and-streaming#handling-custom-tool-calls), the event is cross-posted to the **primary thread** with `session_thread_id` identifying the originating session thread.
```json
{
"type": "session.thread_status_idle",
"id": "sevt_01ABC...",
"session_thread_id": "sth_01DEF...",
"agent_name": "code-reviewer",
"stop_reason": {
"type": "requires_action",
"event_ids": ["toolu_01XYZ..."]
}
}
```
Post `user.tool_confirmation` (with `tool_use_id`) or `user.custom_tool_result` (with `custom_tool_use_id`); the server routes the response to the correct thread automatically.
The example below extends the [tool confirmation handler](/docs/en/managed-agents/events-and-streaming#tool-confirmation) to route replies. The same pattern applies to `user.custom_tool_result`.
````bash
while IFS= read -r event_id; do
jq -n --arg id "$event_id" \
'{events: [{type: "user.tool_confirmation", tool_use_id: $id, result: "allow"}]}' |
curl -fsS "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @-
done < <(jq -r '.stop_reason.event_ids[]' <<<"$data")
````
````bash
# This workflow does not translate well to a one-off shell command.
# Use one of the SDK examples in this code group instead.
````
````python
for event_id in stop.event_ids:
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.tool_confirmation",
"tool_use_id": event_id,
"result": "allow",
}
],
)
````
````typescript
for (const eventId of stop.event_ids) {
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.tool_confirmation",
tool_use_id: eventId,
result: "allow",
},
],
});
}
````
````csharp
foreach (var eventId in requiresAction.EventIds)
{
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserToolConfirmationEventParams
{
Type = BetaManagedAgentsUserToolConfirmationEventParamsType.UserToolConfirmation,
ToolUseID = eventId,
Result = BetaManagedAgentsUserToolConfirmationEventParamsResult.Allow,
},
],
});
}
````
````go
for _, eventID := range stopReason.EventIDs {
params := anthropic.BetaManagedAgentsUserToolConfirmationEventParams{
Type: anthropic.BetaManagedAgentsUserToolConfirmationEventParamsTypeUserToolConfirmation,
ToolUseID: eventID,
Result: anthropic.BetaManagedAgentsUserToolConfirmationEventParamsResultAllow,
}
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.BetaManagedAgentsEventParamsUnion{{OfUserToolConfirmation: ¶ms}},
}); err != nil {
panic(err)
}
}
````
````java
for (var eventId : pendingToolUseIds) {
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserToolConfirmationEventParams.builder()
.toolUseId(eventId)
.result(BetaManagedAgentsUserToolConfirmationEventParams.Result.ALLOW)
.build())
.build()
);
}
````
````php
foreach ($event->stopReason->eventIDs as $eventId) {
$client->beta->sessions->events->send($session->id, events: [[
'type' => 'user.tool_confirmation',
'tool_use_id' => $eventId,
'result' => 'allow',
]]);
}
````
````ruby
event_ids.each do |event_id|
client.beta.sessions.events.send_(session.id, events: [{
type: "user.tool_confirmation",
tool_use_id: event_id,
result: "allow"
}])
end
````
## Admin
### Organization
---
# Admin API
URL: https://platform.claude.com/docs/en/manage-claude/admin-api
# Admin API
---
**The Admin API is unavailable for individual accounts.** To collaborate with teammates and add members, set up your organization in **Console → Settings → Organization**.
The [Admin API](/docs/en/api/admin) allows you to programmatically manage your organization's resources, including organization members, workspaces, and API keys. This provides programmatic control over administrative tasks that would otherwise require manual configuration in the [Claude Console](/).
**The Admin API requires special access**
The Admin API requires a special Admin API key (starting with `sk-ant-admin...`) that differs from standard API keys. Only organization members with the admin role can provision Admin API keys through the Claude Console.
**Claude Platform on AWS:** Most of the Admin API is not available on Claude Platform on AWS. Workspace endpoints (create, get, list, update, and archive on `/v1/organizations/workspaces`) are available. Other endpoints including organization members, workspace members, invites, API keys, usage reports, cost reports, and rate limit reports are not available. See [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws) for details.
## How the Admin API works
When you use the Admin API:
1. You make requests using your Admin API key in the `x-api-key` header
2. The API allows you to manage:
- Organization members and their roles
- Organization member invites
- Workspaces and their members
- API keys
This is useful for:
- Automating user onboarding/offboarding
- Programmatically managing workspace access
- Monitoring and managing API key usage
## Organization roles and permissions
There are five organization-level roles. See more details in the [API Console roles and permissions](https://support.claude.com/en/articles/10186004-api-console-roles-and-permissions) article.
| Role | Permissions |
|------|-------------|
| user | Can use Workbench |
| claude_code_user | Can use Workbench and [Claude Code](https://code.claude.com/docs/en/overview) |
| developer | Can use Workbench and manage API keys |
| billing | Can use Workbench and manage billing details |
| admin | Can do all of the above, plus manage users |
## Key concepts
### Organization Members
You can list [organization members](/docs/en/api/admin-api/users/get-user), update member roles, and remove members.
```bash cURL
# List organization members
curl "https://api.anthropic.com/v1/organizations/users?limit=10" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
# Update member role
curl "https://api.anthropic.com/v1/organizations/users/{user_id}" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY" \
--data '{"role": "developer"}'
# Remove member
curl --request DELETE "https://api.anthropic.com/v1/organizations/users/{user_id}" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
### Organization Invites
You can invite users to organizations and manage those [invites](/docs/en/api/admin-api/invites/get-invite).
```bash cURL
# Create invite
curl --request POST "https://api.anthropic.com/v1/organizations/invites" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY" \
--data '{
"email": "newuser@domain.com",
"role": "developer"
}'
# List invites
curl "https://api.anthropic.com/v1/organizations/invites?limit=10" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
# Delete invite
curl --request DELETE "https://api.anthropic.com/v1/organizations/invites/{invite_id}" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
### Workspaces
For a comprehensive guide to workspaces, including Console and API examples, see [Workspaces](/docs/en/manage-claude/workspaces).
### Workspace Members
Manage [user access to specific workspaces](/docs/en/api/admin-api/workspace_members/get-workspace-member):
```bash cURL
# Add member to workspace
curl --request POST "https://api.anthropic.com/v1/organizations/workspaces/{workspace_id}/members" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY" \
--data '{
"user_id": "user_xxx",
"workspace_role": "workspace_developer"
}'
# List workspace members
curl "https://api.anthropic.com/v1/organizations/workspaces/{workspace_id}/members?limit=10" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
# Update member role
curl --request POST "https://api.anthropic.com/v1/organizations/workspaces/{workspace_id}/members/{user_id}" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY" \
--data '{
"workspace_role": "workspace_admin"
}'
# Remove member from workspace
curl --request DELETE "https://api.anthropic.com/v1/organizations/workspaces/{workspace_id}/members/{user_id}" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
### API Keys
Monitor and manage [API keys](/docs/en/api/admin-api/apikeys/get-api-key):
```bash cURL
# List API keys
curl "https://api.anthropic.com/v1/organizations/api_keys?limit=10&status=active&workspace_id=wrkspc_xxx" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
# Update API key
curl --request POST "https://api.anthropic.com/v1/organizations/api_keys/{api_key_id}" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY" \
--data '{
"status": "inactive",
"name": "New Key Name"
}'
```
## Accessing organization info
Get information about your organization programmatically with the `/v1/organizations/me` endpoint.
For example:
```bash cURL
curl "https://api.anthropic.com/v1/organizations/me" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
```json
{
"id": "12345678-1234-5678-1234-567812345678",
"type": "organization",
"name": "Organization Name"
}
```
This endpoint is useful for programmatically determining which organization an Admin API key belongs to.
For complete parameter details and response schemas, see the [Organization Info API reference](/docs/en/api/admin-api/organization/get-me).
## Usage and cost reports
Track your organization's usage and costs with the [Usage and Cost API](/docs/en/manage-claude/usage-cost-api).
## Claude Code analytics
Monitor developer productivity and Claude Code adoption with the [Claude Code Analytics API](/docs/en/manage-claude/claude-code-analytics-api).
## Rate limits
Read the rate limits configured for your organization and its workspaces with the [Rate Limits API](/docs/en/manage-claude/rate-limits-api).
## Compliance API
Retrieve audit and activity data for your organization with the [Compliance API](/docs/en/manage-claude/compliance-api). Admin API keys can read the Activity Feed only; for full access, see [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access).
## Best practices
To effectively use the Admin API:
- Use meaningful names and descriptions for workspaces and API keys
- Implement proper error handling for failed operations
- Regularly audit member roles and permissions
- Clean up unused workspaces and expired invites
- Monitor API key usage and rotate keys periodically
## FAQ
Only organization members with the admin role can use the Admin API. They must also have a special Admin API key (starting with `sk-ant-admin`).
No, new API keys can only be created through the Claude Console for security reasons. The Admin API can only manage existing API keys.
API keys persist in their current state as they are scoped to the Organization, not to individual users.
No, organization members with the admin role cannot be removed through the API for security reasons.
Organization invites expire after 21 days. There is currently no way to modify this expiration period.
For workspace-specific questions, see the [Workspaces FAQ](/docs/en/manage-claude/workspaces#faq).
---
# Workspaces
URL: https://platform.claude.com/docs/en/manage-claude/workspaces
# Workspaces
Organize API keys, manage team access, and control costs with workspaces.
---
Workspaces provide a way to organize your API usage within an organization. Use workspaces to separate different projects, environments, or teams while maintaining centralized billing and administration.
## How workspaces work
Every organization has a **Default Workspace** that cannot be renamed, archived, or deleted. When you create additional workspaces, you can assign API keys, members, and resource limits to each one.
Key characteristics:
- **Workspace identifiers** use the `wrkspc_` prefix (e.g., `wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ`)
- **Maximum 100 workspaces** per organization (archived workspaces don't count)
- **Default Workspace** has no ID and doesn't appear in list endpoints
- **API keys** are scoped to a single workspace and can only access resources within that workspace
## Workspace roles and permissions
Members can have different roles in each workspace, allowing fine-grained access control.
| Role | Permissions |
|------|-------------|
| Workspace User | Use the Workbench only |
| Workspace Limited Developer | Create and manage API keys, use the API. Cannot access session tracing views or download files. |
| Workspace Developer | Create and manage API keys, use the API |
| Workspace Admin | Full control over workspace settings and members |
| Workspace Billing | View workspace billing information (inherited from organization billing role) |
### Role inheritance
- **Organization admins** automatically receive Workspace Admin access to all workspaces
- **Organization billing members** automatically receive Workspace Billing access to all workspaces
- **Organization users and developers** must be explicitly added to each workspace
The Workspace Billing role cannot be manually assigned. It's inherited from having the organization billing role.
## Managing workspaces
Only organization admins can create workspaces. Organization users and developers must be added to workspaces by an admin.
### Using the Console
Create and manage workspaces in the [Claude Console](/settings/workspaces).
#### Create a workspace
In the Claude Console, go to **Settings > Workspaces**.
Click **Create workspace**.
Enter a workspace name and select a color for visual identification.
Click **Create** to finalize.
To switch between workspaces in the Console, use the **Workspaces** selector in the top-left corner.
#### Edit workspace details
To modify a workspace's name or color:
1. Select the workspace from the list
2. Click the ellipsis menu (**...**) and choose **Edit details**
3. Update the name or color and save your changes
The Default Workspace cannot be renamed or deleted.
#### Add members to a workspace
1. Navigate to the workspace's **Members** tab
2. Click **Add to Workspace**
3. Select an organization member and assign them a [workspace role](#workspace-roles-and-permissions)
4. Confirm the addition
To remove a member, click the trash icon next to their name.
Organization admins and billing members cannot be removed from workspaces while they hold those organization roles.
#### Set workspace limits
In the **Limits** tab, you can configure:
- **Rate limits**: Set limits per model tier for requests per minute, input tokens, or output tokens
- **Spend notifications**: Configure alerts when spending reaches certain thresholds
#### Archive a workspace
To archive a workspace, click the ellipsis menu (**...**) and select **Archive**. Archiving:
- Preserves historical data for reporting
- Deactivates the workspace and all associated API keys
- Cannot be undone
Archiving a workspace immediately revokes all API keys in that workspace. This action cannot be undone.
### Using the Admin API
Programmatically manage workspaces using the [Admin API](/docs/en/manage-claude/admin-api).
Admin API endpoints require an Admin API key (starting with `sk-ant-admin...`) that differs from standard API keys. Only organization members with the admin role can provision Admin API keys through the [Claude Console](/settings/admin-keys).
```bash cURL
# Create a workspace
curl --request POST "https://api.anthropic.com/v1/organizations/workspaces" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY" \
--data '{"name": "Production"}'
# List workspaces
curl "https://api.anthropic.com/v1/organizations/workspaces?limit=10&include_archived=false" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
# Archive a workspace
curl --request POST "https://api.anthropic.com/v1/organizations/workspaces/{workspace_id}/archive" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
For complete parameter details and response schemas, see the [Workspaces API reference](/docs/en/api/admin-api/workspaces/get-workspace).
### Managing workspace members
Add, update, or remove members from a workspace:
```bash cURL
# Add a member to a workspace
curl --request POST "https://api.anthropic.com/v1/organizations/workspaces/{workspace_id}/members" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY" \
--data '{
"user_id": "user_xxx",
"workspace_role": "workspace_developer"
}'
# Update a member's role
curl --request POST "https://api.anthropic.com/v1/organizations/workspaces/{workspace_id}/members/{user_id}" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY" \
--data '{"workspace_role": "workspace_admin"}'
# Remove a member from a workspace
curl --request DELETE "https://api.anthropic.com/v1/organizations/workspaces/{workspace_id}/members/{user_id}" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
For complete parameter details, see the [Workspace Members API reference](/docs/en/api/admin-api/workspace_members/get-workspace-member).
## API keys and resource scoping
API keys are scoped to a specific workspace. When you create an API key in a workspace, it can only access resources within that workspace.
Resources scoped to workspaces include:
- **Files** created through the [Files API](/docs/en/build-with-claude/files)
- **Message Batches** created through the [Batch API](/docs/en/build-with-claude/batch-processing)
- **Skills** created through the [Skills API](/docs/en/build-with-claude/skills-guide)
[Prompt caches](/docs/en/build-with-claude/prompt-caching) are also isolated per workspace on the Claude API, [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), and [Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry) (in beta). On Amazon Bedrock and Vertex AI, prompt caches are isolated per organization.
To retrieve your organization's workspace IDs, use the [List Workspaces](/docs/en/api/admin-api/workspaces/list-workspaces) endpoint, or find them in the [Claude Console](/settings/workspaces).
## Workspace limits
You can set custom spend and rate limits for each workspace to protect against overuse and ensure fair resource distribution.
### Setting workspace limits
Workspace limits can be set lower than (but not higher than) your organization's limits:
- **Spend limits**: Cap monthly spending for a workspace
- **Rate limits**: Limit requests per minute, input tokens per minute, or output tokens per minute
- You cannot set limits on the Default Workspace
- If not set, workspace limits match the organization's limits
- Organization-wide limits always apply, even if workspace limits add up to more
For detailed information on rate limits and how they work, see [Rate limits](/docs/en/api/rate-limits). You can also read your current organization and workspace rate limits programmatically with the [Rate Limits API](/docs/en/manage-claude/rate-limits-api).
## Usage and cost tracking
Track usage and costs by workspace using the [Usage and Cost API](/docs/en/manage-claude/usage-cost-api):
```bash cURL
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2025-01-01T00:00:00Z&\
ending_at=2025-01-08T00:00:00Z&\
workspace_ids[]=wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ&\
group_by[]=workspace_id&\
bucket_width=1d" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
Usage and costs attributed to the Default Workspace have a `null` value for `workspace_id`.
## Common use cases
### Environment separation
Create separate workspaces for development, staging, and production:
| Workspace | Purpose |
|-----------|---------|
| Development | Testing and experimentation with lower rate limits |
| Staging | Pre-production testing with production-like limits |
| Production | Live traffic with full rate limits and monitoring |
### Team or department isolation
Assign workspaces to different teams for cost allocation and access control:
- **Engineering team** with developer access
- **Data science team** with their own API keys
- **Support team** with limited access for customer tools
### Project-based organization
Create workspaces for specific projects or products to track usage and costs separately.
## Best practices
Consider how you'll organize workspaces before creating them. Think about billing, access control, and usage tracking needs.
Name workspaces clearly to indicate their purpose (e.g., "Production - Customer Chatbot", "Dev - Internal Tools").
Configure spend and rate limits to prevent unexpected costs and ensure fair resource distribution.
Review workspace membership periodically to ensure only appropriate users have access.
Use the [Usage and Cost API](/docs/en/manage-claude/usage-cost-api) to track workspace-level consumption.
## FAQ
Every organization has a "Default Workspace" that cannot be edited, renamed, or removed. This workspace has no ID and doesn't appear in workspace list endpoints. Usage attributed to the Default Workspace shows a `null` value for `workspace_id` in API responses.
Yes, you can have a maximum of 100 workspaces per organization. Archived workspaces do not count towards this limit.
Organization admins automatically get the Workspace Admin role in all workspaces. Organization billing members automatically get the Workspace Billing role. Organization users and developers must be manually added to each workspace.
Organization users and developers can be assigned Workspace Admin, Workspace Developer, Workspace Limited Developer, or Workspace User roles. The Workspace Billing role cannot be manually assigned; it's inherited from having the organization `billing` role.
Only organization billing members can have their workspace role upgraded to an admin role. Otherwise, organization admins and billing members cannot have their workspace roles changed or be removed from workspaces while they hold those organization roles. Their workspace access must be modified by changing their organization role first.
If an organization admin or billing member is demoted to user or developer, they lose access to all workspaces except ones where they were manually assigned roles. When users are promoted to admin or billing roles, they gain automatic access to all workspaces.
API keys persist in their current state as they are scoped to the organization and workspace, not to individual users.
## See also
- [Admin API](/docs/en/manage-claude/admin-api)
- [Admin API reference](/docs/en/api/admin)
- [Rate limits](/docs/en/api/rate-limits)
- [Usage and Cost API](/docs/en/manage-claude/usage-cost-api)
### Authentication
---
# Authentication
URL: https://platform.claude.com/docs/en/manage-claude/authentication
# Authentication
Authenticate to the Claude API with API keys or Workload Identity Federation.
---
The Claude API supports two ways to authenticate requests:
| Method | Credential | Best for |
|---|---|---|
| [API key](#api-keys) | Long-lived `sk-ant-api...` secret in the `x-api-key` header | Local development, prototyping, scripts, and single-tenant servers where you control secret storage |
| [Workload Identity Federation](#workload-identity-federation) | Short-lived bearer token exchanged from your identity provider's identity token | Production workloads on cloud platforms (AWS, Google Cloud, Azure), CI/CD pipelines, and Kubernetes, where you want to eliminate static secrets |
Both methods grant the same access to Claude API endpoints. Choose API keys to get started quickly, and move to Workload Identity Federation when your workload already has a platform-issued identity you can federate.
## API keys
API keys are static secrets that you generate in the Claude Console and pass on every request.
- **Create a key:** Go to [Settings → API keys](https://platform.claude.com/settings/keys) in the Claude Console. Use [workspaces](https://platform.claude.com/settings/workspaces) to scope keys by project or environment.
- **Send the key:** Set the `x-api-key` header on direct HTTP requests, or set the `ANTHROPIC_API_KEY` environment variable and the [client SDKs](/docs/en/api/client-sdks) pick it up automatically.
```http
POST /v1/messages
x-api-key: YOUR_API_KEY
anthropic-version: 2023-06-01
content-type: application/json
```
API keys have no expiry. Store them in a secrets manager, rotate them periodically, and revoke any key you suspect has leaked.
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello, Claude"}]
}'
```
```python Python hidelines={1..2}
from anthropic import Anthropic
client = Anthropic(api_key="my-anthropic-api-key")
# or, with ANTHROPIC_API_KEY set in the environment:
client = Anthropic()
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ apiKey: "my-anthropic-api-key" });
// or, with ANTHROPIC_API_KEY set in the environment:
// const client = new Anthropic();
```
```go Go hidelines={1..8,12..13}
package main
import (
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func main() {
client := anthropic.NewClient(
option.WithAPIKey("sk-ant-api03-..."), // defaults to os.LookupEnv("ANTHROPIC_API_KEY")
)
_ = client
}
```
```java Java nocheck
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
// Explicit
AnthropicClient client = AnthropicOkHttpClient.builder()
.apiKey("my-anthropic-api-key")
.build();
// From ANTHROPIC_API_KEY (or anthropic.apiKey system property)
AnthropicClient clientFromEnv = AnthropicOkHttpClient.fromEnv();
```
```csharp C#
using Anthropic;
AnthropicClient client = new() { ApiKey = "my-anthropic-api-key" };
// Or, with ANTHROPIC_API_KEY set in the environment:
// AnthropicClient client = new();
```
```php PHP hidelines={1..4}
## Workload Identity Federation
Workload Identity Federation (WIF) lets a workload authenticate with a short-lived identity token issued by an identity provider (IdP) you already trust, such as AWS IAM, Google Cloud, or any standards-compliant OIDC issuer (such as GitHub Actions, Kubernetes service accounts, SPIFFE, Microsoft Entra ID, or Okta). The workload exchanges its IdP-issued JWT at `POST /v1/oauth/token` for a short-lived Claude API access token, and the SDK refreshes that token automatically before it expires. There is no `sk-ant-api...` string to mint, distribute, or rotate.
Federation removes long-lived Claude API keys from your environment, which shrinks the blast radius of a leaked credential and lets you manage access with the same IdP controls you already use for cloud resources. It does not, on its own, guarantee end-to-end security: the trust chain is only as strong as your identity provider's configuration, and a long-lived secret one hop upstream (for example, a static cloud credential that can mint IdP tokens) can still undermine it. Pair federation with your provider's controls, such as IP allowlists, MFA, and audit logging.
To configure federation, you create three resources in the Claude Console (a service account, a federation issuer, and a federation rule) and then point your SDK at the rule. See [Workload Identity Federation](/docs/en/manage-claude/workload-identity-federation) for the full setup walkthrough.
## Next steps
Configure issuers, rules, and service accounts, then exchange tokens
Step-by-step guides for AWS, Google Cloud, Azure, GitHub Actions, Kubernetes, SPIFFE, and Okta
Environment variables, validation rules, profile configuration, and error reference
Python, TypeScript, Go, Java, C#, Ruby, PHP, and the CLI
---
# WIF reference
URL: https://platform.claude.com/docs/en/manage-claude/wif-reference
# WIF reference
Environment variables, validation rules, profile configuration, and error reference for Workload Identity Federation.
---
This page collects the configuration surfaces, validation constraints, and error mappings for [Workload Identity Federation](/docs/en/manage-claude/workload-identity-federation). For setup walkthroughs, see the [provider guides](/docs/en/manage-claude/workload-identity-federation#identity-providers).
## Token exchange request
`POST /v1/oauth/token` accepts a JSON body using the [RFC 7523](https://www.rfc-editor.org/rfc/rfc7523) `jwt-bearer` grant. The SDKs build this request for you from the [environment variables](#environment-variables) below; the cURL examples on each provider guide show the raw body.
| Field | Required | Description |
| :--- | :--- | :--- |
| `grant_type` | Yes | Always `urn:ietf:params:oauth:grant-type:jwt-bearer`. |
| `assertion` | Yes | The OIDC JWT issued by your identity provider. |
| `federation_rule_id` | Yes | Tagged ID (`fdrl_...`) of the federation rule to evaluate. |
| `organization_id` | Yes | UUID of your Anthropic organization. |
| `service_account_id` | Yes | Tagged ID (`svac_...`) of the target service account. |
| `workspace_id` | Conditional | Tagged ID (`wrkspc_...`) of the workspace to scope the minted token to, or the literal `default` for the organization's default workspace. Required when the rule is enabled for more than one workspace. When omitted, the server selects the rule's sole enabled workspace. |
## Token exchange response
`POST /v1/oauth/token` returns a standard OAuth 2.0 token response ([RFC 6749 §5.1](https://www.rfc-editor.org/rfc/rfc6749#section-5.1)):
| Field | Type | Description |
| --- | --- | --- |
| `access_token` | string | The short-lived Anthropic token, prefixed `sk-ant-oat01-...`. Pass it as `Authorization: Bearer `. |
| `token_type` | string | Always `Bearer`. |
| `expires_in` | integer | Seconds until the token expires. |
| `scope` | string | The OAuth scope granted by the matched rule. |
## Environment variables
The SDK reads these variables to perform a federated token exchange with no constructor arguments.
| Variable | Required | Description | Example |
| :--- | :--- | :--- | :--- |
| `ANTHROPIC_FEDERATION_RULE_ID` | Yes | Tagged ID of the federation rule to evaluate. | `fdrl_...` |
| `ANTHROPIC_ORGANIZATION_ID` | Yes | UUID of your Anthropic organization. Find it in the Claude Console under **Settings > Organization**. | `00000000-0000-0000-0000-000000000000` |
| `ANTHROPIC_IDENTITY_TOKEN_FILE` | One of `_TOKEN_FILE` or `_TOKEN` | Filesystem path to the JWT issued by your identity provider (IdP). The SDK re-reads this file on every exchange so that projected tokens which rotate on disk are always current. | `/var/run/secrets/anthropic.com/token` |
| `ANTHROPIC_IDENTITY_TOKEN` | One of `_TOKEN_FILE` or `_TOKEN` | The literal JWT as a string. Use when your platform injects the token as an environment variable rather than a file. | `eyJhbGciOiJSUzI1NiIs...` |
| `ANTHROPIC_SERVICE_ACCOUNT_ID` | Yes | Tagged ID of the target Anthropic service account that the issued access token acts as. | `svac_...` |
| `ANTHROPIC_WORKSPACE_ID` | Conditional | Tagged ID of the workspace to scope the minted token to, or the literal `default`. Required when the federation rule is enabled for more than one workspace; optional when the rule is bound to a single workspace. The minted token is scoped to this workspace at exchange time, so switching workspaces requires a new exchange. | `wrkspc_...` |
| `ANTHROPIC_PROFILE` | No | Name of a [configuration profile](#profile-configuration-file) to load. Takes precedence over the federation environment variables in this table. | `staging-profile` |
The direct environment-variable federation path activates only when `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID`, and one of `ANTHROPIC_IDENTITY_TOKEN_FILE` or `ANTHROPIC_IDENTITY_TOKEN` are all set. `ANTHROPIC_WORKSPACE_ID` is read alongside but does not gate activation.
A variable that is set to an empty string still occupies its slot in the credential precedence chain. If `ANTHROPIC_API_KEY=""` is exported, the SDK selects the API-key path with an empty key rather than falling through to federation. Unset unused credential variables rather than blanking them.
### Credential precedence
The SDK resolves credentials in this order. The first source that yields a credential wins.
| Order | Source | Notes |
| :--- | :--- | :--- |
| 1 | Constructor argument (`api_key=`, `auth_token=`, `credentials=`) | Always overrides everything else. |
| 2 | `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` | Shadows federation entirely. Unset these when migrating from API keys. |
| 3 | `ANTHROPIC_PROFILE` | Loads `/configs/.json`. A missing named profile is an error, not a fall-through. |
| 4 | Federation environment variables | `ANTHROPIC_FEDERATION_RULE_ID` + `ANTHROPIC_ORGANIZATION_ID` + `ANTHROPIC_SERVICE_ACCOUNT_ID` + `ANTHROPIC_IDENTITY_TOKEN[_FILE]`. |
| 5 | Active profile | Resolved via `/active_config`, falling back to a profile named `default`. |
When a profile is loaded, environment variables fill any fields the profile omits but never override fields the profile sets explicitly. For example, `ANTHROPIC_WORKSPACE_ID` fills `workspace_id` only when the active profile does not set it.
## Profile configuration file
A profile is a named configuration file that the SDK and the `ant` CLI both read. Profiles let you ship federation parameters with your container image or switch between environments without changing code.
### Configuration directory
The SDK locates the configuration directory in this order:
1. `$ANTHROPIC_CONFIG_DIR`
2. `~/.config/anthropic` on Linux and macOS
3. `%APPDATA%\Anthropic` on Windows
### Active profile
The active profile name resolves in this order:
1. `$ANTHROPIC_PROFILE`
2. The contents of `/active_config` (a one-line file written by `ant profile activate `)
3. The literal name `default`
Claude Code and the Claude Agent SDK honor this same resolution order, so a federation profile configured here also authenticates those tools without additional setup.
### File layout
| Path | Contents | Sensitivity |
| :--- | :--- | :--- |
| `/configs/.json` | `version`, the `authentication` block, `organization_id`, `workspace_id`, and `base_url`. | Non-secret. Safe to commit or bake into an image. |
| `/credentials/.json` | `version`, the cached `access_token`, `expires_at`, and (for interactive login) `refresh_token`. | Secret. Written by the SDK with mode `0600`. |
Both the config file and the credentials file carry a top-level string `version` field in `major.minor` format (currently `"1.0"`). The SDK writes this field automatically so future releases can detect and migrate older formats; omit it when authoring a config by hand and the SDK treats the file as the current version.
### Federation profile example
```json configs/production.json
{
"version": "1.0",
"authentication": {
"type": "oidc_federation",
"federation_rule_id": "fdrl_...",
"service_account_id": "svac_...",
"identity_token": {
"source": "file",
"path": "/var/run/secrets/anthropic.com/token"
}
},
"organization_id": "00000000-0000-0000-0000-000000000000",
"workspace_id": "wrkspc_...",
"base_url": "https://api.anthropic.com"
}
```
If `authentication.identity_token` is omitted, the SDK falls back to `ANTHROPIC_IDENTITY_TOKEN_FILE` or `ANTHROPIC_IDENTITY_TOKEN` from the environment.
## OAuth scopes
The `oauth_scope` you set on a federation rule determines which Claude API endpoints the minted access token can call.
| Scope | Grants access to |
| :--- | :--- |
| `workspace:developer` | All non-administrative Claude API endpoints in the rule's workspace: [Messages](/docs/en/api/messages) (including streaming and token counting), [Models](/docs/en/api/models-list), [Managed Agents](/docs/en/managed-agents/overview) and their sessions, [Files](/docs/en/build-with-claude/files), and [Skills](/docs/en/build-with-claude/skills-guide). This matches the access an API key issued for the same workspace has. |
A request to an endpoint outside the token's scope returns HTTP 403. Finer-grained scopes (per resource, or read versus write) are not currently available.
## Validation rules
Anthropic enforces these constraints when you create or update issuers and rules, and when verifying an incoming JWT at exchange time.
### Resource fields
| Field | Constraint |
| :--- | :--- |
| Issuer, rule, and service account `name` | Must match `^[a-z0-9-]+$`, length 1 to 255 characters. |
| `workspace_id` | Optional. The workspace (`wrkspc_...`) whose quota, billing, and rate limits apply to tokens minted under this rule. Must be a workspace in the same organization, and the target service account must be a member of that workspace. May be omitted for rules that are configured for only one workspace. |
| `token_lifetime_seconds` | Integer between `60` and `86400` (1 minute to 24 hours). Default `3600`. Values outside this range are rejected at request time. See [Token lifetime and refresh](/docs/en/manage-claude/workload-identity-federation#token-lifetime-and-refresh). |
### URL fields
The `issuer_url`, `jwks.discovery_base`, and `jwks.url` fields are validated:
| Constraint | Detail |
| :--- | :--- |
| Scheme | Must be `https`. |
| Port | Must be `443` (explicit or default). |
| Host | Must be a public DNS host name for your OIDC provider. Must resolve to public IP addresses; IP literals are not accepted. |
URL validation failures return `400 invalid_request_error` with the field name as a prefix on the error message (for example, `issuer_url: url must use https scheme`).
URL constraints apply only to URLs that Anthropic dials. In `explicit_url` and `inline` JWKS modes, and in `discovery` mode when `jwks.discovery_base` is set, the `issuer_url` is compared against the JWT `iss` claim as a string and is never fetched, so it may reference an internal hostname or non-standard port.
### JWT verification
| Constraint | Detail |
| :--- | :--- |
| Maximum size | The `assertion` JWT must be at most 16 KiB. |
| Signing algorithm | Only asymmetric algorithms (RSA and ECDSA families: ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512) are accepted. HMAC (`HS256`, `HS384`, `HS512`) and `none` are rejected. |
| Key ID | The JWT header must carry a `kid` that matches a key in the issuer's JWKS. Tokens without `kid` are rejected. |
| Required claims | `sub` must be present. `iat` must be present and not in the future. `exp` must be present and in the future. |
| Maximum lifetime | The token's lifetime (`exp` minus `iat`) must not exceed the issuer's configured maximum (1 hour by default, configurable for each issuer in the Claude Console). |
| Clock skew | A 30-second leeway is applied to `exp`, `nbf`, and `iat`. |
## Rule matching semantics
A federation rule's `match` block determines whether an incoming JWT is accepted. All populated fields are evaluated with AND semantics: the JWT must satisfy every populated matcher. At least one of `subject_prefix`, `claims`, or `condition` must be set; a `match` block that contains only `audience` (or no matchers at all) is rejected. This guards against rules that would accept every token from an issuer.
| Matcher | Type | Semantics |
| :--- | :--- | :--- |
| `subject_prefix` | string | Exact match against the JWT `sub` claim. A trailing `*` makes it a prefix match (the `sub` value must begin with the characters before the `*`). Case-sensitive. |
| `audience` | string | The JWT `aud` claim must contain this exact string. When `aud` is an array, any element matching exactly satisfies the check. |
| `claims` | map\ | Each key is a top-level claim name and each value is the required exact string value. For nested, numeric, boolean, or complex claims like lists and maps, use `condition` with a CEL expression instead. |
| `condition` | string (CEL) | A [CEL](https://cel.dev/) expression that must evaluate to `true`. |
### CEL evaluation environment
The `condition` expression has access to a single variable:
| Variable | Type | Contents |
| :--- | :--- | :--- |
| `claims` | map | The full decoded JWT claim set. Nested objects are accessible as nested maps. |
Example:
```text
claims.sub.startsWith("repo:acme-corp/") && claims.ref in ["refs/heads/main", "refs/heads/release"]
```
CEL conditions are security boundaries. An expression that evaluates to `true` for more inputs than intended grants broader access than intended. Prefer the static matchers when they express your constraint.
## Errors
### Token exchange errors
`POST /v1/oauth/token` returns errors in the standard [API error shape](/docs/en/api/errors). The SDK wraps exchange failures in a typed `FederationExchangeError` (or language equivalent) that exposes the HTTP status, the response body, and the `request_id`.
| Status | Error | Cause | Resolution |
| :--- | :--- | :--- | :--- |
| 400 | `invalid_request` | `federation_rule_id` is malformed or a required request field is missing. | Verify the `fdrl_` ID and that the request body includes all required fields. |
| 400 | `invalid_request` | `workspace_id_required`: the federation rule is enabled for more than one workspace and the request omits `workspace_id`. | Set `ANTHROPIC_WORKSPACE_ID` (or the `workspace_id` body field on a raw request) to the `wrkspc_...` ID you want the token scoped to. See [Token exchange request](#token-exchange-request). |
| 400 | `invalid_grant` | The JWT `iss` claim does not equal the registered `issuer_url` exactly. | Compare byte-for-byte, including trailing slashes and scheme: `jq -rR 'split(".")[1] \| gsub("-";"+") \| gsub("_";"/") \| @base64d \| fromjson \| .iss' <<< "$JWT"`. |
| 400 | `invalid_grant` | JWKS fetch failed, JWKS is stale, or the JWT was signed with a key not in the JWKS. | For `inline` mode, update the issuer with the rotated keys. For `discovery` and `explicit_url`, confirm the JWKS endpoint is reachable on port 443; if the issuer recently rotated its signing key, see [Key rotation and caching](#key-rotation-and-caching). |
| 400 | `invalid_grant` | The JWT `exp` claim is in the past (beyond the 30-second skew window). | Confirm your identity provider is projecting a fresh token and the SDK is re-reading the token file. |
| 400 | `invalid_grant` | The JWT was verified but its claims do not satisfy the rule's `match` block. | Decode the JWT and compare each claim against the rule. `subject_prefix` is case-sensitive. `audience` requires an exact element match. |
| 400 | `invalid_grant` | The `federation_rule_id` does not exist, is archived, or the JWT is not authorized for it (consolidated to prevent enumeration). | Confirm the rule ID in the Claude Console and that the rule has not been archived. |
All `invalid_grant` failures return HTTP 400; the specific cause is logged server-side only and not exposed in the response.
### Common SDK-side failures
| Symptom | Cause | Resolution |
| :--- | :--- | :--- |
| SDK reports "no credentials" instead of exchanging | One of `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID`, or `ANTHROPIC_IDENTITY_TOKEN[_FILE]` is unset and no profile is active. | Set all four variables, or configure a profile. |
| SDK authenticates with an API key instead of federating | `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` is set and wins precedence. | Unset the key or token variable. |
| `FileNotFoundError` on first request | The path in `ANTHROPIC_IDENTITY_TOKEN_FILE` does not exist. The SDK opens the file lazily at exchange time. | Confirm the projected-token volume is mounted and the path matches. |
| Token exchange succeeds but a Claude API request returns 403 | The minted token's scope does not grant access to that endpoint. | Check the rule's `oauth_scope` against [OAuth scopes](#oauth-scopes). |
| Authentication fails with empty credential | A credential environment variable is exported but set to an empty string. Empty values still win their precedence slot. | Unset the variable with `unset VAR` rather than `VAR=""`. |
## Troubleshoot a failed exchange
A `400 invalid_grant` response is intentionally opaque; the specific cause is logged server-side only.
Start with the [authentication history page](https://platform.claude.com/settings/workload-identity-federation?tab=history) in the Claude Console. Recent exchange attempts surface the issuer and rule that were evaluated, the JWT claims that were inspected, and which validation step failed, which usually short-circuits the following checks.
If you still need to debug from the JWT itself, work through these checks in order:
Decode the assertion you sent so you can compare each claim against your issuer and rule configuration:
```bash cURL nocheck
jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson' <<< "$JWT"
```
The decoded `iss` claim must equal the registered `issuer_url` byte for byte, including scheme, port, and any trailing slash. A mismatch on a single character fails verification.
The decoded `aud` claim must contain the rule's `audience` value as an exact match. When `aud` is an array, one element must match exactly.
Compare `sub` against the rule's `subject_prefix` (case-sensitive; a trailing `*` is a prefix match, anything else is exact). Compare every key in the rule's `claims` map against the same-named top-level claim.
`exp` must be in the future and `nbf`/`iat` must be in the past, within the 30-second skew window. If the workload host's clock has drifted, an otherwise valid token is rejected.
For `discovery` mode, fetch `/.well-known/openid-configuration` over public HTTPS on port 443 and confirm `jwks_uri` resolves. For `explicit_url`, fetch the JWKS URL directly. For `inline`, confirm the issuer's signing key has not rotated since you registered the keys.
If the issuer rotated its signing key and immediately started signing with it, exchanges can fail for up to a minute while Anthropic's JWKS cache refreshes. See [Key rotation and caching](#key-rotation-and-caching).
## JWKS source modes
When you register a federation issuer, the `jwks` field controls how Anthropic obtains the public keys used to verify JWT signatures from that issuer. It is a discriminated union keyed on `type`:
| `jwks.type` | `jwks` shape | Behavior | Use when |
| :--- | :--- | :--- | :--- |
| `discovery` (default) | `{ "type": "discovery", "discovery_base": "https://..." }` (`discovery_base` is optional; set it when the discovery URL differs from `issuer_url`) | Anthropic fetches `/.well-known/openid-configuration`, reads `jwks_uri` from the discovery document, and fetches the JWKS from there. | Your IdP serves a standard OIDC discovery document on the public internet. Most managed providers (EKS, GKE, Cloud Run, GitHub Actions, Entra ID) support this. |
| `explicit_url` | `{ "type": "explicit_url", "url": "https://..." }` | Anthropic fetches the JWKS directly from `url`. The `issuer_url` is used only for string comparison against the JWT `iss` claim and is never dialed. | Your IdP does not serve a discovery document, or discovery is internal-only but the JWKS is publicly reachable. |
| `inline` | `{ "type": "inline", "keys": [...] }` | You supply the array of JWK objects inline (the `keys` array from the JWKS document, not the wrapper object). Anthropic makes no outbound request. The `issuer_url` is used only for `iss` comparison. | Air-gapped environments, self-managed Kubernetes clusters with cluster-internal issuer URLs, or when you want explicit control over key rotation. |
The discriminated union makes the companion fields mutually exclusive by construction. Both `discovery` and `explicit_url` also accept an optional `ca_cert_pem` string for issuers that serve TLS from a private CA.
### Key rotation and caching
In `discovery` and `explicit_url` modes, Anthropic caches the fetched JWKS. If your identity provider publishes a new signing key and immediately starts signing tokens with it, exchanges that present those tokens may fail with a signature error for up to one minute while the cache refreshes.
To avoid this window, publish a new signing key in the JWKS at least 15 minutes before your identity provider starts signing tokens with it, and keep the superseded key in the JWKS until tokens it signed have expired. Managed identity providers typically follow this discipline on their own. If you operate your own issuer (a self-managed Kubernetes cluster, a SPIRE OIDC discovery provider, or an Okta custom authorization server with a configured rotation cadence), confirm that your rotation policy publishes new keys ahead of first use.
In `inline` mode there is no automatic key refresh. When your identity provider rotates its signing keys, you must update the issuer configuration with the new JWKS or all token exchanges will fail signature verification.
---
# Workload Identity Federation
URL: https://platform.claude.com/docs/en/manage-claude/workload-identity-federation
# Workload Identity Federation
Authenticate workloads to the Claude API with short-lived identity tokens from your own identity provider instead of long-lived static API keys.
---
Workload Identity Federation (WIF) lets your workloads authenticate to the Claude API using short-lived OpenID Connect (OIDC) tokens issued by an identity provider (IdP) you already operate, such as AWS IAM, Google Cloud, or any standards-compliant OIDC issuer (such as GitHub Actions, Kubernetes service accounts, SPIFFE, Microsoft Entra ID, or Okta), instead of long-lived `sk-ant-...` API keys.
Your workload presents a signed JWT from your identity provider. Anthropic validates it against trust rules you configure in the Claude Console and returns a short-lived Anthropic access token bound to a service account in your organization. There are no static secrets to mint, store in CI, rotate, or leak.
Workload Identity Federation strengthens your security posture by removing static credentials from Anthropic's surface and replacing them with tokens that expire in minutes rather than never. It is not a complete security story on its own: federated authentication is only as strong as the upstream identity provider that signs the JWT. Pair Workload Identity Federation with the controls your IdP already supports (workload identity binding, conditional access, audit logging) for defense in depth.
## How it works
1. **Your IdP issues a JWT to the workload.** On most platforms this is ambient: a Kubernetes projected service-account token, the Google Cloud metadata server, Azure IMDS, or the GitHub Actions OIDC endpoint. The JWT's `iss` claim identifies the provider, and its `sub` and other claims identify the specific workload.
2. **The SDK exchanges the JWT for an Anthropic access token.** Given the federation environment variables (or a profile) and the JWT (typically read from a file), the SDK posts the JWT to `POST /v1/oauth/token` using the [RFC 7523](https://www.rfc-editor.org/rfc/rfc7523) `jwt-bearer` grant. Anthropic verifies the signature against the JWKS you registered for the issuer, checks the standard `exp`/`nbf`/`iat` claims, and matches the JWT's claims against the federation rule you specify. The response is a standard OAuth 2.0 token response (`access_token`, `token_type`, `expires_in`, `scope`) with a short-lived `sk-ant-oat01-...` token that acts on behalf of the service account targeted by the matched rule.
3. **The SDK sends the token on every request and refreshes it before it expires.** Your application code constructs the client with no `api_key` and calls the API as usual. The SDK re-runs the exchange before the token expires.
## Concepts
You configure three resources in the Claude Console before any workload can federate. Together they express "tokens signed by issuer X, with claims that look like Y, may act as service account Z."
### Service accounts
A **service account** (`svac_...`) is a named, non-human identity inside your Anthropic organization. It is the principal that a federated token acts as. Service accounts live at the organization level and become active in a workspace when you add them to that workspace's members. At exchange time, Anthropic checks that the federation rule's workspace matches one of the service account's workspace memberships; the minted token then follows that workspace's rate limits and usage attribution, the same as an API key. Unlike a human user, a service account has no email, no password, and no Console login.
The key distinction from an API key: an API key *is* a credential, while a service account *has* credentials minted for it on demand. You can audit which workloads acted as which service account.
### Federation issuers
A **federation issuer** (`fdis_...`) registers an OIDC identity provider with your organization. Registering an issuer tells Anthropic "JWTs signed by this provider may assert workload identity for my org."
An issuer has two pieces of configuration:
- **Issuer URL:** The exact `iss` claim value that appears in the provider's JWTs, for example `https://token.actions.githubusercontent.com` or `https://oidc.eks.us-west-2.amazonaws.com/id/EXAMPLE`.
- **JWKS source:** How Anthropic fetches the public keys to verify JWT signatures. Use `discovery` (the default) for any provider that serves `/.well-known/openid-configuration` at its issuer URL. Use `explicit_url` to point at a JWKS endpoint directly, or `inline` to upload the key set for issuers that are not reachable from the public internet (for example, a private Kubernetes cluster).
Issuer and JWKS URLs must be `https`, on port 443, and use a public DNS host name that resolves to public IP addresses; IP literals are not accepted. These constraints apply only to URLs Anthropic fetches; in `explicit_url` and `inline` modes the `issuer_url` is compared as a string and may reference an internal hostname.
You typically register one issuer per environment: your production EKS cluster, your staging cluster, and GitHub Actions are three separate issuers.
### Federation rules
A **federation rule** (`fdrl_...`) is the bridge between an issuer and a service account: "when a JWT from issuer X has claims that look like Y, mint a token for service account Z with scope S."
A rule defines match conditions, a target, and the authorization scope and token lifetime that apply when the rule matches:
- **Match:** The conditions an incoming JWT must satisfy. You can match on a `subject_prefix` (for example, `system:serviceaccount:prod:worker`, or with a trailing `*` for a prefix match), an exact `audience`, a map of exact claim values, a [CEL](https://cel.dev/) `condition` expression for complex logic, or any combination. At least one of `subject_prefix`, `claims`, or `condition` must be set, and all configured matchers must pass for the JWT to be accepted.
- **Target:** The service account the matched JWT maps to.
- **Authorization:** The OAuth `scope` granted on the minted token. At launch this is always `workspace:developer`, which grants the same access as an API key issued for that workspace (see [OAuth scopes](/docs/en/manage-claude/wif-reference#oauth-scopes)). The rule also sets `token_lifetime_seconds` (60 to 86400, default 3600).
A single issuer can have many rules: one per team, namespace, or permission level. Rules are evaluated by ID: the client specifies which rule to use in the exchange request, and Anthropic verifies the JWT satisfies that rule's match criteria. There is no implicit rule search.
## Set up federation
You need admin access to your Anthropic organization, an OIDC-capable identity provider with a reachable JWKS endpoint (or a JWKS document you can paste, for air-gapped clusters), and a workload that can obtain an identity token from that provider.
In the Claude Console, go to **Settings → Workload identity**.
On the **Issuers** tab, select **Create issuer**.
| Field | Value |
| --- | --- |
| Name | A label for your reference, such as `prod-eks` or `gha`. Lowercase letters, digits, and hyphens. |
| Issuer URL | The exact `iss` claim your IdP puts in its JWTs. If you are unsure, decode a sample token: jq -rR 'split(".")[1] \| gsub("-";"+") \| gsub("_";"/") \| @base64d \| fromjson \| .iss' token |
| JWKS source | `discovery` for most managed IdPs. Choose `explicit_url` or `inline` only if discovery is not available. |
| Discovery base / JWKS URL / Inline keys | Mode-specific. Leave blank for discovery when the IdP serves `.well-known` at the issuer URL. |
| CA cert PEM | Only if your IdP serves TLS from a private CA. Most managed IdPs use public CAs, so leave this blank. |
The Console includes presets for AWS and Google Cloud that pre-fill the issuer URL pattern and a sensible default rule, plus a generic OIDC option for any other standards-compliant provider (such as GitHub Actions, Kubernetes service-account issuers, Microsoft Entra ID, or Okta).
Go to **Settings → Service accounts → Create service account**. Provide a name (for example, `inference-worker` or `ci-deploy`) and an optional description.
This is the identity your minted tokens act as. Add the service account to each workspace it should act in from that workspace's **Members** page. The federation rule in the next step targets one workspace, and the minted token is scoped to that workspace's rate limits and usage attribution. Note the service account ID (`svac_...`).
Back on the **Workload identity** page, open the **Federation rules** tab and select **Create rule**.
| Section | Value |
| --- | --- |
| Basic info | A name and optional description. Select the issuer you registered in step 1. |
| Match | Choose **Static** for subject prefix, audience, and exact-claim matching, or **CEL** for an expression. Be as specific as your IdP's claims allow: a rule that matches too broadly grants more access than you intend. |
| Target | Select the service account you created in step 2. |
| Authorization | OAuth scope (`workspace:developer` at launch; see [OAuth scopes](/docs/en/manage-claude/wif-reference#oauth-scopes)) and token lifetime in seconds. |
Note the rule's ID (`fdrl_...`). Your workload passes this ID in every token-exchange request.
## Authenticate from your workload
With federation configured, your workload exchanges its IdP-issued JWT for an Anthropic token at runtime. The SDKs handle the exchange and refresh loop for you. The cURL tab shows the underlying HTTP exchange for shell scripts, debugging, or languages without SDK support.
### Construct the SDK client
You can construct the client with explicit credentials or with no arguments. With no arguments, the SDK resolves credentials from environment variables or the active profile, as described under [Credential precedence](#credential-precedence). The zero-argument form is the recommended pattern for production workloads: ship the same container image everywhere and inject `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID`, `ANTHROPIC_WORKSPACE_ID`, and `ANTHROPIC_IDENTITY_TOKEN_FILE` per environment.
```bash cURL nocheck
# 1. Acquire your IdP's JWT (platform-specific; see the per-provider guides).
JWT=$(cat /var/run/secrets/anthropic.com/token)
# 2. Exchange it for a short-lived Anthropic access token.
RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
--data @- <messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello, Claude']],
);
echo $message->content[0]->text . PHP_EOL;
```
```ruby Ruby nocheck hidelines={1..2}
require "anthropic"
# Reads ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
# ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID, and ANTHROPIC_IDENTITY_TOKEN_FILE
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{role: "user", content: "Hello, Claude"}]
)
puts message.content.first.text
```
The token-exchange response follows [RFC 6749 §5.1](https://www.rfc-editor.org/rfc/rfc6749#section-5.1). See [Token exchange response](/docs/en/manage-claude/wif-reference#token-exchange-response) for the field reference.
## Credential precedence
Every SDK resolves credentials in the same five-tier order: constructor arguments, then `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`, then an explicit `ANTHROPIC_PROFILE`, then the federation environment variables, then the implicit active profile. The first source that yields a credential wins.
`ANTHROPIC_API_KEY` sits above the federation tiers, so a leftover key in the
environment silently shadows federation. When migrating a workload from API
keys to Workload Identity Federation, confirm `ANTHROPIC_API_KEY` is unset everywhere that workload
runs (container env, CI secrets, shell profiles). The CLI's [`ant auth status`](/docs/en/api/sdks/cli#check-authentication-status)
command reports which source won.
For the full precedence table, the per-tier semantics, and the profile file schema, see [Credential precedence](/docs/en/manage-claude/wif-reference#credential-precedence) in the WIF reference.
## Migrate from API keys
To switch an existing workload from a static API key to federation without downtime:
1. **Configure federation in parallel.** Complete the [setup walkthrough](#set-up-federation) and confirm the federation rule matches your workload's token. Leave the existing `ANTHROPIC_API_KEY` in place for now.
2. **Smoke-test which credential wins.** Run `ant auth status` from inside the workload (or inspect SDK debug logs). Because `ANTHROPIC_API_KEY` sits above the federation tiers in the precedence chain, the API key still wins at this stage.
3. **Unset `ANTHROPIC_API_KEY` everywhere it is injected.** Remove it from CI secrets, container environment, and shell profiles (see the preceding warning). Re-run `ant auth status` and confirm the federation source is now selected.
4. **Revoke the API key.** Once the workload is running on the federated token, delete the key in the Claude Console under **Settings → API keys**.
## Token lifetime and refresh
The minted Anthropic token's lifetime is the lesser of the rule's `token_lifetime_seconds` (default 3600 seconds) and twice the remaining lifetime of the IdP JWT you presented, with a 60-second floor. The second bound prevents an Anthropic token from outliving the upstream identity it was derived from by more than a small margin.
The SDKs cache the token and refresh it on a two-tier schedule modeled on `botocore`:
- **Advisory refresh** at expiry minus 120 seconds. The SDK attempts a new exchange. If the token endpoint is unreachable, the SDK continues serving the cached token, which is still valid for roughly 90 more seconds.
- **Mandatory refresh** at expiry minus 30 seconds. A failed exchange at this point raises an error. The cached token is too close to expiry to be safe.
Because the SDK re-reads `ANTHROPIC_IDENTITY_TOKEN_FILE` on every exchange, it transparently picks up rotated projected tokens (Kubernetes service-account tokens, for example, rotate well before their `exp`).
## Identity providers
Each guide covers where the JWT comes from on that platform, what its claims look like, and the issuer and rule configuration to register.
STS web identity tokens, or EKS IRSA projected tokens.
Google-signed identity tokens via the metadata server.
Managed Identity (IMDS) and Entra Workload ID on AKS.
Keyless CI authentication with the Actions OIDC token.
Self-managed and on-premises clusters using projected service-account tokens.
Workloads with SPIFFE JWT-SVIDs from SPIRE or another conformant issuer.
Okta service applications using client-credentials flow.
## See also
- [WIF reference](/docs/en/manage-claude/wif-reference): environment variables, profile file schema, validation rules, and error codes
- [Authentication](/docs/en/manage-claude/authentication): all authentication options across the Anthropic SDKs
### Authentication > Identity providers
---
# Use WIF with AWS
URL: https://platform.claude.com/docs/en/manage-claude/wif-providers/aws
# Use WIF with AWS
Authenticate AWS workloads on Lambda, EC2, ECS, or EKS to the Claude API with Workload Identity Federation and STS-issued identity tokens.
---
AWS workloads can authenticate to the Claude API without static API keys by exchanging an AWS-signed OIDC identity token. The recommended path calls the AWS STS [`GetWebIdentityToken`](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetWebIdentityToken.html) API, which works anywhere the workload has AWS credentials: Lambda, EC2, ECS, and EKS. EKS workloads can alternatively use the [Kubernetes projected-token path](#use-eks-projected-service-account-tokens), which has fewer configuration steps but only works inside a pod.
This guide shows both paths. For the underlying concepts (service accounts, federation issuers, and federation rules), see [Workload Identity Federation](/docs/en/manage-claude/workload-identity-federation).
## Prerequisites
- Familiarity with [WIF concepts](/docs/en/manage-claude/workload-identity-federation#concepts): service accounts, federation issuers, and federation rules.
- An AWS workload (EKS pod, ECS task, Lambda function, or EC2 instance) with an attached IAM role.
- The `aws` CLI or an AWS SDK available in the workload.
- Permission to create service accounts, federation issuers, and federation rules in the Claude Console for your Anthropic organization.
## Use STS web identity tokens (recommended)
The AWS STS `GetWebIdentityToken` API returns an OIDC token signed by AWS that asserts the caller's IAM identity. Because it uses the workload's ambient AWS credentials, the same integration covers Lambda, EC2, ECS, and EKS.
### Configure AWS
This is an account-level flag, off by default. In the AWS console, open **IAM**, choose **Account settings**, and enable **Outbound web identity federation**. To enable it programmatically:
```bash nocheck
python3 -c "import boto3; boto3.client('iam').enable_outbound_web_identity_federation()"
```
If this is not enabled, calls to `GetWebIdentityToken` fail with `OutboundWebIdentityFederationDisabledException`.
Attach this policy to the IAM role that your Lambda function, EC2 instance, or ECS task runs as:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["sts:GetWebIdentityToken"],
"Resource": "*"
}
]
}
```
After enabling outbound federation, the **IAM > Account settings** page shows a **Get Token Issuer URL** field with a value of the form `https://.tokens.sts.global.api.aws`. This URL is unique to your AWS account; copy it for the next step. To retrieve it programmatically:
```bash nocheck
python3 -c "import boto3; print(boto3.client('iam').get_outbound_web_identity_federation_info())"
```
### Configure Anthropic
Follow the [setup walkthrough](/docs/en/manage-claude/workload-identity-federation#set-up-federation) to register a federation issuer, create an Anthropic service account, and create a federation rule in the Claude Console. Use these STS-specific values.
**Federation issuer:** Register the per-account STS issuer URL you copied in the prior step. It exposes a public JWKS endpoint, so use discovery mode.
```json
{
"name": "aws-sts",
"issuer_url": "https://.tokens.sts.global.api.aws",
"jwks_source": "discovery"
}
```
**Federation rule:** Match the audience you pass to `GetWebIdentityToken` and the calling role's IAM role ARN in the `sub` claim. The `sub` value is the IAM role ARN of the workload that called the API, in the form `arn:aws:iam:::role/`. The token also carries an `https://sts.amazonaws.com/` claim with `aws_account`, `org_id`, `principal_id`, and any `request_tags` you passed; you can match on those with the rule's `claims` map or a CEL `condition` for finer control.
```json
{
"name": "prod-inference",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "arn:aws:iam::123456789012:role/inference-worker",
"audience": "https://api.anthropic.com"
},
"target": { "type": "service_account", "service_account_id": "svac_..." },
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}
```
Be as specific as the workload allows. Match the exact role ARN, and only broaden `subject_prefix` (for example, to `arn:aws:iam::123456789012:role/*`) if multiple IAM roles should map to the same Anthropic service account.
### Acquire and use the token
Call `GetWebIdentityToken` with `https://api.anthropic.com` as the audience, then pass the result to the SDK's federation credentials. The token provider is a callable, so the SDK re-invokes STS on each refresh.
`GetWebIdentityToken` is available only on regional STS endpoints. If you receive `'STS' object has no attribute 'get_web_identity_token'` or a similar error, pin your STS client to a region (for example, `boto3.client("sts", region_name="us-east-1")`) and ensure your AWS SDK is recent enough to include the API.
```bash cURL nocheck
JWT=$(aws sts get-web-identity-token \
--region us-east-1 \
--audience "https://api.anthropic.com" \
--signing-algorithm RS256 \
--duration-seconds 900 \
--query WebIdentityToken --output text)
RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
--data @- < str:
sts = boto3.client("sts", region_name="us-east-1")
resp = sts.get_web_identity_token(
Audience=["https://api.anthropic.com"],
SigningAlgorithm="RS256",
DurationSeconds=900,
)
return resp["WebIdentityToken"]
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=get_sts_web_identity_token,
federation_rule_id=os.environ["ANTHROPIC_FEDERATION_RULE_ID"],
organization_id=os.environ["ANTHROPIC_ORGANIZATION_ID"],
service_account_id=os.environ["ANTHROPIC_SERVICE_ACCOUNT_ID"],
workspace_id=os.environ.get("ANTHROPIC_WORKSPACE_ID"),
),
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello from AWS"}],
)
print(message.content[0].text)
```
```typescript TypeScript nocheck
import Anthropic from "@anthropic-ai/sdk";
import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";
import { STSClient, GetWebIdentityTokenCommand } from "@aws-sdk/client-sts";
const sts = new STSClient({ region: "us-east-1" });
async function getStsWebIdentityToken(): Promise {
const out = await sts.send(
new GetWebIdentityTokenCommand({
Audience: ["https://api.anthropic.com"],
SigningAlgorithm: "RS256",
DurationSeconds: 900
})
);
return out.WebIdentityToken!;
}
const client = new Anthropic({
credentials: oidcFederationProvider({
identityTokenProvider: getStsWebIdentityToken,
federationRuleId: process.env.ANTHROPIC_FEDERATION_RULE_ID!,
organizationId: process.env.ANTHROPIC_ORGANIZATION_ID!,
serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID,
workspaceId: process.env.ANTHROPIC_WORKSPACE_ID,
baseURL: "https://api.anthropic.com",
fetch
})
});
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello from AWS" }]
});
for (const block of message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
```go Go nocheck hidelines={1..15,-1}
package main
import (
"context"
"fmt"
"os"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
func main() {
ctx := context.TODO()
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
if err != nil {
panic(err)
}
stsClient := sts.NewFromConfig(cfg)
getStsToken := option.IdentityTokenFunc(func(ctx context.Context) (string, error) {
out, err := stsClient.GetWebIdentityToken(ctx, &sts.GetWebIdentityTokenInput{
Audience: []string{"https://api.anthropic.com"},
SigningAlgorithm: "RS256",
DurationSeconds: aws.Int32(900),
})
if err != nil {
return "", err
}
return *out.WebIdentityToken, nil
})
client := anthropic.NewClient(
option.WithFederationTokenProvider(getStsToken, option.FederationOptions{
FederationRuleID: os.Getenv("ANTHROPIC_FEDERATION_RULE_ID"),
OrganizationID: os.Getenv("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountID: os.Getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceID: os.Getenv("ANTHROPIC_WORKSPACE_ID"),
}),
)
message, err := client.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello from AWS")),
},
})
if err != nil {
panic(err)
}
fmt.Println(message.Content[0].Text)
}
```
```java Java nocheck hidelines={1..10,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.credentials.IdentityTokenProvider;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.GetWebIdentityTokenRequest;
void main() {
StsClient sts = StsClient.builder().region(Region.US_EAST_1).build();
IdentityTokenProvider getStsToken = () -> sts.getWebIdentityToken(
GetWebIdentityTokenRequest.builder()
.audience("https://api.anthropic.com")
.signingAlgorithm("RS256")
.durationSeconds(900)
.build())
.webIdentityToken();
AnthropicClient client = AnthropicOkHttpClient.builder()
.federationTokenProvider(
getStsToken,
System.getenv("ANTHROPIC_FEDERATION_RULE_ID"),
System.getenv("ANTHROPIC_ORGANIZATION_ID"),
System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
.build();
var message = client.messages().create(MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(1024)
.addUserMessage("Hello from AWS")
.build());
IO.println(message.content());
}
```
```csharp C# nocheck hidelines={1..5}
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
using Anthropic.Models.Messages;
using Anthropic.Oidc;
var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
{
FederationRuleId = Environment.GetEnvironmentVariable("ANTHROPIC_FEDERATION_RULE_ID")!,
OrganizationId = Environment.GetEnvironmentVariable("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountId = Environment.GetEnvironmentVariable("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceId = Environment.GetEnvironmentVariable("ANTHROPIC_WORKSPACE_ID"),
IdentityTokenProvider = new StsTokenProvider(),
});
using var client = new AnthropicOidcClient(credentials);
var message = await client.Messages.Create(new()
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello from AWS" }],
});
foreach (var block in message.Content)
{
if (block.Value is TextBlock textBlock)
{
Console.WriteLine(textBlock.Text);
}
}
class StsTokenProvider : IIdentityTokenProvider
{
private readonly AmazonSecurityTokenServiceClient _sts = new(Amazon.RegionEndpoint.USEast1);
public async Task GetIdentityTokenAsync(CancellationToken ct = default)
{
var resp = await _sts.GetWebIdentityTokenAsync(new GetWebIdentityTokenRequest
{
Audience = ["https://api.anthropic.com"],
SigningAlgorithm = "RS256",
DurationSeconds = 900,
}, ct);
return resp.WebIdentityToken;
}
}
```
```bash CLI nocheck
TOKEN_FILE=$(mktemp)
aws sts get-web-identity-token \
--region us-east-1 \
--audience "https://api.anthropic.com" \
--signing-algorithm RS256 \
--duration-seconds 900 \
--query WebIdentityToken --output text > "$TOKEN_FILE"
export ANTHROPIC_IDENTITY_TOKEN_FILE="$TOKEN_FILE"
# ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, and
# ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID are read from the environment
ant messages create \
--model claude-sonnet-4-6 \
--max-tokens 1024 \
--message '{role: user, content: "Hello from AWS"}'
```
```php PHP nocheck hidelines={1..3}
'us-east-1', 'version' => 'latest']);
$client = new Client(credentials: new WorkloadIdentityCredentials(
identityTokenProvider: fn() => $sts->getWebIdentityToken([
'Audience' => ['https://api.anthropic.com'],
'SigningAlgorithm' => 'RS256',
'DurationSeconds' => 900,
])['WebIdentityToken'],
federationRuleId: getenv('ANTHROPIC_FEDERATION_RULE_ID'),
organizationId: getenv('ANTHROPIC_ORGANIZATION_ID'),
serviceAccountId: getenv('ANTHROPIC_SERVICE_ACCOUNT_ID'),
workspaceId: getenv('ANTHROPIC_WORKSPACE_ID') ?: null,
));
$message = $client->messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello from AWS']],
);
echo $message->content[0]->text, PHP_EOL;
```
```ruby Ruby nocheck
require "anthropic"
require "aws-sdk-sts"
sts = Aws::STS::Client.new(region: "us-east-1")
client = Anthropic::Client.new(
credentials: Anthropic::WorkloadIdentityCredentials.new(
identity_token_provider: -> {
sts.get_web_identity_token(
audience: ["https://api.anthropic.com"],
signing_algorithm: "RS256",
duration_seconds: 900,
).web_identity_token
},
federation_rule_id: ENV.fetch("ANTHROPIC_FEDERATION_RULE_ID"),
organization_id: ENV.fetch("ANTHROPIC_ORGANIZATION_ID"),
service_account_id: ENV.fetch("ANTHROPIC_SERVICE_ACCOUNT_ID"),
workspace_id: ENV["ANTHROPIC_WORKSPACE_ID"],
),
)
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{role: "user", content: "Hello from AWS"}]
)
puts message.content.first.text
```
### Verify the setup
From inside the workload, exchange an STS-issued token directly and inspect the response:
```bash cURL nocheck
JWT=$(aws sts get-web-identity-token \
--region us-east-1 \
--audience "https://api.anthropic.com" \
--signing-algorithm RS256 \
--duration-seconds 900 \
--query WebIdentityToken --output text)
curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
-d "{
\"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\",
\"assertion\": \"$JWT\",
\"federation_rule_id\": \"fdrl_...\",
\"organization_id\": \"00000000-0000-0000-0000-000000000000\",
\"service_account_id\": \"svac_...\",
\"workspace_id\": \"wrkspc_...\"
}" | jq
```
A successful exchange returns an `access_token` beginning with `sk-ant-oat01-` and an `expires_in` value in seconds. On `400 invalid_grant`, see [Troubleshoot a failed exchange](/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange); the most common AWS-side cause is an `iss` mismatch (the per-account STS issuer URL must match the registered `issuer_url` exactly).
## Use EKS projected service-account tokens
If your workload runs in an EKS pod, you can skip the STS call and read a Kubernetes-projected service-account token directly from disk. Kubernetes natively projects an OIDC-compatible token into the pod, and the SDK can read it from a file path, so no token-provider callable is required. This path has two fewer AWS configuration steps than the STS path but only works inside a pod; the underlying mechanism is the same as the [generic Kubernetes integration](/docs/en/manage-claude/wif-providers/kubernetes).
This path additionally requires an EKS cluster with an [IAM OIDC provider enabled](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html) and `kubectl` access to the cluster.
### Configure your EKS cluster
Each EKS cluster has a unique OIDC issuer. Retrieve it with the AWS CLI:
```bash CLI nocheck
aws eks describe-cluster \
--name \
--query "cluster.identity.oidc.issuer" \
--output text
```
The output looks like `https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...`. You'll register this URL as a federation issuer in the next section.
The EKS pod identity webhook detects the `eks.amazonaws.com/role-arn` annotation and automatically projects a token with `aud: sts.amazonaws.com`, exposing its path as `AWS_WEB_IDENTITY_TOKEN_FILE`. That token is for AWS role assumption. For the Anthropic exchange, project a second token with `audience: https://api.anthropic.com` and mount it at a dedicated path.
```yaml nocheck
apiVersion: v1
kind: ServiceAccount
metadata:
name: inference-worker
namespace: inference
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/inference-worker
```
```yaml nocheck
apiVersion: v1
kind: Pod
metadata:
name: inference-worker
namespace: inference
spec:
serviceAccountName: inference-worker
volumes:
- name: anthropic-token
projected:
sources:
- serviceAccountToken:
audience: https://api.anthropic.com
expirationSeconds: 3600
path: token
containers:
- name: app
image: your-registry/inference-worker:latest
env:
- name: ANTHROPIC_IDENTITY_TOKEN_FILE
value: /var/run/secrets/anthropic.com/token
- name: ANTHROPIC_FEDERATION_RULE_ID
value: fdrl_...
- name: ANTHROPIC_ORGANIZATION_ID
value: 00000000-0000-0000-0000-000000000000
- name: ANTHROPIC_SERVICE_ACCOUNT_ID
value: svac_...
- name: ANTHROPIC_WORKSPACE_ID # required when the rule covers multiple workspaces
value: wrkspc_...
volumeMounts:
- name: anthropic-token
mountPath: /var/run/secrets/anthropic.com
readOnly: true
```
The projected token is a JSON Web Token (JWT) signed by your cluster's OIDC issuer. Its `sub` claim follows the Kubernetes convention `system:serviceaccount::`:
```json
{
"iss": "https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...",
"sub": "system:serviceaccount:inference:inference-worker",
"aud": ["https://api.anthropic.com"],
"kubernetes.io": {
"namespace": "inference",
"serviceaccount": { "name": "inference-worker", "uid": "..." }
},
"exp": 1775527120,
"iat": 1775523520
}
```
The `serviceAccountToken` projection sets `aud` to `https://api.anthropic.com`. The separate IRSA-injected token at `AWS_WEB_IDENTITY_TOKEN_FILE` carries `aud: sts.amazonaws.com` and is for AWS API calls, not this exchange.
### Configure Anthropic
Follow the [setup walkthrough](/docs/en/manage-claude/workload-identity-federation#set-up-federation) to register a federation issuer, create an Anthropic service account, and create a federation rule in the Claude Console. Use these EKS-specific values.
**Federation issuer:** EKS issuers expose a public JWKS endpoint, so use discovery mode. The issuer URL must exactly match the token's `iss` claim. Register one issuer per cluster.
```json
{
"name": "prod-eks-uswest2",
"issuer_url": "https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...",
"jwks_source": "discovery"
}
```
**Federation rule:** Match the Kubernetes `sub` claim and the Anthropic audience `https://api.anthropic.com`. (Project a dedicated service-account token with that audience; don't reuse the IRSA default `sts.amazonaws.com` token.)
```json
{
"name": "prod-inference",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "system:serviceaccount:inference:inference-worker",
"audience": "https://api.anthropic.com"
},
"target": { "type": "service_account", "service_account_id": "svac_..." },
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}
```
Be as specific as the workload allows. Loosen `subject_prefix` to `system:serviceaccount:inference:*` (the trailing `*` makes it a prefix match) only if every service account in the namespace should map to the same Anthropic service account.
### Acquire and use the token
Inside the pod, the projected token is at `/var/run/secrets/anthropic.com/token` (exposed as `ANTHROPIC_IDENTITY_TOKEN_FILE` in the Pod spec). Pass that file to the SDK's federation credentials and the SDK handles the exchange and refresh.
```bash cURL nocheck
JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")
RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
--data @- <messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello from EKS']],
);
echo $message->content[0]->text, PHP_EOL;
```
```ruby Ruby nocheck
require "anthropic"
# Reads ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
# ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID, and ANTHROPIC_IDENTITY_TOKEN_FILE
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{role: "user", content: "Hello from EKS"}]
)
puts message.content.first.text
```
The Pod spec already sets `ANTHROPIC_IDENTITY_TOKEN_FILE`, `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID`, and `ANTHROPIC_WORKSPACE_ID`, so you can construct the client with no arguments and the SDK reads the federation environment variables automatically.
### Verify the setup
From inside the pod, exchange the projected token directly and inspect the response:
```bash cURL nocheck
JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")
curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
-d "{
\"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\",
\"assertion\": \"$JWT\",
\"federation_rule_id\": \"$ANTHROPIC_FEDERATION_RULE_ID\",
\"organization_id\": \"$ANTHROPIC_ORGANIZATION_ID\",
\"service_account_id\": \"$ANTHROPIC_SERVICE_ACCOUNT_ID\",
\"workspace_id\": \"$ANTHROPIC_WORKSPACE_ID\"
}" | jq
```
A successful exchange returns an `access_token` beginning with `sk-ant-oat01-` and an `expires_in` value in seconds. On `400 invalid_grant`, see [Troubleshoot a failed exchange](/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange); the most common EKS-side cause is the projected token's `aud` not matching the rule (project a token with `audience: https://api.anthropic.com`, not the IRSA default `sts.amazonaws.com`).
## Scope your rule
A `subject_prefix` of `arn:aws:iam::123456789012:role/*` matches every IAM role in the account. Any principal that can assume any matching role can obtain a federated Anthropic token.
Lock the rule's `match` block to the narrowest scope that fits your use case:
- **Pin the full role ARN:** Use `subject_prefix: "arn:aws:iam:::role/"` with no trailing `*` so other roles in the account do not match.
- **Pin the account ID:** Match the `aws_account` field of the token's `https://sts.amazonaws.com/` claim via the `claims` map or a CEL `condition` as a defense-in-depth check against a misconfigured prefix.
- **Pin namespace and service account on EKS:** Use the exact `system:serviceaccount::` value with no `*` after the `system:serviceaccount:` prefix.
- **Use a separate rule per environment:** Create distinct rules for production, staging, and development workloads rather than widening one prefix to cover them all.
## Next steps
- Review the [WIF reference](/docs/en/manage-claude/wif-reference) for the full credential precedence, profile configuration, and rule matching reference.
- For self-managed Kubernetes clusters that aren't on EKS, see [Use WIF with Kubernetes](/docs/en/manage-claude/wif-providers/kubernetes).
---
# Use WIF with GitHub Actions
URL: https://platform.claude.com/docs/en/manage-claude/wif-providers/github-actions
# Use WIF with GitHub Actions
Authenticate GitHub Actions workflows to the Claude API with short-lived identity tokens instead of long-lived API keys.
---
Every GitHub Actions workflow run can request a signed identity token from GitHub's hosted issuer at `https://token.actions.githubusercontent.com`. With Workload Identity Federation, your workflow exchanges that token for a short-lived Anthropic access token, so your CI jobs can call the Claude API without an `ANTHROPIC_API_KEY` secret stored in your repository.
The token's `sub` claim encodes the repository and trigger context. For a push to a branch it has the form `repo:/:ref:refs/heads/`. Pull-request runs use `repo:/:pull_request`, and environment-gated deployments use `repo:/:environment:`. Your federation rule matches against this claim (and others, such as `repository_owner` and `ref`) to decide which workflow runs are allowed to authenticate.
## Prerequisites
- Familiarity with [WIF concepts](/docs/en/manage-claude/workload-identity-federation#concepts): service accounts, federation issuers, and federation rules.
- A GitHub repository where you can edit workflow files and grant the `id-token: write` permission.
- Permission to create service accounts, federation issuers, and federation rules in the Claude Console for your Anthropic organization.
- Your Anthropic organization ID. You can find it in the Claude Console under **Settings → Organization**.
## Configure your workflow
GitHub only issues an identity token to jobs that explicitly request it. Add the `id-token: write` permission at the workflow or job level:
```yaml
permissions:
id-token: write
contents: read
```
Inside the job, the runner exposes two environment variables: `ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN`. Call the request URL with the request token as a bearer credential and your chosen audience as a query parameter, then write the returned JSON Web Token (JWT) to a file:
```yaml nocheck
- name: Fetch GitHub OIDC token
run: |
curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://api.anthropic.com" \
| jq -r .value > /tmp/gha-jwt
```
If you prefer JavaScript, `actions/github-script` exposes the same capability through `core.getIDToken(audience)`:
```yaml nocheck
- name: Fetch GitHub OIDC token
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const token = await core.getIDToken('https://api.anthropic.com');
fs.writeFileSync('/tmp/gha-jwt', token);
```
The decoded token carries claims that describe the workflow run. Your federation rule matches against these:
```json
{
"iss": "https://token.actions.githubusercontent.com",
"sub": "repo:your-org/your-repo:ref:refs/heads/main",
"aud": "https://api.anthropic.com",
"repository": "your-org/your-repo",
"repository_owner": "your-org",
"ref": "refs/heads/main",
"sha": "abc123...",
"workflow": "CI",
"actor": "octocat",
"event_name": "push"
}
```
See [GitHub's OIDC subject claim reference](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#example-subject-claims) for the full list of `sub` formats.
## Configure Anthropic
Follow the [setup walkthrough](/docs/en/manage-claude/workload-identity-federation#set-up-federation) to register a federation issuer, create an Anthropic service account, and create a federation rule in the Claude Console. Use these GitHub Actions-specific values.
**Federation issuer:** GitHub publishes its OIDC discovery document and JWKS publicly, so use discovery mode. Anthropic refreshes the keys automatically when GitHub rotates them.
```json
{
"name": "github-actions",
"issuer_url": "https://token.actions.githubusercontent.com",
"jwks_source": "discovery"
}
```
**Federation rule:** Match only the workflow runs you intend to trust. See [Restrict which workflows can authenticate](#restrict-which-workflows-can-authenticate) for how to scope these claims safely.
```json
{
"name": "gha-main",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "repo:your-org/your-repo:ref:refs/heads/main",
"audience": "https://api.anthropic.com",
"claims": {
"repository_owner": "your-org"
}
},
"target": {
"type": "service_account",
"service_account_id": "svac_..."
},
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}
```
Be as specific as the workload allows. Loosen `subject_prefix` to `repo:your-org/your-repo:*` (paired with a `claims.ref` constraint) only if the rule must match multiple event types from the same repository, since the trailing segment of `sub` varies between `ref:...`, `environment:...`, and `pull_request` events.
## Acquire and use a token
Set the federation environment variables on the job and call the SDK normally. `Anthropic()` reads `ANTHROPIC_IDENTITY_TOKEN_FILE`, exchanges the JWT on the first request, and refreshes the access token automatically before it expires.
```yaml Workflow nocheck
name: Call Claude
on: push
permissions:
id-token: write
contents: read
jobs:
call-claude:
runs-on: ubuntu-latest
env:
ANTHROPIC_FEDERATION_RULE_ID: fdrl_...
ANTHROPIC_ORGANIZATION_ID: 00000000-0000-0000-0000-000000000000
ANTHROPIC_SERVICE_ACCOUNT_ID: svac_...
ANTHROPIC_WORKSPACE_ID: wrkspc_... # required when the rule covers multiple workspaces
ANTHROPIC_IDENTITY_TOKEN_FILE: /tmp/gha-jwt
steps:
- uses: actions/checkout@v5
- name: Fetch GitHub OIDC token
run: |
curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://api.anthropic.com" \
| jq -r .value > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
- name: Run your script
run: |
pip install anthropic
python your_script.py
```
```bash cURL nocheck
JWT=$(cat /tmp/gha-jwt)
RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
--data @- <messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello, Claude']],
);
echo $message->content[0]->text, PHP_EOL;
```
```ruby Ruby nocheck
require "anthropic"
# Reads ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
# ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID, and ANTHROPIC_IDENTITY_TOKEN_FILE
# from the job environment.
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{role: "user", content: "Hello, Claude"}]
)
puts message.content.first.text
```
Each GitHub-issued identity token expires roughly five minutes after issuance. The token-request endpoint (`ACTIONS_ID_TOKEN_REQUEST_URL`) stays valid for the entire job, so you can fetch a fresh token at any point. The SDK exchanges the token on first use and caches the resulting Anthropic access token. For jobs that run longer than the Anthropic token's lifetime, the SDK re-reads `ANTHROPIC_IDENTITY_TOKEN_FILE` on each refresh, so re-run the fetch step periodically (or wrap it in a background loop) to keep the file current. Alternatively, pass a token-provider callback to the SDK that calls `ACTIONS_ID_TOKEN_REQUEST_URL` directly instead of using the file path.
## Verify the setup
A successful exchange returns an `access_token` beginning with `sk-ant-oat01-` and an `expires_in` value in seconds. On `400 invalid_grant`, see [Troubleshoot a failed exchange](/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange); the most common GitHub Actions-side cause is the `sub` claim format not matching (its trailing segment varies between `ref:...`, `environment:...`, and `pull_request` events).
## Restrict which workflows can authenticate
A `subject_prefix` of `repo:your-org/*` alone matches every repository in your organization, and without a `ref` constraint it also matches `pull_request` runs triggered from forks. Anyone who can open a pull request against a matching repository could obtain a federated Anthropic token.
Lock the rule's `match` block to the narrowest scope that fits your use case:
- **Pin to a single repository:** Use `subject_prefix: "repo:your-org/your-repo:*"` so other repositories in the organization do not match.
- **Pin to a protected branch:** Add `"ref": "refs/heads/main"` (or your release branch) under `claims` so pull-request runs and feature branches do not match.
- **Pin the owner explicitly:** Add `"repository_owner": "your-org"` under `claims` as a defense-in-depth check against `sub` parsing edge cases.
- **Pin to a deployment environment:** For deploy jobs, match `subject_prefix: "repo:your-org/your-repo:environment:production"` and gate that environment with required reviewers in GitHub.
## Next steps
- [Workload Identity Federation](/docs/en/manage-claude/workload-identity-federation): full setup walkthrough, environment variables, and credential precedence.
- [Authentication](/docs/en/manage-claude/authentication): how federation compares to API keys.
---
# Use WIF with Google Cloud
URL: https://platform.claude.com/docs/en/manage-claude/wif-providers/gcp
# Use WIF with Google Cloud
Federate Google Cloud workloads (Cloud Run, Cloud Functions, App Engine, GCE, GKE) to the Claude API using Google-signed identity tokens instead of static API keys.
---
Any Google Cloud compute environment with access to the instance metadata server (Cloud Run, Cloud Functions, App Engine, Compute Engine (GCE), and GKE with Workload Identity) can request a Google-signed identity token for its attached service account. The token's issuer is `https://accounts.google.com`, and Anthropic can validate it directly through standard OIDC discovery, with no extra Google Cloud configuration required.
This guide shows how to register the Google issuer with Anthropic, bind a Google service account to an Anthropic service account, and have your workload exchange its identity token for a short-lived Claude API access token.
## Prerequisites
- Familiarity with [WIF concepts](/docs/en/manage-claude/workload-identity-federation#concepts): service accounts, federation issuers, and federation rules.
- A Google Cloud project with a workload running on Cloud Run, Cloud Functions, App Engine, Compute Engine, or GKE.
- A user-managed Google service account attached to that workload (not the Compute Engine default service account).
- Permission to create service accounts, federation issuers, and federation rules in the Claude Console for your Anthropic organization.
## Configure Google Cloud
Google issues identity tokens automatically to any workload with an attached service account. There is nothing to enable on the Google side beyond attaching the right service account, but the steps differ slightly between standard compute and GKE.
Attach a dedicated service account to your service or instance:
```bash CLI nocheck
gcloud run deploy my-service \
--service-account inference-worker@my-project.iam.gserviceaccount.com
```
Inside the workload, the metadata server returns a signed identity token on demand. Request it with the `audience` you intend to register on the Anthropic side, and include `format=full` so the response carries the `email` claim:
```text
GET http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full
Metadata-Flavor: Google
```
Or, with the gcloud CLI:
```bash CLI nocheck
gcloud auth print-identity-token \
--audiences="https://api.anthropic.com" \
--include-email
```
The SDK equivalents are shown in [Acquire and use the token](#acquire-and-use-the-token).
The decoded token payload looks like this:
```json
{
"iss": "https://accounts.google.com",
"aud": "https://api.anthropic.com",
"sub": "104892...",
"azp": "104892...",
"email": "inference-worker@my-project.iam.gserviceaccount.com",
"email_verified": true,
"exp": 1775527120
}
```
The `sub` claim is the Google service account's opaque numeric unique ID. The `email` claim is the human-readable service account address. Match on both `sub` and `email` in your federation rule.
Enable [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) on your cluster and bind your Kubernetes service account to a Google service account with the `iam.gke.io/gcp-service-account` annotation:
```yaml nocheck
apiVersion: v1
kind: ServiceAccount
metadata:
name: inference-worker
namespace: prod
annotations:
iam.gke.io/gcp-service-account: inference-worker@my-project.iam.gserviceaccount.com
```
With this binding in place, the GKE metadata server returns a Google-signed token identical to the Cloud Run and GCE case: same `https://accounts.google.com` issuer, same `email` claim, same fetch URL. Configure Anthropic exactly as in the next section.
A `format=full` token from GKE additionally includes `google.compute_engine.project_id`, `google.compute_engine.zone`, and `google.compute_engine.instance_name` claims, which you can reference in a federation rule's `condition` matcher (a CEL expression like `claims.google.compute_engine.project_id == "my-project"`) to scope access to a specific cluster or node pool.
If you do not want to bind Kubernetes service accounts to Google service accounts, GKE pods can instead use the cluster's own OIDC issuer (`https://container.googleapis.com/v1/projects/PROJECT/locations/REGION/clusters/CLUSTER`) with a projected `serviceAccountToken` volume. That path uses a per-cluster issuer rather than `accounts.google.com`. See [Use WIF with Kubernetes](/docs/en/manage-claude/wif-providers/kubernetes) for that pattern.
## Configure Anthropic
Follow the [setup walkthrough](/docs/en/manage-claude/workload-identity-federation#set-up-federation) to register a federation issuer, create an Anthropic service account, and create a federation rule in the Claude Console. Use these Google Cloud-specific values.
**Federation issuer:** Google publishes its OIDC discovery document publicly, so use discovery mode. This single issuer covers every Google Cloud surface (Cloud Run, GCE, Cloud Functions, App Engine, and GKE with Workload Identity). Differentiate workloads with rules, not issuers.
```json
{
"name": "gcp",
"issuer_url": "https://accounts.google.com",
"jwks_source": "discovery"
}
```
**Federation rule:** Match on both the `sub` and `email` claims. `email` is the readable service-account address; `sub` is the service account's numeric unique ID, which Google never reuses, so pinning it protects the rule if the service account is deleted and a new one is later created with the same email. Find the unique ID with `gcloud iam service-accounts describe SA_EMAIL --format='value(uniqueId)'`.
```json
{
"name": "gcp-inference-worker",
"issuer_id": "fdis_...",
"match": {
"audience": "https://api.anthropic.com",
"claims": {
"sub": "104892101234567890123",
"email": "inference-worker@my-project.iam.gserviceaccount.com"
}
},
"target": {
"type": "service_account",
"service_account_id": "svac_..."
},
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}
```
## Acquire and use the token
Inside your Google Cloud workload, fetch the identity token from the metadata server, exchange it at `POST /v1/oauth/token`, and use the returned bearer token to call the Claude API. Each Anthropic SDK handles the exchange and refresh loop for you when you supply a token-provider callable that returns a fresh identity token from the metadata server, as shown in the following examples.
```bash cURL nocheck
# Fetch the Google-signed identity token from the metadata server
JWT=$(curl -sS -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full")
# Exchange it for an Anthropic access token
RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
--data @- < str:
request = google.auth.transport.requests.Request()
return google.oauth2.id_token.fetch_id_token(request, AUDIENCE)
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=fetch_google_identity_token,
federation_rule_id=os.environ["ANTHROPIC_FEDERATION_RULE_ID"],
organization_id=os.environ["ANTHROPIC_ORGANIZATION_ID"],
service_account_id=os.environ["ANTHROPIC_SERVICE_ACCOUNT_ID"],
workspace_id=os.environ.get("ANTHROPIC_WORKSPACE_ID"),
),
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello from Cloud Run"}],
)
print(message.content[0].text)
```
```typescript TypeScript nocheck
import Anthropic from "@anthropic-ai/sdk";
import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";
const METADATA_URL =
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full";
async function fetchGoogleIdentityToken(): Promise {
const response = await fetch(METADATA_URL, {
headers: { "Metadata-Flavor": "Google" }
});
return response.text();
}
const client = new Anthropic({
credentials: oidcFederationProvider({
identityTokenProvider: fetchGoogleIdentityToken,
federationRuleId: process.env.ANTHROPIC_FEDERATION_RULE_ID!,
organizationId: process.env.ANTHROPIC_ORGANIZATION_ID!,
serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID,
workspaceId: process.env.ANTHROPIC_WORKSPACE_ID,
baseURL: "https://api.anthropic.com",
fetch
})
});
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello from Cloud Run" }]
});
for (const block of message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
```go Go nocheck hidelines={1..13,-1}
package main
import (
"context"
"fmt"
"os"
"cloud.google.com/go/auth/credentials/idtoken"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func main() {
const audience = "https://api.anthropic.com"
googleIDToken := func(ctx context.Context) (string, error) {
creds, err := idtoken.NewCredentials(&idtoken.Options{Audience: audience})
if err != nil {
return "", err
}
tok, err := creds.Token(ctx)
if err != nil {
return "", err
}
return tok.Value, nil
}
client := anthropic.NewClient(
option.WithFederationTokenProvider(googleIDToken, option.FederationOptions{
FederationRuleID: os.Getenv("ANTHROPIC_FEDERATION_RULE_ID"),
OrganizationID: os.Getenv("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountID: os.Getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceID: os.Getenv("ANTHROPIC_WORKSPACE_ID"),
}),
)
message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello from Cloud Run")),
},
})
if err != nil {
panic(err)
}
fmt.Println(message.Content[0].Text)
}
```
```java Java nocheck hidelines={1..11,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.credentials.IdentityTokenProvider;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
void main() {
HttpClient http = HttpClient.newHttpClient();
HttpRequest metadataRequest = HttpRequest.newBuilder()
.uri(URI.create("http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full"))
.header("Metadata-Flavor", "Google")
.build();
IdentityTokenProvider fetchGoogleIdentityToken = () -> {
try {
return http.send(metadataRequest, HttpResponse.BodyHandlers.ofString()).body();
} catch (Exception e) {
throw new RuntimeException(e);
}
};
AnthropicClient client = AnthropicOkHttpClient.builder()
.federationTokenProvider(
fetchGoogleIdentityToken,
System.getenv("ANTHROPIC_FEDERATION_RULE_ID"),
System.getenv("ANTHROPIC_ORGANIZATION_ID"),
System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
.build();
var message = client.messages().create(MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(1024)
.addUserMessage("Hello from Cloud Run")
.build());
IO.println(message.content());
}
```
```csharp C# nocheck hidelines={1..3}
using Anthropic.Models.Messages;
using Anthropic.Oidc;
var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
{
FederationRuleId = Environment.GetEnvironmentVariable("ANTHROPIC_FEDERATION_RULE_ID")!,
OrganizationId = Environment.GetEnvironmentVariable("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountId = Environment.GetEnvironmentVariable("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceId = Environment.GetEnvironmentVariable("ANTHROPIC_WORKSPACE_ID"),
IdentityTokenProvider = new MetadataTokenProvider(),
});
using var client = new AnthropicOidcClient(credentials);
var message = await client.Messages.Create(new()
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello from Cloud Run" }],
});
foreach (var block in message.Content)
{
if (block.Value is TextBlock textBlock)
{
Console.WriteLine(textBlock.Text);
}
}
class MetadataTokenProvider : IIdentityTokenProvider
{
private const string METADATA_URL =
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full";
private static readonly HttpClient httpClient = new()
{
DefaultRequestHeaders = { { "Metadata-Flavor", "Google" } },
};
public async Task GetIdentityTokenAsync(CancellationToken ct = default)
{
return await httpClient.GetStringAsync(METADATA_URL, ct);
}
}
```
```bash CLI nocheck
# Write the Google-signed identity token to a file the CLI can read
ANTHROPIC_IDENTITY_TOKEN_FILE=$(mktemp)
curl -sS -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full" \
> "$ANTHROPIC_IDENTITY_TOKEN_FILE"
export ANTHROPIC_IDENTITY_TOKEN_FILE
# ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, and
# ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID are read from the environment.
ant messages create \
--model claude-sonnet-4-6 \
--max-tokens 1024 \
--message '{role: user, content: "Hello from Cloud Run"}'
```
```php PHP nocheck hidelines={1..3}
['header' => "Metadata-Flavor: Google\r\n"],
]);
$credentials = new WorkloadIdentityCredentials(
identityTokenProvider: fn() => file_get_contents(METADATA_URL, false, $context),
federationRuleId: getenv('ANTHROPIC_FEDERATION_RULE_ID'),
organizationId: getenv('ANTHROPIC_ORGANIZATION_ID'),
serviceAccountId: getenv('ANTHROPIC_SERVICE_ACCOUNT_ID'),
workspaceId: getenv('ANTHROPIC_WORKSPACE_ID') ?: null,
);
$client = new Client(credentials: $credentials);
$message = $client->messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello from Cloud Run']],
);
echo $message->content[0]->text, PHP_EOL;
```
```ruby Ruby nocheck
require "anthropic"
require "net/http"
METADATA_URL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full"
credentials = Anthropic::WorkloadIdentityCredentials.new(
identity_token_provider: -> { Net::HTTP.get(URI(METADATA_URL), {"Metadata-Flavor" => "Google"}) },
federation_rule_id: ENV.fetch("ANTHROPIC_FEDERATION_RULE_ID"),
organization_id: ENV.fetch("ANTHROPIC_ORGANIZATION_ID"),
service_account_id: ENV.fetch("ANTHROPIC_SERVICE_ACCOUNT_ID"),
workspace_id: ENV["ANTHROPIC_WORKSPACE_ID"]
)
client = Anthropic::Client.new(credentials: credentials)
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{role: "user", content: "Hello from Cloud Run"}]
)
puts message.content.first.text
```
Google identity tokens expire after roughly one hour. The SDKs re-invoke the token provider and re-exchange automatically before expiry. For shell scripts that run longer than the access token's `expires_in`, refresh on a timer and repeat the exchange.
## Verify the setup
From inside your workload, decode the identity token and confirm the claims match your rule:
```bash cURL nocheck
curl -sS -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full" \
| jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson'
```
Check that `iss` is `https://accounts.google.com`, `aud` is `https://api.anthropic.com`, and `email` matches the value in your federation rule. Then run the exchange from the previous section. A successful exchange returns an `access_token` beginning with `sk-ant-oat01-` and an `expires_in` value in seconds. On `400 invalid_grant`, see [Troubleshoot a failed exchange](/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange); the most common Google Cloud-side cause is the `email` claim missing (request the token with `format=full` so it is included).
## Scope your rule
The Google `sub` claim is the service account's opaque numeric unique ID and
has no stable prefix. A `subject_prefix` with a trailing `*` matches
arbitrary service accounts across every Google Cloud project, and any of
them could obtain a federated Anthropic token.
Lock the rule's `match` block to the narrowest scope that fits your use case:
- **Match `sub` exactly:** Set the full numeric unique ID in `claims.sub` and never use `subject_prefix` for Google tokens.
- **Pin the `email` claim:** Add `claims.email` alongside `sub` so both the stable ID and the readable address must match.
- **Pin the audience:** Set `audience` to the exact value you request from the metadata server so tokens minted for other consumers are rejected.
- **Pin the project on GKE:** For `format=full` tokens, add a `condition` such as `claims.google.compute_engine.project_id == "my-project"` to restrict the rule to one project's nodes.
## Next steps
- Read the [Workload Identity Federation](/docs/en/manage-claude/workload-identity-federation) page for the full resource model and SDK credential precedence.
- Add a separate federation rule per environment (production, staging) so you can revoke one without affecting the others.
---
# Use WIF with Kubernetes
URL: https://platform.claude.com/docs/en/manage-claude/wif-providers/kubernetes
# Use WIF with Kubernetes
Authenticate to the Claude API from self-managed Kubernetes clusters using projected service account tokens.
---
Self-managed Kubernetes clusters (kubeadm, k3s, OpenShift, and on-premises distributions) sign OIDC JSON Web Tokens (JWTs) for every pod through [projected service account tokens](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#serviceaccount-token-volume-projection). The cluster's API server acts as the OIDC issuer, and each token's `sub` claim follows the form `system:serviceaccount::`. You can find your cluster's issuer URL by reading its discovery document:
```bash cURL nocheck
kubectl get --raw /.well-known/openid-configuration | jq -r .issuer
```
The mechanism on this page (projected service-account token, cluster API server as the OIDC issuer) is native to Kubernetes itself, so it underlies every Kubernetes distribution. If you run on a managed Kubernetes service, the cloud provider guides walk through where to find the provider-managed issuer URL: [AWS (EKS)](/docs/en/manage-claude/wif-providers/aws#use-eks-projected-service-account-tokens), [Google Cloud (GKE)](/docs/en/manage-claude/wif-providers/gcp), or [Azure (AKS)](/docs/en/manage-claude/wif-providers/azure). If your cluster runs SPIRE, the SPIRE OIDC Discovery Provider is the issuer rather than the cluster API server; see [SPIFFE](/docs/en/manage-claude/wif-providers/spiffe). For any other distribution or a managed provider not listed there, follow this guide and use the issuer URL your cluster reports.
## Prerequisites
- Familiarity with [WIF concepts](/docs/en/manage-claude/workload-identity-federation#concepts): service accounts, federation issuers, and federation rules.
- A Kubernetes cluster with the [`--service-account-issuer`](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/) flag configured on the API server. Most distributions set this by default; kubeadm clusters typically use `https://kubernetes.default.svc.cluster.local`. Your platform team can confirm the value if you don't have direct access to the API server configuration.
- One of the following so Anthropic can validate token signatures:
- The issuer's JWKS endpoint is reachable from the public internet over HTTPS on port 443, or
- You can fetch the JWKS from inside the cluster and register it in `inline` mode (covered in [Configure Anthropic](#configure-anthropic)).
- Permission to create service accounts, federation issuers, and federation rules in the Claude Console for your Anthropic organization.
## Configure Kubernetes
Project a service account token into your pod with the audience and lifetime that your federation rule expects. The `serviceAccountToken` projection writes a fresh JWT to the mount path and rotates it before `expirationSeconds` elapses.
```yaml Pod nocheck
apiVersion: v1
kind: Pod
metadata:
name: inference-worker
namespace: inference
spec:
serviceAccountName: inference-worker
volumes:
- name: anthropic-token
projected:
sources:
- serviceAccountToken:
audience: https://api.anthropic.com
expirationSeconds: 3600
path: token
containers:
- name: app
image: your-registry/inference-worker:latest
env:
- name: ANTHROPIC_IDENTITY_TOKEN_FILE
value: /var/run/secrets/anthropic.com/token
- name: ANTHROPIC_FEDERATION_RULE_ID
value: fdrl_...
- name: ANTHROPIC_ORGANIZATION_ID
value: 00000000-0000-0000-0000-000000000000
- name: ANTHROPIC_SERVICE_ACCOUNT_ID
value: svac_...
- name: ANTHROPIC_WORKSPACE_ID # required when the rule covers multiple workspaces
value: wrkspc_...
volumeMounts:
- name: anthropic-token
mountPath: /var/run/secrets/anthropic.com
readOnly: true
```
The token issued for this pod carries `sub: "system:serviceaccount:inference:inference-worker"` and `aud: ["https://api.anthropic.com"]`.
## Configure Anthropic
Follow the [setup walkthrough](/docs/en/manage-claude/workload-identity-federation#set-up-federation) to register a federation issuer, create an Anthropic service account, and create a federation rule in the Claude Console. Use these Kubernetes-specific values.
**Federation issuer:** Many self-managed clusters use an issuer URL such as `https://kubernetes.default.svc.cluster.local` that is not reachable from the public internet. If that applies to your cluster, choose the **inline** JWKS source and paste the cluster's keys. Fetch them from inside the cluster:
```bash cURL nocheck
kubectl get --raw /openid/v1/jwks
```
Then configure the issuer with the contents of the returned `keys` array (not the surrounding `{"keys": [...]}` wrapper):
```json
{
"name": "onprem-k8s",
"issuer_url": "https://kubernetes.default.svc.cluster.local",
"jwks_source": "inline",
"jwks_keys": [{ "kty": "RSA", "kid": "...", "n": "...", "e": "AQAB" }]
}
```
In `inline` mode the `issuer_url` is only compared against the JWT's `iss` claim; Anthropic never attempts to reach it. If your issuer is publicly reachable, use `"jwks_source": "discovery"` instead and omit `jwks_keys`.
With `inline` keys you are responsible for updating the issuer when the cluster rotates its service account signing key. Rotation is rare (typically only during cluster upgrades), but token exchanges fail with a signature error until you push the new JWKS.
**Federation rule:** Match the service account's `sub` claim and the audience you set on the projected token.
```json
{
"name": "onprem-inference",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "system:serviceaccount:inference:inference-worker",
"audience": "https://api.anthropic.com"
},
"target": {
"type": "service_account",
"service_account_id": "svac_..."
},
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}
```
Be as specific as the workload allows. Loosen `subject_prefix` to `system:serviceaccount:inference:*` (the trailing `*` makes it a prefix match) only if every service account in the namespace should map to the same Anthropic service account. Add the rule's `fdrl_...` ID to your pod's `ANTHROPIC_FEDERATION_RULE_ID` environment variable.
## Acquire and use the token
The pod spec in [Configure Kubernetes](#configure-kubernetes) sets `ANTHROPIC_IDENTITY_TOKEN_FILE` to the projected mount path, along with `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID`, and `ANTHROPIC_WORKSPACE_ID`. With those in place, the SDK reads the token from disk on every exchange and refreshes the Anthropic access token automatically.
```bash cURL nocheck
JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")
ACCESS_TOKEN=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
--data @- <messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello, Claude']],
);
echo $message->content[0]->text, PHP_EOL;
```
```ruby Ruby nocheck
require "anthropic"
# Reads ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
# ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID, and ANTHROPIC_IDENTITY_TOKEN_FILE
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{role: "user", content: "Hello, Claude"}]
)
puts message.content.first.text
```
## Verify the setup
A successful exchange returns an `access_token` beginning with `sk-ant-oat01-` and an `expires_in` value in seconds. On `400 invalid_grant`, see [Troubleshoot a failed exchange](/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange); the most common Kubernetes-side cause is a JWKS key mismatch (for `inline` mode, re-fetch with `kubectl get --raw /openid/v1/jwks` and update the issuer).
## Scope your rule
A `subject_prefix` of `system:serviceaccount:*` matches every service account in the cluster, so any pod can obtain a federated Anthropic token. Without an `audience` matcher, the rule also matches the cluster's default-audience tokens, which every pod already has projected.
Lock the rule's `match` block to the narrowest scope that fits your use case:
- **Pin namespace and service-account name:** Use the full `system:serviceaccount::` value with no trailing `*`.
- **Always set an audience:** Require `audience` on the rule and set the same value on the pod's `serviceAccountToken` projection so default-audience tokens are rejected.
- **Use a separate rule per namespace:** Create a distinct rule and Anthropic service account for each namespace rather than widening one rule.
- **Scope inline-JWKS issuers to one cluster:** When several clusters share an issuer URL, register each cluster's JWKS as its own federation issuer and bind rules to that issuer only.
## Next steps
- [Workload Identity Federation](/docs/en/manage-claude/workload-identity-federation): concepts, the token-exchange flow, and SDK configuration options.
- [WIF reference](/docs/en/manage-claude/wif-reference): environment variables, JWKS source modes, and rule match modes.
---
# Use WIF with Microsoft Azure
URL: https://platform.claude.com/docs/en/manage-claude/wif-providers/azure
# Use WIF with Microsoft Azure
Federate Azure managed identities and Entra Workload Identity with the Claude API so your Azure workloads can call Claude without static API keys.
---
Azure workloads authenticate to the Claude API by presenting a JSON Web Token (JWT) issued by Microsoft Entra ID, then exchanging it for a short-lived Anthropic access token. There are two common ways to obtain the Entra-issued token:
- **Managed identity (VMs, App Service, Functions, Container Apps):** The workload calls the Azure Instance Metadata Service (IMDS) at `http://169.254.169.254/metadata/identity/oauth2/token` and receives a JWT for its assigned identity.
- **Entra Workload Identity (AKS pods):** Kubernetes projects a service account token (signed by the AKS cluster's OIDC issuer) into the pod at the path in `AZURE_FEDERATED_TOKEN_FILE`. The workload exchanges that token at Entra for an Entra-issued access token.
In both cases the Entra-issued token you present to Anthropic carries a tenant-specific Entra issuer (the [Configure Anthropic](#configure-anthropic) step below shows the exact URL to register) and the managed identity's object ID in the `sub` and `oid` claims. You register that issuer with Anthropic once, write a federation rule that matches the expected claims, and your workload exchanges its Entra token for an `sk-ant-oat01-...` access token at runtime.
AKS pods can alternatively skip the Entra exchange and present the Kubernetes-projected service account token to Anthropic directly. That path registers your AKS cluster's OIDC issuer with Anthropic instead of your Entra tenant. See [Kubernetes](/docs/en/manage-claude/wif-providers/kubernetes) for that flow.
## Prerequisites
- Familiarity with [WIF concepts](/docs/en/manage-claude/workload-identity-federation#concepts): service accounts, federation issuers, and federation rules.
- An Azure subscription with permission to assign managed identities (or configure Entra Workload Identity on AKS).
- Your Microsoft Entra tenant ID. Find it in the Azure portal under **Microsoft Entra ID → Overview → Tenant ID**.
- Permission to create service accounts, federation issuers, and federation rules in the Claude Console for your Anthropic organization.
## Configure Azure
Set up the identity that Azure will issue tokens for. Choose the path that matches where your workload runs.
Enable a system-assigned or user-assigned managed identity on your Azure resource. In the Azure portal, open the resource, go to **Identity**, and turn on **System assigned** (or attach a user-assigned identity).
After the identity is created, note its **Object (principal) ID**. This GUID appears as both the `sub` and `oid` claims in the issued token, and your Anthropic federation rule will match on it. You can find it on the resource's **Identity** page, or under **Microsoft Entra ID → Enterprise applications** for user-assigned identities.
No further Azure-side configuration is required. The Azure Instance Metadata Service is reachable at `169.254.169.254` from inside the resource once the identity is attached.
Entra Workload Identity federates a Kubernetes service account with an Entra application so pods can exchange their cluster-issued service account token for an Entra-issued access token.
1. Enable the OIDC issuer on your AKS cluster (`az aks update --enable-oidc-issuer --enable-workload-identity ...`).
2. Deploy the `azure-workload-identity` mutating webhook.
3. Create a user-assigned managed identity and a federated credential that trusts the cluster's OIDC issuer for your Kubernetes service account.
4. Label your pod spec with `azure.workload.identity/use: "true"` and set `serviceAccountName` to the federated service account.
The webhook injects `AZURE_FEDERATED_TOKEN_FILE`, `AZURE_CLIENT_ID`, and `AZURE_TENANT_ID` into the pod. The file at `AZURE_FEDERATED_TOKEN_FILE` contains the Kubernetes-projected service account token, signed by the AKS cluster's OIDC issuer.
### Token claims
An Entra-issued token for a managed identity carries these claims:
```json
{
"iss": "https://login.microsoftonline.com//v2.0",
"sub": "9f8e7d6c-1a2b-3c4d-5e6f-...",
"aud": "https://api.anthropic.com",
"oid": "9f8e7d6c-1a2b-3c4d-5e6f-...",
"tid": "",
"azp": "",
"exp": 1775527120
}
```
`sub` and `oid` are identical (the managed identity's object ID). `azp` is the application or client ID. Match on `oid` to authorize one specific identity, or on `azp` to authorize any identity associated with an application registration. The `tid` claim repeats your tenant ID; matching on it is defense in depth, because the issuer URL already pins the tenant.
## Configure Anthropic
Follow the [setup walkthrough](/docs/en/manage-claude/workload-identity-federation#set-up-federation) to register a federation issuer, create an Anthropic service account, and create a federation rule in the Claude Console. In the Console, choose the **OIDC** provider option and supply the Entra-specific values that follow.
**Federation issuer:** Entra publishes an OIDC discovery document at the per-tenant issuer URL, so use discovery mode. Each Azure tenant you federate needs its own issuer record.
```json
{
"name": "azure-prod-tenant",
"issuer_url": "https://login.microsoftonline.com//v2.0",
"jwks_source": "discovery"
}
```
Depending on the token version, the `iss` claim may be `https://sts.windows.net//` instead. Decode your managed-identity token (the Verify section below shows how) and register whichever `iss` value it contains. The two URLs share the same JWKS, so discovery mode works for either.
**Federation rule:** Match on the managed identity's object ID and your tenant ID.
```json
{
"name": "azure-inference-worker",
"issuer_id": "fdis_...",
"match": {
"audience": "https://api.anthropic.com",
"claims": {
"oid": "9f8e7d6c-1a2b-3c4d-5e6f-...",
"tid": ""
}
},
"target": {
"type": "service_account",
"service_account_id": "svac_..."
},
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}
```
## Acquire and use the token
At runtime your workload fetches its Entra token, exchanges it at `POST /v1/oauth/token`, and uses the returned bearer token to call Claude. Each Anthropic SDK handles the exchange and refresh loop when you supply a token-provider callable, as shown in the following examples. The cURL tab shows the raw flow.
```bash cURL nocheck
# 1. Fetch the Entra-issued token from IMDS (managed identity).
# For AKS with Entra Workload Identity, use the two-hop exchange in the
# "On AKS with Entra Workload Identity" section instead.
ENTRA_TOKEN=$(curl -sS -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://api.anthropic.com" \
| jq -r .access_token)
# 2. Exchange it for an Anthropic access token.
RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
--data @- < str:
"""Fetch a managed identity token from Azure IMDS."""
response = requests.get(
IMDS_URL,
headers={"Metadata": "true"},
params={"api-version": "2018-02-01", "resource": "https://api.anthropic.com"},
timeout=5,
)
response.raise_for_status()
return response.json()["access_token"]
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=fetch_entra_token,
federation_rule_id=os.environ["ANTHROPIC_FEDERATION_RULE_ID"],
organization_id=os.environ["ANTHROPIC_ORGANIZATION_ID"],
service_account_id=os.environ["ANTHROPIC_SERVICE_ACCOUNT_ID"],
workspace_id=os.environ.get("ANTHROPIC_WORKSPACE_ID"),
),
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello from Azure"}],
)
print(message.content[0].text)
```
```typescript TypeScript nocheck
import Anthropic from "@anthropic-ai/sdk";
import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";
const IMDS_URL =
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://api.anthropic.com";
async function fetchEntraToken(): Promise {
const response = await fetch(IMDS_URL, {
headers: { Metadata: "true" }
});
const body = (await response.json()) as { access_token: string };
return body.access_token;
}
const client = new Anthropic({
credentials: oidcFederationProvider({
identityTokenProvider: fetchEntraToken,
federationRuleId: process.env.ANTHROPIC_FEDERATION_RULE_ID!,
organizationId: process.env.ANTHROPIC_ORGANIZATION_ID!,
serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID,
workspaceId: process.env.ANTHROPIC_WORKSPACE_ID,
baseURL: "https://api.anthropic.com",
fetch
})
});
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello from Azure" }]
});
for (const block of message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
```go Go nocheck
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
const imdsURL = "http://169.254.169.254/metadata/identity/oauth2/token" +
"?api-version=2018-02-01&resource=https://api.anthropic.com"
// azureIMDSToken fetches a managed identity token from Azure IMDS.
func azureIMDSToken(ctx context.Context) (string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, imdsURL, nil)
if err != nil {
return "", err
}
req.Header.Set("Metadata", "true")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", fmt.Errorf("call IMDS: %w", err)
}
defer resp.Body.Close()
var body struct {
AccessToken string `json:"access_token"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return "", fmt.Errorf("decode IMDS response: %w", err)
}
return body.AccessToken, nil
}
func main() {
client := anthropic.NewClient(
option.WithFederationTokenProvider(azureIMDSToken, option.FederationOptions{
FederationRuleID: os.Getenv("ANTHROPIC_FEDERATION_RULE_ID"),
OrganizationID: os.Getenv("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountID: os.Getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceID: os.Getenv("ANTHROPIC_WORKSPACE_ID"),
}),
)
message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello from Azure")),
},
})
if err != nil {
panic(err)
}
fmt.Println(message.Content[0].Text)
}
```
```java Java nocheck hidelines={1..12,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.credentials.IdentityTokenProvider;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
void main() {
HttpClient http = HttpClient.newHttpClient();
HttpRequest metadataRequest = HttpRequest.newBuilder()
.uri(URI.create("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://api.anthropic.com"))
.header("Metadata", "true")
.build();
IdentityTokenProvider fetchEntraToken = () -> {
try {
var response = http.send(metadataRequest, HttpResponse.BodyHandlers.ofString());
return new ObjectMapper().readTree(response.body()).get("access_token").asText();
} catch (Exception e) {
throw new RuntimeException(e);
}
};
AnthropicClient client = AnthropicOkHttpClient.builder()
.federationTokenProvider(
fetchEntraToken,
System.getenv("ANTHROPIC_FEDERATION_RULE_ID"),
System.getenv("ANTHROPIC_ORGANIZATION_ID"),
System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
.build();
var message = client.messages().create(MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(1024)
.addUserMessage("Hello from Azure")
.build());
IO.println(message.content());
}
```
```csharp C# nocheck hidelines={1..4}
using System.Text.Json;
using Anthropic.Models.Messages;
using Anthropic.Oidc;
var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
{
FederationRuleId = Environment.GetEnvironmentVariable("ANTHROPIC_FEDERATION_RULE_ID")!,
OrganizationId = Environment.GetEnvironmentVariable("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountId = Environment.GetEnvironmentVariable("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceId = Environment.GetEnvironmentVariable("ANTHROPIC_WORKSPACE_ID"),
IdentityTokenProvider = new EntraTokenProvider(),
});
using var client = new AnthropicOidcClient(credentials);
var message = await client.Messages.Create(new()
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello from Azure" }],
});
foreach (var block in message.Content)
{
if (block.Value is TextBlock textBlock)
{
Console.WriteLine(textBlock.Text);
}
}
class EntraTokenProvider : IIdentityTokenProvider
{
private const string IMDS_URL =
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://api.anthropic.com";
private static readonly HttpClient httpClient = new()
{
DefaultRequestHeaders = { { "Metadata", "true" } },
};
public async Task GetIdentityTokenAsync(CancellationToken ct = default)
{
using var json = await JsonDocument.ParseAsync(
await httpClient.GetStreamAsync(IMDS_URL, ct), default, ct);
return json.RootElement.GetProperty("access_token").GetString()!;
}
}
```
```php PHP nocheck hidelines={1..3}
['header' => "Metadata: true\r\n"],
]);
$body = json_decode(file_get_contents(IMDS_URL, false, $context), true);
return $body['access_token'];
}
$credentials = new WorkloadIdentityCredentials(
identityTokenProvider: fetchEntraToken(...),
federationRuleId: getenv('ANTHROPIC_FEDERATION_RULE_ID'),
organizationId: getenv('ANTHROPIC_ORGANIZATION_ID'),
serviceAccountId: getenv('ANTHROPIC_SERVICE_ACCOUNT_ID'),
workspaceId: getenv('ANTHROPIC_WORKSPACE_ID') ?: null,
);
$client = new Client(credentials: $credentials);
$message = $client->messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello from Azure']],
);
echo $message->content[0]->text, PHP_EOL;
```
```ruby Ruby nocheck
require "anthropic"
require "json"
require "net/http"
IMDS_URL = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://api.anthropic.com"
def fetch_entra_token
response = Net::HTTP.get(URI(IMDS_URL), {"Metadata" => "true"})
JSON.parse(response).fetch("access_token")
end
credentials = Anthropic::WorkloadIdentityCredentials.new(
identity_token_provider: -> { fetch_entra_token },
federation_rule_id: ENV.fetch("ANTHROPIC_FEDERATION_RULE_ID"),
organization_id: ENV.fetch("ANTHROPIC_ORGANIZATION_ID"),
service_account_id: ENV.fetch("ANTHROPIC_SERVICE_ACCOUNT_ID"),
workspace_id: ENV["ANTHROPIC_WORKSPACE_ID"]
)
client = Anthropic::Client.new(credentials: credentials)
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{role: "user", content: "Hello from Azure"}]
)
puts message.content.first.text
```
```bash CLI nocheck
# Write the Entra-issued access token to a file the CLI can read
ANTHROPIC_IDENTITY_TOKEN_FILE=$(mktemp)
curl -sS -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://api.anthropic.com" \
| jq -r .access_token > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
export ANTHROPIC_IDENTITY_TOKEN_FILE
# ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, and
# ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID are read from the environment.
ant messages create \
--model claude-sonnet-4-6 \
--max-tokens 1024 \
--message '{role: user, content: "Hello from Azure"}'
```
### On AKS with Entra Workload Identity
On AKS, the file at `AZURE_FEDERATED_TOKEN_FILE` is a Kubernetes-projected service account token signed by your cluster's OIDC issuer, not an Entra-issued token. To stay on the Entra-mediated path described on this page, exchange that token at `https://login.microsoftonline.com//oauth2/v2.0/token` (federated `client_credentials` grant) first, then pass the resulting Entra access token to the Anthropic SDK as the identity token.
```bash cURL nocheck
# 1. Exchange the Kubernetes-projected token (at $AZURE_FEDERATED_TOKEN_FILE)
# for an Entra-issued JWT.
ENTRA_JWT=$(curl -sS "https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/v2.0/token" \
-d grant_type=client_credentials \
-d "client_id=$AZURE_CLIENT_ID" \
--data-urlencode "scope=https://api.anthropic.com/.default" \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
--data-urlencode "client_assertion@$AZURE_FEDERATED_TOKEN_FILE" \
| jq -r .access_token)
# 2. Exchange the Entra JWT for an Anthropic access token.
ACCESS_TOKEN=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
-d @- < str:
federated_token = Path(os.environ["AZURE_FEDERATED_TOKEN_FILE"]).read_text()
response = httpx.post(
f"https://login.microsoftonline.com/{os.environ['AZURE_TENANT_ID']}/oauth2/v2.0/token",
data={
"client_id": os.environ["AZURE_CLIENT_ID"],
"grant_type": "client_credentials",
"scope": "https://api.anthropic.com/.default",
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": federated_token,
},
)
response.raise_for_status()
return response.json()["access_token"]
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=fetch_entra_token_via_federation,
federation_rule_id=os.environ["ANTHROPIC_FEDERATION_RULE_ID"],
organization_id=os.environ["ANTHROPIC_ORGANIZATION_ID"],
service_account_id=os.environ["ANTHROPIC_SERVICE_ACCOUNT_ID"],
workspace_id=os.environ.get("ANTHROPIC_WORKSPACE_ID"),
),
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello from Azure"}],
)
print(message.content[0].text)
```
```typescript TypeScript nocheck
import Anthropic from "@anthropic-ai/sdk";
import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";
import { readFile } from "node:fs/promises";
async function fetchEntraTokenViaFederation(): Promise {
const federatedToken = await readFile(process.env.AZURE_FEDERATED_TOKEN_FILE!, "utf8");
const response = await fetch(
`https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/oauth2/v2.0/token`,
{
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: process.env.AZURE_CLIENT_ID!,
grant_type: "client_credentials",
scope: "https://api.anthropic.com/.default",
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: federatedToken
})
}
);
const body = (await response.json()) as { access_token: string };
return body.access_token;
}
const client = new Anthropic({
credentials: oidcFederationProvider({
identityTokenProvider: fetchEntraTokenViaFederation,
federationRuleId: process.env.ANTHROPIC_FEDERATION_RULE_ID!,
organizationId: process.env.ANTHROPIC_ORGANIZATION_ID!,
serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID,
workspaceId: process.env.ANTHROPIC_WORKSPACE_ID,
baseURL: "https://api.anthropic.com",
fetch
})
});
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello from Azure" }]
});
for (const block of message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
```go Go nocheck
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func fetchEntraTokenViaFederation(ctx context.Context) (string, error) {
federatedToken, err := os.ReadFile(os.Getenv("AZURE_FEDERATED_TOKEN_FILE"))
if err != nil {
return "", err
}
form := url.Values{
"client_id": {os.Getenv("AZURE_CLIENT_ID")},
"grant_type": {"client_credentials"},
"scope": {"https://api.anthropic.com/.default"},
"client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"},
"client_assertion": {strings.TrimSpace(string(federatedToken))},
}
tokenURL := "https://login.microsoftonline.com/" + os.Getenv("AZURE_TENANT_ID") + "/oauth2/v2.0/token"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenURL, strings.NewReader(form.Encode()))
if err != nil {
return "", err
}
req.Header.Set("content-type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
var body struct {
AccessToken string `json:"access_token"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return "", err
}
return body.AccessToken, nil
}
func main() {
client := anthropic.NewClient(
option.WithFederationTokenProvider(option.IdentityTokenFunc(fetchEntraTokenViaFederation), option.FederationOptions{
FederationRuleID: os.Getenv("ANTHROPIC_FEDERATION_RULE_ID"),
OrganizationID: os.Getenv("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountID: os.Getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceID: os.Getenv("ANTHROPIC_WORKSPACE_ID"),
}),
)
message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello from Azure")),
},
})
if err != nil {
panic(err)
}
fmt.Println(message.Content[0].Text)
}
```
```java Java nocheck hidelines={1..18,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.credentials.IdentityTokenProvider;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.stream.Collectors;
import static java.nio.charset.StandardCharsets.UTF_8;
void main() {
IdentityTokenProvider fetchEntraTokenViaFederation = () -> {
try {
var form = Map.of(
"client_id", System.getenv("AZURE_CLIENT_ID"),
"grant_type", "client_credentials",
"scope", "https://api.anthropic.com/.default",
"client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion", Files.readString(Path.of(System.getenv("AZURE_FEDERATED_TOKEN_FILE"))))
.entrySet().stream()
.map(entry -> entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), UTF_8))
.collect(Collectors.joining("&"));
var request = HttpRequest.newBuilder(URI.create(
"https://login.microsoftonline.com/" + System.getenv("AZURE_TENANT_ID") + "/oauth2/v2.0/token"))
.header("content-type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(form))
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
return new ObjectMapper().readTree(response.body()).get("access_token").asText();
} catch (Exception e) {
throw new RuntimeException(e);
}
};
AnthropicClient client = AnthropicOkHttpClient.builder()
.federationTokenProvider(
fetchEntraTokenViaFederation,
System.getenv("ANTHROPIC_FEDERATION_RULE_ID"),
System.getenv("ANTHROPIC_ORGANIZATION_ID"),
System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
.build();
var message = client.messages().create(MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(1024)
.addUserMessage("Hello from Azure")
.build());
IO.println(message.content());
}
```
```csharp C# nocheck hidelines={1..4}
using System.Text.Json;
using Anthropic.Models.Messages;
using Anthropic.Oidc;
var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
{
FederationRuleId = Environment.GetEnvironmentVariable("ANTHROPIC_FEDERATION_RULE_ID")!,
OrganizationId = Environment.GetEnvironmentVariable("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountId = Environment.GetEnvironmentVariable("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceId = Environment.GetEnvironmentVariable("ANTHROPIC_WORKSPACE_ID"),
IdentityTokenProvider = new EntraFederationTokenProvider(),
});
using var client = new AnthropicOidcClient(credentials);
var message = await client.Messages.Create(new()
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello from Azure" }],
});
foreach (var block in message.Content)
{
if (block.Value is TextBlock textBlock)
{
Console.WriteLine(textBlock.Text);
}
}
class EntraFederationTokenProvider : IIdentityTokenProvider
{
private static readonly HttpClient Http = new();
public async Task GetIdentityTokenAsync(CancellationToken ct = default)
{
var federatedToken = await File.ReadAllTextAsync(
Environment.GetEnvironmentVariable("AZURE_FEDERATED_TOKEN_FILE")!, ct);
var tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
var form = new FormUrlEncodedContent(new Dictionary
{
["client_id"] = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID")!,
["grant_type"] = "client_credentials",
["scope"] = "https://api.anthropic.com/.default",
["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
["client_assertion"] = federatedToken,
});
var response = await Http.PostAsync(
$"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", form, ct);
response.EnsureSuccessStatusCode();
using var json = await JsonDocument.ParseAsync(
await response.Content.ReadAsStreamAsync(ct), default, ct);
return json.RootElement.GetProperty("access_token").GetString()!;
}
}
```
```php PHP nocheck hidelines={1..3}
true,
CURLOPT_POSTFIELDS => http_build_query([
'client_id' => getenv('AZURE_CLIENT_ID'),
'grant_type' => 'client_credentials',
'scope' => 'https://api.anthropic.com/.default',
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'client_assertion' => file_get_contents(getenv('AZURE_FEDERATED_TOKEN_FILE')),
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);
return $body['access_token'];
}
$client = new Client(
credentials: new WorkloadIdentityCredentials(
identityTokenProvider: fetchEntraTokenViaFederation(...),
federationRuleId: getenv('ANTHROPIC_FEDERATION_RULE_ID'),
organizationId: getenv('ANTHROPIC_ORGANIZATION_ID'),
serviceAccountId: getenv('ANTHROPIC_SERVICE_ACCOUNT_ID'),
workspaceId: getenv('ANTHROPIC_WORKSPACE_ID') ?: null,
),
);
$message = $client->messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello from Azure']],
);
echo $message->content[0]->text, PHP_EOL;
```
```ruby Ruby nocheck
require "anthropic"
require "json"
require "net/http"
def fetch_entra_token_via_federation
tenant_id = ENV.fetch("AZURE_TENANT_ID")
federated_token = File.read(ENV.fetch("AZURE_FEDERATED_TOKEN_FILE"))
response = Net::HTTP.post_form(
URI("https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/token"),
"client_id" => ENV.fetch("AZURE_CLIENT_ID"),
"grant_type" => "client_credentials",
"scope" => "https://api.anthropic.com/.default",
"client_assertion_type" => "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion" => federated_token
)
JSON.parse(response.body).fetch("access_token")
end
client = Anthropic::Client.new(
credentials: Anthropic::WorkloadIdentityCredentials.new(
identity_token_provider: -> { fetch_entra_token_via_federation },
federation_rule_id: ENV.fetch("ANTHROPIC_FEDERATION_RULE_ID"),
organization_id: ENV.fetch("ANTHROPIC_ORGANIZATION_ID"),
service_account_id: ENV.fetch("ANTHROPIC_SERVICE_ACCOUNT_ID"),
workspace_id: ENV["ANTHROPIC_WORKSPACE_ID"]
)
)
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{role: "user", content: "Hello from Azure"}]
)
puts message.content.first.text
```
```bash CLI nocheck
# 1. Exchange the Kubernetes-projected token for an Entra-issued access
# token and write it to a temp file the CLI can read.
ANTHROPIC_IDENTITY_TOKEN_FILE=$(mktemp)
curl -sS "https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/v2.0/token" \
-d client_id="$AZURE_CLIENT_ID" \
-d grant_type=client_credentials \
--data-urlencode scope=https://api.anthropic.com/.default \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
--data-urlencode client_assertion@"$AZURE_FEDERATED_TOKEN_FILE" \
| jq -r .access_token > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
export ANTHROPIC_IDENTITY_TOKEN_FILE
# 2. Call the Claude API. ANTHROPIC_FEDERATION_RULE_ID,
# ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID are read
# from the environment.
ant messages create \
--model claude-sonnet-4-6 \
--max-tokens 1024 \
--message '{role: user, content: "Hello from Azure"}'
```
Alternatively, register your AKS cluster's OIDC issuer with Anthropic directly and skip the Entra hop. See [Kubernetes](/docs/en/manage-claude/wif-providers/kubernetes) for that pattern.
## Verify the setup
From your Azure resource, run the cURL exchange shown earlier and confirm that `POST /v1/oauth/token` returns a `200` with an `access_token` beginning with `sk-ant-oat01-` and an `expires_in` value in seconds. On `400 invalid_grant`, see [Troubleshoot a failed exchange](/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange); the most common Azure-side cause is a mismatch between the `issuer_url` you registered and the `iss` claim in your decoded token. They must match exactly. For managed-identity tokens the `iss` value is either `https://login.microsoftonline.com//v2.0` or `https://sts.windows.net//`.
## Scope your rule
The `oid` claim is a managed identity's GUID and has no stable prefix. A
`subject_prefix` with `*` matches arbitrary identities in the tenant, so any
workload that holds a managed identity could obtain a federated Anthropic
token.
Lock the rule's `match` block to the narrowest scope that fits your use case:
- **Match `oid` as an exact value:** Set `claims.oid` to the managed identity's full object ID and never use `subject_prefix` for Azure tokens.
- **Pin `tid` as defense in depth:** The issuer URL already pins your tenant, but adding `claims.tid` guards against configuration drift if the issuer record is later edited.
- **Pin the audience:** Set `audience` to `https://api.anthropic.com` so tokens minted for other resources are rejected.
- **Use a separate rule per managed identity:** Create one rule per identity rather than one rule that authorizes several, so you can revoke a single workload's access without affecting others.
## Next steps
- Review the full configuration model in [Workload Identity Federation](/docs/en/manage-claude/workload-identity-federation).
- See the [provider guides](/docs/en/manage-claude/workload-identity-federation#identity-providers) for AWS, Google Cloud, GitHub Actions, and Kubernetes.
- For environment variables, profile files, and credential precedence, see the [WIF reference](/docs/en/manage-claude/wif-reference).
---
# Use WIF with Okta
URL: https://platform.claude.com/docs/en/manage-claude/wif-providers/okta
# Use WIF with Okta
Federate Okta service application identities to the Claude API with Workload Identity Federation.
---
Okta can act as a workload identity provider by issuing OIDC access tokens to a **service application** through the OAuth 2.0 `client_credentials` grant. Your workload authenticates to Okta (typically with `private_key_jwt`, so no shared secret is stored), receives a signed JSON Web Token (JWT), and exchanges that JWT with Anthropic for a short-lived access token.
The Okta authorization server's issuer URL takes the form `https://.okta.com/oauth2/`. If you use the built-in default server, the path is `/oauth2/default`.
You must use an Okta **custom authorization server** (including the `default` one). Tokens issued directly by the Okta org authorization server (the `/oauth2/v1/token` endpoint with no authorization server ID in the path) cannot be validated by external parties because Okta does not publish signing keys for them.
There are many ways to configure and authenticate to Okta that are outside the scope of this documentation. Ensure that your configuration and authentication mechanisms follow your company's guidance and security practices.
## Prerequisites
- Familiarity with [WIF concepts](/docs/en/manage-claude/workload-identity-federation#concepts): service accounts, federation issuers, and federation rules.
- An Okta organization with API Access Management enabled (required for custom authorization servers).
- Permission to create service accounts, federation issuers, and federation rules in the Claude Console for your Anthropic organization.
- A workload that can request a token from Okta's `/v1/token` endpoint and reach `api.anthropic.com`.
## Configure Okta
At a high level you need to:
1. Create an Okta service application.
2. Configure your default authorization server (or create a new custom authorization server) with an audience, a scope, an access policy, and any custom claims you want to match on.
The exact navigation depends on your Okta org configuration and admin console version. The numbered steps below walk through one common path:
1. **Create a service app integration.** In the Okta Admin Console, create a new app integration of type **API Services** (OIDC, machine-to-machine). Note the generated **Client ID**.
2. **Configure client authentication.** For a keyless setup, choose **Public key / Private key** (`private_key_jwt`) and register your workload's public JWK. Alternatively, use a client secret if your environment can store one securely. For the following example you may need to disable the DPoP requirement on the application; ensure that your production setup adheres to your organization's security requirements.
3. **Set the audience.** On your custom authorization server, set the audience to `https://api.anthropic.com` so issued access tokens carry that `aud` claim. Anthropic validates `aud` against this fixed value.
4. **Grant a scope.** On your custom authorization server, ensure at least one scope exists that the service app is allowed to request (for example, `anthropic.access`). Okta rejects `client_credentials` requests that do not include a granted scope.
5. **Create an access policy.** On your custom authorization server, create an access policy with at least one rule that allows your service app to request the scope you granted in step 4.
6. **(Optional) Add custom claims.** If you want to match on something other than the client ID, add a claim to the access token in your authorization server's **Claims** tab.
For a service app using `client_credentials`, Okta sets the `sub` claim of the issued access token to the application's **Client ID**, and `iss` to the authorization server's issuer URL.
## Configure Anthropic
Follow the [setup walkthrough](/docs/en/manage-claude/workload-identity-federation#set-up-federation) to register a federation issuer, create an Anthropic service account, and create a federation rule in the Claude Console. Use these Okta-specific values.
**Federation issuer:** Use your Okta custom authorization server URL and discovery mode. Anthropic reads Okta's `.well-known/openid-configuration` discovery document and fetches the JWKS from the `jwks_uri` it advertises.
```json
{
"name": "okta-prod",
"issuer_url": "https://acme.okta.com/oauth2/aus1a2b3c4d5e6f7g8h9",
"jwks_source": "discovery"
}
```
**Federation rule:** Match on the Okta `sub` claim, which is the service app's Client ID. If you defined custom claims in Okta, you can match on those instead with the `claims` map or a CEL `condition`.
```json
{
"name": "okta-pipeline",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "0oa1b2c3d4e5f6g7h8i9",
"audience": "https://api.anthropic.com"
},
"target": { "type": "service_account", "service_account_id": "svac_..." },
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}
```
## Acquire a token and call the Claude API
Unlike platform-native providers (AWS, Google Cloud, Kubernetes), which make a token available inside the workload's runtime (through a projected file or local metadata endpoint), Okta does not. Your workload must call Okta's token endpoint to obtain a JWT, then pass that JWT to the Anthropic SDK as the identity token.
```bash cURL nocheck
# 1. Request an access token from Okta (client_credentials with private_key_jwt).
OKTA_JWT=$(curl -sS "https://acme.okta.com/oauth2/aus1a2b3c4d5e6f7g8h9/v1/token" \
-d grant_type=client_credentials \
-d scope=anthropic.access \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
--data-urlencode client_assertion="$SIGNED_CLIENT_ASSERTION" \
| jq -r .access_token)
# 2. Exchange the Okta JWT for an Anthropic access token.
ACCESS_TOKEN=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
-d @- < str:
response = httpx.post(
f"{os.environ['OKTA_ISSUER']}/v1/token",
data={
"grant_type": "client_credentials",
"scope": "anthropic.access",
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
# Build the RFC 7523 client_assertion JWT signed with your Okta app's private key
"client_assertion": build_signed_client_assertion(),
},
)
response.raise_for_status()
return response.json()["access_token"]
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=fetch_okta_token,
federation_rule_id=os.environ["ANTHROPIC_FEDERATION_RULE_ID"],
organization_id=os.environ["ANTHROPIC_ORGANIZATION_ID"],
service_account_id=os.environ["ANTHROPIC_SERVICE_ACCOUNT_ID"],
workspace_id=os.environ.get("ANTHROPIC_WORKSPACE_ID"),
),
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(message.content[0].text)
```
```typescript TypeScript nocheck
import Anthropic from "@anthropic-ai/sdk";
import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";
async function fetchOktaToken(): Promise {
const response = await fetch(`${process.env.OKTA_ISSUER}/v1/token`, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
scope: "anthropic.access",
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
// Build the RFC 7523 client_assertion JWT signed with your Okta app's private key
client_assertion: buildSignedClientAssertion()
})
});
const body = (await response.json()) as { access_token: string };
return body.access_token;
}
const client = new Anthropic({
credentials: oidcFederationProvider({
identityTokenProvider: fetchOktaToken,
federationRuleId: process.env.ANTHROPIC_FEDERATION_RULE_ID!,
organizationId: process.env.ANTHROPIC_ORGANIZATION_ID!,
serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID,
workspaceId: process.env.ANTHROPIC_WORKSPACE_ID,
baseURL: "https://api.anthropic.com",
fetch
})
});
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello, Claude" }]
});
for (const block of message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
```go Go nocheck
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func fetchOktaToken(ctx context.Context) (string, error) {
form := url.Values{
"grant_type": {"client_credentials"},
"scope": {"anthropic.access"},
"client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"},
// Build the RFC 7523 client_assertion JWT signed with your Okta app's private key
"client_assertion": {buildSignedClientAssertion()},
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
os.Getenv("OKTA_ISSUER")+"/v1/token", strings.NewReader(form.Encode()))
if err != nil {
return "", err
}
req.Header.Set("content-type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
var body struct {
AccessToken string `json:"access_token"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return "", err
}
return body.AccessToken, nil
}
func main() {
client := anthropic.NewClient(
option.WithFederationTokenProvider(option.IdentityTokenFunc(fetchOktaToken), option.FederationOptions{
FederationRuleID: os.Getenv("ANTHROPIC_FEDERATION_RULE_ID"),
OrganizationID: os.Getenv("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountID: os.Getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceID: os.Getenv("ANTHROPIC_WORKSPACE_ID"),
}),
)
message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, Claude")),
},
})
if err != nil {
panic(err)
}
fmt.Println(message.Content[0].Text)
}
```
```java Java nocheck hidelines={1..16,-1}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.credentials.IdentityTokenProvider;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Map;
import java.util.stream.Collectors;
import static java.nio.charset.StandardCharsets.UTF_8;
void main() {
IdentityTokenProvider fetchOktaToken = () -> {
try {
var form = Map.of(
"grant_type", "client_credentials",
"scope", "anthropic.access",
"client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
// Build the RFC 7523 client_assertion JWT signed with your Okta app's private key
"client_assertion", buildSignedClientAssertion())
.entrySet().stream()
.map(entry -> entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), UTF_8))
.collect(Collectors.joining("&"));
var request = HttpRequest.newBuilder(URI.create(System.getenv("OKTA_ISSUER") + "/v1/token"))
.header("content-type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(form))
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
return new ObjectMapper().readTree(response.body()).get("access_token").asText();
} catch (Exception e) {
throw new RuntimeException(e);
}
};
AnthropicClient client = AnthropicOkHttpClient.builder()
.federationTokenProvider(
fetchOktaToken,
System.getenv("ANTHROPIC_FEDERATION_RULE_ID"),
System.getenv("ANTHROPIC_ORGANIZATION_ID"),
System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
.build();
var message = client.messages().create(MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(1024)
.addUserMessage("Hello, Claude")
.build());
IO.println(message.content());
}
```
```csharp C# nocheck hidelines={1..4}
using System.Text.Json;
using Anthropic.Models.Messages;
using Anthropic.Oidc;
var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
{
FederationRuleId = Environment.GetEnvironmentVariable("ANTHROPIC_FEDERATION_RULE_ID")!,
OrganizationId = Environment.GetEnvironmentVariable("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountId = Environment.GetEnvironmentVariable("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceId = Environment.GetEnvironmentVariable("ANTHROPIC_WORKSPACE_ID"),
IdentityTokenProvider = new OktaTokenProvider(),
});
using var client = new AnthropicOidcClient(credentials);
var message = await client.Messages.Create(new()
{
Model = Model.ClaudeSonnet4_6,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello, Claude" }],
});
foreach (var block in message.Content)
{
if (block.Value is TextBlock textBlock)
{
Console.WriteLine(textBlock.Text);
}
}
class OktaTokenProvider : IIdentityTokenProvider
{
private static readonly HttpClient Http = new();
public async Task GetIdentityTokenAsync(CancellationToken ct = default)
{
var form = new FormUrlEncodedContent(new Dictionary
{
["grant_type"] = "client_credentials",
["scope"] = "anthropic.access",
["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
// Build the RFC 7523 client_assertion JWT signed with your Okta app's private key
["client_assertion"] = BuildSignedClientAssertion(),
});
var response = await Http.PostAsync(
$"{Environment.GetEnvironmentVariable("OKTA_ISSUER")}/v1/token", form, ct);
response.EnsureSuccessStatusCode();
using var json = await JsonDocument.ParseAsync(
await response.Content.ReadAsStreamAsync(ct), default, ct);
return json.RootElement.GetProperty("access_token").GetString()!;
}
}
```
```bash CLI nocheck
# 1. Request an access token from Okta and write it to a temp file.
ANTHROPIC_IDENTITY_TOKEN_FILE=$(mktemp)
curl -sS "$OKTA_ISSUER/v1/token" \
-d grant_type=client_credentials \
-d scope=anthropic.access \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
--data-urlencode client_assertion="$SIGNED_CLIENT_ASSERTION" \
| jq -r .access_token > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
export ANTHROPIC_IDENTITY_TOKEN_FILE
# 2. Call the Claude API. The CLI reads ANTHROPIC_FEDERATION_RULE_ID,
# ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID, and
# ANTHROPIC_IDENTITY_TOKEN_FILE and performs the exchange.
ant messages create \
--model claude-sonnet-4-6 \
--max-tokens 1024 \
--message '{role: user, content: "Hello, Claude"}'
```
```php PHP nocheck hidelines={1..3}
true,
CURLOPT_POSTFIELDS => http_build_query([
'grant_type' => 'client_credentials',
'scope' => 'anthropic.access',
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
// Build the RFC 7523 client_assertion JWT signed with your Okta app's private key
'client_assertion' => buildSignedClientAssertion(),
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);
return $body['access_token'];
}
$client = new Client(
credentials: new WorkloadIdentityCredentials(
identityTokenProvider: fetchOktaToken(...),
federationRuleId: getenv('ANTHROPIC_FEDERATION_RULE_ID'),
organizationId: getenv('ANTHROPIC_ORGANIZATION_ID'),
serviceAccountId: getenv('ANTHROPIC_SERVICE_ACCOUNT_ID'),
workspaceId: getenv('ANTHROPIC_WORKSPACE_ID') ?: null,
),
);
$message = $client->messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello, Claude']],
);
echo $message->content[0]->text, PHP_EOL;
```
```ruby Ruby nocheck
require "anthropic"
require "json"
require "net/http"
def fetch_okta_token
uri = URI("#{ENV.fetch('OKTA_ISSUER')}/v1/token")
response = Net::HTTP.post_form(
uri,
"grant_type" => "client_credentials",
"scope" => "anthropic.access",
"client_assertion_type" => "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
# Build the RFC 7523 client_assertion JWT signed with your Okta app's private key
"client_assertion" => build_signed_client_assertion
)
JSON.parse(response.body).fetch("access_token")
end
client = Anthropic::Client.new(
credentials: Anthropic::WorkloadIdentityCredentials.new(
identity_token_provider: -> { fetch_okta_token },
federation_rule_id: ENV.fetch("ANTHROPIC_FEDERATION_RULE_ID"),
organization_id: ENV.fetch("ANTHROPIC_ORGANIZATION_ID"),
service_account_id: ENV.fetch("ANTHROPIC_SERVICE_ACCOUNT_ID"),
workspace_id: ENV["ANTHROPIC_WORKSPACE_ID"]
)
)
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{role: "user", content: "Hello, Claude"}]
)
puts message.content.first.text
```
Each SDK tab shows the callable pattern: the Anthropic SDK calls your identity-token provider again whenever the Anthropic access token approaches expiry, so your Okta fetcher should return a fresh token on each call rather than caching one indefinitely. The `ant` CLI re-reads `ANTHROPIC_IDENTITY_TOKEN_FILE` on each exchange, so refresh that file on a timer for long-running shells.
## Verify the setup
A successful exchange returns an `access_token` beginning with `sk-ant-oat01-` and an `expires_in` value in seconds. On `400 invalid_grant`, see [Troubleshoot a failed exchange](/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange); the most common Okta-side cause is an `issuer_url` mismatch (it must include the `/oauth2/` path; the Okta org authorization server is not usable).
## Scope your rule
Multiple service apps under the same Okta authorization server share the same
issuer. A rule that omits `subject_prefix` matches every service app on that
server, so any team that can register one could obtain a federated Anthropic
token.
Lock the rule's `match` block to the narrowest scope that fits your use case:
- **Pin the exact Client ID:** Set `subject_prefix` to the service app's full Client ID with no trailing `*`.
- **Pin the audience:** Match the `audience` value you configured on the authorization server so tokens minted for a different audience are rejected.
- **Match on custom claims:** For finer-grained scoping, add claims in the authorization server's **Claims** tab and match them with the rule's `claims` map or a CEL `condition`.
- **Use one rule per service app:** Create a separate federation rule for each service app rather than sharing one rule across apps.
## Next steps
- Review the [WIF reference](/docs/en/manage-claude/wif-reference) for the full credential resolution order and profile configuration.
- See the [WIF reference](/docs/en/manage-claude/wif-reference#rule-matching-semantics) to match on custom Okta claims with CEL expressions.
---
# Use WIF with SPIFFE
URL: https://platform.claude.com/docs/en/manage-claude/wif-providers/spiffe
# Use WIF with SPIFFE
Authenticate SPIFFE workloads to the Claude API using JWT-SVIDs from SPIRE or any other SPIFFE-conformant issuer.
---
[SPIFFE](https://spiffe.io/) is the CNCF standard for issuing identity to workloads. [SPIRE](https://spiffe.io/docs/latest/spire-about/) is its open-source reference implementation, and several commercial products also issue SPIFFE-conformant identities. Anthropic federates with any SPIFFE implementation that emits OIDC-compatible JWT-SVIDs. Federation works either through an OIDC discovery document at a public HTTPS URL (`discovery` mode; see the [URL constraints](/docs/en/manage-claude/wif-reference#url-fields)) or by registering the JWKS directly (`inline` mode). The JWT-SVID spec defines `sub` as the workload's SPIFFE ID, and the SPIFFE Workload API requires the caller to supply `aud` at fetch time, so those claims are the same across implementations. Anthropic additionally requires `iss` and `iat`, neither of which the JWT-SVID spec mandates, so configure your implementation to populate both (in SPIRE, `iss` is the `jwt_issuer` server setting and `iat` is set automatically). With those in place, the [Configure Anthropic](#configure-anthropic), [Acquire and use the token](#acquire-and-use-the-token), and [Scope your rule](#scope-your-rule) sections of this guide apply to any SPIFFE implementation. For a current list, see [Commercial software that implements SPIFFE](https://spiffe.io/docs/latest/spiffe-about/overview/#commercial-software-that-implements-spiffe) on the SPIFFE project site.
SPIFFE assigns every workload a stable identity URI of the form `spiffe:///`, and SPIRE issues that identity as a JWT-SVID on demand through the Workload API. A JWT-SVID is an ordinary signed JWT whose `sub` claim is the workload's SPIFFE ID and whose `aud` claim is supplied by the workload at fetch time.
The bridge from a SPIRE trust domain to standard OIDC is the [SPIRE OIDC Discovery Provider](https://github.com/spiffe/spire/blob/main/support/oidc-discovery-provider/README.md), a standalone helper that publishes `/.well-known/openid-configuration` and a JWKS endpoint for the trust domain's JWT signing keys. With the discovery provider running, a JWT-SVID validates like any other OIDC token: register the discovery URL as a federation issuer, write a federation rule that matches the workload's SPIFFE ID, and have the workload present its JWT-SVID to Anthropic's token-exchange endpoint.
This page's examples use SPIRE and apply anywhere SPIRE Agent runs: Kubernetes pods, virtual machines, and bare-metal hosts.
If your Kubernetes cluster does not run SPIRE and you want to authenticate with the cluster's native projected service-account tokens instead, see [Use WIF with Kubernetes](/docs/en/manage-claude/wif-providers/kubernetes).
## Prerequisites
- Familiarity with [WIF concepts](/docs/en/manage-claude/workload-identity-federation#concepts): service accounts, federation issuers, and federation rules.
- A SPIFFE deployment with workload identities issued (the examples on this page use SPIRE Server and Agent), and registration entries for the workloads that need to call the Claude API.
- An OIDC discovery endpoint for the trust domain (in SPIRE, the [OIDC Discovery Provider](https://github.com/spiffe/spire/blob/main/support/oidc-discovery-provider/README.md)) running with a publicly reachable HTTPS endpoint, or the JWKS exported for `inline` registration.
- Your SPIFFE issuer configured to set the `iss` claim on JWT-SVIDs to the value you will register as the federation issuer's `issuer_url`. For `discovery` mode, this is the discovery endpoint's public URL (in SPIRE, the `jwt_issuer` server setting).
- JWT-SVIDs available to your workloads. WIF accepts JWT-SVIDs only; X.509-SVIDs are not used.
- Permission to create service accounts, federation issuers, and federation rules in the Claude Console for your Anthropic organization.
The audience value to request when fetching a JWT-SVID is always `https://api.anthropic.com`. Use this value in spiffe-helper's `jwt_audience`, the Workload API `FetchJWTSVID` call, and the federation rule's `audience` matcher.
## Configure SPIRE
The instructions in this section are SPIRE-specific. If you use a different SPIFFE issuer, configure its OIDC discovery endpoint and JWT-SVID retrieval according to its own documentation, then continue at [Configure Anthropic](#configure-anthropic).
If you already run SPIRE with the OIDC Discovery Provider, federating with Anthropic requires three things on the SPIRE side: a `jwt_issuer` that matches the discovery URL, a registration entry for the workload that will call the Claude API, and a way for that workload to fetch a JWT-SVID with the Anthropic audience. The following subsections walk through each. The configuration snippets show only the settings relevant to Anthropic federation; they are not complete SPIRE deployment configs.
Setting up SPIRE for the first time? Deploy SPIRE Server and Agent following the [SPIRE quickstart](https://spiffe.io/docs/latest/try/), then add the [OIDC Discovery Provider](https://github.com/spiffe/spire/blob/main/support/oidc-discovery-provider/README.md) as a separate service alongside SPIRE Server. Discovery-mode federation depends on the provider being deployed and publicly reachable; it is not part of a default SPIRE install.
### Verify the JWT issuer
Anthropic validates a JWT-SVID by matching its `iss` claim against a registered federation issuer and fetching the JWKS from that issuer's discovery document. Two SPIRE settings must agree on the same URL: SPIRE Server's `jwt_issuer` (which becomes the `iss` claim in every minted JWT-SVID) and the OIDC Discovery Provider's `domains` list (which determines the host the discovery document and JWKS are served from). That shared URL is what you register with Anthropic.
The trust domain and the issuer URL are independent. The trust domain (`spiffe://prod.example.com`) scopes the `sub` claim; the issuer URL (`https://oidc-discovery.prod.example.com`) is where Anthropic fetches signing keys. They do not need to share a hostname.
Confirm `jwt_issuer` is set in SPIRE Server's configuration and points at the discovery provider's public URL. The following example also shows a default JWT-SVID lifetime; SPIRE's built-in default is 5 minutes, which is short enough that continuous rotation is required (see [Run spiffe-helper](#run-spiffe-helper)). Anthropic's token-exchange endpoint rejects any identity token whose lifetime exceeds the federation issuer's configured maximum (1 hour by default; see [Validation rules](/docs/en/manage-claude/wif-reference#validation-rules)). This check applies to every SPIFFE implementation, not just SPIRE, so keep `default_jwt_svid_ttl` (or any per-entry override) at or below that maximum.
```text server.conf
server {
trust_domain = "prod.example.com"
jwt_issuer = "https://oidc-discovery.prod.example.com"
default_jwt_svid_ttl = "5m"
# ...
}
```
In the OIDC Discovery Provider's configuration, the same hostname must appear under `domains`, and the provider must be able to reach SPIRE Server's API socket. The provider serves the discovery document and JWKS over HTTPS; terminate TLS with its built-in ACME support or front it with a load balancer that does.
```text oidc-discovery-provider.conf
domains = ["oidc-discovery.prod.example.com"]
server_api {
address = "unix:///run/spire/sockets/private/api.sock"
}
acme {
email = "platform@example.com"
tos_accepted = true
}
```
The example uses `server_api`, which connects the discovery provider to SPIRE Server's privileged API socket. The provider also accepts a `workload_api` block (with `socket_path` and `trust_domain`) that obtains the bundle through a SPIRE Agent's Workload API instead; use it when the discovery provider should not have access to the Server API or runs on a node that cannot reach the Server. On Windows, the `address` field is Unix-only; supply the Server API pipe name by using `server_api { experimental { named_pipe_name = "\\spire-server\\private\\api" } }` instead.
### Register the workload
Each workload that calls the Claude API needs a SPIRE registration entry that maps its runtime selectors to a SPIFFE ID. If the workload is already registered, note its SPIFFE ID; you use it in the federation rule's `subject_prefix`. If not, register it. For a Kubernetes pod, the selectors are typically the namespace and Kubernetes service account:
```bash CLI nocheck
spire-server entry create \
-spiffeID spiffe://prod.example.com/ns/inference/sa/worker \
-parentID spiffe://prod.example.com/spire/agent/k8s_psat/prod-cluster/NODE_UID \
-selector k8s:ns:inference \
-selector k8s:sa:worker
```
The `parentID` shown is a single node's auto-generated agent ID. For cluster-wide registration, parent the entry to a [node alias](https://spiffe.io/docs/latest/deploying/registering/#mapping-workloads-to-multiple-nodes) so it matches workloads on every node, as the [SPIRE Kubernetes quickstart](https://spiffe.io/docs/latest/try/getting-started-k8s/) does.
Workloads outside Kubernetes use host-level selectors such as `unix:uid:1000` (`unix:path` is also available but requires `discover_workload_path = true` in the agent's unix workload attestor configuration). Clusters running [spire-controller-manager](https://github.com/spiffe/spire-controller-manager) can declare entries with the `ClusterSPIFFEID` custom resource instead of calling `spire-server entry create` directly.
### Run spiffe-helper
[spiffe-helper](https://github.com/spiffe/spiffe-helper) is a sidecar utility that connects to the SPIRE Agent socket, fetches a JWT-SVID for a given audience, writes it to a file, and re-fetches it before expiry. The helper runs in daemon mode by default; the example below sets `daemon_mode = true` explicitly.
```text helper.conf
agent_address = "/run/spire/sockets/agent.sock"
cert_dir = "/var/run/secrets/anthropic.com"
daemon_mode = true
jwt_svids = [{
jwt_audience = "https://api.anthropic.com"
jwt_svid_file_name = "token"
}]
```
In Kubernetes, run spiffe-helper as a sidecar container that shares a memory-backed `emptyDir` volume (`medium: Memory`) with your application container so the bearer SVID never lands on the node's disk. Mount the SPIRE Agent socket from the host into the sidecar, mount the shared volume at `/var/run/secrets/anthropic.com` in both containers, and set `ANTHROPIC_IDENTITY_TOKEN_FILE=/var/run/secrets/anthropic.com/token` on the application container. On VMs and bare metal, run spiffe-helper as a system service alongside the workload and point both at a shared directory.
## Configure Anthropic
Follow the [setup walkthrough](/docs/en/manage-claude/workload-identity-federation#set-up-federation) to register a federation issuer, create an Anthropic service account, and create a federation rule in the Claude Console. Use these SPIFFE-specific values.
**Federation issuer:** Register the OIDC Discovery Provider's public URL in `discovery` mode. Anthropic fetches `/.well-known/openid-configuration` from this URL and follows the returned `jwks_uri` to retrieve the trust domain's signing keys.
```json
{
"name": "spire-prod",
"issuer_url": "https://oidc-discovery.prod.example.com",
"jwks": { "type": "discovery" }
}
```
If the discovery provider is not reachable from the public internet, fetch the JWKS yourself (`curl https://oidc-discovery.prod.example.com/keys`) and register the issuer with `"jwks": {"type": "inline", "keys": [...]}` using the contents of the returned `keys` array. In `inline` mode the `issuer_url` is only compared against the JWT-SVID's `iss` claim; Anthropic never attempts to reach it.
SPIRE rotates JWT signing keys frequently, by default on the same cadence as the CA (`ca_ttl`, 24 hours). If you register the issuer with an inline JWKS instead of a discovery URL, you must update the JWKS every time SPIRE rotates: add the new key before workloads start presenting it, and **remove superseded keys** once tokens signed with them have expired. Stale keys left in an inline JWKS remain trusted indefinitely.
To automate JWKS updates without exposing a public discovery endpoint, configure a SPIRE Server [BundlePublisher](https://spiffe.io/docs/latest/deploying/spire_server/#built-in-plugins) plugin (`aws_s3`, `gcp_cloudstorage`, or `k8s_configmap`) with `format = "jwks"` to push the JWT signing keys to external storage on every rotation, then sync that into the issuer's inline keys.
**Federation rule:** Match the JWT-SVID's `sub` (the SPIFFE ID) and the `aud` you configured spiffe-helper to request. SPIFFE IDs are URI strings and `subject_prefix` matches them as opaque text, so an exact value or a trailing-`*` prefix match both work against them. For more complex patterns, use a CEL `condition`.
```json
{
"name": "spire-inference-worker",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "spiffe://prod.example.com/ns/inference/sa/worker",
"audience": "https://api.anthropic.com"
},
"target": {
"type": "service_account",
"service_account_id": "svac_..."
},
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}
```
Be as specific as the workload allows. Loosen `subject_prefix` to `spiffe://prod.example.com/ns/inference/*` only if every workload registered under that path should map to the same Anthropic service account. Add the rule's `fdrl_...` ID to the workload's `ANTHROPIC_FEDERATION_RULE_ID` environment variable.
## Acquire and use the token
The Anthropic SDKs can either read the JWT-SVID from the file that spiffe-helper maintains or call the SPIFFE Workload API directly through a token-provider callable. The file path is the simplest integration and works in every SDK language; the callable path removes the sidecar but requires a SPIFFE Workload API client in your application's language.
With spiffe-helper writing a fresh JWT-SVID to `/var/run/secrets/anthropic.com/token`, set `ANTHROPIC_IDENTITY_TOKEN_FILE` to that path along with `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID`, and `ANTHROPIC_WORKSPACE_ID`. The SDK reads the file on every token exchange, so it always picks up the most recently rotated SVID, and refreshes the Anthropic access token automatically before it expires.
```bash cURL nocheck
JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")
ACCESS_TOKEN=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
--data @- <messages->create(
model: 'claude-sonnet-4-6',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello, Claude']],
);
echo $message->content[0]->text, PHP_EOL;
```
```ruby Ruby nocheck
require "anthropic"
# Reads the JWT-SVID that spiffe-helper writes to
# ANTHROPIC_IDENTITY_TOKEN_FILE, plus ANTHROPIC_FEDERATION_RULE_ID,
# ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID.
client = Anthropic::Client.new
message = client.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{role: "user", content: "Hello, Claude"}]
)
puts message.content.first.text
```
Workloads that link a SPIFFE Workload API client directly can skip spiffe-helper and pass the SDK a callable that fetches a fresh JWT-SVID from the agent socket. The SDK invokes the callable before each token exchange, so the workload always presents an unexpired SVID. Go ([go-spiffe](https://github.com/spiffe/go-spiffe)) and Python ([py-spiffe](https://github.com/HewlettPackard/py-spiffe)) have mature Workload API clients.
```go Go nocheck hidelines={1..14,-1}
package main
import (
"context"
"fmt"
"os"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
"github.com/spiffe/go-spiffe/v2/svid/jwtsvid"
"github.com/spiffe/go-spiffe/v2/workloadapi"
)
func main() {
const audience = "https://api.anthropic.com"
ctx := context.Background()
source, err := workloadapi.NewJWTSource(ctx)
if err != nil {
panic(err)
}
defer source.Close()
fetchJWTSVID := func(ctx context.Context) (string, error) {
svid, err := source.FetchJWTSVID(ctx, jwtsvid.Params{Audience: audience})
if err != nil {
return "", err
}
return svid.Marshal(), nil
}
client := anthropic.NewClient(
option.WithFederationTokenProvider(fetchJWTSVID, option.FederationOptions{
FederationRuleID: os.Getenv("ANTHROPIC_FEDERATION_RULE_ID"),
OrganizationID: os.Getenv("ANTHROPIC_ORGANIZATION_ID"),
ServiceAccountID: os.Getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"),
WorkspaceID: os.Getenv("ANTHROPIC_WORKSPACE_ID"),
}),
)
message, err := client.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, Claude")),
},
})
if err != nil {
panic(err)
}
fmt.Println(message.Content[0].Text)
}
```
```python Python nocheck
import os
import anthropic
from anthropic import WorkloadIdentityCredentials
from spiffe import JwtSource
AUDIENCE = "https://api.anthropic.com"
# Connects to the SPIRE Agent socket at SPIFFE_ENDPOINT_SOCKET.
jwt_source = JwtSource()
def fetch_jwt_svid() -> str:
svid = jwt_source.fetch_svid(audience={AUDIENCE})
return svid.token
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=fetch_jwt_svid,
federation_rule_id=os.environ["ANTHROPIC_FEDERATION_RULE_ID"],
organization_id=os.environ["ANTHROPIC_ORGANIZATION_ID"],
service_account_id=os.environ["ANTHROPIC_SERVICE_ACCOUNT_ID"],
workspace_id=os.environ.get("ANTHROPIC_WORKSPACE_ID"),
),
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(message.content[0].text)
```
For other languages, fetch the JWT-SVID with your runtime's SPIFFE Workload API client (or shell out to `spire-agent api fetch jwt`), write it to a file, and set `ANTHROPIC_IDENTITY_TOKEN_FILE` to that path as in the file-based tab.
## Verify the setup
Before wiring the SDK in, fetch a JWT-SVID directly from SPIRE Agent and confirm the claims match what your federation rule expects. If you use a different SPIFFE implementation, fetch a JWT-SVID with its CLI or Workload API client and decode the payload the same way.
The Workload API attests the calling process. For a Kubernetes registration entry, run this command inside a pod that satisfies the entry's selectors and has the agent socket mounted (for example, by using `kubectl exec`). On VMs and bare metal, run it as the user or process that matches the entry's `unix:` selectors. Running from an unattested host shell returns `no identity issued`, which is the most common verify-step failure.
```bash CLI nocheck
spire-agent api fetch jwt \
-audience https://api.anthropic.com \
-socketPath /run/spire/sockets/agent.sock \
-output json \
| jq -r '.[0].svids[0].svid' \
| jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson'
```
The `-output json` flag returns the SVID response and bundle response as a two-element JSON array, so `jq -r '.[0].svids[0].svid'` extracts the bare token. On older SPIRE versions without `-output`, the command prints a labeled block instead; in that case pipe the default output through `awk '/^[[:space:]]*eyJ/{print $1; exit}'` to extract the token line. Check that `iss` is the OIDC Discovery Provider URL you registered, `sub` is the workload's SPIFFE ID, and `aud` contains `https://api.anthropic.com`. Then run the cURL example from [Acquire and use the token](#acquire-and-use-the-token); a successful exchange returns an `access_token` beginning with `sk-ant-oat01-`. On `400 invalid_grant`, see [Troubleshoot a failed exchange](/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange); the most common SPIRE-side cause is a mismatch between SPIRE Server's `jwt_issuer` and the URL registered as the federation issuer.
## Scope your rule
SPIFFE ID path conventions are operator-defined, so the federation rule's `subject_prefix` matcher should reflect the path scheme your registration entries use. Common schemes include `spiffe:///ns//sa/` (the default emitted by the `ClusterSPIFFEID` resource in spire-controller-manager) and `spiffe:///host//` for VM and bare-metal workloads.
A `subject_prefix` of `spiffe://prod.example.com/*` matches every workload in the trust domain. Without an `audience` matcher, the rule also accepts JWT-SVIDs minted for any audience, including ones the workload requested for unrelated relying parties.
Lock the rule's `match` block to the narrowest scope that fits your use case:
- **Pin to one workload:** Set `subject_prefix` to the full SPIFFE ID with no trailing `*`.
- **Always set an audience:** Require `audience` on the rule and configure spiffe-helper (or the Workload API call) with the same value so SVIDs minted for other relying parties are rejected.
- **Scope by path segment:** Use `spiffe://prod.example.com/ns/inference/*` to grant every workload registered under a namespace, and create a separate rule and Anthropic service account per namespace rather than widening one rule.
- **One issuer per trust domain:** Each SPIRE trust domain has its own signing keys and OIDC Discovery Provider. Register each as a separate federation issuer and bind rules to the issuer that owns the SPIFFE IDs they match.
## Next steps
- [Workload Identity Federation](/docs/en/manage-claude/workload-identity-federation): concepts, the token-exchange flow, and SDK configuration options.
- [WIF reference](/docs/en/manage-claude/wif-reference): environment variables, JWKS source modes, and rule match modes.
- [Use WIF with Kubernetes](/docs/en/manage-claude/wif-providers/kubernetes): for clusters that use native projected service-account tokens instead of SPIRE.
### Monitoring
---
# Claude Code Analytics API
URL: https://platform.claude.com/docs/en/manage-claude/claude-code-analytics-api
# Claude Code Analytics API
Programmatically access your organization's Claude Code usage analytics and productivity metrics with the Claude Code Analytics Admin API.
---
**The Admin API is unavailable for individual accounts.** To collaborate with teammates and add members, set up your organization in **Console → Settings → Organization**.
The Claude Code Analytics Admin API provides programmatic access to daily aggregated usage metrics for Claude Code users, enabling organizations to analyze developer productivity and build custom dashboards. This API bridges the gap between the basic [Analytics dashboard](/claude-code) and the complex OpenTelemetry integration.
This API enables you to better monitor, analyze, and optimize your Claude Code adoption:
* **Developer productivity analysis:** Track sessions, lines of code added/removed, commits, and pull requests created using Claude Code
* **Tool usage metrics:** Monitor acceptance and rejection rates for different Claude Code tools (Edit, MultiEdit, Write, NotebookEdit)
* **Cost analysis:** View estimated costs and token usage broken down by Claude model
* **Custom reporting:** Export data to build executive dashboards and reports for management teams
* **Usage justification:** Provide metrics to justify and expand Claude Code adoption internally
**Admin API key required**
This API is part of the [Admin API](/docs/en/manage-claude/admin-api). These endpoints require an Admin API key (starting with `sk-ant-admin...`) that differs from standard API keys. Only organization members with the admin role can provision Admin API keys through the [Claude Console](/settings/admin-keys).
**Claude Platform on AWS:** The Claude Code Analytics API is not currently available. View Claude Code usage on the **Usage** page in the Claude Console instead.
## Quick start
Get your organization's Claude Code analytics for a specific day:
```bash cURL
curl "https://api.anthropic.com/v1/organizations/usage_report/claude_code?\
starting_at=2025-09-08&\
limit=20" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ADMIN_API_KEY"
```
**Set a User-Agent header for integrations**
If you're building an integration, set your User-Agent header to help us understand usage patterns:
```text
User-Agent: YourApp/1.0.0 (https://yourapp.com)
```
## Claude Code Analytics API
Track Claude Code usage, productivity metrics, and developer activity across your organization with the `/v1/organizations/usage_report/claude_code` endpoint.
### Key concepts
- **Daily aggregation**: Returns metrics for a single day specified by the `starting_at` parameter
- **User-level data**: Each record represents one user's activity for the specified day
- **Productivity metrics**: Track sessions, lines of code, commits, pull requests, and tool usage
- **Token and cost data**: Monitor usage and estimated costs broken down by Claude model
- **Cursor-based pagination**: Handle large datasets with stable pagination using opaque cursors
- **Data freshness**: Metrics are available with up to 1-hour delay for consistency
For complete parameter details and response schemas, see the [Claude Code Analytics API reference](/docs/en/api/admin-api/claude-code/get-claude-code-usage-report).
### Basic examples
#### Get analytics for a specific day
```bash cURL
curl "https://api.anthropic.com/v1/organizations/usage_report/claude_code?\
starting_at=2025-09-08" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ADMIN_API_KEY"
```
#### Get analytics with pagination
```bash cURL
# First request
curl "https://api.anthropic.com/v1/organizations/usage_report/claude_code?\
starting_at=2025-09-08&\
limit=20" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ADMIN_API_KEY"
# Subsequent request using cursor from response
curl "https://api.anthropic.com/v1/organizations/usage_report/claude_code?\
starting_at=2025-09-08&\
page=page_MjAyNS0wNS0xNFQwMDowMDowMFo=" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ADMIN_API_KEY"
```
### Request parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `starting_at` | string | Yes | UTC date in YYYY-MM-DD format; returns metrics for this single day only |
| `limit` | integer | No | Number of records per page (default: 20, max: 1000) |
| `page` | string | No | Opaque cursor token from previous response's `next_page` field |
### Available metrics
Each response record contains the following metrics for a single user on a single day:
#### Dimensions
- **date**: Date in RFC 3339 format (UTC timestamp)
- **actor**: The user or API key that performed the Claude Code actions (either `user_actor` with `email_address` or `api_actor` with `api_key_name`)
- **organization_id**: Organization UUID
- **customer_type**: Type of customer account (`api` for API customers, `subscription` for Pro/Team customers)
- **terminal_type**: Type of terminal or environment where Claude Code was used (e.g., `vscode`, `iTerm.app`, `tmux`)
#### Core metrics
- **num_sessions**: Number of distinct Claude Code sessions initiated by this actor
- **lines_of_code.added**: Total number of lines of code added across all files by Claude Code
- **lines_of_code.removed**: Total number of lines of code removed across all files by Claude Code
- **commits_by_claude_code**: Number of git commits created through Claude Code's commit functionality
- **pull_requests_by_claude_code**: Number of pull requests created through Claude Code's PR functionality
#### Tool action metrics
Breakdown of tool action acceptance and rejection rates by tool type:
- **edit_tool.accepted/rejected:** Number of Edit tool proposals that the user accepted/rejected
- **multi_edit_tool.accepted/rejected:** Number of MultiEdit tool proposals that the user accepted/rejected
- **write_tool.accepted/rejected:** Number of Write tool proposals that the user accepted/rejected
- **notebook_edit_tool.accepted/rejected:** Number of NotebookEdit tool proposals that the user accepted/rejected
#### Model breakdown
For each Claude model used:
- **model**: Claude model identifier (e.g., `claude-opus-4-7`)
- **tokens.input/output**: Input and output token counts for this model
- **tokens.cache_read/cache_creation**: Cache-related token usage for this model
- **estimated_cost.amount**: Estimated cost in cents USD for this model
- **estimated_cost.currency**: Currency code for the cost amount (currently always `USD`)
### Response structure
The API returns data in the following format:
```json
{
"data": [
{
"date": "2025-09-08T00:00:00Z",
"actor": {
"type": "user_actor",
"email_address": "developer@company.com"
},
"organization_id": "dc9f6c26-b22c-4831-8d01-0446bada88f1",
"customer_type": "api",
"terminal_type": "vscode",
"core_metrics": {
"num_sessions": 5,
"lines_of_code": {
"added": 1543,
"removed": 892
},
"commits_by_claude_code": 12,
"pull_requests_by_claude_code": 2
},
"tool_actions": {
"edit_tool": {
"accepted": 45,
"rejected": 5
},
"multi_edit_tool": {
"accepted": 12,
"rejected": 2
},
"write_tool": {
"accepted": 8,
"rejected": 1
},
"notebook_edit_tool": {
"accepted": 3,
"rejected": 0
}
},
"model_breakdown": [
{
"model": "claude-opus-4-7",
"tokens": {
"input": 100000,
"output": 35000,
"cache_read": 10000,
"cache_creation": 5000
},
"estimated_cost": {
"currency": "USD",
"amount": 1025
}
}
]
}
],
"has_more": false,
"next_page": null
}
```
## Pagination
The API supports cursor-based pagination for organizations with large numbers of users:
1. Make your initial request with optional `limit` parameter
2. If `has_more` is `true` in the response, use the `next_page` value in your next request
3. Continue until `has_more` is `false`
The cursor encodes the position of the last record and ensures stable pagination even as new data arrives. Each pagination session maintains a consistent data boundary to ensure you don't miss or duplicate records.
## Common use cases
- **Executive dashboards**: Create high-level reports showing Claude Code impact on development velocity
- **AI tool comparison**: Export metrics to compare Claude Code with other AI coding tools like Copilot and Cursor
- **Developer productivity analysis**: Track individual and team productivity metrics over time
- **Cost tracking and allocation**: Monitor spending patterns and allocate costs by team or project
- **Adoption monitoring**: Identify which teams and users are getting the most value from Claude Code
- **ROI justification**: Provide concrete metrics to justify and expand Claude Code adoption internally
## Frequently asked questions
### How fresh is the analytics data?
Claude Code analytics data typically appears within 1 hour of user activity completion. To ensure consistent pagination results, only data older than 1 hour is included in responses.
### Can I get real-time metrics?
No, this API provides daily aggregated metrics only. For real-time monitoring, consider using the [OpenTelemetry integration](https://code.claude.com/docs/en/monitoring-usage).
### How are users identified in the data?
Users are identified through the `actor` field in two ways:
- **`user_actor`:** Contains `email_address` for users who authenticate through OAuth (most common)
- **`api_actor`:** Contains `api_key_name` for users who authenticate with an API key
The `customer_type` field indicates whether the usage is from `api` customers (pay-as-you-go API) or `subscription` customers (Pro/Team plans).
### What's the data retention period?
Historical Claude Code analytics data is retained and accessible through the API. There is no specified deletion period for this data.
### Which Claude Code deployments are supported?
This API only tracks Claude Code usage on the Claude API. Usage through [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws), [Claude in Microsoft Foundry](/docs/en/build-with-claude/claude-in-microsoft-foundry), [Claude in Amazon Bedrock](/docs/en/build-with-claude/claude-in-amazon-bedrock), or [Claude on Vertex AI](/docs/en/build-with-claude/claude-on-vertex-ai) is not included.
### What does it cost to use this API?
The Claude Code Analytics API is free to use for all organizations with access to the Admin API.
### How do I calculate tool acceptance rates?
Tool acceptance rate = `accepted / (accepted + rejected)` for each tool type. For example, if the edit tool shows 45 accepted and 5 rejected, the acceptance rate is 90%.
### What time zone is used for the date parameter?
All dates are in UTC. The `starting_at` parameter should be in YYYY-MM-DD format and represents UTC midnight for that day.
## See also
The Claude Code Analytics API helps you understand and optimize your team's development workflow. Learn more about related features:
- [Admin API](/docs/en/manage-claude/admin-api)
- [Admin API reference](/docs/en/api/admin)
- [Claude Code Analytics dashboard](/claude-code)
- [Usage and Cost API](/docs/en/manage-claude/usage-cost-api) - Track API usage across all Anthropic services
- [Compliance API](/docs/en/manage-claude/compliance-api) - Retrieve audit and activity data
- [Identity and access management](https://code.claude.com/docs/en/iam)
- [Monitoring usage with OpenTelemetry](https://code.claude.com/docs/en/monitoring-usage) for custom metrics and alerting
---
# Rate Limits API
URL: https://platform.claude.com/docs/en/manage-claude/rate-limits-api
# Rate Limits API
Programmatically query your organization's API rate limits with the Rate Limits API.
---
**The Admin API is unavailable for individual accounts.** To collaborate with teammates and add members, set up your organization in **Console → Settings → Organization**.
The Rate Limits API provides programmatic access to the rate limits configured for your organization and its workspaces. This is the same information shown on the [Limits](/settings/limits) page in the Claude Console.
Use this API to:
- **Keep gateways and proxies in sync:** Read your current limits at startup and on a schedule instead of hardcoding values that drift when Anthropic adjusts them.
- **Power internal alerting:** Compare usage data from the [Usage and Cost API](/docs/en/manage-claude/usage-cost-api) against your configured limits.
- **Audit workspace configuration:** Verify that workspace overrides match what your provisioning automation expects.
**Admin API key required**
This API is part of the [Admin API](/docs/en/manage-claude/admin-api). These endpoints require an Admin API key (starting with `sk-ant-admin...`) that differs from standard API keys. Only organization members with the admin role can provision Admin API keys through the [Claude Console](/settings/admin-keys).
## Quick start
List the rate limits configured for your organization:
```bash cURL
curl "https://api.anthropic.com/v1/organizations/rate_limits" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
## Organization rate limits
The `/v1/organizations/rate_limits` endpoint returns the rate limits applied at the organization level for the Messages API and its supporting resources. Limits for other products, such as [Claude Managed Agents](/docs/en/managed-agents/overview), are not included.
### Key concepts
- **Rate limit groups:** Each entry in the response represents one rate limit group. Model rate limits are grouped so that several model versions share a single set of limits, and other groups cover resources such as the Message Batches API, the Files API, the Token Counting API, agent skills, and the web search tool.
- **`group_type`:** Identifies which category of limits the entry covers. See [Filtering by group type](#filtering-by-group-type) for the list of values.
- **`models` list:** For `model_group` entries, the `models` field lists every model ID and alias that counts against that group's limits. Use this list to look up which group any model string falls under. For other group types, `models` is `null`.
- **`limits` list:** Each group carries a list of `{type, value}` pairs. The `type` field identifies the limiter (such as `requests_per_minute`, `input_tokens_per_minute`, or `output_tokens_per_minute`) and `value` is the configured limit. See [Rate limits](/docs/en/api/rate-limits) for how each limiter is measured and enforced.
For complete parameter details and response schemas, see the [Organization Rate Limits API reference](/docs/en/api/admin/rate_limits/list).
### List all organization rate limits
```bash cURL
curl "https://api.anthropic.com/v1/organizations/rate_limits" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
```json
{
"data": [
{
"type": "rate_limit",
"group_type": "model_group",
"models": [
"claude-opus-4-5",
"claude-opus-4-5-20251101",
"claude-opus-4-6",
"claude-opus-4-7"
],
"limits": [
{ "type": "requests_per_minute", "value": 4000 },
{ "type": "input_tokens_per_minute", "value": 10000000 },
{ "type": "output_tokens_per_minute", "value": 800000 }
]
},
{
"type": "rate_limit",
"group_type": "batch",
"models": null,
"limits": [{ "type": "enqueued_batch_requests", "value": 500000 }]
}
],
"next_page": null
}
```
### Look up the limits for a specific model
Pass any model ID or alias as the `model` query parameter to return only the entry that contains it:
```bash cURL
curl "https://api.anthropic.com/v1/organizations/rate_limits?model=claude-opus-4-7" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
If the model string doesn't match any group, the endpoint returns a 404 error. The `model` parameter is supported on the organization endpoint only; the workspace endpoint doesn't accept it.
## Workspace rate limits
The `/v1/organizations/workspaces/{workspace_id}/rate_limits` endpoint returns the rate limit overrides configured for a single workspace.
The response only includes overrides, so anything missing from it is inherited from the organization:
- A group that is absent from `data` has no workspace override at all. The workspace inherits the organization-level limits for that group (it is not unlimited).
- Within a group that is present, a limiter type that is absent from `limits[]` has no workspace override for that limiter. The workspace inherits the organization value for it.
- For each limiter that is present, `org_limit` is the organization-level value for the same limiter, or `null` if the organization has no configured limit for that limiter type.
For complete parameter details and response schemas, see the [Workspace Rate Limits API reference](/docs/en/api/admin/workspaces/rate_limits/list).
To retrieve your organization's workspace IDs, use the [List Workspaces](/docs/en/api/admin/workspaces/list) endpoint, or find them in the [Claude Console](/settings/workspaces). The default workspace cannot have rate limit overrides, so it has no entry on this endpoint; use the organization endpoint to read its limits.
```bash cURL
curl "https://api.anthropic.com/v1/organizations/workspaces/wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ/rate_limits" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
```json
{
"data": [
{
"type": "workspace_rate_limit",
"group_type": "model_group",
"models": [
"claude-opus-4-5",
"claude-opus-4-5-20251101",
"claude-opus-4-6",
"claude-opus-4-7"
],
"limits": [
{ "type": "requests_per_minute", "value": 1000, "org_limit": 4000 },
{ "type": "input_tokens_per_minute", "value": 500000, "org_limit": 10000000 }
]
}
],
"next_page": null
}
```
## Filtering by group type
Both endpoints accept an optional `group_type` query parameter that restricts the response to a single category:
```bash cURL
curl "https://api.anthropic.com/v1/organizations/rate_limits?group_type=batch" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
Valid values are `model_group`, `batch`, `token_count`, `files`, `skills`, and `web_search`.
## Pagination
Both endpoints accept a `page` query parameter and return a `next_page` field. Responses are currently always a single page, so `next_page` is `null`. Loop on `next_page` so your client paginates correctly without changes when the response grows.
## Frequently asked questions
### Which model strings appear in the `models` list?
Every model ID and alias that counts against the group, including dated IDs (such as `claude-sonnet-4-5-20250929`) and undated aliases (such as `claude-sonnet-4-5`). Look up any model string you pass to the Messages API and you'll find it in exactly one `model_group` entry.
### What does it mean if a group is missing from the workspace response?
The workspace has no override for that group and inherits the organization-level limit. Query the organization endpoint to see the inherited values.
### Can I update rate limits with this API?
No. To set workspace rate limits, open the workspace in the [Claude Console](/settings/workspaces) and use the **Limits** tab.
## See also
- [Rate limits](/docs/en/api/rate-limits)
- [Admin API](/docs/en/manage-claude/admin-api)
- [Admin API reference](/docs/en/api/admin)
- [Workspaces](/docs/en/manage-claude/workspaces)
- [Usage and Cost API](/docs/en/manage-claude/usage-cost-api)
---
# Usage and Cost API
URL: https://platform.claude.com/docs/en/manage-claude/usage-cost-api
# Usage and Cost API
Programmatically access your organization's API usage and cost data with the Usage & Cost Admin API.
---
**The Admin API is unavailable for individual accounts.** To collaborate with teammates and add members, set up your organization in **Console → Settings → Organization**.
The Usage & Cost Admin API provides programmatic and granular access to historical API usage and cost data for your organization. This data is similar to the information available in the [Usage](/usage) and [Cost](/cost) pages of the Claude Console.
This API enables you to better monitor, analyze, and optimize your Claude implementations:
* **Accurate Usage Tracking:** Get precise token counts and usage patterns instead of relying solely on response token counting
* **Cost Reconciliation:** Match internal records with Anthropic billing for finance and accounting teams
* **Product performance and improvement:** Monitor product performance while measuring if changes to the system have improved it, or setup alerting
* **[Rate limit](/docs/en/api/rate-limits) and [Priority Tier](/docs/en/api/service-tiers#get-started-with-priority-tier) optimization:** Optimize features like [prompt caching](/docs/en/build-with-claude/prompt-caching) or specific prompts to make the most of one’s allocated capacity, or purchase dedicated capacity.
* **Advanced Analysis:** Perform deeper data analysis than what's available in Console
**Admin API key required**
This API is part of the [Admin API](/docs/en/manage-claude/admin-api). These endpoints require an Admin API key (starting with `sk-ant-admin...`) that differs from standard API keys. Only organization members with the admin role can provision Admin API keys through the [Claude Console](/settings/admin-keys).
**Claude Platform on AWS:** The programmatic Usage and Cost API endpoints are not currently available. View usage and cost data on the **Usage** and **Cost** pages in the Claude Console instead.
## Partner solutions
Leading observability platforms offer ready-to-use integrations for monitoring your Claude API usage and cost, without writing custom code. These integrations provide dashboards, alerting, and analytics to help you manage your API usage effectively.
Cloud intelligence platform for tracking and forecasting costs
LLM Observability with automatic tracing and monitoring
Agentless integration for easy LLM observability with out-of-the-box dashboards and alerts
Advanced querying and visualization through OpenTelemetry
FinOps platform for LLM cost & usage observability
## Quick start
Get your organization's daily usage for the last 7 days:
```bash cURL
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2025-01-08T00:00:00Z&\
ending_at=2025-01-15T00:00:00Z&\
bucket_width=1d" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
**Set a User-Agent header for integrations**
If you're building an integration, set your User-Agent header to help us understand usage patterns:
```text
User-Agent: YourApp/1.0.0 (https://yourapp.com)
```
## Usage API
Track token consumption across your organization with detailed breakdowns by model, workspace, and service tier with the `/v1/organizations/usage_report/messages` endpoint.
### Key concepts
- **Time buckets**: Aggregate usage data in fixed intervals (`1m`, `1h`, or `1d`)
- **Token tracking**: Measure uncached input, cached input, cache creation, and output tokens
- **Filtering & grouping**: Filter by API key, workspace, model, service tier, context window, [data residency](/docs/en/manage-claude/data-residency), or speed (beta), and group results by these dimensions
- **Server tool usage**: Track usage of server-side tools like web search
For complete parameter details and response schemas, see the [Usage API reference](/docs/en/api/admin-api/usage-cost/get-messages-usage-report).
### Basic examples
#### Daily usage by model
```bash cURL
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2025-01-01T00:00:00Z&\
ending_at=2025-01-08T00:00:00Z&\
group_by[]=model&\
bucket_width=1d" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
#### Hourly usage with filtering
```bash cURL
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2025-01-15T00:00:00Z&\
ending_at=2025-01-15T23:59:59Z&\
models[]=claude-opus-4-7&\
service_tiers[]=batch&\
context_window[]=0-200k&\
bucket_width=1h" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
#### Filter usage by API keys and workspaces
```bash cURL
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2025-01-01T00:00:00Z&\
ending_at=2025-01-08T00:00:00Z&\
api_key_ids[]=apikey_01Rj2N8SVvo6BePZj99NhmiT&\
api_key_ids[]=apikey_01ABC123DEF456GHI789JKL&\
workspace_ids[]=wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ&\
workspace_ids[]=wrkspc_01XYZ789ABC123DEF456MNO&\
bucket_width=1d" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
To retrieve your organization's API key IDs, use the [List API Keys](/docs/en/api/admin-api/apikeys/list-api-keys) endpoint.
To retrieve your organization's workspace IDs, use the [List Workspaces](/docs/en/api/admin-api/workspaces/list-workspaces) endpoint, or find your organization's workspace IDs in the Claude Console.
#### Data residency
Track your [data residency controls](/docs/en/manage-claude/data-residency) by grouping and filtering usage with the `inference_geo` dimension. This is useful for verifying geographic routing across your organization.
```bash cURL
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2026-02-01T00:00:00Z&\
ending_at=2026-02-08T00:00:00Z&\
group_by[]=inference_geo&\
group_by[]=model&\
bucket_width=1d" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
You can also filter to a specific geo. Valid values are `global`, `us`, and `not_available`:
```bash cURL
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2026-02-01T00:00:00Z&\
ending_at=2026-02-08T00:00:00Z&\
inference_geos[]=us&\
group_by[]=model&\
bucket_width=1d" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
Models released before February 2026 (prior to Claude Opus 4.6 and Claude Sonnet 4.6) don't support the `inference_geo` request parameter, so their usage reports return `"not_available"` for this dimension. You can use `not_available` as a filter value in `inference_geos[]` to target those models.
#### Fast mode (beta: research preview)
Track [fast mode](/docs/en/build-with-claude/fast-mode) usage by grouping and filtering with the `speed` dimension. This is useful for monitoring standard vs. fast mode usage.
```bash cURL
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2026-02-01T00:00:00Z&\
ending_at=2026-02-08T00:00:00Z&\
group_by[]=speed&\
group_by[]=model&\
bucket_width=1d" \
--header "anthropic-version: 2023-06-01" \
--header "anthropic-beta: fast-mode-2026-02-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
You can also filter to a specific speed. Valid values are `standard` and `fast`:
```bash cURL
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2026-02-01T00:00:00Z&\
ending_at=2026-02-08T00:00:00Z&\
speeds[]=fast&\
group_by[]=model&\
bucket_width=1d" \
--header "anthropic-version: 2023-06-01" \
--header "anthropic-beta: fast-mode-2026-02-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
Both the `speeds[]` filter and the `speed` group_by value require the `fast-mode-2026-02-01` beta header.
### Time granularity limits
| Granularity | Default Limit | Maximum Limit | Use Case |
|-------------|---------------|---------------|----------|
| `1m` | 60 buckets | 1440 buckets | Real-time monitoring |
| `1h` | 24 buckets | 168 buckets | Daily patterns |
| `1d` | 7 buckets | 31 buckets | Weekly/monthly reports |
## Cost API
Retrieve service-level cost breakdowns in USD with the `/v1/organizations/cost_report` endpoint.
### Key concepts
- **Currency**: All costs in USD, reported as decimal strings in lowest units (cents)
- **Cost types**: Track token usage, web search, and code execution costs
- **Grouping**: Group costs by workspace or description for detailed breakdowns. When grouping by `description`, responses include parsed fields like `model` and `inference_geo`
- **Time buckets**: Daily granularity only (`1d`)
For complete parameter details and response schemas, see the [Cost API reference](/docs/en/api/admin-api/usage-cost/get-cost-report).
Priority Tier costs use a different billing model and are not included in the cost endpoint. Track Priority Tier usage through the usage endpoint instead.
### Basic example
```bash cURL
curl "https://api.anthropic.com/v1/organizations/cost_report?\
starting_at=2025-01-01T00:00:00Z&\
ending_at=2025-01-31T00:00:00Z&\
group_by[]=workspace_id&\
group_by[]=description" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
## Pagination
Both endpoints support pagination for large datasets:
1. Make your initial request
2. If `has_more` is `true`, use the `next_page` value in your next request
3. Continue until `has_more` is `false`
```bash cURL
# First request
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2025-01-01T00:00:00Z&\
ending_at=2025-01-31T00:00:00Z&\
limit=7" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
# Response includes: "has_more": true, "next_page": "page_xyz..."
# Next request with pagination
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2025-01-01T00:00:00Z&\
ending_at=2025-01-31T00:00:00Z&\
limit=7&\
page=page_xyz..." \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ANTHROPIC_ADMIN_KEY"
```
## Common use cases
Explore detailed implementations in [Claude Cookbook](https://platform.claude.com/cookbooks):
- **Daily usage reports**: Track token consumption trends
- **Cost attribution**: Allocate expenses by workspace for chargebacks
- **Cache efficiency**: Measure and optimize prompt caching
- **Budget monitoring**: Set up alerts for spending thresholds
- **CSV export**: Generate reports for finance teams
## Frequently asked questions
### How fresh is the data?
Usage and cost data typically appears within 5 minutes of API request completion, though delays may occasionally be longer.
### What's the recommended polling frequency?
The API supports polling once per minute for sustained use. For short bursts (e.g., downloading paginated data), more frequent polling is acceptable. Cache results for dashboards that need frequent updates.
### How do I track code execution usage?
Code execution costs appear in the cost endpoint grouped under `Code Execution Usage` in the description field. Code execution is not included in the usage endpoint.
### How do I track Priority Tier usage?
Filter or group by `service_tier` in the usage endpoint and look for the `priority` value. Priority Tier costs are not available in the cost endpoint.
### What happens with Workbench usage?
API usage from the Workbench is not associated with an API key, so `api_key_id` will be `null` even when grouping by that dimension.
### How is the default workspace represented?
Usage and costs attributed to the default workspace have a `null` value for `workspace_id`.
### How do I get per-user cost breakdowns for Claude Code?
Use the [Claude Code Analytics API](/docs/en/manage-claude/claude-code-analytics-api), which provides per-user estimated costs and productivity metrics without the performance limitations of breaking down costs by many API keys. For general API usage with many keys, use the [Usage API](#usage-api) to track token consumption as a cost proxy.
## See also
The Usage and Cost APIs can be used to help you deliver a better experience for your users, help you manage costs, and preserve your rate limit. Learn more about some of these other features:
- [Admin API](/docs/en/manage-claude/admin-api)
- [Admin API reference](/docs/en/api/admin)
- [Pricing](/docs/en/about-claude/pricing)
- [Prompt caching](/docs/en/build-with-claude/prompt-caching) - Optimize costs with caching
- [Batch processing](/docs/en/build-with-claude/batch-processing) - 50% discount on batch requests
- [Rate limits](/docs/en/api/rate-limits) - Understand usage tiers
- [Rate Limits API](/docs/en/manage-claude/rate-limits-api) - Read your configured rate limits
- [Data residency](/docs/en/manage-claude/data-residency) - Control inference geography
### Data & compliance
---
# API and data retention
URL: https://platform.claude.com/docs/en/manage-claude/api-and-data-retention
# API and data retention
Learn about how Anthropic's APIs and associated features retain data, including information about zero data retention (ZDR) and HIPAA-ready API access.
---
Information about Anthropic's standard retention policies is set out in [Anthropic's commercial data retention policy](https://privacy.claude.com/en/articles/7996866-how-long-do-you-store-my-organization-s-data) and [consumer data retention policy](https://privacy.claude.com/en/articles/10023548-how-long-do-you-store-my-data).
Anthropic offers two data handling arrangements for the Claude API:
- **Zero data retention (ZDR):** Customer data is not stored at rest after the API response is returned, except where needed to comply with law or combat misuse.
- **HIPAA readiness:** For organizations handling protected health information (PHI), Anthropic offers HIPAA-ready API access with a signed Business Associate Agreement (BAA). See [HIPAA readiness](#hipaa-readiness).
## Anthropic's approach to data retention
Different APIs and features have different storage and retention needs. Where an API or feature doesn't require storage of customer prompts or responses, it may be eligible for ZDR. Where an API or feature necessarily requires storage of customer prompts or responses, Anthropic designs for the smallest possible retention footprint. For these features:
- Retained data is never used for model training without your express permission.
- Only what is technically necessary for the API and feature to work is retained. Conversation content (your prompts and Claude's outputs) is never retained unless explicitly noted.
- Data is purged on the shortest practical TTL, and Anthropic aims to give customers control over how long data is retained. What is held, and the retention duration where a specific TTL applies, is documented on each feature's page.
Data accessible through the [Compliance API](/docs/en/manage-claude/compliance-api) follows its own retention model. The [Activity Feed](/docs/en/manage-claude/compliance-activity-feed) retains data for 6 years. Chat, file, and project content from claude.ai follows your organization's retention policy, set in **claude.ai** > **Organization settings** > **Data and privacy**.
In the [feature eligibility table](#feature-eligibility), some features are marked "Yes (qualified)" in the ZDR eligible column. If your organization has a ZDR arrangement, you can use these features with confidence that what Anthropic retains is narrow and is required for optimal performance.
## Zero data retention (ZDR) scope
**What ZDR covers**
- **Certain Claude APIs:** ZDR applies to the Claude Messages and Token Counting APIs
- **Claude Code:** ZDR applies when used with Commercial organization API keys or through Claude Enterprise (see [Claude Code ZDR docs](https://code.claude.com/docs/en/zero-data-retention))
**What ZDR does NOT cover**
- **Console and Workbench:** Any usage on Console or Workbench
- **Claude Managed Agents:** Claude Managed Agents is a stateful resource. You can delete session transcripts, but there is no automatic deletion.
- **Claude consumer products:** Claude Free, Pro, or Max plans, including when customers on those plans use Claude's web, desktop, or mobile apps or Claude Code
- **Claude Teams and Claude Enterprise:** Claude Teams and Claude Enterprise product interfaces are **not ZDR-eligible**, except for Claude Code when used through Claude Enterprise with ZDR enabled for the organization. For other product interfaces, only Commercial organization API keys are eligible for ZDR.
- **Third-party integrations:** Data processed by third-party websites, tools, or other integrations is **not ZDR-eligible**, though some may have similar offerings. When using external services in conjunction with the Claude API, make sure to review those services' data handling practices.
For the most up-to-date information on what products and features are ZDR-eligible, refer to your contract terms or contact your Anthropic account representative.
## HIPAA readiness
The Claude API supports HIPAA-ready integrations for organizations that handle protected health information (PHI). With a signed BAA and a HIPAA-enabled organization, you can use supported API features to process PHI while supporting your organization's HIPAA compliance.
Previously, organizations that required HIPAA readiness for the Claude API needed to enable ZDR. HIPAA-ready API access removes this requirement and provides a foundation for Anthropic to progressively enable additional features as they are audited for HIPAA readiness.
This page covers HIPAA readiness for the Claude API. For the full HIPAA Implementation Guide covering Claude Enterprise and configuration requirements, see the [Anthropic Trust Center](https://trust.anthropic.com/resources).
### Getting started
To set up HIPAA-ready API access:
Contact the [Anthropic sales team](https://claude.com/contact-sales) to sign a BAA that covers API usage.
Anthropic provisions a dedicated organization with HIPAA readiness controls enabled. This organization automatically enforces feature restrictions, blocking API requests that use non-eligible features.
Use the [feature eligibility table](#feature-eligibility) to confirm which features are supported. Review the [PHI handling guidelines](#phi-handling-guidelines) for features that require specific restrictions on where PHI can appear. For detailed configuration and compliance requirements, refer to the [HIPAA Implementation Guide](https://trust.anthropic.com/resources).
HIPAA readiness is enforced at the organization level. If you need both HIPAA-ready and general-purpose API access, use separate organizations for each.
### HIPAA readiness scope
**What HIPAA readiness covers**
- **Claude API:** HIPAA readiness applies to the Claude API (`api.anthropic.com`) for eligible features listed in the [feature eligibility table](#feature-eligibility).
**What HIPAA readiness does NOT cover**
- **Claude consumer products:** Claude Free, Pro, or Max plans
- **Console and Workbench:** Usage through the Claude Console interface
- **Partner-operated platforms:** Amazon Bedrock or Vertex AI (refer to those platforms' compliance documentation)
- **Claude Platform on AWS and Microsoft Foundry:** HIPAA readiness is not available
- **Third-party integrations:** Data processed by external tools or services connected to your application
- **Claude Code:** Claude Code is not covered under HIPAA readiness
- **Beta features:** Features in beta are generally not covered under the BAA unless explicitly listed as eligible in the [feature eligibility table](#feature-eligibility)
### PHI handling guidelines
Protected health information (PHI) includes any individually identifiable health information. In the context of the Claude API, PHI typically appears in:
- Message content (prompts and responses from Claude)
- Attached files (images, PDFs)
- File names and metadata associated with message content
The following fields are not expected to contain PHI under the BAA: workspace names, user information (name, email, phone number), billing data, and support tickets.
#### Schema and tool definition restrictions
When using [structured outputs](/docs/en/build-with-claude/structured-outputs) or tools with `strict: true`, the API compiles JSON schemas into grammars that are cached separately from message content. These cached schemas do not receive the same PHI protections as prompts and responses.
**Do not include PHI in JSON schema definitions.** This restriction applies to:
- Schema property names
- `enum` values
- `const` values
- `pattern` regular expressions
Patient-specific information should only appear in message content, where it is protected under HIPAA safeguards.
### HIPAA error handling
Your signed BAA is the official source of truth for which features are covered. The API also enforces these restrictions automatically: when a HIPAA-enabled organization sends a request that includes a non-eligible feature, the API returns a `400` error to prevent accidental use of features not covered by your BAA:
```json
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "The requested features are not available for HIPAA-regulated organizations without Zero Data Retention: code_execution."
}
}
```
The error message lists the non-eligible features detected in the request. Remove these features from your request and retry.
## Feature eligibility
The following table lists which Claude API features are eligible for ZDR and HIPAA readiness arrangements. For HIPAA-enabled organizations, features marked "No" in the HIPAA column are automatically blocked, and requests that include them return a `400` error.
| Feature | Endpoint | ZDR eligible | HIPAA eligible | Details |
| ------- | -------- | ------------ | -------------- | ------- |
| [Messages API](/docs/en/build-with-claude/working-with-messages) | `/v1/messages` | Yes | Yes | Standard API calls for generating Claude responses. |
| [Token counting](/docs/en/build-with-claude/token-counting) | `/v1/messages/count_tokens` | Yes | Yes | Count tokens before sending requests. |
| [Web search](/docs/en/agents-and-tools/tool-use/web-search-tool) | `/v1/messages` (with `web_search` tool) | Yes1 | Yes1 | Real-time web search results returned in the API response. |
| [Web fetch](/docs/en/agents-and-tools/tool-use/web-fetch-tool) | `/v1/messages` (with `web_fetch` tool) | Yes12 | No | Fetched web content returned in the API response. |
| [Advisor tool](/docs/en/agents-and-tools/tool-use/advisor-tool) | `/v1/messages` (with `advisor` tool) | Yes | No | Advisor model output is returned in the API response; nothing is stored server-side after the response. |
| [Memory tool](/docs/en/agents-and-tools/tool-use/memory-tool) | `/v1/messages` (with `memory` tool) | Yes | Yes | Client-side memory storage where you control data retention. |
| [Context management (compaction)](/docs/en/build-with-claude/compaction) | `/v1/messages` (with `context_management`) | Yes | No | Server-side compaction results are returned/round-tripped statelessly through the API response. |
| [Context editing](/docs/en/build-with-claude/context-editing) | `/v1/messages` (with `context_management`) | Yes | No | Context edits (tool use clearing + thinking clearing) are applied in real time. |
| [Fast mode](/docs/en/build-with-claude/fast-mode) | `/v1/messages` (with `speed: "fast"`) | Yes | Yes | Same Messages API endpoint with faster inference. ZDR applies regardless of speed setting. |
| [1M token context window](/docs/en/build-with-claude/context-windows) | `/v1/messages` | Yes | Yes | Extended context processing uses the standard Messages API. |
| [Adaptive thinking](/docs/en/build-with-claude/adaptive-thinking) | `/v1/messages` | Yes | Yes | Dynamic thinking depth uses the standard Messages API. |
| [Citations](/docs/en/build-with-claude/citations) | `/v1/messages` | Yes | Yes | Source attribution uses the standard Messages API. |
| [Data residency](/docs/en/manage-claude/data-residency) | `/v1/messages` (with `inference_geo`) | Yes | Yes | Geographic routing uses the standard Messages API. |
| [Effort](/docs/en/build-with-claude/effort) | `/v1/messages` (with `effort`) | Yes | Yes | Token efficiency control uses the standard Messages API. |
| [Extended thinking](/docs/en/build-with-claude/extended-thinking) | `/v1/messages` (with `thinking`) | Yes | Yes | Step-by-step reasoning uses the standard Messages API. |
| [PDF support](/docs/en/build-with-claude/pdf-support) | `/v1/messages` | Yes | Yes | PDF document processing uses the standard Messages API. HIPAA eligibility applies to PDFs sent inline via the Messages API, not through the Files API. |
| [Search results](/docs/en/build-with-claude/search-results) | `/v1/messages` (with `search_results` source) | Yes | Yes | RAG citation support uses the standard Messages API. |
| [Bash tool](/docs/en/agents-and-tools/tool-use/bash-tool) | `/v1/messages` (with `bash` tool) | Yes | Yes | Client-side tool executed in your environment. |
| [Text editor tool](/docs/en/agents-and-tools/tool-use/text-editor-tool) | `/v1/messages` (with `text_editor` tool) | Yes | Yes | Client-side tool executed in your environment. |
| [Computer use](/docs/en/agents-and-tools/tool-use/computer-use-tool) | `/v1/messages` (with `computer` tool) | Yes | No | Client-side tool where screenshots and files are captured and stored in your environment, not by Anthropic. See [Computer use](/docs/en/agents-and-tools/tool-use/computer-use-tool#data-retention). |
| [Fine-grained tool streaming](/docs/en/agents-and-tools/tool-use/fine-grained-tool-streaming) | `/v1/messages` | Yes | Yes | Streaming tool parameters uses the standard Messages API. |
| [Prompt caching](/docs/en/build-with-claude/prompt-caching) | `/v1/messages` | Yes | Yes | Your prompts and Claude's outputs are not stored. KV cache representations and cryptographic hashes are held in memory for the cache TTL and promptly deleted after expiry. See [Prompt caching](/docs/en/build-with-claude/prompt-caching#data-retention). |
| [Structured outputs](/docs/en/build-with-claude/structured-outputs) | `/v1/messages` | Yes (qualified) | Yes3 | Your prompts and Claude's outputs are not stored. Only the JSON schema is cached, for up to 24 hours since last use. This also covers [strict tool use](/docs/en/agents-and-tools/tool-use/strict-tool-use) (`strict: true` on tools), which uses the same grammar pipeline. See [Structured outputs](/docs/en/build-with-claude/structured-outputs#data-retention). |
| [Tool search](/docs/en/agents-and-tools/tool-use/tool-search-tool) | `/v1/messages` (with `tool_search` tool) | Yes | No | Tool search uses the standard Messages API. |
| [Batch processing](/docs/en/build-with-claude/batch-processing) | `/v1/messages/batches` | No | No | 29-day retention; async storage required. See [Batch processing](/docs/en/build-with-claude/batch-processing#data-retention). |
| [Code execution](/docs/en/agents-and-tools/tool-use/code-execution-tool) | `/v1/messages` (with `code_execution` tool) | No | No | Container data retained up to 30 days. See [Code execution](/docs/en/agents-and-tools/tool-use/code-execution-tool#data-retention). |
| [Programmatic tool calling](/docs/en/agents-and-tools/tool-use/programmatic-tool-calling) | `/v1/messages` (with `code_execution` tool) | No | No | Built on code execution containers; data retained up to 30 days. See [Programmatic tool calling](/docs/en/agents-and-tools/tool-use/programmatic-tool-calling#data-retention). |
| [Files API](/docs/en/build-with-claude/files) | `/v1/files` | No | No | Files retained until explicitly deleted. See [Files API](/docs/en/build-with-claude/files#data-retention). |
| [Agent skills](/docs/en/agents-and-tools/agent-skills/overview) | `/v1/messages` (with `skills`) / `/v1/skills` | No | No | Skill data retained per standard policy. See [Agent skills](/docs/en/agents-and-tools/agent-skills/overview#data-retention). |
| [MCP connector](/docs/en/agents-and-tools/mcp-connector) | `/v1/messages` (with `mcp_servers`) | No | No | Data retained per standard policy. See [MCP connector](/docs/en/agents-and-tools/mcp-connector#data-retention). |
1 [Dynamic filtering](/docs/en/agents-and-tools/tool-use/web-search-tool#dynamic-filtering) is not eligible for ZDR or HIPAA.
2 While web fetch is ZDR-eligible, website publishers may retain request data (such as fetched URLs and request metadata) according to their own policies.
3 PHI must not be included in JSON schema definitions. See [PHI handling guidelines](#phi-handling-guidelines).
## Limitations and exclusions
### CORS not supported for ZDR
**Cross-Origin Resource Sharing (CORS)** is not supported for organizations with ZDR arrangements. If you need to make API calls from browser-based applications, you must:
- Use a backend proxy server to make API calls on behalf of your front end
- Implement your own CORS handling on the proxy server
- Never expose API keys directly in browser JavaScript
### Data retention for policy violations and where required by law
Even with ZDR or HIPAA arrangements in place, Anthropic may retain data where required by law or to combat Usage Policy violations and malicious uses of Anthropic's platform. As a result, if a chat or session is flagged for such a violation, Anthropic may retain inputs and outputs for up to 2 years.
## Frequently asked questions
Check your contract terms or contact your Anthropic account representative to confirm if your organization has ZDR arrangements in place.
Yes. These features retain a minimal, documented set of technical data, not your prompts or Claude's outputs. See [Anthropic's approach to data retention](#anthropics-approach-to-data-retention) for the commitments that govern these features.
Features marked "No" for ZDR are fundamentally stateful: the Batch API stores your jobs, the Files API stores your files, and code execution runs in persistent containers. Data for these features is retained per the feature's documented policy. Using them is a choice to step outside your ZDR arrangement for that specific data.
Contact your Anthropic account representative to discuss deletion options for non-ZDR features.
ZDR prevents customer data from being stored at rest after the API response is returned. HIPAA readiness involves a broader set of privacy and security safeguards that protect PHI throughout its lifecycle, including encryption, access controls, and audit logging. HIPAA-ready API access provides a foundation for progressively enabling more features because data can be retained with proper safeguards rather than requiring immediate deletion.
No. HIPAA-ready API access is designed as an alternative to ZDR for organizations handling PHI. With HIPAA readiness enabled, you get access to supported API features while maintaining the privacy and security protections that HIPAA requires.
The API returns a `400` error with an `invalid_request_error` type. The error message identifies which features are not available. Remove the non-eligible features from your request and retry.
No. HIPAA readiness is enforced at the organization level and automatically blocks all non-eligible features. Use a separate organization for workloads that do not require HIPAA readiness.
Contact the [Anthropic sales team](https://claude.com/contact-sales) to discuss HIPAA-ready API access and sign a Business Associate Agreement.
No. The ZDR and HIPAA arrangements described on this page apply to the Claude API, where Anthropic is the data processor. On Bedrock and Vertex AI, the cloud provider is the data processor; refer to those platforms' data retention and compliance policies for their equivalent controls.
Claude Platform on AWS follows the same data retention policy as the first-party Claude API. ZDR is available on request; contact your Anthropic account representative to enable it. HIPAA readiness is not available on Claude Platform on AWS. See [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws) for details.
Claude Code is eligible for ZDR through two paths:
- **API keys:** Claude Code used with pay-as-you-go API keys from a Commercial organization
- **Claude Enterprise:** Claude Code used through Claude Enterprise with ZDR enabled for the organization
ZDR is enabled on a per-organization basis. Each new organization requires ZDR to be enabled separately by your account team. ZDR does not automatically apply to new organizations created under the same account.
Additionally, if you have metrics logging enabled in Claude Code, productivity data (such as usage statistics) is exempted from ZDR and may be retained.
For full details on ZDR for Claude Code on Claude Enterprise, including disabled features and how to request enablement, see the [Claude Code ZDR documentation](https://code.claude.com/docs/en/zero-data-retention).
No, Claude for Excel is not currently ZDR-eligible.
To request a ZDR arrangement, contact the [Anthropic sales team](https://claude.com/contact-sales).
## Related resources
- [Privacy Policy](https://www.anthropic.com/legal/privacy)
- [Structured outputs](/docs/en/build-with-claude/structured-outputs)
- [Prompt caching](/docs/en/build-with-claude/prompt-caching)
- [Batch processing](/docs/en/build-with-claude/batch-processing)
- [Files API](/docs/en/api/files-create)
- [Trust Center](https://trust.anthropic.com/resources)
---
# Data residency
URL: https://platform.claude.com/docs/en/manage-claude/data-residency
# Data residency
Manage where model inference runs and where data is stored with geographic controls.
---
This feature is eligible for [Zero Data Retention (ZDR)](/docs/en/build-with-claude/api-and-data-retention). When your organization has a ZDR arrangement, data sent through this feature is not stored after the API response is returned.
Data residency controls let you manage where your data is processed and stored. Two independent settings govern this:
- **Inference geo:** Controls where model inference runs, on a per-request basis. Set through the `inference_geo` API parameter or as a workspace default.
- **Workspace geo:** Controls where data is stored at rest and where endpoint processing (such as image transcoding and code execution) happens. Configured at the workspace level in the [Claude Console](https://platform.claude.com).
[Claude Managed Agents](/docs/en/managed-agents/overview) does not support the `inference_geo` parameter, but respects the Workspace geo configured in Console.
## Inference geo
The `inference_geo` parameter controls where model inference runs for a specific API request. Add it to any `POST /v1/messages` call.
| Value | Description |
|:------|:------------|
| `"global"` | Default. Inference may run in any available geography for optimal performance and availability. |
| `"us"` | Inference runs only in US-based infrastructure. |
### API usage
```bash cURL
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"inference_geo": "us",
"messages": [{
"role": "user",
"content": "Summarize the key points of this document."
}]
}'
```
```bash CLI
ant messages create \
--model claude-opus-4-7 \
--max-tokens 1024 \
--inference-geo us \
--message '{role: user, content: "Summarize the key points of this document."}' \
--transform '{content.0.text,usage.inference_geo}' --format yaml
```
```python Python hidelines={1..2}
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
inference_geo="us",
messages=[
{"role": "user", "content": "Summarize the key points of this document."}
],
)
print(response.content[0].text)
# Check where inference actually ran
print(f"Inference geo: {response.usage.inference_geo}")
```
```typescript TypeScript hidelines={1..2}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
inference_geo: "us",
messages: [
{
role: "user",
content: "Summarize the key points of this document."
}
]
});
const textBlock = response.content.find(
(block): block is Anthropic.TextBlock => block.type === "text"
);
console.log(textBlock?.text);
// Check where inference actually ran
console.log(`Inference geo: ${response.usage.inference_geo}`);
```
### Response
The response `usage` object includes an `inference_geo` field indicating where inference ran:
```json Output
{
"usage": {
"input_tokens": 25,
"output_tokens": 150,
"inference_geo": "us"
}
}
```
### Model availability
The `inference_geo` parameter is supported on Claude Opus 4.6, Claude Sonnet 4.6, and later models. Requests with `inference_geo` on Claude Opus 4.5, Claude Sonnet 4.5, Claude Haiku 4.5, or earlier models return a 400 error.
The `inference_geo` parameter is available on the Claude API (first-party) and [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws). On Amazon Bedrock, Vertex AI, and Microsoft Foundry, the inference region is determined by the endpoint URL or inference profile, so `inference_geo` is not applicable. The `inference_geo` parameter is also not available through the [OpenAI SDK compatibility endpoint](/docs/en/api/openai-sdk).
### Workspace-level restrictions
Workspace settings also support restricting which inference geos are available:
- **`allowed_inference_geos`:** Restricts which geos a workspace can use. If a request specifies an `inference_geo` not in this list, the API returns an error.
- **`default_inference_geo`:** Sets the fallback geo when `inference_geo` is omitted from a request. Individual requests can override this by setting `inference_geo` explicitly.
These settings can be configured through the Console or the [Admin API](/docs/en/manage-claude/admin-api) under the `data_residency` field.
**Claude Platform on AWS:** Workspace-level inference geography controls (`allowed_inference_geos` and `default_inference_geo`) are not available. Use the per-request `inference_geo` parameter instead.
## Workspace geo
Workspace geo is set when you create a workspace and can't be changed afterwards. Currently, `"us"` is the only available workspace geo.
To set workspace geo, create a new workspace in the [Console](https://platform.claude.com):
1. Go to **Settings** > **Workspaces**.
2. Create a new workspace.
3. Select the workspace geo.
**Claude Platform on AWS:** Workspace geo is not configurable. Workspaces are provisioned through the AWS Console, and the Claude Console Workspaces page is read-only. Claude Managed Agents sessions on this platform run with an effective Workspace geo of `"us"`, which is currently the only available workspace geo. See [Claude Platform on AWS](/docs/en/build-with-claude/claude-platform-on-aws) for data residency considerations specific to that platform.
## Pricing
Data residency pricing varies by model generation:
- **Claude Opus 4.6, Claude Sonnet 4.6, and later:** US-only inference (`inference_geo: "us"`) is priced at 1.1x the standard rate across all token pricing categories (input tokens, output tokens, cache writes, and cache reads).
- **Global routing** (`inference_geo: "global"`): Standard pricing applies.
- **Older models:** Don't support `inference_geo` (see [Model availability](#model-availability)); standard pricing applies. Requests that include the parameter return a 400 error.
This pricing applies to the Claude API (first-party) and Claude Platform on AWS. Partner-operated platforms (Bedrock and Vertex AI) have their own regional pricing. See [Data residency pricing](/docs/en/about-claude/pricing#data-residency-pricing) for details.
If you use [Priority Tier](/docs/en/api/service-tiers), the 1.1x multiplier for US-only inference also affects how tokens are counted against your Priority Tier capacity. Each token consumed with `inference_geo: "us"` draws down 1.1 tokens from your committed TPM, consistent with how other pricing multipliers (such as prompt caching) affect burndown rates.
## Batch API support
The `inference_geo` parameter is supported on the [Batch API](/docs/en/build-with-claude/batch-processing). Each request in a batch can specify its own `inference_geo` value.
## Migration from legacy opt-outs
If your organization previously opted out of global routing to keep inference in the US, your workspace has been automatically configured with `allowed_inference_geos: ["us"]` and `default_inference_geo: "us"`. No code changes are required. Your existing data residency requirements continue to be enforced through the new geo controls.
### What changed
The legacy opt-out was an organization-level setting that restricted all requests to US-based infrastructure. The new data residency controls replace this with two mechanisms:
- **Per-request control:** The `inference_geo` parameter lets you specify `"us"` or `"global"` on each API call, giving you request-level flexibility.
- **Workspace controls:** The `default_inference_geo` and `allowed_inference_geos` settings in the Console let you enforce geo policies across all keys in a workspace.
### What happened to your workspace
Your workspace was migrated automatically:
| Legacy setting | New equivalent |
|:---------------|:---------------|
| Global routing opt-out (US only) | `allowed_inference_geos: ["us"]`, `default_inference_geo: "us"` |
All API requests using keys from your workspace continue to run on US-based infrastructure. No action is needed to maintain your current behavior.
### If you want to use global routing
If your data residency requirements have changed and you want to take advantage of global routing for better performance and availability, update your workspace's inference geo settings to include `"global"` in the allowed geos and set `default_inference_geo` to `"global"`. See [Workspace-level restrictions](#workspace-level-restrictions) for details.
### Pricing impact
Legacy models are unaffected by this migration. For current pricing on newer models, see [Pricing](#pricing).
## Current limitations
- **Shared rate limits:** Rate limits are shared across all geos.
- **Inference geo:** Only `"us"` and `"global"` are available.
- **Workspace geo:** Only `"us"` is currently available. Workspace geo can't be changed after workspace creation.
## Next steps
View data residency pricing details.
Learn about workspace configuration.
Track usage and costs by data residency.
### Compliance API
---
# Compliance API
URL: https://platform.claude.com/docs/en/manage-claude/compliance-api
# Compliance API
Programmatic access to your organization's Claude activity, chats, files, projects, and users for compliance, audit, and governance.
---
The Compliance API is available only on the Claude Enterprise plan and must be enabled before use. See [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access).
The Compliance API gives Claude Enterprise customers programmatic access to their organization's Activity Feed, the directory of users, roles, and groups across every linked organization, and, for claude.ai organizations, the underlying chats, files, and projects. Security, legal, and compliance teams use it to audit activity, retrieve or delete content, and feed events into downstream tooling.
Two key types unlock the Compliance API. A **Compliance Access Key** (created in claude.ai) reaches every endpoint, and an **Admin API key** (created in Claude Console) reaches the Activity Feed only. The following key-type table shows where each is created and what it unlocks.
The following call returns the most recent activity event in your organization. Any key with the `read:compliance_activities` scope can make it. To create a key and grant it that scope, see [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access).
```bash cURL nocheck
curl --fail-with-body -sS \
"https://api.anthropic.com/v1/compliance/activities?limit=1" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY"
```
A successful response returns a JSON object containing `data` (an array of `Activity` records), `has_more`, `first_id`, and `last_id`:
```json Response
{
"data": [
{
"id": "activity_01XyDMpzjS89pFZXqSFUBDr6",
"created_at": "2026-04-10T08:09:10Z",
"organization_id": "org_01Wv6QeBcDfGhJkLmNpQrSt8",
"organization_uuid": "abcdef01-2345-6789-abcd-ef0123456789",
"actor": {
"type": "user_actor",
"email_address": "user@example.com",
"user_id": "user_01TuVwXyZaBcDeFgH2JkLmN4",
"ip_address": "192.0.2.34",
"user_agent": "Mozilla/5.0..."
},
"type": "claude_chat_created",
"claude_chat_id": "claude_chat_01XyDMpzjS89pFZXqSFUBDr6",
"claude_project_id": "claude_proj_01KGp4eZNug9ri4kE35RSppq"
}
],
"has_more": true,
"first_id": "activity_01XyDMpzjS89pFZXqSFUBDr6",
"last_id": "activity_01XyDMpzjS89pFZXqSFUBDr6"
}
```
Two key types can carry the `read:compliance_activities` scope; they differ in where you create them and which endpoints they unlock. For the full provisioning flow, scopes, and key prefixes, see [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access).
| Key type | Created in | Compliance API access |
| :---- | :---- | :---- |
| **Compliance Access Key** | claude.ai | Full Compliance API |
| **Admin API key** | Claude Console | Activity Feed only |
---
## How the Compliance API works
Every endpoint lives under `/v1/compliance/*` on `https://api.anthropic.com` and authenticates through the `x-api-key` header. The Activity Feed (`GET /v1/compliance/activities`) is the shared endpoint available to any key that carries the `read:compliance_activities` scope; see [Query the Activity Feed](/docs/en/manage-claude/compliance-activity-feed) for filters, pagination, and the full `Activity` object. The remaining endpoints require a Compliance Access Key carrying the relevant scope. The directory endpoints (organizations, users, roles, and groups) return data from every linked organization under the parent, including Claude Console-linked organizations; the content endpoints (chats, files, projects, and project attachments) serve claude.ai data only. To provision a key, see [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access).
---
## Compliance API versus related features
Two adjacent features overlap with the Compliance API; here is how to choose.
### Export audit logs
The audit log export is a separate feature in **claude.ai** > **Organization settings** > **Data and privacy** that lets owners and primary owners download a CSV of organization events. It's significantly narrower than the Compliance API: a capped lookback window, CSV download only, and no access to chat, file, or project content. Standardize on the Compliance API for ongoing programmatic use.
### Analytics API
Anthropic provides two analytics APIs: the Claude Enterprise Analytics API and the [Claude Code Analytics API](/docs/en/manage-claude/claude-code-analytics-api). Both return aggregated usage and cost figures for IT, FinOps, and platform teams, while the Compliance API returns per-event records for security, legal, and compliance teams. The two API families answer different questions, use different keys, and are provisioned separately.
---
## In this section
Create a Compliance Access Key or Admin API key, choose scopes, and enable the Compliance API for your organization.
Retrieve, filter, and paginate the shared Activity Feed. Supported by both key types.
Read chat content and attachments, then delete on demand. Compliance Access Key required.
Enumerate linked organizations, members, roles, and directory groups.
Choose a feed-consumption pattern, plan SIEM correlation, and decide your retention approach.
Every 400, 401, 403, 404, 409, 429, and 5xx response the Compliance API returns, with the fix for each.
Endpoint paths, parameters, and response schemas for every Compliance API call.
Answers to common key, scope, availability, and integration questions.
---
# Compliance API FAQ
URL: https://platform.claude.com/docs/en/manage-claude/compliance-faq
# Compliance API FAQ
Answers to common questions about Compliance API access, scopes, retention, and integration.
---
The Compliance API is available only on the Claude Enterprise plan and must be enabled before use. See [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access).
## Access and scopes
This is expected. A Claude Enterprise parent organization centralizes identity across all linked organizations; it does not carry workloads, and it does not appear in Claude Console at all. Claude Console only ever shows the Claude Console organizations linked beneath the parent.
To call the Compliance API, you create one of two key types instead:
- **For full Compliance API access ([Activity Feed](/docs/en/manage-claude/compliance-activity-feed) plus chats, files, projects, users, and organization metadata),** the primary owner of the parent organization creates a [Compliance Access Key](/docs/en/manage-claude/compliance-api-access#create-a-compliance-access-key) in claude.ai.
- **For Activity Feed access only,** an organization admin in your Claude Console organization creates an [Admin API key](/docs/en/manage-claude/compliance-api-access#create-an-admin-api-key) in Claude Console. The Compliance API must already be enabled for the organization, and the admin must create the Admin API key after enablement for it to carry the `read:compliance_activities` scope.
No. A Claude API key (`sk-ant-api03-...`) authenticates calls to Claude models on the Claude API; it does not authenticate calls to `/v1/compliance/*`. The Compliance API accepts only Compliance Access Keys (`sk-ant-api01-...`) and Admin API keys (`sk-ant-admin01-...`). See [Which key do you need?](/docs/en/manage-claude/compliance-api-access#which-key-do-you-need) for the full mapping.
Admin API keys carry a fixed `read:compliance_activities` scope, which authorizes the Activity Feed only. Every other Compliance API endpoint requires a scope that only a Compliance Access Key created in claude.ai can carry. Calling a content or directory endpoint with an Admin API key returns a 403 naming the scope that endpoint family requires: `read:compliance_user_data` for chats, files, projects, project attachments, users, and group members, and `read:compliance_org_data` for organizations, roles, and groups. For example, listing chats returns the following response.
```json Response
{
"error": {
"type": "permission_error",
"message": "Missing required scopes. Got: ['read:compliance_activities'] Needed: ['read:compliance_user_data']"
}
}
```
To access content endpoints, the primary owner of your parent organization must [create a Compliance Access Key](/docs/en/manage-claude/compliance-api-access#create-a-compliance-access-key) with `read:compliance_user_data` (and `delete:compliance_user_data` for deletes), or `read:compliance_org_data` for organization, role, and group endpoints. See [Handle Compliance API errors](/docs/en/manage-claude/compliance-errors#403-forbidden) for the full per-endpoint catalog.
## Data coverage and retention
The Activity Feed retains 6 years of organization activity, and new events are queryable within 1 minute of occurring. Activity Feed retention is independent of your organization's content retention policy: chat, file, and project content follows the retention rules configured for your organization (indefinite by default).
No. The Activity Feed records who did what and when (authentication, chat creation, file uploads, project changes, administrative actions, and similar resource events), but it does not capture the prompt text or model responses inside chats or messages.
To retrieve message bodies and file contents, use the chat, message, and file endpoints with a Compliance Access Key carrying `read:compliance_user_data`. Those endpoints serve claude.ai content only; Claude Console and Claude API workloads expose administrative and resource events through the Activity Feed but do not expose prompt text or model responses through the Compliance API.
No. Deletes performed through the Compliance API are immediate, permanent, and not recoverable. Chats that a user deleted through claude.ai are soft-deleted: they remain visible through the Compliance API with `deleted_at` populated until your organization's retention window expires or you hard-delete them through this API. Pull any content you need to retain (for legal hold or archival) before issuing a `DELETE` request.
The Compliance API has known coverage boundaries: the Activity Feed records resource events but not prompt or response text, Claude Console and Claude API workloads expose no message content at all, and content removed by your retention policy or by a hard delete is not recoverable. For the full coverage boundaries and delivery contract, see [Delivery guarantees and completeness](/docs/en/manage-claude/compliance-integration-patterns#delivery-guarantees-and-completeness).
## Integration and pagination
Join `Activity` records to your SIEM on `actor.user_id`, `actor.email_address`, `actor.ip_address`, and `created_at`. See [Design your compliance integration](/docs/en/manage-claude/compliance-integration-patterns#correlate-with-your-siem) for the join-key table and consumption patterns.
Yes. A Claude Enterprise parent organization can have many linked organizations, including a mix of claude.ai organizations and Claude Console organizations (for example, separate production and staging Claude Console organizations). Identity, SSO, and SCIM are shared across the parent; billing, members, projects, and API keys remain separate for each organization. Compliance API enablement happens at the parent organization level and cascades to all linked organizations, and a Compliance Access Key with `read:compliance_org_data` can enumerate every organization beneath the parent through `GET /v1/compliance/organizations`.
Activities are returned newest first, with ties in `created_at` broken by activity ID. To catch up, walk pages forward by `before_id` until `has_more` is `false`; that final response's `first_id` is your new cursor and you have reached the present. The full loop, including initial backfill and the safety conditions on cursor persistence, is in [Cursor-driven incremental reads](/docs/en/manage-claude/compliance-integration-patterns#cursor-driven-incremental-reads).
Set up a Claude Enterprise sandbox organization linked to a Claude Console organization under the same parent. This lets the sandbox exercise both the Activity Feed (through an Admin API key) and the chat, file, and project endpoints (through a Compliance Access Key).
1. **Provision the Claude Enterprise organization.** Contact your Anthropic representative to set up a Claude Enterprise sandbox organization. On an existing Claude Enterprise organization, the primary owner can enable the Compliance API directly instead.
2. **Create the Claude Console organization.** Create a Claude Console organization yourself at `platform.claude.com` using the same email address.
3. **Link the two organizations.** Sign in as the primary owner of the Claude Enterprise organization, go to **claude.ai** > **Organization settings** > [**Identity and access**](https://claude.ai/admin-settings/identity), and use **Merge Organizations** to link the two under a shared parent.
Once linked, follow [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access) to create keys and start querying. Test organizations use the same enablement process as production organizations.
---
# Design your compliance integration
URL: https://platform.claude.com/docs/en/manage-claude/compliance-integration-patterns
# Design your compliance integration
Choose between polling and cursor-driven Activity Feed consumption, correlate Compliance API events with your SIEM, and plan retention.
---
The Compliance API is available only on the Claude Enterprise plan and must be enabled before use. See [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access).
**Required scope:** `read:compliance_activities` on the Compliance Access Key or Admin API key.
A production Compliance API integration makes three design choices: how it consumes the Activity Feed, how its output correlates with your security information and event management (SIEM) system, and where long-term copies of activity and content live. These choices are independent of the endpoints themselves; this page helps you evaluate the tradeoffs.
This page assumes you have read [Query the Activity Feed](/docs/en/manage-claude/compliance-activity-feed), which defines the parameters and pagination contract referenced throughout, and [Retrieve and delete chats, files, and projects](/docs/en/manage-claude/compliance-content-data), which defines the content endpoints and `deleted_at` semantics referenced in [Plan content retention](#plan-content-retention).
## Choose a feed-consumption pattern
The Activity Feed supports two consumption patterns: periodic window polling bounded by `created_at.gte` and `created_at.lt`, and cursor-driven incremental reads that persist a cursor from one response and pass it on the next request. Both return identical `Activity` objects; the difference is the state your client persists between calls.
Both patterns share these constraints:
- Activities are queryable within 1 minute of occurring and retained for 6 years.
- The maximum `limit` for each page is 5,000.
- Cursor values are opaque strings that you must not parse.
| Pattern | Choose when |
| :---- | :---- |
| Window polling | Your pipeline runs on a fixed schedule, you prefer stateless workers, and you can tolerate replaying or overlapping windows |
| Cursor-driven incremental reads | You want the lowest latency between an activity occurring and your pipeline ingesting it, you want to avoid re-reading pages you already drained, and you have a durable place to persist a cursor between runs |
### Window polling
Set `created_at.lt` at least 1 minute in the past so that every activity in the window is already queryable. Use `created_at.gte` for the lower bound and `created_at.lt` for the upper bound so that consecutive windows tile without gaps or overlap; reuse the previous window's `lt` value as the next window's `gte`.
```bash cURL nocheck
curl --fail-with-body -sS -G \
"https://api.anthropic.com/v1/compliance/activities" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
--data-urlencode "created_at.gte=2026-04-20T07:00:00Z" \
--data-urlencode "created_at.lt=2026-04-20T08:00:00Z" \
--data-urlencode "limit=5000"
```
When the response has `has_more: true`, the window contains more than one page of activities. Either page within the window by passing the response's `last_id` as `after_id` on the next request (stopping when `has_more` is `false`), or choose a smaller time window. See [Paginate results](/docs/en/manage-claude/compliance-activity-feed#paginate-results) for the full contract.
Even with clean tiling, an activity that indexes after its window has closed never appears in a later window. Deduplicate on the activity `id` and either widen each new window so it overlaps the previous one by a few minutes or run a periodic reconciliation pass that re-queries an older window.
A `created_at.lt` bound too close to the present silently and permanently drops late-indexed activities: once `created_at.gte` advances past them, no later window can recover them. Treat the 1-minute queryability figure as the documented indexing lag, not a soft recommendation.
### Cursor-driven incremental reads
```bash cURL nocheck
first_id="activity_01XyDMpzjS89pFZXqSFUBDr6" # first_id from a previous response
curl --fail-with-body -sS -G \
"https://api.anthropic.com/v1/compliance/activities" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
--data-urlencode "limit=5000" \
--data-urlencode "before_id=$first_id"
```
Page through until `has_more` is `false`, then persist `first_id` from the final response and pass it unchanged as `before_id` on the next run to retrieve activities newer than the saved cursor. To walk in the opposite direction for a backfill, persist `last_id` and pass it as `after_id` instead. For the full cursor-vs-page-token reference and retry semantics, see [Paginate results](/docs/en/manage-claude/compliance-activity-feed#paginate-results).
A production **catch-up** loop fetches activities recorded since your last poll by driving iteration off `has_more` and `first_id`:
```text
cursor = stored_cursor
loop:
page = GET /v1/compliance/activities?before_id={cursor}&limit=5000
store(page.data)
if page.first_id is not null:
cursor = page.first_id
if not page.has_more: break
persist(cursor)
```
Cursors survive key rotation; see [Manage and rotate keys](/docs/en/manage-claude/compliance-api-access#manage-and-rotate-keys).
Each page is adjacent to the cursor you pass: the loop walks forward toward the present, one page at a time. Do not treat a single response as caught up while `has_more` is `true`. Persist the cursor only after `has_more` is `false`; the unfetched pages are the newer ones between this response's `first_id` and the present, and they stay unread until you finish the loop or run again.
## Correlate with your SIEM
Each `Activity` carries fields you can join against events already in your SIEM (Splunk, Datadog, Microsoft Sentinel, Cribl, or similar):
| Compliance API field | Join target |
| :---- | :---- |
| `actor.user_id` | Your identity provider's stable user identifier |
| `actor.email_address` | Directory email when a stable ID is unavailable |
| `actor.ip_address` | Network, VPN, and endpoint logs |
| `created_at` | Time-window correlation across any source |
`actor.user_id` and `actor.email_address` are present when `actor.type` is `user_actor`; check the discriminator before reading them. `user_id` is a stable, opaque identifier for the user account: it is consistent across every Compliance API endpoint and activity payload, and it does not change when the user's email or display name changes. Use `user_id`, not `email_address`, as the primary join key.
Calls to the Compliance API itself emit `compliance_api_accessed` activities. Ingest these alongside other activity types so your SIEM records who queried compliance data, and when. Filter on `actor.type` `api_actor` and `actor.api_key_id` to attribute each access to a specific Compliance Access Key or Admin API key.
## Plan content retention
Three retention horizons govern what you can retrieve later:
| Data | Retained for | Controlled by |
| :---- | :---- | :---- |
| Activity Feed records | 6 years | Anthropic |
| Chat, file, and project content | Your organization's claude.ai retention policy | Your organization |
| Content hard-deleted through the Compliance API | Not retained; deletion is immediate and permanent | The caller of the `DELETE` endpoint |
For how the rest of the Claude Platform handles retention, see [API and data retention](/docs/en/manage-claude/api-and-data-retention).
Decide between export-and-archive and on-demand API retrieval as follows:
- If your legal-hold or audit horizon exceeds 6 years for activity metadata, export Activity Feed pages to your own archive as you ingest them.
- If your content-retention policy is shorter than your eDiscovery horizon, export chat and file content before the retention window expires; the Compliance API cannot return content that retention has already removed.
- If a workflow might issue a Compliance API hard-delete (for example, DLP enforcement), retrieve and archive the target content first. There is no recovery window after a hard-delete; soft-deletes from claude.ai remain retrievable with `deleted_at` populated, but Compliance API deletes do not.
In every other case, rely on direct API retrieval and avoid maintaining a parallel copy.
### Delivery guarantees and completeness
Treat the Activity Feed as **at-least-once**: a correctly paginated traversal returns every activity at least once, but a retry after a partial failure can re-deliver activities you already stored. Deduplicate on the activity `id` field.
The list endpoints do not return a `total_count` field or a checksum. To attest that an export run is complete, log:
- The starting cursor and the terminal `last_id`.
- The number of records exported.
- The run timestamp and the `request-id` of the final page.
The content endpoints (chats, files, projects, and project attachments) serve claude.ai data only; the Activity Feed surfaces administrative and resource events organization-wide. The Compliance API does not include:
- Prompt text or model responses from Claude Console or Claude API workloads.
- Content removed by your organization's retention policy.
- Content hard-deleted through the Compliance API.
See the [Compliance API FAQ](/docs/en/manage-claude/compliance-faq#data-coverage-and-retention) for more on what the Compliance API does and does not capture.
For chain of custody, store the exported records with provenance metadata: source endpoint, query parameters, run timestamp, and a content hash of each record.
## Next steps
Filter parameters, pagination, and the `Activity` object schema.
The content and hard-delete endpoints.
---
# Get access to the Compliance API
URL: https://platform.claude.com/docs/en/manage-claude/compliance-api-access
# Get access to the Compliance API
Create a Compliance Access Key or Admin API key, choose the right scopes, and enable the Compliance API for your organization.
---
The Compliance API is available only on the Claude Enterprise plan and must be enabled before use. This page describes how.
**Required role:** organization admin (Claude Console) or primary owner (claude.ai).
The Compliance API uses two key types, and which one you create depends on which Claude product your organization uses. Primary owners create Compliance Access Keys in claude.ai; these keys unlock the full Compliance API. Organization admins create Admin API keys in Claude Console; these keys unlock the [Activity Feed](/docs/en/manage-claude/compliance-activity-feed) only.
## Which key do you need?
| Key type | Created in | Used for | Works with the Compliance API? |
| ---------------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| **Compliance Access Key** (`sk-ant-api01-...`) | **claude.ai** > **Organization settings** > **Data and privacy** | Activity Feed, chats, files, projects, users, and organization metadata | Yes (all endpoints) |
| **Admin API key** (`sk-ant-admin01-...`) | **Claude Console** > **Settings** > **Admin keys** | The [Admin API](/docs/en/manage-claude/admin-api) and the Compliance API Activity Feed | Activity Feed only |
| **Analytics API key** | **claude.ai** > **Analytics** > **API keys** | The [Claude Enterprise Analytics API](https://support.claude.com/en/articles/13694757-claude-enterprise-analytics-api-access-engagement-and-adoption-data) | No |
| **Claude API key** (`sk-ant-api03-...`) | **Claude Console** > **Settings** > **API keys** | Calling Claude models through the [Claude API](/docs/en/api/overview) | No |
A Claude Enterprise tenant has one **parent organization** that centralizes identity, SSO, and SCIM for every workload organization beneath it. These workload organizations are the parent's **linked organizations**.
**Claude Enterprise parent organizations do not appear in Claude Console (`platform.claude.com`).** The parent carries no workloads and no API keys. Create Compliance Access Keys in claude.ai **Organization settings**, not in Claude Console.
## Enable the Compliance API for your organization
The enablement path depends on which Claude product your organization uses. Whichever path you use, enablement happens at the parent organization level and cascades to every linked organization, both claude.ai and Claude Console.
### Enable for claude.ai organizations
For claude.ai organizations on the Claude Enterprise plan, the primary owner enables the Compliance API in **Organization settings** > **Data and privacy** at [claude.ai/admin-settings/data-privacy-controls](https://claude.ai/admin-settings/data-privacy-controls): click **Enable** under **Compliance API**. After enablement, a **Compliance access keys** section appears on the same page.
### Enable for Claude Console organizations
Contact your Anthropic representative to request access. Admin API keys created after the Compliance API is enabled carry the `read:compliance_activities` scope. Admin API keys created before enablement continue to work with the Admin API, but calling the Activity Feed with one returns [403 Forbidden](/docs/en/manage-claude/compliance-errors#403-forbidden).
## Create a Compliance Access Key
The Compliance API must already be [enabled for your claude.ai parent organization](#enable-the-compliance-api-for-your-organization) before a Compliance Access Key can be created.
A Compliance Access Key with `read:compliance_user_data` can read every chat,
file, and project in every linked organization, including content the primary
owner has not seen. A key with `delete:compliance_user_data` can permanently
delete that content. Treat Compliance Access Keys like production database
credentials: store them in a secrets manager, never in source control or SIEM
forwarder configuration.
Only the primary owner of the parent organization can create Compliance Access Keys. If the **Compliance access keys** section described in the next step is not visible, either you are not the primary owner, or the Compliance API has not been enabled for your organization yet (see [Enable the Compliance API for your organization](#enable-the-compliance-api-for-your-organization)).
Go to [claude.ai > Organization settings > Data and privacy](https://claude.ai/admin-settings/data-privacy-controls) and find the **Compliance access keys** section.
Click **Create key**, name the key, and select one or more scopes from the following table. Click **Create**.
| Scope | Grants |
| ------------------------------ | ------------------------------------------------------------------------------- |
| `read:compliance_activities` | Read the Activity Feed for the parent organization and all linked organizations |
| `read:compliance_user_data` | Read user chats, messages, files, projects, organization users, and group members |
| `delete:compliance_user_data` | Delete user chats, files, and projects |
| `read:compliance_org_data` | Read organization metadata (names, types, roles, and groups). User listings and group membership require `read:compliance_user_data`. |
Choose the smallest scope set that your integration needs:
- An audit pipeline that reads the Activity Feed only needs `read:compliance_activities`.
- An eDiscovery tool that reads chats and files but never deletes them does not need `delete:compliance_user_data`.
- If your workflow both reads and deletes, use **two keys** with separate scopes so a leaked read key cannot delete data.
Compliance Access Key scopes are immutable after creation. To change scopes, create a new key with the scopes you want, then delete the old one.
Copy the displayed secret key (starting with `sk-ant-api01-`) and store it in your secrets manager. The full secret is displayed only once.
Set the key as an environment variable so the shell samples in this guide can read it:
```bash
export ANTHROPIC_COMPLIANCE_ACCESS_KEY=sk-ant-api01-...
```
## Create an Admin API key
The Compliance API must already be [enabled for your Claude Console organization](#enable-the-compliance-api-for-your-organization) before an Admin API key can call the Activity Feed.
Only an organization member with the **admin** role can create Admin API keys. See [Organization roles and permissions](/docs/en/manage-claude/admin-api#organization-roles-and-permissions) for the full role list.
Go to [Claude Console > Settings > Admin keys](https://platform.claude.com/settings/admin-keys).
Click **Create key**, name the key, and click **Create**.
Copy the displayed secret key (starting with `sk-ant-admin01-`) and store it in your secrets manager. The full secret is displayed only once.
Set the key as an environment variable:
```bash
export ANTHROPIC_ADMIN_KEY=sk-ant-admin01-...
```
The distinct variable name keeps the Admin API key from overwriting a Compliance Access Key if you provision both. The cURL examples in this guide read the key from `$ANTHROPIC_COMPLIANCE_ACCESS_KEY`; substitute `$ANTHROPIC_ADMIN_KEY` when calling the [Activity Feed](/docs/en/manage-claude/compliance-activity-feed) with an Admin API key.
Admin API keys carry the `read:compliance_activities` scope only when the Compliance API was enabled for the organization before the key was created; see [Enable for Claude Console organizations](#enable-for-claude-console-organizations). They cannot be granted any other Compliance API scope, so calls to any endpoint other than the Activity Feed return [403 Forbidden](/docs/en/manage-claude/compliance-errors#403-forbidden).
For the same key's role in managing your Claude Console organization, see [Admin API](/docs/en/manage-claude/admin-api).
## Check your key's scopes
To inspect the scopes on a key you already have, use one of the following signals.
- **Key prefix.** `sk-ant-admin01-` is an Admin API key (carries `read:compliance_activities` only, subject to the enablement timing in the preceding section). `sk-ant-api01-` is a Compliance Access Key; its scopes are the subset you selected at creation.
- **Settings UI.** Open the **Compliance access keys** section in claude.ai **Organization settings** > **Data and privacy**, or the **Admin keys** section in Claude Console, and read the **Scopes** column for the key.
- **Error responses.** A call that exceeds the key's scopes returns a 403 with a message in the format `Missing required scopes. Got: [] Needed: []`. See [Handle Compliance API errors](/docs/en/manage-claude/compliance-errors#403-forbidden) for the full error catalog.
```json
{
"error": {
"type": "permission_error",
"message": "Missing required scopes. Got: ['read:compliance_activities'] Needed: ['read:compliance_user_data']"
}
}
```
## Manage and rotate keys
Delete a Compliance Access Key from the same **Compliance access keys** panel where you created it: go to [claude.ai > Organization settings > Data and privacy](https://claude.ai/admin-settings/data-privacy-controls). Delete an Admin API key from [Claude Console > Settings > Admin keys](https://platform.claude.com/settings/admin-keys).
Deleting a key takes effect on the next request: there is no grace period. Compliance Access Keys do not expire on their own.
To rotate a key without an outage:
1. Create a new key with the same scopes.
2. Update your integration to use the new key.
3. Verify the integration succeeds with the new key.
4. Delete the old key.
Pagination cursors stored before a rotation remain valid: cursors are scoped to the organization, not the key.
If a Compliance Access Key leaks, delete it immediately, audit the [Activity Feed](/docs/en/manage-claude/compliance-activity-feed) for `compliance_api_accessed` activities by the compromised key, and rotate any downstream credentials that the leaked key could reach. Filter on `actor.type` `api_actor` and `actor.api_key_id` to find requests made by the compromised key.
## Next steps
Read organization-wide activity events with any key that has `read:compliance_activities`.
Use a Compliance Access Key with `read:compliance_user_data` to retrieve claude.ai content, and `delete:compliance_user_data` to delete it.
---
# Handle Compliance API errors
URL: https://platform.claude.com/docs/en/manage-claude/compliance-errors
# Handle Compliance API errors
Every Compliance API error message with cause and fix, organized by HTTP status code.
---
The Compliance API is available only on the Claude Enterprise plan and must be enabled before use. See [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access).
This page lists the response messages each documented Compliance API endpoint returns, the cause, and the fix.
The Compliance API returns errors in an error format consistent with the rest of the [Anthropic error format](/docs/en/api/errors): a non-2xx status code, a `request-id` response header, and a JSON body with an `error` object containing `type` and `message`. Include the `request-id` header value when you escalate to support.
```json
{
"error": {
"type": "authentication_error",
"message": "The API key provided is invalid or has been revoked."
}
}
```
Match on `error.type`, not on the message string. Messages are stable enough to copy into runbooks but might be reworded over time; the type values are part of the API contract.
The following table tells you at a glance whether to retry. Each section that follows shows the verbatim error body and the fix.
| Status | Retry? | When |
| ------------------------------------------------------- | --------------------------- | --------------------------------------------------------------------- |
| [400 Bad Request](#400-bad-request) | No | Fix the request and resend. |
| [401 Unauthorized](#401-unauthorized) | No | Fix or rotate the key, then resend. |
| [403 Forbidden](#403-forbidden) | No | Add the missing scope or use the right key type, then resend. |
| [404 Not Found](#404-not-found) | No | The resource was deleted or never existed; remove it from your queue. |
| [409 Conflict](#409-conflict) | No | The request conflicts with the resource's current state; resolve the conflict (such as detaching child resources), then retry. |
| [429 Too Many Requests](#429-too-many-requests) | Yes, with backoff | Back off and retry; do not advance your cursor. |
| [500 Internal Server Error](#500-internal-server-error) | Depends on `x-should-retry` | Check the `x-should-retry` response header before retrying. |
| [502, 503, 504, 529](#500-internal-server-error) | Yes, with backoff | Transient; retry with exponential backoff. |
## 400 Bad Request
The request was syntactically valid but contained a parameter the server rejected. Fix the parameter and retry.
### Invalid timestamp format
**Type:** `invalid_request_error`
```text
The `created_at.gte` parameter contains an invalid timestamp format. Timestamps must be provided in RFC 3339 format e.g., "2024-03-01T00:00:00Z". Got "2024-01-01".
```
**Cause:** A `created_at.*` or `updated_at.*` value (`.gte`, `.gt`, `.lte`, `.lt`) could not be parsed as a datetime. The message names the parameter that failed and echoes the value that was sent.
**Fix:** Send a full RFC 3339 timestamp including time and time zone, for example, `2024-03-01T00:00:00Z` or `2024-03-01T00:00:00+00:00`.
### Invalid limit
**Type:** `invalid_request_error`
```text
The limit parameter must be between 1 and 1000, inclusive. Got 1500.
```
**Cause:** The `limit` query parameter was outside the accepted range. The bound named in the message reflects the maximum for the specific endpoint that was called.
**Fix:** Send a `limit` within the range the endpoint accepts. Each list endpoint has its own `limit` range; see the parameter constraints on the corresponding [Compliance API reference](/docs/en/api/compliance) page.
### Invalid pagination ID
**Type:** `invalid_request_error`
```text
Invalid `after_id`. No activity found for `after_id` "activity_invalid123"
```
**Cause:** The `after_id` or `before_id` cursor could not be decoded as an opaque cursor or parsed as an activity ID.
**Fix:** Treat pagination cursors as opaque strings. Always copy the `first_id` or `last_id` value returned by the previous page; stop when `has_more` is `false`. Do not construct cursors from object IDs.
The directory and project endpoints (users, roles, role permissions, groups, group members, projects, and project attachments) paginate with an opaque `page` token rather than `after_id` and `before_id`. The same advice applies: pass the `next_page` value from the previous response unchanged, and stop when `has_more` is `false`. A malformed `page` token returns the same 400 `invalid_request_error` as a malformed `after_id` or `before_id`.
## 401 Unauthorized
The `x-api-key` header was missing or did not match a known key. A valid key with the wrong scopes returns [403 Forbidden](#403-forbidden) instead.
### Invalid API key
**Type:** `authentication_error`
```text
The API key provided is invalid or has been revoked.
```
**Cause:** The key in `x-api-key` does not exist, has been deleted, or has been disabled. A missing or empty `x-api-key` header returns the same body, so check both your secret store and the key's revocation status.
**Fix:** Confirm the key value, check that it has not been deleted in claude.ai (Compliance Access Keys) or Claude Console (Admin API keys), and confirm it is enabled. See [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access).
## 403 Forbidden
The key in `x-api-key` is valid but does not carry the scope the endpoint requires. The verbatim message lists the scopes the key carries (`Got:`) and the scopes the endpoint requires (`Needed:`), so you can confirm what the key carries without rechecking Claude Console or claude.ai. Compliance Access Key scopes are immutable after creation, so each insufficient-scope fix directs you to create a new key rather than edit the existing one.
### Insufficient scope: Activity Feed
**Type:** `permission_error`
```text
Missing required scopes. Got: ['read:compliance_user_data'] Needed: ['read:compliance_activities']
```
**Cause:** A key without `read:compliance_activities` was used to call `GET /v1/compliance/activities`. There are two common paths to this error:
- A Compliance Access Key (`sk-ant-api01-...`) was created without the `read:compliance_activities` scope.
- A Claude Console Admin API key (`sk-ant-admin01-...`) was created before the Compliance API was enabled for the organization. Keys created before enablement do not carry the scope; see [Enable for Claude Console organizations](/docs/en/manage-claude/compliance-api-access#enable-for-claude-console-organizations).
**Fix:** Compliance Access Key scopes are immutable after creation. Create a new key that includes `read:compliance_activities`, or use a Claude Console Admin API key. See [Which key do you need?](/docs/en/manage-claude/compliance-api-access#which-key-do-you-need) for the conditions under which an Admin API key carries this scope.
### Insufficient scope: organization data
**Type:** `permission_error`
```text
Missing required scopes. Got: ['read:compliance_user_data'] Needed: ['read:compliance_org_data']
```
**Cause:** A key without `read:compliance_org_data` was used to call an organizations, roles, or groups endpoint. There are two common paths to this error:
- A Compliance Access Key (`sk-ant-api01-...`) was created without the `read:compliance_org_data` scope.
- A Claude Console Admin API key (`sk-ant-admin01-...`) was used. Admin API keys carry only `read:compliance_activities` and cannot read organization metadata.
**Fix:** [Create a new Compliance Access Key](/docs/en/manage-claude/compliance-api-access#create-a-compliance-access-key) with `read:compliance_org_data` selected. Admin API keys cannot read organization metadata; the Compliance Access Key is required.
### Insufficient scope: user data
**Type:** `permission_error`
```text
Missing required scopes. Got: ['read:compliance_activities'] Needed: ['read:compliance_user_data']
```
**Cause:** A key without `read:compliance_user_data` was used to call a chats, messages, files, projects, organization users, or group-members endpoint. There are two common paths to this error:
- A Compliance Access Key (`sk-ant-api01-...`) was created without the `read:compliance_user_data` scope.
- A Claude Console Admin API key (`sk-ant-admin01-...`) was used. Admin API keys carry only `read:compliance_activities` and cannot be granted `read:compliance_user_data`, so they cannot call the chat, file, project, project attachment, user, or group-member endpoints.
**Fix:** Use a [Compliance Access Key](/docs/en/manage-claude/compliance-api-access#create-a-compliance-access-key) created in claude.ai with `read:compliance_user_data` selected. If the request really should be Activity Feed only, point the Admin API key at `GET /v1/compliance/activities` instead.
### Insufficient scope: delete
**Type:** `permission_error`
```text
Missing required scopes. Got: ['read:compliance_user_data'] Needed: ['delete:compliance_user_data']
```
**Cause:** A Compliance Access Key without `delete:compliance_user_data` was used to call a `DELETE` endpoint on chats, files, or projects.
**Fix:** [Create a new Compliance Access Key](/docs/en/manage-claude/compliance-api-access#create-a-compliance-access-key) with `delete:compliance_user_data` selected. The delete scope is separate from `read:compliance_user_data` so that read-only audit keys cannot delete content.
## 404 Not Found
The endpoint resolved but the resource ID does not exist or has already been deleted. Compliance API deletes are immediate and permanent, so a 404 on a previously known ID usually means the content was hard-deleted through a Compliance API delete call or removed by a retention policy. The activity-type strings cited in each Fix (for example, `claude_chat_created`) are values you can pass to the Activity Feed `activity_types[]` filter; see [Query compliance activities](/docs/en/api/compliance/activities/list) for every supported value.
### Chat not found
**Type:** `not_found_error`
```text
Chat claude_chat_01H5CWunD7RpVJ5bHa8RCkja not found.
```
**Cause:** The chat ID in the path does not match a chat readable through the Compliance API. The chat might have been hard-deleted through a previous Compliance API call or removed by your organization's retention policy, or it might belong to an organization the calling key cannot read. Chats that a user soft-deleted in claude.ai do not return 404; they remain readable with `deleted_at` populated.
**Fix:** Confirm the chat ID against a recent `claude_chat_created` or `claude_chat_viewed` activity. If the activity is recent and the read still fails, the chat has been hard-deleted (through this API or by retention-policy expiry) or belongs to an organization outside your key's scope.
### File not found
**Type:** `not_found_error`
```text
No file found with provided id, or it has already been deleted.
```
**Cause:** The file ID does not exist or has been deleted. This error applies to both chat-attached files (`claude_file_...`) and project files.
**Fix:** Reconcile against recent `claude_file_uploaded` or `claude_file_deleted` activities. If the file was deleted, the binary is gone; the activity record remains in the feed for the 6-year retention window.
### Project not found
**Type:** `not_found_error`
```text
No project is found with the provided id.
```
**Cause:** The project ID does not exist or has been deleted.
**Fix:** Reconcile against recent `claude_project_created` or `claude_project_deleted` activities. The Activity Feed continues to expose the project's lifecycle events even after the project itself is gone.
### Project document not found
**Type:** `not_found_error`
```text
No project document found with provided id, or it has already been deleted.
```
**Cause:** The project document ID does not exist or has been deleted. This error applies to text project documents (`claude_proj_doc_...`), not to project files.
**Fix:** Use `GET /v1/compliance/apps/projects/{project_id}/attachments` to list current attachments. If the document is missing, it was deleted; retrieve it through a `claude_project_document_uploaded` activity record if you only need the metadata.
### Organization, role, or group not found
**Type:** `not_found_error`
```text
The "ce86b5f3-7c16-48b3-a9f3-e1d2c4b8a0f1" organization does not exist or the requester is not authorized to access it.
```
The organization, role, and group endpoints return a 404 `not_found_error` in the standard error format. The organization message names the `org_uuid`; the role and group messages are generic (`Role not found.`, `Group not found.`). This occurs when a path ID (`org_uuid`, `role_id`, or `group_id`) does not exist or no longer belongs to a tree the calling key can read.
**Cause:** The ID in the path does not match a record readable through the Compliance API. Roles and groups can be deleted, and organizations can be unlinked from the parent tree.
**Fix:** Verify the ID against the corresponding list endpoint, and reconcile against recent organization, role, or group activities in the [Activity Feed](/docs/en/manage-claude/compliance-activity-feed).
## 409 Conflict
The request is well-formed and authorized but conflicts with the resource's current state.
### Project has attached chats
**Type:** `conflict_error`
```text
The "claude_proj_01KGp4eZNug9ri4kE35RSppq" project cannot be deleted as it has chats attached to it. Delete or detach all chats, and try deleting the project again.
```
**Cause:** `DELETE /v1/compliance/apps/projects/{project_id}` was called on a project that still has chats attached.
**Fix:** List the project's chats with `GET /v1/compliance/apps/chats?user_ids[]={user_id}&project_ids[]={project_id}` (the chat list endpoint requires at least one `user_ids[]` value; enumerate IDs through [List organization users](/docs/en/manage-claude/compliance-org-data#list-organization-users)), delete each one with `DELETE /v1/compliance/apps/chats/{claude_chat_id}`, and then retry the project delete.
## 429 Too Many Requests
Requests to the Compliance API are limited to 600 requests per minute per API key. Contact your Anthropic representative if your integration needs a higher limit. When you exceed the limit, retry with exponential backoff (start at 1 second, double up to 60 seconds).
```json
{
"error": {
"type": "rate_limit_error",
"message": "Rate limit exceeded. Please wait before retrying."
}
}
```
**Cause:** You sent too many requests in a short window.
**Fix:** Wait and retry with exponential backoff. Do not advance your pagination cursor on a 429: the failed request returned no data, so the cursor from the last successful page is still correct.
If you poll the [Activity Feed](/docs/en/manage-claude/compliance-activity-feed) on a schedule, set your polling interval to stay under the rate limit. See [Design your compliance integration](/docs/en/manage-claude/compliance-integration-patterns) for cadence guidance.
## 500 Internal Server Error
A 500 from the Compliance API carries an `x-should-retry: false` response header when the failure is deterministic. Anthropic SDKs honor this header automatically. If you use a generic HTTP retry library that retries on every 5xx, suppress retries when `x-should-retry` is `false`; retrying this error fails identically on every attempt.
A 500 without the `x-should-retry: false` header is transient: retry with exponential backoff (start at 1 second, double up to 60 seconds). The same applies to 502, 503, 504, and 529 responses. See [Errors](/docs/en/api/errors) for the platform-wide retry semantics.
For service-wide incidents, check [status.anthropic.com](https://status.anthropic.com).
### Maximum response size exceeded
**Type:** `api_error`
```text
Response exceeds maximum of 1,000 organizations. Contact support for assistance with larger organization lists.
```
**Cause:** A list endpoint without pagination (notably `GET /v1/compliance/organizations`) would have returned more than its hard cap of 1,000 records.
**Fix:** The organizations endpoint returns the full tree in one call, up to 1,000 linked organizations. If your tree exceeds 1,000, contact Anthropic support for assistance with larger organization lists. If you are polling this endpoint to track organization-membership changes, the [Activity Feed](/docs/en/manage-claude/compliance-activity-feed) covers removals through the `org_deletion_requested` and `org_deleted_via_bulk` activity types; there is currently no activity type for an organization being created or joining the tree, so detect additions by relisting periodically.
## Next steps
Common questions about access, scopes, retention, and integration.
The platform-wide error catalog and retry semantics.
---
# List organizations, users, roles, and groups
URL: https://platform.claude.com/docs/en/manage-claude/compliance-org-data
# List organizations, users, roles, and groups
Enumerate organizations under your parent organization, their users, roles, and groups through the Compliance API.
---
The Compliance API is available only on the Claude Enterprise plan and must be enabled before use. See [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access).
**Required scope:** `read:compliance_org_data` on the Compliance Access Key. The user and group-member endpoints require `read:compliance_user_data` instead.
Compliance Access Keys (`sk-ant-api01-...`) created in claude.ai are the only key type accepted; see [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access) to provision one. Calls authenticated with an Admin API key (`sk-ant-admin01-...`) return [403 Forbidden](/docs/en/manage-claude/compliance-errors#403-forbidden).
The endpoints on this page expose the directory side of a Claude Enterprise organization: its linked organizations, the users in each one, the roles defined on each, and its role-based access control (RBAC) or SCIM (System for Cross-domain Identity Management)-provisioned groups and their members. Use them to seed eDiscovery user lists, build reporting dashboards, and reconcile group membership against an external system of record. Compliance Access Keys are bound to a parent organization and return data from every linked organization underneath, so a single key reaches the entire tree.
## List organizations
The [List organizations](/docs/en/api/compliance/organizations/list) endpoint returns every organization under the parent the key is bound to.
The following call lists every organization under your parent. The response is a single `data` array of organization records sorted by `created_at` ascending. The endpoint returns up to 1,000 organizations in one call; if your tree exceeds that, it returns a [500 error](/docs/en/manage-claude/compliance-errors#500-internal-server-error).
```bash cURL nocheck
curl --fail-with-body -sS \
"https://api.anthropic.com/v1/compliance/organizations" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY"
```
```json Response
{
"data": [
{
"uuid": "91012d09-e48b-438e-a489-1bebfd8fa6f9",
"name": "Acme Engineering",
"created_at": "2025-06-01T10:00:00Z"
},
{
"uuid": "5a1b2c3d-4e5f-6789-abcd-ef0123456789",
"name": "Acme Legal",
"created_at": "2025-07-15T14:30:00Z"
}
]
}
```
The `uuid` field is the canonical identifier for downstream lookups. The following table maps it to the other organization identifiers across the Compliance API:
| Field | Where | Relationship to `uuid` |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------- |
| `{org_uuid}` | Path parameter on per-organization endpoints on this page | Same value |
| `organization_uuid` | Activity Feed record | Same value; join on these two fields directly |
| `organization_id` | Activity Feed record | Same organization, `org_`-prefixed |
| `organization_ids[]` | Filter on [Query the Activity Feed](/docs/en/manage-claude/compliance-activity-feed) and [Retrieve chats and messages](/docs/en/manage-claude/compliance-content-data#retrieve-chats-and-messages) | Accepts `uuid` or the `org_`-prefixed form |
Most other Anthropic APIs use the `org_`-prefixed form.
If your tree exceeds the 1,000-organization cap, contact Anthropic support. There is currently no activity type for an organization being created or joining the tree, so relist periodically to detect newly linked organizations. To detect deleted organizations, watch the `org_deletion_requested` and `org_deleted_via_bulk` activity types; see [Query the Activity Feed](/docs/en/manage-claude/compliance-activity-feed).
## List organization users
The [List organization users](/docs/en/api/compliance/organizations/users/list) endpoint returns a paginated list of user records for one organization.
This endpoint requires `read:compliance_user_data`, not `read:compliance_org_data`. Create the Compliance Access Key with both scopes when you intend to use it for directory enumeration; otherwise the call returns [403 Forbidden](/docs/en/manage-claude/compliance-errors#403-forbidden).
See [List organization users](/docs/en/api/compliance/organizations/users/list) in the API reference for the `limit` and `page` query parameter defaults and ranges.
Results are sorted by account creation date ascending. Unlike the Activity Feed's `before_id`/`after_id` cursors (see [Paginate results](/docs/en/manage-claude/compliance-activity-feed#paginate-results)), the directory endpoints paginate with a `next_page` token: when `has_more` is `true`, pass `next_page` back unchanged as the `page` query parameter on the next request.
```bash cURL nocheck
org_uuid="91012d09-e48b-438e-a489-1bebfd8fa6f9"
curl --fail-with-body -sS -G \
"https://api.anthropic.com/v1/compliance/organizations/$org_uuid/users" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
--data-urlencode "limit=500"
```
```json Response
{
"data": [
{
"id": "user_01XyDMpzjS89pFZXqSFUBDr6",
"full_name": "Priya Sharma",
"email": "priya@example.com",
"created_at": "2025-06-01T10:00:00Z"
}
],
"has_more": true,
"next_page": "page_8aW5kZXgicG9zaXRpb25fdG9rZW5fOTE0"
}
```
The user IDs returned here are the same `user_...` identifiers accepted by the [Query the Activity Feed](/docs/en/manage-claude/compliance-activity-feed) `actor_ids[]` filter and the [Retrieve chats and messages](/docs/en/manage-claude/compliance-content-data#retrieve-chats-and-messages) `user_ids[]` filter. A typical eDiscovery flow lists users for one or more organizations, filters against your own external records, and feeds the resulting IDs into chat and project queries.
A user only appears here while they are an active member of the organization. Removed users are dropped from the list immediately. Their historical activity remains queryable through the Activity Feed for the full retention window, indexed by the same `user_...` ID.
## List roles
The [List Compliance Roles](/docs/en/api/compliance/organizations/roles/list) endpoint returns a paginated list of role records defined on one organization, and [Get Compliance Role](/docs/en/api/compliance/organizations/roles/retrieve) returns one role by ID.
Both role endpoints require `read:compliance_org_data`. The list endpoint accepts the same `limit` and `page` parameters as [List organization users](#list-organization-users).
```bash cURL nocheck
org_uuid="91012d09-e48b-438e-a489-1bebfd8fa6f9"
curl --fail-with-body -sS \
"https://api.anthropic.com/v1/compliance/organizations/${org_uuid}/roles" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY"
```
```json Response
{
"data": [
{
"id": "rbac_role_01N2pQrS8tUvWxYz5AbCdEfGh",
"name": "Compliance Reviewer",
"description": "Read-only access to chat and project content for legal review.",
"created_at": "2025-06-01T10:00:00Z",
"updated_at": "2025-06-15T14:30:00Z"
}
],
"has_more": false,
"next_page": null
}
```
See the [List Compliance Roles](/docs/en/api/compliance/organizations/roles/list) response schema for the full role record shape. To list the permissions currently granted to a role, use [List Compliance Role Permissions](/docs/en/api/compliance/organizations/roles/permissions/list). To audit historical role assignments and permission changes, query the RBAC activity types (for example, `rbac_role_assigned` and `rbac_role_permission_added`) through the Activity Feed; see [Filter activities](/docs/en/manage-claude/compliance-activity-feed#filter-activities).
## List groups and members
The [List Compliance Groups](/docs/en/api/compliance/groups/list) endpoint returns a paginated list of RBAC and SCIM-provisioned groups, and [Get Compliance Group](/docs/en/api/compliance/groups/retrieve) returns one group by ID. The [List Compliance Group Members](/docs/en/api/compliance/groups/members/list) endpoint returns the members of one group.
The group list and retrieval endpoints require `read:compliance_org_data`. The members endpoint requires `read:compliance_user_data`. Create the key with both scopes to walk groups end to end. Both list endpoints accept the same `limit` and `page` parameters as [List organization users](#list-organization-users).
See the [List Compliance Groups](/docs/en/api/compliance/groups/list) response schema for the full group record shape. The `roles` array lists role IDs assigned to the group, matching IDs from [List roles](#list-roles). `source_type` is the discriminator between groups created manually through claude.ai (`direct`) and groups synced from an external identity provider through SCIM (`scim`).
List groups, then for each group list its members:
```bash cURL nocheck
curl --fail-with-body -sS -G \
"https://api.anthropic.com/v1/compliance/groups" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY"
```
```json Response
{
"data": [
{
"id": "rbac_group_01P9qRsTuVwXyZa2BcDeFgHjK",
"name": "Engineering",
"description": "Engineering team members",
"source_type": "scim",
"roles": ["rbac_role_01N2pQrS8tUvWxYz5AbCdEfGh"],
"created_at": "2025-06-01T10:00:00Z",
"updated_at": "2025-06-15T14:30:00Z"
}
],
"has_more": false,
"next_page": null
}
```
For each group ID, list its members:
```bash cURL nocheck
group_id="rbac_group_01P9qRsTuVwXyZa2BcDeFgHjK"
curl --fail-with-body -sS -G \
"https://api.anthropic.com/v1/compliance/groups/$group_id/members" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY"
```
```json Response
{
"data": [
{
"user_id": "user_01XyDMpzjS89pFZXqSFUBDr6",
"email": "priya@example.com",
"created_at": "2025-06-01T10:00:00Z",
"updated_at": "2025-06-15T14:30:00Z"
}
],
"has_more": false,
"next_page": null
}
```
See the [List Compliance Group Members](/docs/en/api/compliance/groups/members/list) response schema for the full member record shape. The `user_id` field is the same `user_...` identifier the Activity Feed and chat list accept. To get a member's full name, look it up through the organization users list.
## Next steps
The full request and response schema for every organization, user, role, and group endpoint.
Verbatim error payloads and the fix for each.
---
# Query the Activity Feed
URL: https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed
# Query the Activity Feed
Retrieve, filter, and paginate your organization's Compliance API Activity Feed.
---
The Compliance API is available only on the Claude Enterprise plan and must be enabled before use. See [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access).
**Required scope:** `read:compliance_activities` on the Compliance Access Key or Admin API key.
Both Compliance Access Keys (`sk-ant-api01-...`) carrying this scope and Admin API keys (`sk-ant-admin01-...`) can call the Activity Feed. See [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access) for the conditions under which each key type carries the scope.
The Activity Feed records every authentication, chat, file, project, administrative, and platform action that occurs in your organization, in reverse chronological order. Activities are queryable within 1 minute of occurring and are retained for 6 years.
```bash cURL nocheck
curl --fail-with-body -sS \
"https://api.anthropic.com/v1/compliance/activities?limit=1" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY"
```
```json Response
{
"data": [
{
"id": "activity_01XyDMpzjS89pFZXqSFUBDr6",
"created_at": "2026-04-10T08:09:10Z",
"organization_id": "org_01Wv6QeBcDfGhJkLmNpQrSt8",
"organization_uuid": "abcdef01-2345-6789-abcd-ef0123456789",
"actor": {
"type": "user_actor",
"email_address": "user@example.com",
"user_id": "user_01TuVwXyZaBcDeFgH2JkLmN4",
"ip_address": "192.0.2.34",
"user_agent": "Mozilla/5.0..."
},
"type": "claude_chat_created",
"claude_chat_id": "claude_chat_01XyDMpzjS89pFZXqSFUBDr6",
"claude_project_id": "claude_proj_01KGp4eZNug9ri4kE35RSppq"
}
],
"has_more": true,
"first_id": "activity_01XyDMpzjS89pFZXqSFUBDr6",
"last_id": "activity_01XyDMpzjS89pFZXqSFUBDr6"
}
```
## Filter activities
Filter by organization, actor, activity type, or a `created_at` time window using the dotted sub-parameters `created_at.gte`, `.gt`, `.lte`, and `.lt`. See the [API reference](/docs/en/api/compliance/activities/list) for each parameter's type and accepted values.
Repeatable parameters use array-bracket query syntax: pass `activity_types[]=...`, `actor_ids[]=...`, or `organization_ids[]=...` once for each value.
```bash cURL nocheck
curl --fail-with-body -sS -G \
"https://api.anthropic.com/v1/compliance/activities" \
--data-urlencode "activity_types[]=claude_file_uploaded" \
--data-urlencode "activity_types[]=claude_chat_created" \
--data-urlencode "created_at.gte=2026-04-01T00:00:00Z" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY"
```
The Activity Feed produces hundreds of distinct activity types. See [Query compliance activities](/docs/en/api/compliance/activities/list) in the API reference for the full list of values that `activity_types[]` accepts.
## Paginate results
Activities are returned newest first, with ties in `created_at` broken by activity ID, and capped at `limit` results in each response (default 100, max 5,000). See the [API reference](/docs/en/api/compliance/activities/list) for the full response schema.
The Compliance API uses two pagination schemes depending on the endpoint family:
| Endpoint family | Sort order | Scheme | Parameters |
| :---- | :---- | :---- | :---- |
| Activities | Newest first | Cursor | `after_id`, `before_id` (returned as `first_id`, `last_id`) |
| Chats and chat messages | Oldest first | Cursor | `after_id`, `before_id` (returned as `first_id`, `last_id`) |
| Projects, project attachments, users, roles, role permissions, groups, group members | Endpoint-specific | Page token | `page` (returned as `next_page`) |
Organizations and files do not paginate: [List organizations](/docs/en/manage-claude/compliance-org-data#list-organizations) returns all results in one response, and files are retrieved individually by ID.
Pagination cursors and page tokens are opaque strings: pass them back unchanged. Their internal format is not stable, and parsing them will break without notice. Only one of `after_id` or `before_id` may be set in each request, and both schemes return `has_more` so you know when to stop.
To page through activities:
- Pass the response's `last_id` as `after_id` to advance to the next page in result order. With activities sorted newest first, the next page contains older entries.
- Pass `first_id` as `before_id` to return to the previous page.
- Stop when `has_more` is `false`.
The cursor parameter sets the page direction; the endpoint's sort order sets the time direction. The same `after_id` parameter reaches older activities here. Chats sort oldest first; see [Retrieve and delete chats, files, and projects](/docs/en/manage-claude/compliance-content-data) for the cursor semantics there.
**Cursors are safe to reuse on retry.** A cursor or page token from a
successfully returned page remains valid; a request that fails (5xx, timeout,
network error) does not advance your position. Retry the same request with the
same cursor. Only move to the next cursor after you have stored the page it
points past.
```bash cURL nocheck
# Fetch the first page (newest activities first) and capture its trailing cursor.
last_id=$(curl --fail-with-body -sS \
"https://api.anthropic.com/v1/compliance/activities?limit=2" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" | jq -er '.last_id')
# Pass the cursor back unchanged to fetch the next (older) page.
curl --fail-with-body -sS -G \
"https://api.anthropic.com/v1/compliance/activities" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
--data-urlencode "limit=2" \
--data-urlencode "after_id=${last_id}"
```
A production **backfill** loop pages through older activities by driving iteration off `has_more` and `last_id`:
1. Start from your stored cursor (or omit `after_id` to start from the beginning).
2. Page through with `after_id=` until `has_more` is `false`.
3. Persist the final `last_id` only after you've stored every page it covers.
```text
cursor = stored_cursor
loop:
if cursor is not null:
page = GET /v1/compliance/activities?after_id={cursor}&limit=100
else:
page = GET /v1/compliance/activities?limit=100
store(page.data)
if page.last_id is not null:
cursor = page.last_id
if not page.has_more: break
persist(cursor)
```
## Understand the Activity object
Every entry in `data` is an Activity with this top-level shape:
| Field | Type | Description |
| :---- | :---- | :---- |
| `id` | string | Unique identifier for the activity. |
| `created_at` | RFC 3339 string | When the activity occurred. |
| `organization_id` | string or null | Organization where the activity occurred, or `null` for events not tied to an organization (sign-in, sign-out, Compliance API calls). |
| `organization_uuid` | string or null | Same scoping as `organization_id`, expressed as a UUID. |
| `actor` | Actor union | Who or what performed the activity. See the following actor table. |
| `type` | string | The activity type, for example `claude_chat_created`. |
| _additional fields_ | varies | Type-specific fields, for example `claude_chat_id` on chat events or `filename` on file events. See [Query compliance activities](/docs/en/api/compliance/activities/list) in the API reference for the per-type field list. |
The `actor` field is a discriminated union. The `type` discriminator tells you which other fields are present:
| `actor.type` | When it appears | Key fields |
| :---- | :---- | :---- |
| `user_actor` | A signed-in claude.ai or Claude Console user took the action. | `email_address`, `user_id`, `ip_address`, `user_agent` |
| `api_actor` | A request called the Claude API or the Compliance API with a customer-issued API key. Compliance API calls produce this actor type for both Compliance Access Keys and Admin API keys. | `api_key_id`, `ip_address`, `user_agent` |
| `admin_api_key_actor` | An organization admin used an Admin API key to manage users, invites, workspaces, or API keys. | `admin_api_key_id` |
| `unauthenticated_user_actor` | An action occurred before sign-in completed, for example `sso_login_initiated`. | `unauthenticated_email_address`, `ip_address`, `user_agent` |
| `anthropic_actor` | Anthropic acted on the organization, for example through internal tooling. | `email_address` (always `null`; present for shape consistency with `user_actor`, since Anthropic operators are not represented by individual email) |
| `scim_directory_sync_actor` | An identity provider (such as Okta, Microsoft Entra ID, or JumpCloud) pushed a change through SCIM directory sync. | `workos_event_id`, `directory_id`, `idp_connection_type` (nullable; for example `OktaSCIMV2`, `AzureSCIMV2`) |
**Build forward-compatible parsers.** Pass through unrecognized `type` and
`actor.type` values, and ignore fields your parser does not expect, so your
integration keeps working when new activity types ship.
## Next steps
The full request and response schema for `GET /v1/compliance/activities`, including every supported `activity_types[]` value.
Query and delete the underlying content for activities you find in the feed (Compliance Access Key required).
Choose a polling or batch consumption pattern and plan SIEM correlation.
The full error catalog.
---
# Retrieve and delete chats, files, and projects
URL: https://platform.claude.com/docs/en/manage-claude/compliance-content-data
# Retrieve and delete chats, files, and projects
Access chat content, file attachments, and projects for claude.ai organizations through the Compliance API.
---
The Compliance API is available only on the Claude Enterprise plan and must be enabled before use. See [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access).
**Required scope:** `read:compliance_user_data` on the Compliance Access Key. The delete endpoints also require `delete:compliance_user_data`.
**Prerequisite:** To list chats, at least one user ID from [List organization users](/docs/en/manage-claude/compliance-org-data#list-organization-users). The other endpoints on this page take resource IDs directly.
The endpoints on this page expose claude.ai chat content, file uploads, projects, and project attachments to compliance reviewers. They support eDiscovery (electronic discovery) exports, data loss prevention (DLP) enforcement, and account-deletion responses. Content is retained for as long as your organization's retention policy allows. Chats that a user has soft-deleted in claude.ai remain visible through the Compliance API with `deleted_at` populated; chats that have been hard-deleted (through the Compliance API itself, or after the organization's retention window expires) are not retrievable.
Both scopes are granted only on Compliance Access Keys (`sk-ant-api01-...`) created in claude.ai; see [Get access to the Compliance API](/docs/en/manage-claude/compliance-api-access) to provision one. The `read:compliance_user_data` scope covers retrieval; `delete:compliance_user_data` is required only for the delete endpoints. The chat, file, project, and attachment endpoints are not available to Admin API keys (`sk-ant-admin01-...`); calls authenticated with an Admin API key return [403 Forbidden](/docs/en/manage-claude/compliance-errors#403-forbidden).
Endpoints on this page paginate two ways; see [Paginate results](/docs/en/manage-claude/compliance-activity-feed#paginate-results) for the full reference. Each section notes which scheme applies.
## Retrieve chats and messages
Use [List chats](/docs/en/api/compliance/apps/chats/list) to page through chat metadata, then [Get chat messages](/docs/en/api/compliance/apps/chats/messages) to fetch the full message content of one chat.
The chat list endpoint requires at least one `user_ids[]` value (and accepts up to 10 in one request), so enumerate user IDs first with [List organization users](/docs/en/manage-claude/compliance-org-data#list-organization-users), then list chats for each user or for each batch of users. The following request lists chats owned by a specific user since a given date.
```bash cURL nocheck
curl --fail-with-body -sS -G \
"https://api.anthropic.com/v1/compliance/apps/chats" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
--data-urlencode "user_ids[]=user_01XyDMpzjS89pFZXqSFUBDr6" \
--data-urlencode "organization_ids[]=91012d09-e48b-438e-a489-1bebfd8fa6f9" \
--data-urlencode "created_at.gte=2025-06-01T00:00:00Z" \
--data-urlencode "limit=100"
```
```json Response
{
"data": [
{
"id": "claude_chat_01H5CWunD7RpVJ5bHa8RCkja",
"name": "Product Requirements Discussion",
"created_at": "2026-04-10T08:09:10Z",
"updated_at": "2026-04-10T09:10:11Z",
"deleted_at": null,
"href": "https://claude.ai/chat/abcdef01-2345-6789-abcd-ef0123456789",
"model": "claude-opus-4-7",
"organization_id": "org_01Wv6QeBcDfGhJkLmNpQrSt8",
"organization_uuid": "91012d09-e48b-438e-a489-1bebfd8fa6f9",
"project_id": "claude_proj_01KGp4eZNug9ri4kE35RSppq",
"user": {
"id": "user_01XyDMpzjS89pFZXqSFUBDr6",
"email_address": "user@example.com"
}
}
],
"has_more": true,
"first_id": "claude_chat_01H5CWunD7RpVJ5bHa8RCkja",
"last_id": "claude_chat_01H5CWunD7RpVJ5bHa8RCkja"
}
```
Listing chats returns metadata only. See [List chats](/docs/en/api/compliance/apps/chats/list) for the full filter list; in addition to the required `user_ids[]`, the `updated_at.*` bounds are useful for incremental review of chats that have changed since a previous export.
Chat results are sorted by `created_at` ascending (oldest first), with ties broken by `id`. Pagination uses the same `first_id`/`last_id`/`has_more` cursor fields as [Paginate results](/docs/en/manage-claude/compliance-activity-feed#paginate-results); pass `last_id` as `after_id` to walk forward toward newer chats, or `first_id` as `before_id` to walk back toward older ones.
To pull the actual chat content, attached files, and inline artifacts (structured documents Claude generates inside a chat), follow up with the messages endpoint for each chat ID:
```bash cURL nocheck
chat_id="claude_chat_01H5CWunD7RpVJ5bHa8RCkja"
curl --fail-with-body -sS \
"https://api.anthropic.com/v1/compliance/apps/chats/$chat_id/messages" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY"
```
The messages endpoint returns the chat's metadata plus a `chat_messages` array sorted by `created_at`. When `limit` is omitted, the full message set is returned in one response; pass `limit`, `after_id`, or `before_id` to page through very long chats (see [Get chat messages](/docs/en/api/compliance/apps/chats/messages) for the cursor parameters). For user messages, `created_at` is when the message was sent; for assistant messages, it is when Claude finished generating the message. Each message carries its text content and, when present, any uploaded files (typically on user messages), any tool-generated files, and any artifacts the assistant produced or updated (typically on assistant messages):
```json Response
{
"id": "claude_chat_01H5CWunD7RpVJ5bHa8RCkja",
"name": "Product Requirements Discussion",
"created_at": "2026-04-10T08:09:10Z",
"updated_at": "2026-04-10T09:10:11Z",
"deleted_at": null,
"href": "https://claude.ai/chat/abcdef01-2345-6789-abcd-ef0123456789",
"model": "claude-opus-4-7",
"organization_id": "org_01Wv6QeBcDfGhJkLmNpQrSt8",
"organization_uuid": "91012d09-e48b-438e-a489-1bebfd8fa6f9",
"project_id": "claude_proj_01KGp4eZNug9ri4kE35RSppq",
"user": {
"id": "user_01XyDMpzjS89pFZXqSFUBDr6",
"email_address": "user@example.com"
},
"chat_messages": [
{
"id": "claude_chat_msg_01VnBPkLmtj7YdW5QrXKEA8c",
"role": "user",
"created_at": "2026-04-10T08:09:10Z",
"content": [
{
"type": "text",
"text": "Can you help me draft requirements for our new dashboard feature?"
}
],
"files": [
{
"id": "claude_file_01UaT9wBcDfGhJkLmNpQrSv7",
"filename": "dashboard_mockup_v1.pdf",
"mime_type": "application/pdf"
}
]
},
{
"id": "claude_chat_msg_01M8tFcHwbQ2kY6NpEjRZv4D",
"role": "assistant",
"created_at": "2026-04-10T08:09:11Z",
"content": [
{
"type": "text",
"text": "I'd be happy to help you draft requirements for your dashboard feature..."
}
],
"generated_files": [
{
"id": "claude_gen_file_01TbR8wAcCeFhJkLnPqStUvX",
"filename": "requirements_summary.csv",
"mime_type": "text/csv"
}
],
"artifacts": [
{
"id": "claude_artifact_01HqRsTuVwXyZa2BcDeFgH4J",
"version_id": "claude_artifact_version_01KmNpQrSt3UvWxYz5AbCdEfG",
"title": "Dashboard Requirements Draft",
"artifact_type": "text/markdown"
}
]
}
],
"has_more": false,
"first_id": "eyJtc2dfdXVpZCI6ICIwZjcwYjA2Ni0uLi4ifQ==",
"last_id": "eyJtc2dfdXVpZCI6ICJhNGUwYjE3Mi0uLi4ifQ=="
}
```
`files`, `generated_files`, and `artifacts` can each be `null` on a given message. `generated_files` lists downloadable files the assistant created during the conversation through tool use (for example, PDFs, spreadsheets, or slide decks). It is distinct from `files`, which are uploads the user attached to the message. Pass each entry's `id` to the [generated file content endpoint](/docs/en/api/compliance/apps/chats/generated_files/content) to download it.
## Retrieve files and artifacts
Files and artifacts are downloaded by ID, not listed independently. The IDs come from the chat messages endpoint in [Retrieve chats and messages](#retrieve-chats-and-messages) (the `files`, `generated_files`, and `artifacts` arrays on each message) or, for project-level uploads, from the [project attachments endpoint](#retrieve-projects-and-attachments).
Pick the endpoint that matches your ID type and the data you need. The same file content endpoint serves both chat files and project files.
| You have | You want | Use this endpoint |
| --- | --- | --- |
| `claude_file_*` ID | The file's binary content | [Download file content](/docs/en/api/compliance/apps/chats/files/content) |
| `claude_gen_file_*` ID | A tool-generated file's binary content | [Download a Claude-generated file](/docs/en/api/compliance/apps/chats/generated_files/content) |
| `claude_artifact_version_*` ID | One artifact version's text | [Download artifact content](/docs/en/api/compliance/apps/artifacts/content) |
| `claude_file_*` ID | The file's metadata only (filename, MIME type, size) | [Get file metadata](/docs/en/api/compliance/apps/chats/files/retrieve) |
| `claude_proj_doc_*` ID | A project document's plain-text content | [Get project document content](/docs/en/api/compliance/apps/projects/documents/retrieve) |
The file content endpoint streams the original upload as a chunked binary response with these headers:
- `Content-Disposition: attachment; filename*=utf-8''` carries the original upload filename in RFC 5987 extended form. The extended form is used for every filename, not only non-ASCII ones.
- `Content-Type` carries the upload's MIME type.
- `Transfer-Encoding: chunked` is always set.
```bash cURL nocheck
file_id="claude_file_01UaT9wBcDfGhJkLmNpQrSv7"
curl --fail-with-body -sS -OJ \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
"https://api.anthropic.com/v1/compliance/apps/chats/files/$file_id/content"
```
The `-OJ` flags tell curl to save the response under the filename from `Content-Disposition`, which is the original filename the user uploaded.
The artifact content endpoint returns the text body of one artifact version. Pass the `version_id` from one of the entries in an assistant message's `artifacts` array, not the artifact's stable `id`. Each new version of an artifact has its own `version_id`, and the Compliance API serves the exact bytes of that version.
## Retrieve projects and attachments
Projects bundle related chats together with custom instructions, knowledge base content, and attached files or text documents. The Compliance API exposes project metadata, project details, and the list of attachments belonging to a project.
- [List projects](/docs/en/api/compliance/apps/projects/list)
- [Get project details](/docs/en/api/compliance/apps/projects/retrieve)
- [List project attachments](/docs/en/api/compliance/apps/projects/attachments)
- [Get project document content](/docs/en/api/compliance/apps/projects/documents/retrieve)
Project results are sorted by creation date ascending. Attachment results are sorted by `created_at` ascending, with ties broken by `id`. Project list and attachment list responses paginate with an opaque `next_page` page token instead of the `first_id`/`last_id` cursors used by chats and the Activity Feed. Pass the token back as the `page` query parameter on the next request.
### Project files versus project documents
A project attachment is one of two distinct shapes, identified by the `type` discriminator on each entry:
Entries with `type` of `project_file` are binary uploads (PDFs, images, spreadsheets) whose IDs start with `claude_file_`; download them with [Download file content](/docs/en/api/compliance/apps/chats/files/content). Entries with `type` of `project_doc` are plain-text documents (always `text/plain`) whose IDs start with `claude_proj_doc_`; fetch them with [Get project document content](/docs/en/api/compliance/apps/projects/documents/retrieve).
A consumer that walks the attachment list must branch on `type` and call the matching content endpoint for each entry. The following request lists one page of attachments; paginate by passing `next_page` back as the `page` parameter until `has_more` is `false`.
```bash cURL nocheck
project_id="claude_proj_01KGp4eZNug9ri4kE35RSppq"
curl --fail-with-body -sS -G \
"https://api.anthropic.com/v1/compliance/apps/projects/$project_id/attachments" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY"
```
```json Response
{
"data": [
{
"id": "claude_file_01UaT9wBcDfGhJkLmNpQrSv7",
"created_at": "2026-04-10T08:09:10Z",
"filename": "dashboard_mockup_v1.pdf",
"mime_type": "application/pdf",
"type": "project_file"
},
{
"id": "claude_proj_doc_01YnT8sBcWvUtXzQpMkRfDgH",
"created_at": "2026-04-10T08:09:11Z",
"filename": "requirements.md",
"mime_type": "text/plain",
"type": "project_doc"
}
],
"has_more": false,
"next_page": null
}
```
## Delete content
Every successful delete is permanent and immediate. There is no recovery window.
The Compliance API exposes hard-delete endpoints for chats, files, project documents, and entire projects. A hard-deleted chat cannot be restored, and it stops appearing in list responses afterward (whereas a chat soft-deleted from claude.ai still appears with `deleted_at` populated).
- [Delete chat](/docs/en/api/compliance/apps/chats/delete): also removes the chat's messages and any files attached to those messages.
- [Delete file](/docs/en/api/compliance/apps/chats/files/delete): handles both chat files and project files.
- [Delete project document](/docs/en/api/compliance/apps/projects/documents/delete): removes a single project document by ID.
- [Delete project](/docs/en/api/compliance/apps/projects/delete): see [Detach chats before deleting a project](#detach-chats-before-deleting-a-project).
All four endpoints require the `delete:compliance_user_data` scope, which is granted separately from the read scope when the Compliance Access Key is created.
The following request deletes one chat. The same pattern applies to the other delete endpoints; only the URL changes.
```bash cURL nocheck
# WARNING: This operation PERMANENTLY deletes the chat, all of its messages,
# and any attached files. Deletion is immediate and cannot be undone. It
# requires the `delete:compliance_user_data` scope, which is granted separately
# from `read:compliance_user_data` when the Compliance Access Key is created.
# Ensure you have explicit authorization before running this.
chat_id="claude_chat_01H5CWunD7RpVJ5bHa8RCkja"
curl --fail-with-body -sS -X DELETE \
"https://api.anthropic.com/v1/compliance/apps/chats/$chat_id" \
--header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY"
```
```json Response
{
"id": "claude_chat_01H5CWunD7RpVJ5bHa8RCkja",
"type": "claude_chat_deleted"
}
```
Each successful delete returns a small confirmation envelope with an `id` and a `type` discriminator. The chat endpoint returns `claude_chat_deleted`; check the `type` field before treating the delete as confirmed. See the response schema on each delete endpoint's [API reference](/docs/en/api/compliance/apps) page for the exact `type` value the other endpoints return.
### Detach chats before deleting a project
A project cannot be deleted while any chats remain attached to it. The API returns 409 with this body:
```json
{
"error": {
"type": "conflict_error",
"message": "The \"claude_proj_01KGp4eZNug9ri4kE35RSppq\" project cannot be deleted as it has chats attached to it. Delete or detach all chats, and try deleting the project again."
}
}
```
To resolve, list the project's chats with `GET /v1/compliance/apps/chats?user_ids[]={user_id}&project_ids[]={project_id}` (the chat list endpoint requires at least one `user_ids[]` value; enumerate IDs through [List organization users](/docs/en/manage-claude/compliance-org-data#list-organization-users)), delete each one with `DELETE /v1/compliance/apps/chats/{claude_chat_id}` (or move it out of the project from claude.ai), and then retry the project delete.
## Next steps
The full request and response schema for every chat, file, project, and artifact endpoint.
Enumerate the people and teams associated with the chats and projects on this page.
## API Reference
### Public API
---
# Activities
URL: https://platform.claude.com/docs/en/api/compliance/activities
# Activities
## List
**get** `/v1/compliance/activities`
List compliance activities for the authenticated parent organization.
Returns a paginated list of compliance activities that can be filtered by various criteria.
### Query Parameters
- `activity_types: optional array of "account_deleted" or "admin_api_key_created" or "admin_api_key_deleted" or 292 more`
Filter activities by type
- `"account_deleted"`
- `"admin_api_key_created"`
- `"admin_api_key_deleted"`
- `"admin_api_key_updated"`
- `"api_key_created"`
- `"scoped_api_key_deleted"`
- `"scoped_api_key_updated"`
- `"claude_artifact_access_failed"`
- `"claude_published_artifact_deleted"`
- `"claude_artifact_published"`
- `"claude_artifact_sharing_updated"`
- `"claude_artifact_viewed"`
- `"claude_chat_access_failed"`
- `"claude_chat_snapshot_created"`
- `"claude_chat_snapshot_viewed"`
- `"claude_chat_created"`
- `"claude_chat_deleted"`
- `"claude_chat_deletion_failed"`
- `"claude_chat_settings_updated"`
- `"claude_chat_updated"`
- `"desktop_extension_allowlisted"`
- `"desktop_extension_blocklisted"`
- `"desktop_extension_deleted"`
- `"desktop_extension_removed_from_allowlist"`
- `"desktop_extension_unblocked"`
- `"desktop_extension_uploaded"`
- `"desktop_extension_version_uploaded"`
- `"plugin_installation_preference_updated"`
- `"claude_chat_viewed"`
- `"claude_code_review_config_updated"`
- `"claude_code_review_repository_added"`
- `"claude_code_review_repository_removed"`
- `"claude_code_review_repository_updated"`
- `"claude_code_security_center_config_updated"`
- `"claude_file_access_failed"`
- `"claude_file_deleted"`
- `"claude_file_uploaded"`
- `"claude_file_viewed"`
- `"claude_gdrive_integration_created"`
- `"claude_gdrive_integration_deleted"`
- `"claude_gdrive_integration_updated"`
- `"claude_github_integration_created"`
- `"claude_github_integration_deleted"`
- `"claude_github_integration_updated"`
- `"claude_project_archived"`
- `"claude_project_created"`
- `"claude_project_deleted"`
- `"claude_project_document_access_failed"`
- `"claude_project_document_deleted"`
- `"claude_project_document_deletion_failed"`
- `"claude_project_document_uploaded"`
- `"claude_project_document_viewed"`
- `"claude_project_file_access_failed"`
- `"claude_project_file_deleted"`
- `"claude_project_file_deletion_failed"`
- `"claude_project_file_uploaded"`
- `"claude_project_reported"`
- `"claude_project_sharing_updated"`
- `"claude_project_viewed"`
- `"claude_user_role_updated"`
- `"claude_user_settings_updated"`
- `"admin_request_created"`
- `"compliance_api_accessed"`
- `"domain_claim_initiated"`
- `"end_user_invite_requested"`
- `"environment_archived"`
- `"environment_created"`
- `"environment_deleted"`
- `"environment_token_minted"`
- `"environment_token_revoked"`
- `"environment_updated"`
- `"group_created"`
- `"group_deleted"`
- `"group_list_viewed"`
- `"group_member_added"`
- `"group_member_list_viewed"`
- `"group_member_removed"`
- `"group_updated"`
- `"group_viewed"`
- `"magic_link_login_failed"`
- `"magic_link_login_initiated"`
- `"magic_link_login_succeeded"`
- `"social_login_succeeded"`
- `"sso_login_failed"`
- `"sso_login_initiated"`
- `"sso_login_succeeded"`
- `"service_created"`
- `"service_deleted"`
- `"service_key_created"`
- `"service_key_revoked"`
- `"platform_signing_key_created"`
- `"platform_signing_key_deleted"`
- `"platform_signing_key_rotated"`
- `"user_logged_out"`
- `"age_verified"`
- `"anonymous_mobile_login_attempted"`
- `"phone_code_sent"`
- `"phone_code_verified"`
- `"session_revoked"`
- `"sso_second_factor_magic_link"`
- `"org_user_deleted"`
- `"org_user_invite_accepted"`
- `"org_user_invite_deleted"`
- `"org_user_invite_re_sent"`
- `"org_user_invite_rejected"`
- `"org_user_invite_sent"`
- `"org_user_left"`
- `"org_domain_add_initiated"`
- `"org_domain_removed"`
- `"org_domain_verified"`
- `"org_join_proposal_decided"`
- `"org_magic_link_second_factor_toggled"`
- `"org_sso_add_initiated"`
- `"org_sso_connection_activated"`
- `"org_sso_connection_deactivated"`
- `"org_sso_connection_deleted"`
- `"org_sso_group_role_mappings_updated"`
- `"org_sso_provisioning_mode_changed"`
- `"org_sso_seat_tier_assignment_toggled"`
- `"org_sso_seat_tier_mappings_updated"`
- `"org_sso_toggled"`
- `"org_directory_resync_completed"`
- `"org_directory_resync_failed"`
- `"org_directory_resync_started"`
- `"org_directory_sync_activated"`
- `"org_directory_sync_add_initiated"`
- `"org_directory_sync_deleted"`
- `"claude_organization_settings_updated"`
- `"org_claude_code_data_sharing_disabled"`
- `"org_claude_code_data_sharing_enabled"`
- `"org_analytics_api_capability_updated"`
- `"org_compliance_api_settings_updated"`
- `"org_creation_blocked"`
- `"org_parent_join_proposal_created"`
- `"org_parent_search_performed"`
- `"org_sync_deleting_synchronized_files_started"`
- `"org_sync_synchronized_files_deleted"`
- `"org_data_export_completed"`
- `"org_data_export_started"`
- `"org_members_exported"`
- `"owned_projects_access_restored"`
- `"audit_log_export_accessed"`
- `"audit_log_export_started"`
- `"org_data_export_accessed"`
- `"organization_address_updated"`
- `"primary_owner_transferred"`
- `"role_assignment_granted"`
- `"role_assignment_revoked"`
- `"integration_user_connected"`
- `"integration_user_disconnected"`
- `"billing_emails_updated"`
- `"extra_usage_billing_enabled"`
- `"extra_usage_credit_granted"`
- `"extra_usage_spend_limit_created"`
- `"extra_usage_spend_limit_deleted"`
- `"extra_usage_spend_limit_updated"`
- `"invoice_collection_method_updated"`
- `"managed_organization_setup_completed"`
- `"payment_method_updated"`
- `"platform_spend_limit_alert_emails_updated"`
- `"platform_spend_limit_created"`
- `"platform_spend_limit_deleted"`
- `"platform_spend_limit_updated"`
- `"prepaid_auto_recharge_disabled"`
- `"prepaid_auto_recharge_updated"`
- `"prepaid_extra_usage_auto_reload_disabled"`
- `"prepaid_extra_usage_auto_reload_enabled"`
- `"prepaid_extra_usage_auto_reload_settings_updated"`
- `"seat_tier_changes_cancelled"`
- `"seat_tiers_purchased"`
- `"subscription_cancellation_scheduled"`
- `"subscription_quantity_updated"`
- `"subscription_renewed"`
- `"subscription_resumed"`
- `"subscription_started"`
- `"subscription_upgraded"`
- `"tunnel_token_minted"`
- `"tunnel_token_revoked"`
- `"user_consent_recorded"`
- `"user_consent_revoked"`
- `"workspace_member_spend_limit_created"`
- `"workspace_member_spend_limit_deleted"`
- `"workspace_member_spend_limit_updated"`
- `"workspace_spend_limit_created"`
- `"workspace_spend_limit_deleted"`
- `"organization_icon_deleted"`
- `"organization_icon_updated"`
- `"org_ip_restriction_created"`
- `"org_ip_restriction_deleted"`
- `"org_ip_restriction_updated"`
- `"org_bulk_delete_initiated"`
- `"org_deleted_via_bulk"`
- `"claude_skill_created"`
- `"claude_skill_deleted"`
- `"claude_skill_disabled"`
- `"claude_skill_enabled"`
- `"claude_skill_replaced"`
- `"claude_command_created"`
- `"claude_command_deleted"`
- `"claude_command_replaced"`
- `"claude_plugin_created"`
- `"claude_plugin_deleted"`
- `"claude_plugin_replaced"`
- `"claude_plugin_updated"`
- `"session_share_accessed"`
- `"session_share_created"`
- `"session_share_revoked"`
- `"org_deletion_requested"`
- `"org_invite_link_disabled"`
- `"org_invite_link_generated"`
- `"org_invite_link_regenerated"`
- `"rbac_role_created"`
- `"rbac_role_updated"`
- `"rbac_role_deleted"`
- `"rbac_role_assigned"`
- `"rbac_role_unassigned"`
- `"rbac_role_permission_added"`
- `"rbac_role_permission_removed"`
- `"org_claude_code_desktop_disabled"`
- `"org_claude_code_desktop_enabled"`
- `"org_cowork_disabled"`
- `"org_cowork_enabled"`
- `"org_cowork_agent_disabled"`
- `"org_cowork_agent_enabled"`
- `"org_work_across_apps_disabled"`
- `"org_work_across_apps_enabled"`
- `"org_hipaa_self_serve_enabled"`
- `"org_taint_added"`
- `"org_taint_removed"`
- `"mcp_server_created"`
- `"mcp_server_deleted"`
- `"mcp_server_updated"`
- `"mcp_tool_policy_updated"`
- `"cli_plugin_exec_policy_updated"`
- `"marketplace_created"`
- `"marketplace_deleted"`
- `"marketplace_updated"`
- `"ghe_configuration_created"`
- `"ghe_configuration_deleted"`
- `"ghe_configuration_updated"`
- `"ghe_user_connected"`
- `"ghe_user_disconnected"`
- `"ghe_webhook_signature_invalid"`
- `"org_discoverability_enabled"`
- `"org_discoverability_disabled"`
- `"org_discoverability_settings_updated"`
- `"org_join_request_created"`
- `"org_join_request_approved"`
- `"org_join_request_instant_approved"`
- `"org_join_request_dismissed"`
- `"org_join_requests_bulk_dismissed"`
- `"org_member_invites_enabled"`
- `"org_member_invites_disabled"`
- `"lti_launch_initiated"`
- `"lti_launch_success"`
- `"lti_platform_created"`
- `"lti_platform_updated"`
- `"org_users_listed"`
- `"org_user_viewed"`
- `"org_invites_listed"`
- `"org_invite_viewed"`
- `"org_external_key_created"`
- `"org_external_key_updated"`
- `"org_external_key_deleted"`
- `"org_external_key_validated"`
- `"platform_workspace_created"`
- `"platform_workspace_updated"`
- `"platform_workspace_archived"`
- `"platform_federation_issuer_archived"`
- `"platform_federation_issuer_updated"`
- `"platform_federation_rule_archived"`
- `"platform_federation_rule_updated"`
- `"platform_service_account_archived"`
- `"platform_service_account_updated"`
- `"platform_workspace_members_listed"`
- `"platform_workspace_member_viewed"`
- `"platform_workspace_member_added"`
- `"platform_workspace_member_updated"`
- `"platform_workspace_member_removed"`
- `"platform_usage_report_messages_viewed"`
- `"platform_usage_report_claude_code_viewed"`
- `"platform_cost_report_viewed"`
- `"platform_api_key_updated"`
- `"platform_api_key_created"`
- `"platform_workspace_rate_limit_updated"`
- `"platform_workspace_rate_limit_deleted"`
- `"platform_file_uploaded"`
- `"platform_file_content_downloaded"`
- `"platform_file_deleted"`
- `"platform_skill_version_created"`
- `"platform_skill_version_deleted"`
- `"scim_user_created"`
- `"scim_user_updated"`
- `"scim_user_deleted"`
- `"claude_pubsec_identity_configured"`
- `actor_ids: optional array of string`
Filter activities by actor IDs (currently only `user_...` IDs are supported). Enumerate IDs via `GET /v1/compliance/organizations/{org_uuid}/users`.
- `after_id: optional string`
Pagination cursor for retrieving the next page of results (heading backwards in time). To paginate, pass the `last_id` value from the most recent response. Clients should treat this value as an opaque string and not attempt to parse or interpret its contents, as the format may change without notice.
- `before_id: optional string`
Pagination cursor for retrieving the previous page of results (heading forwards in time). To paginate, pass the `first_id` value from the most recent response. Clients should treat this value as an opaque string and not attempt to parse or interpret its contents, as the format may change without notice.
- `created_at: optional object { gt, gte, lt, lte }`
- `gt: optional string`
Filter activities created after this time (RFC 3339 format)
- `gte: optional string`
Filter activities created at or after this time (RFC 3339 format)
- `lt: optional string`
Filter activities created before this time (RFC 3339 format)
- `lte: optional string`
Filter activities created at or before this time (RFC 3339 format)
- `limit: optional number`
Maximum results (default: 100, max: 5000)
- `organization_ids: optional array of string`
Filter activities by organization IDs (accepts `org_...` or organization UUID). Enumerate IDs via `GET /v1/compliance/organizations`.
### Header Parameters
- `"x-api-key": optional string`
### Returns
- `data: optional array of map[unknown]`
- `first_id: optional string`
- `has_more: optional boolean`
- `last_id: optional string`
### Example
```http
curl https://api.anthropic.com/v1/compliance/activities \
-H "Authorization: Bearer $ANTHROPIC_COMPLIANCE_API_KEY"
```
## Domain Types
### Activity List Response
- `ActivityListResponse = map[unknown]`
---
# Add Session Resource (Beta)
URL: https://platform.claude.com/docs/en/api/beta/sessions/resources/add
## Add
**post** `/v1/sessions/{session_id}/resources`
Add Session Resource
### Path Parameters
- `session_id: string`
### Header Parameters
- `"anthropic-beta": optional array of AnthropicBeta`
Optional header to specify the beta version(s) you want to use.
- `UnionMember0 = string`
- `UnionMember1 = "message-batches-2024-09-24" or "prompt-caching-2024-07-31" or "computer-use-2024-10-22" or 21 more`
- `"message-batches-2024-09-24"`
- `"prompt-caching-2024-07-31"`
- `"computer-use-2024-10-22"`
- `"computer-use-2025-01-24"`
- `"pdfs-2024-09-25"`
- `"token-counting-2024-11-01"`
- `"token-efficient-tools-2025-02-19"`
- `"output-128k-2025-02-19"`
- `"files-api-2025-04-14"`
- `"mcp-client-2025-04-04"`
- `"mcp-client-2025-11-20"`
- `"dev-full-thinking-2025-05-14"`
- `"interleaved-thinking-2025-05-14"`
- `"code-execution-2025-05-22"`
- `"extended-cache-ttl-2025-04-11"`
- `"context-1m-2025-08-07"`
- `"context-management-2025-06-27"`
- `"model-context-window-exceeded-2025-08-26"`
- `"skills-2025-10-02"`
- `"fast-mode-2026-02-01"`
- `"output-300k-2026-03-24"`
- `"user-profiles-2026-03-24"`
- `"advisor-tool-2026-03-01"`
- `"managed-agents-2026-04-01"`
### Body Parameters
- `file_id: string`
ID of a previously uploaded file.
- `type: "file"`
- `"file"`
- `mount_path: optional string`
Mount path in the container. Defaults to `/mnt/session/uploads/`.
### Returns
- `BetaManagedAgentsFileResource = object { id, created_at, file_id, 3 more }`
- `id: string`
- `created_at: string`
A timestamp in RFC 3339 format
- `file_id: string`
- `mount_path: string`
- `type: "file"`
- `"file"`
- `updated_at: string`
A timestamp in RFC 3339 format
### Example
```http
curl https://api.anthropic.com/v1/sessions/$SESSION_ID/resources \
-H 'Content-Type: application/json' \
-H 'anthropic-version: 2023-06-01' \
-H 'anthropic-beta: managed-agents-2026-04-01' \
-H "X-Api-Key: $ANTHROPIC_API_KEY" \
-d '{
"file_id": "file_011CNha8iCJcU1wXNR6q4V8w",
"type": "file",
"mount_path": "/uploads/receipt.pdf"
}'
```
---
# Add Session Resource (Beta) (cli)
URL: https://platform.claude.com/docs/en/api/cli/beta/sessions/resources/add
## Add
`$ ant beta:sessions:resources add`
**post** `/v1/sessions/{session_id}/resources`
Add Session Resource
### Parameters
- `--session-id: string`
Path param: Path parameter session_id
- `--file-id: string`
Body param: ID of a previously uploaded file.
- `--type: "file"`
Body param
- `--mount-path: optional string`
Body param: Mount path in the container. Defaults to `/mnt/session/uploads/`.
- `--beta: optional array of AnthropicBeta`
Header param: Optional header to specify the beta version(s) you want to use.
### Returns
- `beta_managed_agents_file_resource: object { id, created_at, file_id, 3 more }`
- `id: string`
- `created_at: string`
A timestamp in RFC 3339 format
- `file_id: string`
- `mount_path: string`
- `type: "file"`
- `"file"`
- `updated_at: string`
A timestamp in RFC 3339 format
### Example
```cli
ant beta:sessions:resources add \
--api-key my-anthropic-api-key \
--session-id sesn_011CZkZAtmR3yMPDzynEDxu7 \
--file-id file_011CNha8iCJcU1wXNR6q4V8w \
--type file
```
---
# Add Session Resource (Beta) (csharp)
URL: https://platform.claude.com/docs/en/api/csharp/beta/sessions/resources/add
## Add
`BetaManagedAgentsFileResource Beta.Sessions.Resources.Add(ResourceAddParamsparameters, CancellationTokencancellationToken = default)`
**post** `/v1/sessions/{session_id}/resources`
Add Session Resource
### Parameters
- `ResourceAddParams parameters`
- `required string sessionID`
Path param: Path parameter session_id
- `required string fileID`
Body param: ID of a previously uploaded file.
- `required Type type`
Body param
- `"file"File`
- `string? mountPath`
Body param: Mount path in the container. Defaults to `/mnt/session/uploads/`.
- `IReadOnlyList betas`
Header param: Optional header to specify the beta version(s) you want to use.
- `"message-batches-2024-09-24"MessageBatches2024_09_24`
- `"prompt-caching-2024-07-31"PromptCaching2024_07_31`
- `"computer-use-2024-10-22"ComputerUse2024_10_22`
- `"computer-use-2025-01-24"ComputerUse2025_01_24`
- `"pdfs-2024-09-25"Pdfs2024_09_25`
- `"token-counting-2024-11-01"TokenCounting2024_11_01`
- `"token-efficient-tools-2025-02-19"TokenEfficientTools2025_02_19`
- `"output-128k-2025-02-19"Output128k2025_02_19`
- `"files-api-2025-04-14"FilesApi2025_04_14`
- `"mcp-client-2025-04-04"McpClient2025_04_04`
- `"mcp-client-2025-11-20"McpClient2025_11_20`
- `"dev-full-thinking-2025-05-14"DevFullThinking2025_05_14`
- `"interleaved-thinking-2025-05-14"InterleavedThinking2025_05_14`
- `"code-execution-2025-05-22"CodeExecution2025_05_22`
- `"extended-cache-ttl-2025-04-11"ExtendedCacheTtl2025_04_11`
- `"context-1m-2025-08-07"Context1m2025_08_07`
- `"context-management-2025-06-27"ContextManagement2025_06_27`
- `"model-context-window-exceeded-2025-08-26"ModelContextWindowExceeded2025_08_26`
- `"skills-2025-10-02"Skills2025_10_02`
- `"fast-mode-2026-02-01"FastMode2026_02_01`
- `"output-300k-2026-03-24"Output300k2026_03_24`
- `"user-profiles-2026-03-24"UserProfiles2026_03_24`
- `"advisor-tool-2026-03-01"AdvisorTool2026_03_01`
- `"managed-agents-2026-04-01"ManagedAgents2026_04_01`
### Returns
- `class BetaManagedAgentsFileResource:`
- `required string ID`
- `required DateTimeOffset CreatedAt`
A timestamp in RFC 3339 format
- `required string FileID`
- `required string MountPath`
- `required Type Type`
- `"file"File`
- `required DateTimeOffset UpdatedAt`
A timestamp in RFC 3339 format
### Example
```csharp
ResourceAddParams parameters = new()
{
SessionID = "sesn_011CZkZAtmR3yMPDzynEDxu7",
FileID = "file_011CNha8iCJcU1wXNR6q4V8w",
Type = Type.File,
};
var betaManagedAgentsFileResource = await client.Beta.Sessions.Resources.Add(parameters);
Console.WriteLine(betaManagedAgentsFileResource);
```
---
# Add Session Resource (Beta) (Go)
URL: https://platform.claude.com/docs/en/api/go/beta/sessions/resources/add
## Add
`client.Beta.Sessions.Resources.Add(ctx, sessionID, params) (*BetaManagedAgentsFileResource, error)`
**post** `/v1/sessions/{session_id}/resources`
Add Session Resource
### Parameters
- `sessionID string`
- `params BetaSessionResourceAddParams`
- `BetaManagedAgentsFileResourceParams param.Field[BetaManagedAgentsFileResourceParamsResp]`
Body param: Mount a file uploaded via the Files API into the session.
- `Betas param.Field[[]AnthropicBeta]`
Header param: Optional header to specify the beta version(s) you want to use.
- `string`
- `type AnthropicBeta string`
- `const AnthropicBetaMessageBatches2024_09_24 AnthropicBeta = "message-batches-2024-09-24"`
- `const AnthropicBetaPromptCaching2024_07_31 AnthropicBeta = "prompt-caching-2024-07-31"`
- `const AnthropicBetaComputerUse2024_10_22 AnthropicBeta = "computer-use-2024-10-22"`
- `const AnthropicBetaComputerUse2025_01_24 AnthropicBeta = "computer-use-2025-01-24"`
- `const AnthropicBetaPDFs2024_09_25 AnthropicBeta = "pdfs-2024-09-25"`
- `const AnthropicBetaTokenCounting2024_11_01 AnthropicBeta = "token-counting-2024-11-01"`
- `const AnthropicBetaTokenEfficientTools2025_02_19 AnthropicBeta = "token-efficient-tools-2025-02-19"`
- `const AnthropicBetaOutput128k2025_02_19 AnthropicBeta = "output-128k-2025-02-19"`
- `const AnthropicBetaFilesAPI2025_04_14 AnthropicBeta = "files-api-2025-04-14"`
- `const AnthropicBetaMCPClient2025_04_04 AnthropicBeta = "mcp-client-2025-04-04"`
- `const AnthropicBetaMCPClient2025_11_20 AnthropicBeta = "mcp-client-2025-11-20"`
- `const AnthropicBetaDevFullThinking2025_05_14 AnthropicBeta = "dev-full-thinking-2025-05-14"`
- `const AnthropicBetaInterleavedThinking2025_05_14 AnthropicBeta = "interleaved-thinking-2025-05-14"`
- `const AnthropicBetaCodeExecution2025_05_22 AnthropicBeta = "code-execution-2025-05-22"`
- `const AnthropicBetaExtendedCacheTTL2025_04_11 AnthropicBeta = "extended-cache-ttl-2025-04-11"`
- `const AnthropicBetaContext1m2025_08_07 AnthropicBeta = "context-1m-2025-08-07"`
- `const AnthropicBetaContextManagement2025_06_27 AnthropicBeta = "context-management-2025-06-27"`
- `const AnthropicBetaModelContextWindowExceeded2025_08_26 AnthropicBeta = "model-context-window-exceeded-2025-08-26"`
- `const AnthropicBetaSkills2025_10_02 AnthropicBeta = "skills-2025-10-02"`
- `const AnthropicBetaFastMode2026_02_01 AnthropicBeta = "fast-mode-2026-02-01"`
- `const AnthropicBetaOutput300k2026_03_24 AnthropicBeta = "output-300k-2026-03-24"`
- `const AnthropicBetaUserProfiles2026_03_24 AnthropicBeta = "user-profiles-2026-03-24"`
- `const AnthropicBetaAdvisorTool2026_03_01 AnthropicBeta = "advisor-tool-2026-03-01"`
- `const AnthropicBetaManagedAgents2026_04_01 AnthropicBeta = "managed-agents-2026-04-01"`
### Returns
- `type BetaManagedAgentsFileResource struct{…}`
- `ID string`
- `CreatedAt Time`
A timestamp in RFC 3339 format
- `FileID string`
- `MountPath string`
- `Type BetaManagedAgentsFileResourceType`
- `const BetaManagedAgentsFileResourceTypeFile BetaManagedAgentsFileResourceType = "file"`
- `UpdatedAt Time`
A timestamp in RFC 3339 format
### Example
```go
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func main() {
client := anthropic.NewClient(
option.WithAPIKey("my-anthropic-api-key"),
)
betaManagedAgentsFileResource, err := client.Beta.Sessions.Resources.Add(
context.TODO(),
"sesn_011CZkZAtmR3yMPDzynEDxu7",
anthropic.BetaSessionResourceAddParams{
BetaManagedAgentsFileResourceParams: anthropic.BetaManagedAgentsFileResourceParams{
FileID: "file_011CNha8iCJcU1wXNR6q4V8w",
Type: anthropic.BetaManagedAgentsFileResourceParamsTypeFile,
},
},
)
if err != nil {
panic(err.Error())
}
fmt.Printf("%+v\n", betaManagedAgentsFileResource.ID)
}
```
---
# Add Session Resource (Beta) (Java)
URL: https://platform.claude.com/docs/en/api/java/beta/sessions/resources/add
## Add
`BetaManagedAgentsFileResource beta().sessions().resources().add(ResourceAddParamsparams, RequestOptionsrequestOptions = RequestOptions.none())`
**post** `/v1/sessions/{session_id}/resources`
Add Session Resource
### Parameters
- `ResourceAddParams params`
- `Optional sessionId`
- `Optional> betas`
Optional header to specify the beta version(s) you want to use.
- `MESSAGE_BATCHES_2024_09_24("message-batches-2024-09-24")`
- `PROMPT_CACHING_2024_07_31("prompt-caching-2024-07-31")`
- `COMPUTER_USE_2024_10_22("computer-use-2024-10-22")`
- `COMPUTER_USE_2025_01_24("computer-use-2025-01-24")`
- `PDFS_2024_09_25("pdfs-2024-09-25")`
- `TOKEN_COUNTING_2024_11_01("token-counting-2024-11-01")`
- `TOKEN_EFFICIENT_TOOLS_2025_02_19("token-efficient-tools-2025-02-19")`
- `OUTPUT_128K_2025_02_19("output-128k-2025-02-19")`
- `FILES_API_2025_04_14("files-api-2025-04-14")`
- `MCP_CLIENT_2025_04_04("mcp-client-2025-04-04")`
- `MCP_CLIENT_2025_11_20("mcp-client-2025-11-20")`
- `DEV_FULL_THINKING_2025_05_14("dev-full-thinking-2025-05-14")`
- `INTERLEAVED_THINKING_2025_05_14("interleaved-thinking-2025-05-14")`
- `CODE_EXECUTION_2025_05_22("code-execution-2025-05-22")`
- `EXTENDED_CACHE_TTL_2025_04_11("extended-cache-ttl-2025-04-11")`
- `CONTEXT_1M_2025_08_07("context-1m-2025-08-07")`
- `CONTEXT_MANAGEMENT_2025_06_27("context-management-2025-06-27")`
- `MODEL_CONTEXT_WINDOW_EXCEEDED_2025_08_26("model-context-window-exceeded-2025-08-26")`
- `SKILLS_2025_10_02("skills-2025-10-02")`
- `FAST_MODE_2026_02_01("fast-mode-2026-02-01")`
- `OUTPUT_300K_2026_03_24("output-300k-2026-03-24")`
- `USER_PROFILES_2026_03_24("user-profiles-2026-03-24")`
- `ADVISOR_TOOL_2026_03_01("advisor-tool-2026-03-01")`
- `MANAGED_AGENTS_2026_04_01("managed-agents-2026-04-01")`
- `BetaManagedAgentsFileResourceParams betaManagedAgentsFileResourceParams`
Mount a file uploaded via the Files API into the session.
### Returns
- `class BetaManagedAgentsFileResource:`
- `String id`
- `LocalDateTime createdAt`
A timestamp in RFC 3339 format
- `String fileId`
- `String mountPath`
- `Type type`
- `FILE("file")`
- `LocalDateTime updatedAt`
A timestamp in RFC 3339 format
### Example
```java
package com.anthropic.example;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.sessions.BetaManagedAgentsFileResourceParams;
import com.anthropic.models.beta.sessions.resources.BetaManagedAgentsFileResource;
import com.anthropic.models.beta.sessions.resources.ResourceAddParams;
public final class Main {
private Main() {}
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
ResourceAddParams params = ResourceAddParams.builder()
.sessionId("sesn_011CZkZAtmR3yMPDzynEDxu7")
.betaManagedAgentsFileResourceParams(BetaManagedAgentsFileResourceParams.builder()
.fileId("file_011CNha8iCJcU1wXNR6q4V8w")
.type(BetaManagedAgentsFileResourceParams.Type.FILE)
.build())
.build();
BetaManagedAgentsFileResource betaManagedAgentsFileResource = client.beta().sessions().resources().add(params);
}
}
```
---
# Add Session Resource (Beta) (Python)
URL: https://platform.claude.com/docs/en/api/python/beta/sessions/resources/add
## Add
`beta.sessions.resources.add(strsession_id, ResourceAddParams**kwargs) -> BetaManagedAgentsFileResource`
**post** `/v1/sessions/{session_id}/resources`
Add Session Resource
### Parameters
- `session_id: str`
- `file_id: str`
ID of a previously uploaded file.
- `type: Literal["file"]`
- `"file"`
- `mount_path: Optional[str]`
Mount path in the container. Defaults to `/mnt/session/uploads/`.
- `betas: Optional[List[AnthropicBetaParam]]`
Optional header to specify the beta version(s) you want to use.
- `str`
- `Literal["message-batches-2024-09-24", "prompt-caching-2024-07-31", "computer-use-2024-10-22", 21 more]`
- `"message-batches-2024-09-24"`
- `"prompt-caching-2024-07-31"`
- `"computer-use-2024-10-22"`
- `"computer-use-2025-01-24"`
- `"pdfs-2024-09-25"`
- `"token-counting-2024-11-01"`
- `"token-efficient-tools-2025-02-19"`
- `"output-128k-2025-02-19"`
- `"files-api-2025-04-14"`
- `"mcp-client-2025-04-04"`
- `"mcp-client-2025-11-20"`
- `"dev-full-thinking-2025-05-14"`
- `"interleaved-thinking-2025-05-14"`
- `"code-execution-2025-05-22"`
- `"extended-cache-ttl-2025-04-11"`
- `"context-1m-2025-08-07"`
- `"context-management-2025-06-27"`
- `"model-context-window-exceeded-2025-08-26"`
- `"skills-2025-10-02"`
- `"fast-mode-2026-02-01"`
- `"output-300k-2026-03-24"`
- `"user-profiles-2026-03-24"`
- `"advisor-tool-2026-03-01"`
- `"managed-agents-2026-04-01"`
### Returns
- `class BetaManagedAgentsFileResource: …`
- `id: str`
- `created_at: datetime`
A timestamp in RFC 3339 format
- `file_id: str`
- `mount_path: str`
- `type: Literal["file"]`
- `"file"`
- `updated_at: datetime`
A timestamp in RFC 3339 format
### Example
```python
import os
from anthropic import Anthropic
client = Anthropic(
api_key=os.environ.get("ANTHROPIC_API_KEY"), # This is the default and can be omitted
)
beta_managed_agents_file_resource = client.beta.sessions.resources.add(
session_id="sesn_011CZkZAtmR3yMPDzynEDxu7",
file_id="file_011CNha8iCJcU1wXNR6q4V8w",
type="file",
)
print(beta_managed_agents_file_resource.id)
```
---
# Add Session Resource (Beta) (Ruby)
URL: https://platform.claude.com/docs/en/api/ruby/beta/sessions/resources/add
## Add
`beta.sessions.resources.add(session_id, **kwargs) -> BetaManagedAgentsFileResource`
**post** `/v1/sessions/{session_id}/resources`
Add Session Resource
### Parameters
- `session_id: String`
- `file_id: String`
ID of a previously uploaded file.
- `type: :file`
- `:file`
- `mount_path: String`
Mount path in the container. Defaults to `/mnt/session/uploads/`.
- `betas: Array[AnthropicBeta]`
Optional header to specify the beta version(s) you want to use.
- `String`
- `:"message-batches-2024-09-24" | :"prompt-caching-2024-07-31" | :"computer-use-2024-10-22" | 21 more`
- `:"message-batches-2024-09-24"`
- `:"prompt-caching-2024-07-31"`
- `:"computer-use-2024-10-22"`
- `:"computer-use-2025-01-24"`
- `:"pdfs-2024-09-25"`
- `:"token-counting-2024-11-01"`
- `:"token-efficient-tools-2025-02-19"`
- `:"output-128k-2025-02-19"`
- `:"files-api-2025-04-14"`
- `:"mcp-client-2025-04-04"`
- `:"mcp-client-2025-11-20"`
- `:"dev-full-thinking-2025-05-14"`
- `:"interleaved-thinking-2025-05-14"`
- `:"code-execution-2025-05-22"`
- `:"extended-cache-ttl-2025-04-11"`
- `:"context-1m-2025-08-07"`
- `:"context-management-2025-06-27"`
- `:"model-context-window-exceeded-2025-08-26"`
- `:"skills-2025-10-02"`
- `:"fast-mode-2026-02-01"`
- `:"output-300k-2026-03-24"`
- `:"user-profiles-2026-03-24"`
- `:"advisor-tool-2026-03-01"`
- `:"managed-agents-2026-04-01"`
### Returns
- `class BetaManagedAgentsFileResource`
- `id: String`
- `created_at: Time`
A timestamp in RFC 3339 format
- `file_id: String`
- `mount_path: String`
- `type: :file`
- `:file`
- `updated_at: Time`
A timestamp in RFC 3339 format
### Example
```ruby
require "anthropic"
anthropic = Anthropic::Client.new(api_key: "my-anthropic-api-key")
beta_managed_agents_file_resource = anthropic.beta.sessions.resources.add(
"sesn_011CZkZAtmR3yMPDzynEDxu7",
file_id: "file_011CNha8iCJcU1wXNR6q4V8w",
type: :file
)
puts(beta_managed_agents_file_resource)
```
---
# Add Session Resource (Beta) (TypeScript)
URL: https://platform.claude.com/docs/en/api/typescript/beta/sessions/resources/add
## Add
`client.beta.sessions.resources.add(stringsessionID, ResourceAddParamsparams, RequestOptionsoptions?): BetaManagedAgentsFileResource`
**post** `/v1/sessions/{session_id}/resources`
Add Session Resource
### Parameters
- `sessionID: string`
- `params: ResourceAddParams`
- `file_id: string`
Body param: ID of a previously uploaded file.
- `type: "file"`
Body param
- `"file"`
- `mount_path?: string | null`
Body param: Mount path in the container. Defaults to `/mnt/session/uploads/`.
- `betas?: Array`
Header param: Optional header to specify the beta version(s) you want to use.
- `(string & {})`
- `"message-batches-2024-09-24" | "prompt-caching-2024-07-31" | "computer-use-2024-10-22" | 21 more`
- `"message-batches-2024-09-24"`
- `"prompt-caching-2024-07-31"`
- `"computer-use-2024-10-22"`
- `"computer-use-2025-01-24"`
- `"pdfs-2024-09-25"`
- `"token-counting-2024-11-01"`
- `"token-efficient-tools-2025-02-19"`
- `"output-128k-2025-02-19"`
- `"files-api-2025-04-14"`
- `"mcp-client-2025-04-04"`
- `"mcp-client-2025-11-20"`
- `"dev-full-thinking-2025-05-14"`
- `"interleaved-thinking-2025-05-14"`
- `"code-execution-2025-05-22"`
- `"extended-cache-ttl-2025-04-11"`
- `"context-1m-2025-08-07"`
- `"context-management-2025-06-27"`
- `"model-context-window-exceeded-2025-08-26"`
- `"skills-2025-10-02"`
- `"fast-mode-2026-02-01"`
- `"output-300k-2026-03-24"`
- `"user-profiles-2026-03-24"`
- `"advisor-tool-2026-03-01"`
- `"managed-agents-2026-04-01"`
### Returns
- `BetaManagedAgentsFileResource`
- `id: string`
- `created_at: string`
A timestamp in RFC 3339 format
- `file_id: string`
- `mount_path: string`
- `type: "file"`
- `"file"`
- `updated_at: string`
A timestamp in RFC 3339 format
### Example
```typescript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
apiKey: process.env['ANTHROPIC_API_KEY'], // This is the default and can be omitted
});
const betaManagedAgentsFileResource = await client.beta.sessions.resources.add(
'sesn_011CZkZAtmR3yMPDzynEDxu7',
{ file_id: 'file_011CNha8iCJcU1wXNR6q4V8w', type: 'file' },
);
console.log(betaManagedAgentsFileResource.id);
```
---
# Admin
URL: https://platform.claude.com/docs/en/api/admin
# Admin
# Organizations
## Me
**get** `/v1/organizations/me`
Retrieve information about the organization associated with the authenticated API key.
### Returns
- `Organization = object { id, name, type }`
- `id: string`
ID of the Organization.
- `name: string`
Name of the Organization.
- `type: "organization"`
Object type.
For Organizations, this is always `"organization"`.
- `"organization"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/me \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Domain Types
### Organization
- `Organization = object { id, name, type }`
- `id: string`
ID of the Organization.
- `name: string`
Name of the Organization.
- `type: "organization"`
Object type.
For Organizations, this is always `"organization"`.
- `"organization"`
# Invites
## Create
**post** `/v1/organizations/invites`
Create Invite
### Body Parameters
- `email: string`
Email of the User.
- `role: "user" or "developer" or "billing" or "claude_code_user"`
Role for the invited User. Cannot be "admin".
- `"user"`
- `"developer"`
- `"billing"`
- `"claude_code_user"`
### Returns
- `Invite = object { id, email, expires_at, 4 more }`
- `id: string`
ID of the Invite.
- `email: string`
Email of the User being invited.
- `expires_at: string`
RFC 3339 datetime string indicating when the Invite expires.
- `invited_at: string`
RFC 3339 datetime string indicating when the Invite was created.
- `role: "user" or "developer" or "billing" or 2 more`
Organization role of the User.
- `"user"`
- `"developer"`
- `"billing"`
- `"admin"`
- `"claude_code_user"`
- `status: "accepted" or "expired" or "deleted" or "pending"`
Status of the Invite.
- `"accepted"`
- `"expired"`
- `"deleted"`
- `"pending"`
- `type: "invite"`
Object type.
For Invites, this is always `"invite"`.
- `"invite"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/invites \
-H 'Content-Type: application/json' \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY" \
-d '{
"email": "user@emaildomain.com",
"role": "user"
}'
```
## Retrieve
**get** `/v1/organizations/invites/{invite_id}`
Get Invite
### Path Parameters
- `invite_id: string`
ID of the Invite.
### Returns
- `Invite = object { id, email, expires_at, 4 more }`
- `id: string`
ID of the Invite.
- `email: string`
Email of the User being invited.
- `expires_at: string`
RFC 3339 datetime string indicating when the Invite expires.
- `invited_at: string`
RFC 3339 datetime string indicating when the Invite was created.
- `role: "user" or "developer" or "billing" or 2 more`
Organization role of the User.
- `"user"`
- `"developer"`
- `"billing"`
- `"admin"`
- `"claude_code_user"`
- `status: "accepted" or "expired" or "deleted" or "pending"`
Status of the Invite.
- `"accepted"`
- `"expired"`
- `"deleted"`
- `"pending"`
- `type: "invite"`
Object type.
For Invites, this is always `"invite"`.
- `"invite"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/invites/$INVITE_ID \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## List
**get** `/v1/organizations/invites`
List Invites
### Query Parameters
- `after_id: optional string`
ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately after this object.
- `before_id: optional string`
ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately before this object.
- `limit: optional number`
Number of items to return per page.
Defaults to `20`. Ranges from `1` to `1000`.
### Returns
- `data: array of Invite`
- `id: string`
ID of the Invite.
- `email: string`
Email of the User being invited.
- `expires_at: string`
RFC 3339 datetime string indicating when the Invite expires.
- `invited_at: string`
RFC 3339 datetime string indicating when the Invite was created.
- `role: "user" or "developer" or "billing" or 2 more`
Organization role of the User.
- `"user"`
- `"developer"`
- `"billing"`
- `"admin"`
- `"claude_code_user"`
- `status: "accepted" or "expired" or "deleted" or "pending"`
Status of the Invite.
- `"accepted"`
- `"expired"`
- `"deleted"`
- `"pending"`
- `type: "invite"`
Object type.
For Invites, this is always `"invite"`.
- `"invite"`
- `first_id: string`
First ID in the `data` list. Can be used as the `before_id` for the previous page.
- `has_more: boolean`
Indicates if there are more results in the requested page direction.
- `last_id: string`
Last ID in the `data` list. Can be used as the `after_id` for the next page.
### Example
```http
curl https://api.anthropic.com/v1/organizations/invites \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Delete
**delete** `/v1/organizations/invites/{invite_id}`
Delete Invite
### Path Parameters
- `invite_id: string`
ID of the Invite.
### Returns
- `id: string`
ID of the Invite.
- `type: "invite_deleted"`
Deleted object type.
For Invites, this is always `"invite_deleted"`.
- `"invite_deleted"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/invites/$INVITE_ID \
-X DELETE \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Domain Types
### Invite
- `Invite = object { id, email, expires_at, 4 more }`
- `id: string`
ID of the Invite.
- `email: string`
Email of the User being invited.
- `expires_at: string`
RFC 3339 datetime string indicating when the Invite expires.
- `invited_at: string`
RFC 3339 datetime string indicating when the Invite was created.
- `role: "user" or "developer" or "billing" or 2 more`
Organization role of the User.
- `"user"`
- `"developer"`
- `"billing"`
- `"admin"`
- `"claude_code_user"`
- `status: "accepted" or "expired" or "deleted" or "pending"`
Status of the Invite.
- `"accepted"`
- `"expired"`
- `"deleted"`
- `"pending"`
- `type: "invite"`
Object type.
For Invites, this is always `"invite"`.
- `"invite"`
### Invite Delete Response
- `InviteDeleteResponse = object { id, type }`
- `id: string`
ID of the Invite.
- `type: "invite_deleted"`
Deleted object type.
For Invites, this is always `"invite_deleted"`.
- `"invite_deleted"`
# Users
## Retrieve
**get** `/v1/organizations/users/{user_id}`
Get User
### Path Parameters
- `user_id: string`
ID of the User.
### Returns
- `User = object { id, added_at, email, 3 more }`
- `id: string`
ID of the User.
- `added_at: string`
RFC 3339 datetime string indicating when the User joined the Organization.
- `email: string`
Email of the User.
- `name: string`
Name of the User.
- `role: "user" or "developer" or "billing" or 2 more`
Organization role of the User.
- `"user"`
- `"developer"`
- `"billing"`
- `"admin"`
- `"claude_code_user"`
- `type: "user"`
Object type.
For Users, this is always `"user"`.
- `"user"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/users/$USER_ID \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## List
**get** `/v1/organizations/users`
List Users
### Query Parameters
- `after_id: optional string`
ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately after this object.
- `before_id: optional string`
ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately before this object.
- `email: optional string`
Filter by user email.
- `limit: optional number`
Number of items to return per page.
Defaults to `20`. Ranges from `1` to `1000`.
### Returns
- `data: array of User`
- `id: string`
ID of the User.
- `added_at: string`
RFC 3339 datetime string indicating when the User joined the Organization.
- `email: string`
Email of the User.
- `name: string`
Name of the User.
- `role: "user" or "developer" or "billing" or 2 more`
Organization role of the User.
- `"user"`
- `"developer"`
- `"billing"`
- `"admin"`
- `"claude_code_user"`
- `type: "user"`
Object type.
For Users, this is always `"user"`.
- `"user"`
- `first_id: string`
First ID in the `data` list. Can be used as the `before_id` for the previous page.
- `has_more: boolean`
Indicates if there are more results in the requested page direction.
- `last_id: string`
Last ID in the `data` list. Can be used as the `after_id` for the next page.
### Example
```http
curl https://api.anthropic.com/v1/organizations/users \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Update
**post** `/v1/organizations/users/{user_id}`
Update User
### Path Parameters
- `user_id: string`
ID of the User.
### Body Parameters
- `role: "user" or "developer" or "billing" or "claude_code_user"`
New role for the User. Cannot be "admin".
- `"user"`
- `"developer"`
- `"billing"`
- `"claude_code_user"`
### Returns
- `User = object { id, added_at, email, 3 more }`
- `id: string`
ID of the User.
- `added_at: string`
RFC 3339 datetime string indicating when the User joined the Organization.
- `email: string`
Email of the User.
- `name: string`
Name of the User.
- `role: "user" or "developer" or "billing" or 2 more`
Organization role of the User.
- `"user"`
- `"developer"`
- `"billing"`
- `"admin"`
- `"claude_code_user"`
- `type: "user"`
Object type.
For Users, this is always `"user"`.
- `"user"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/users/$USER_ID \
-H 'Content-Type: application/json' \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY" \
-d '{
"role": "user"
}'
```
## Delete
**delete** `/v1/organizations/users/{user_id}`
Remove User
### Path Parameters
- `user_id: string`
ID of the User.
### Returns
- `id: string`
ID of the User.
- `type: "user_deleted"`
Deleted object type.
For Users, this is always `"user_deleted"`.
- `"user_deleted"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/users/$USER_ID \
-X DELETE \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Domain Types
### User
- `User = object { id, added_at, email, 3 more }`
- `id: string`
ID of the User.
- `added_at: string`
RFC 3339 datetime string indicating when the User joined the Organization.
- `email: string`
Email of the User.
- `name: string`
Name of the User.
- `role: "user" or "developer" or "billing" or 2 more`
Organization role of the User.
- `"user"`
- `"developer"`
- `"billing"`
- `"admin"`
- `"claude_code_user"`
- `type: "user"`
Object type.
For Users, this is always `"user"`.
- `"user"`
### User Delete Response
- `UserDeleteResponse = object { id, type }`
- `id: string`
ID of the User.
- `type: "user_deleted"`
Deleted object type.
For Users, this is always `"user_deleted"`.
- `"user_deleted"`
# Workspaces
## Create
**post** `/v1/organizations/workspaces`
Create Workspace
### Body Parameters
- `name: string`
Name of the Workspace.
- `data_residency: optional object { allowed_inference_geos, default_inference_geo, workspace_geo }`
Data residency configuration for the workspace. If omitted, defaults to workspace_geo=`"us"`, allowed_inference_geos=`"unrestricted"`, and default_inference_geo=`"global"`.
- `allowed_inference_geos: optional array of string or "unrestricted"`
Permitted inference geo values. Defaults to 'unrestricted' if omitted, which allows all geos. Use the string 'unrestricted' to allow all geos, or a list of specific geos.
- `UnionMember0 = array of string`
- `UnionMember1 = "unrestricted"`
- `"unrestricted"`
- `default_inference_geo: optional string`
Default inference geo applied when requests omit the parameter. Defaults to 'global' if omitted. Must be a member of allowed_inference_geos unless allowed_inference_geos is `"unrestricted"`.
- `workspace_geo: optional string`
Geographic region for workspace data storage. Immutable after creation. Defaults to 'us' if omitted.
### Returns
- `Workspace = object { id, archived_at, created_at, 4 more }`
- `id: string`
ID of the Workspace.
- `archived_at: string`
RFC 3339 datetime string indicating when the Workspace was archived, or `null` if the Workspace is not archived.
- `created_at: string`
RFC 3339 datetime string indicating when the Workspace was created.
- `data_residency: object { allowed_inference_geos, default_inference_geo, workspace_geo }`
Data residency configuration.
- `allowed_inference_geos: array of string or "unrestricted"`
Permitted inference geo values. 'unrestricted' means all geos are allowed.
- `UnionMember0 = array of string`
- `UnionMember1 = "unrestricted"`
- `"unrestricted"`
- `default_inference_geo: string`
Default inference geo applied when requests omit the parameter.
- `workspace_geo: string`
Geographic region for workspace data storage. Immutable after creation.
- `display_color: string`
Hex color code representing the Workspace in the Anthropic Console.
- `name: string`
Name of the Workspace.
- `type: "workspace"`
Object type.
For Workspaces, this is always `"workspace"`.
- `"workspace"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/workspaces \
-H 'Content-Type: application/json' \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY" \
-d '{
"name": "x"
}'
```
## Retrieve
**get** `/v1/organizations/workspaces/{workspace_id}`
Get Workspace
### Path Parameters
- `workspace_id: string`
ID of the Workspace.
### Returns
- `Workspace = object { id, archived_at, created_at, 4 more }`
- `id: string`
ID of the Workspace.
- `archived_at: string`
RFC 3339 datetime string indicating when the Workspace was archived, or `null` if the Workspace is not archived.
- `created_at: string`
RFC 3339 datetime string indicating when the Workspace was created.
- `data_residency: object { allowed_inference_geos, default_inference_geo, workspace_geo }`
Data residency configuration.
- `allowed_inference_geos: array of string or "unrestricted"`
Permitted inference geo values. 'unrestricted' means all geos are allowed.
- `UnionMember0 = array of string`
- `UnionMember1 = "unrestricted"`
- `"unrestricted"`
- `default_inference_geo: string`
Default inference geo applied when requests omit the parameter.
- `workspace_geo: string`
Geographic region for workspace data storage. Immutable after creation.
- `display_color: string`
Hex color code representing the Workspace in the Anthropic Console.
- `name: string`
Name of the Workspace.
- `type: "workspace"`
Object type.
For Workspaces, this is always `"workspace"`.
- `"workspace"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/workspaces/$WORKSPACE_ID \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## List
**get** `/v1/organizations/workspaces`
List Workspaces
### Query Parameters
- `after_id: optional string`
ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately after this object.
- `before_id: optional string`
ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately before this object.
- `include_archived: optional boolean`
Whether to include Workspaces that have been archived in the response
- `limit: optional number`
Number of items to return per page.
Defaults to `20`. Ranges from `1` to `1000`.
### Returns
- `data: array of Workspace`
- `id: string`
ID of the Workspace.
- `archived_at: string`
RFC 3339 datetime string indicating when the Workspace was archived, or `null` if the Workspace is not archived.
- `created_at: string`
RFC 3339 datetime string indicating when the Workspace was created.
- `data_residency: object { allowed_inference_geos, default_inference_geo, workspace_geo }`
Data residency configuration.
- `allowed_inference_geos: array of string or "unrestricted"`
Permitted inference geo values. 'unrestricted' means all geos are allowed.
- `UnionMember0 = array of string`
- `UnionMember1 = "unrestricted"`
- `"unrestricted"`
- `default_inference_geo: string`
Default inference geo applied when requests omit the parameter.
- `workspace_geo: string`
Geographic region for workspace data storage. Immutable after creation.
- `display_color: string`
Hex color code representing the Workspace in the Anthropic Console.
- `name: string`
Name of the Workspace.
- `type: "workspace"`
Object type.
For Workspaces, this is always `"workspace"`.
- `"workspace"`
- `first_id: string`
First ID in the `data` list. Can be used as the `before_id` for the previous page.
- `has_more: boolean`
Indicates if there are more results in the requested page direction.
- `last_id: string`
Last ID in the `data` list. Can be used as the `after_id` for the next page.
### Example
```http
curl https://api.anthropic.com/v1/organizations/workspaces \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Update
**post** `/v1/organizations/workspaces/{workspace_id}`
Update Workspace
### Path Parameters
- `workspace_id: string`
### Body Parameters
- `name: string`
Name of the Workspace.
- `data_residency: optional object { allowed_inference_geos, default_inference_geo }`
Data residency configuration for the workspace.
- `allowed_inference_geos: optional array of string or "unrestricted"`
Permitted inference geo values. Use 'unrestricted' to allow all geos, or a list of specific geos.
- `UnionMember0 = array of string`
- `UnionMember1 = "unrestricted"`
- `"unrestricted"`
- `default_inference_geo: optional string`
Default inference geo applied when requests omit the parameter. Must be a member of allowed_inference_geos unless allowed_inference_geos is `"unrestricted"`.
### Returns
- `Workspace = object { id, archived_at, created_at, 4 more }`
- `id: string`
ID of the Workspace.
- `archived_at: string`
RFC 3339 datetime string indicating when the Workspace was archived, or `null` if the Workspace is not archived.
- `created_at: string`
RFC 3339 datetime string indicating when the Workspace was created.
- `data_residency: object { allowed_inference_geos, default_inference_geo, workspace_geo }`
Data residency configuration.
- `allowed_inference_geos: array of string or "unrestricted"`
Permitted inference geo values. 'unrestricted' means all geos are allowed.
- `UnionMember0 = array of string`
- `UnionMember1 = "unrestricted"`
- `"unrestricted"`
- `default_inference_geo: string`
Default inference geo applied when requests omit the parameter.
- `workspace_geo: string`
Geographic region for workspace data storage. Immutable after creation.
- `display_color: string`
Hex color code representing the Workspace in the Anthropic Console.
- `name: string`
Name of the Workspace.
- `type: "workspace"`
Object type.
For Workspaces, this is always `"workspace"`.
- `"workspace"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/workspaces/$WORKSPACE_ID \
-H 'Content-Type: application/json' \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY" \
-d '{
"name": "x"
}'
```
## Archive
**post** `/v1/organizations/workspaces/{workspace_id}/archive`
Archive Workspace
### Path Parameters
- `workspace_id: string`
### Returns
- `Workspace = object { id, archived_at, created_at, 4 more }`
- `id: string`
ID of the Workspace.
- `archived_at: string`
RFC 3339 datetime string indicating when the Workspace was archived, or `null` if the Workspace is not archived.
- `created_at: string`
RFC 3339 datetime string indicating when the Workspace was created.
- `data_residency: object { allowed_inference_geos, default_inference_geo, workspace_geo }`
Data residency configuration.
- `allowed_inference_geos: array of string or "unrestricted"`
Permitted inference geo values. 'unrestricted' means all geos are allowed.
- `UnionMember0 = array of string`
- `UnionMember1 = "unrestricted"`
- `"unrestricted"`
- `default_inference_geo: string`
Default inference geo applied when requests omit the parameter.
- `workspace_geo: string`
Geographic region for workspace data storage. Immutable after creation.
- `display_color: string`
Hex color code representing the Workspace in the Anthropic Console.
- `name: string`
Name of the Workspace.
- `type: "workspace"`
Object type.
For Workspaces, this is always `"workspace"`.
- `"workspace"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/workspaces/$WORKSPACE_ID/archive \
-X POST \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
# Members
## Create
**post** `/v1/organizations/workspaces/{workspace_id}/members`
Create Workspace Member
### Path Parameters
- `workspace_id: string`
ID of the Workspace.
### Body Parameters
- `user_id: string`
ID of the User.
- `workspace_role: "workspace_user" or "workspace_developer" or "workspace_restricted_developer" or "workspace_admin"`
Role of the new Workspace Member. Cannot be "workspace_billing".
- `"workspace_user"`
- `"workspace_developer"`
- `"workspace_restricted_developer"`
- `"workspace_admin"`
### Returns
- `WorkspaceMember = object { type, user_id, workspace_id, workspace_role }`
- `type: "workspace_member"`
Object type.
For Workspace Members, this is always `"workspace_member"`.
- `"workspace_member"`
- `user_id: string`
ID of the User.
- `workspace_id: string`
ID of the Workspace.
- `workspace_role: "workspace_user" or "workspace_developer" or "workspace_restricted_developer" or 2 more`
Role of the Workspace Member.
- `"workspace_user"`
- `"workspace_developer"`
- `"workspace_restricted_developer"`
- `"workspace_admin"`
- `"workspace_billing"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/workspaces/$WORKSPACE_ID/members \
-H 'Content-Type: application/json' \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY" \
-d '{
"user_id": "user_01WCz1FkmYMm4gnmykNKUu3Q",
"workspace_role": "workspace_user"
}'
```
## Retrieve
**get** `/v1/organizations/workspaces/{workspace_id}/members/{user_id}`
Get Workspace Member
### Path Parameters
- `workspace_id: string`
ID of the Workspace.
- `user_id: string`
ID of the User.
### Returns
- `WorkspaceMember = object { type, user_id, workspace_id, workspace_role }`
- `type: "workspace_member"`
Object type.
For Workspace Members, this is always `"workspace_member"`.
- `"workspace_member"`
- `user_id: string`
ID of the User.
- `workspace_id: string`
ID of the Workspace.
- `workspace_role: "workspace_user" or "workspace_developer" or "workspace_restricted_developer" or 2 more`
Role of the Workspace Member.
- `"workspace_user"`
- `"workspace_developer"`
- `"workspace_restricted_developer"`
- `"workspace_admin"`
- `"workspace_billing"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/workspaces/$WORKSPACE_ID/members/$USER_ID \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## List
**get** `/v1/organizations/workspaces/{workspace_id}/members`
List Workspace Members
### Path Parameters
- `workspace_id: string`
ID of the Workspace.
### Query Parameters
- `after_id: optional string`
ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately after this object.
- `before_id: optional string`
ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately before this object.
- `limit: optional number`
Number of items to return per page.
Defaults to `20`. Ranges from `1` to `1000`.
### Returns
- `data: array of WorkspaceMember`
- `type: "workspace_member"`
Object type.
For Workspace Members, this is always `"workspace_member"`.
- `"workspace_member"`
- `user_id: string`
ID of the User.
- `workspace_id: string`
ID of the Workspace.
- `workspace_role: "workspace_user" or "workspace_developer" or "workspace_restricted_developer" or 2 more`
Role of the Workspace Member.
- `"workspace_user"`
- `"workspace_developer"`
- `"workspace_restricted_developer"`
- `"workspace_admin"`
- `"workspace_billing"`
- `first_id: string`
First ID in the `data` list. Can be used as the `before_id` for the previous page.
- `has_more: boolean`
Indicates if there are more results in the requested page direction.
- `last_id: string`
Last ID in the `data` list. Can be used as the `after_id` for the next page.
### Example
```http
curl https://api.anthropic.com/v1/organizations/workspaces/$WORKSPACE_ID/members \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Update
**post** `/v1/organizations/workspaces/{workspace_id}/members/{user_id}`
Update Workspace Member
### Path Parameters
- `workspace_id: string`
ID of the Workspace.
- `user_id: string`
ID of the User.
### Body Parameters
- `workspace_role: "workspace_user" or "workspace_developer" or "workspace_restricted_developer" or 2 more`
New workspace role for the User.
- `"workspace_user"`
- `"workspace_developer"`
- `"workspace_restricted_developer"`
- `"workspace_admin"`
- `"workspace_billing"`
### Returns
- `WorkspaceMember = object { type, user_id, workspace_id, workspace_role }`
- `type: "workspace_member"`
Object type.
For Workspace Members, this is always `"workspace_member"`.
- `"workspace_member"`
- `user_id: string`
ID of the User.
- `workspace_id: string`
ID of the Workspace.
- `workspace_role: "workspace_user" or "workspace_developer" or "workspace_restricted_developer" or 2 more`
Role of the Workspace Member.
- `"workspace_user"`
- `"workspace_developer"`
- `"workspace_restricted_developer"`
- `"workspace_admin"`
- `"workspace_billing"`
### Example
```http
curl https://api.anthropic.com/v1/organizations/workspaces/$WORKSPACE_ID/members/$USER_ID \
-H 'Content-Type: application/json' \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY" \
-d '{
"workspace_role": "workspace_user"
}'
```
## Delete
**delete** `/v1/organizations/workspaces/{workspace_id}/members/{user_id}`
Delete Workspace Member
### Path Parameters
- `workspace_id: string`
ID of the Workspace.
- `user_id: string`
ID of the User.
### Returns
- `type: "workspace_member_deleted"`
Deleted object type.
For Workspace Members, this is always `"workspace_member_deleted"`.
- `"workspace_member_deleted"`
- `user_id: string`
ID of the User.
- `workspace_id: string`
ID of the Workspace.
### Example
```http
curl https://api.anthropic.com/v1/organizations/workspaces/$WORKSPACE_ID/members/$USER_ID \
-X DELETE \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Domain Types
### Workspace Member
- `WorkspaceMember = object { type, user_id, workspace_id, workspace_role }`
- `type: "workspace_member"`
Object type.
For Workspace Members, this is always `"workspace_member"`.
- `"workspace_member"`
- `user_id: string`
ID of the User.
- `workspace_id: string`
ID of the Workspace.
- `workspace_role: "workspace_user" or "workspace_developer" or "workspace_restricted_developer" or 2 more`
Role of the Workspace Member.
- `"workspace_user"`
- `"workspace_developer"`
- `"workspace_restricted_developer"`
- `"workspace_admin"`
- `"workspace_billing"`
### Member Delete Response
- `MemberDeleteResponse = object { type, user_id, workspace_id }`
- `type: "workspace_member_deleted"`
Deleted object type.
For Workspace Members, this is always `"workspace_member_deleted"`.
- `"workspace_member_deleted"`
- `user_id: string`
ID of the User.
- `workspace_id: string`
ID of the Workspace.
# Rate Limits
## List
**get** `/v1/organizations/workspaces/{workspace_id}/rate_limits`
List rate-limit overrides configured for a workspace.
Returns only the groups and limiter types that have a workspace-level
override. Groups without overrides inherit the organization limits and
are not listed; use `GET /v1/organizations/rate_limits` to see those.
### Path Parameters
- `workspace_id: string`
The ID of the workspace.
### Query Parameters
- `group_type: optional "model_group" or "batch" or "token_count" or 3 more`
Filter by group type.
- `"model_group"`
- `"batch"`
- `"token_count"`
- `"files"`
- `"skills"`
- `"web_search"`
- `page: optional string`
Opaque cursor from a previous response's `next_page`.
### Returns
- `data: array of object { group_type, limits, models, type }`
Rate-limit entries for the workspace, one per group that has at least one override.
- `group_type: "model_group" or "batch" or "token_count" or 3 more`
The kind of rate-limit group this entry represents. `model_group` entries apply to a family of models (listed in `models`); other values apply to an API-surface category and have `models` set to `null`.
- `"model_group"`
- `"batch"`
- `"token_count"`
- `"files"`
- `"skills"`
- `"web_search"`
- `limits: array of object { org_limit, type, value }`
The limiter values overridden for this group in this workspace. Limiter types without a workspace override are omitted and inherit the organization value.
- `org_limit: number`
The organization-level value for the same limiter type, for reference. `null` when the organization has no limit configured for this limiter type.
- `type: string`
The limiter type (for example, `requests_per_minute` or `input_tokens_per_minute`).
- `value: number`
The workspace-level override value for this limiter type.
- `models: array of string`
Model names this entry's limits apply to, including aliases. `null` when `group_type` is not `"model_group"`.
- `type: "workspace_rate_limit"`
Object type. Always `workspace_rate_limit` for workspace rate-limit entries.
- `"workspace_rate_limit"`
- `next_page: string`
Token to provide in as `page` in the subsequent request to retrieve the next page of data.
### Example
```http
curl https://api.anthropic.com/v1/organizations/workspaces/$WORKSPACE_ID/rate_limits \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Domain Types
### Rate Limit List Response
- `RateLimitListResponse = object { data, next_page }`
- `data: array of object { group_type, limits, models, type }`
Rate-limit entries for the workspace, one per group that has at least one override.
- `group_type: "model_group" or "batch" or "token_count" or 3 more`
The kind of rate-limit group this entry represents. `model_group` entries apply to a family of models (listed in `models`); other values apply to an API-surface category and have `models` set to `null`.
- `"model_group"`
- `"batch"`
- `"token_count"`
- `"files"`
- `"skills"`
- `"web_search"`
- `limits: array of object { org_limit, type, value }`
The limiter values overridden for this group in this workspace. Limiter types without a workspace override are omitted and inherit the organization value.
- `org_limit: number`
The organization-level value for the same limiter type, for reference. `null` when the organization has no limit configured for this limiter type.
- `type: string`
The limiter type (for example, `requests_per_minute` or `input_tokens_per_minute`).
- `value: number`
The workspace-level override value for this limiter type.
- `models: array of string`
Model names this entry's limits apply to, including aliases. `null` when `group_type` is not `"model_group"`.
- `type: "workspace_rate_limit"`
Object type. Always `workspace_rate_limit` for workspace rate-limit entries.
- `"workspace_rate_limit"`
- `next_page: string`
Token to provide in as `page` in the subsequent request to retrieve the next page of data.
# API Keys
## Retrieve
**get** `/v1/organizations/api_keys/{api_key_id}`
Get API Key
### Path Parameters
- `api_key_id: string`
ID of the API key.
### Returns
- `APIKey = object { id, created_at, created_by, 6 more }`
- `id: string`
ID of the API key.
- `created_at: string`
RFC 3339 datetime string indicating when the API Key was created.
- `created_by: object { id, type }`
The ID and type of the actor that created the API key.
- `id: string`
ID of the actor that created the object.
- `type: string`
Type of the actor that created the object.
- `expires_at: string`
RFC 3339 datetime string indicating when the API Key expires, or `null` if it never expires.
- `name: string`
Name of the API key.
- `partial_key_hint: string`
Partially redacted hint for the API key.
- `status: "active" or "inactive" or "archived" or "expired"`
Status of the API key.
- `"active"`
- `"inactive"`
- `"archived"`
- `"expired"`
- `type: "api_key"`
Object type.
For API Keys, this is always `"api_key"`.
- `"api_key"`
- `workspace_id: string`
ID of the Workspace associated with the API key, or `null` if the API key belongs to the default Workspace.
### Example
```http
curl https://api.anthropic.com/v1/organizations/api_keys/$API_KEY_ID \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## List
**get** `/v1/organizations/api_keys`
List API Keys
### Query Parameters
- `after_id: optional string`
ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately after this object.
- `before_id: optional string`
ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately before this object.
- `created_by_user_id: optional string`
Filter by the ID of the User who created the object.
- `limit: optional number`
Number of items to return per page.
Defaults to `20`. Ranges from `1` to `1000`.
- `status: optional "active" or "inactive" or "archived" or "expired"`
Filter by API key status.
- `"active"`
- `"inactive"`
- `"archived"`
- `"expired"`
- `workspace_id: optional string`
Filter by Workspace ID.
### Returns
- `data: array of APIKey`
- `id: string`
ID of the API key.
- `created_at: string`
RFC 3339 datetime string indicating when the API Key was created.
- `created_by: object { id, type }`
The ID and type of the actor that created the API key.
- `id: string`
ID of the actor that created the object.
- `type: string`
Type of the actor that created the object.
- `expires_at: string`
RFC 3339 datetime string indicating when the API Key expires, or `null` if it never expires.
- `name: string`
Name of the API key.
- `partial_key_hint: string`
Partially redacted hint for the API key.
- `status: "active" or "inactive" or "archived" or "expired"`
Status of the API key.
- `"active"`
- `"inactive"`
- `"archived"`
- `"expired"`
- `type: "api_key"`
Object type.
For API Keys, this is always `"api_key"`.
- `"api_key"`
- `workspace_id: string`
ID of the Workspace associated with the API key, or `null` if the API key belongs to the default Workspace.
- `first_id: string`
First ID in the `data` list. Can be used as the `before_id` for the previous page.
- `has_more: boolean`
Indicates if there are more results in the requested page direction.
- `last_id: string`
Last ID in the `data` list. Can be used as the `after_id` for the next page.
### Example
```http
curl https://api.anthropic.com/v1/organizations/api_keys \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Update
**post** `/v1/organizations/api_keys/{api_key_id}`
Update API Key
### Path Parameters
- `api_key_id: string`
ID of the API key.
### Body Parameters
- `name: optional string`
Name of the API key.
- `status: optional "active" or "inactive" or "archived"`
Status of the API key.
- `"active"`
- `"inactive"`
- `"archived"`
### Returns
- `APIKey = object { id, created_at, created_by, 6 more }`
- `id: string`
ID of the API key.
- `created_at: string`
RFC 3339 datetime string indicating when the API Key was created.
- `created_by: object { id, type }`
The ID and type of the actor that created the API key.
- `id: string`
ID of the actor that created the object.
- `type: string`
Type of the actor that created the object.
- `expires_at: string`
RFC 3339 datetime string indicating when the API Key expires, or `null` if it never expires.
- `name: string`
Name of the API key.
- `partial_key_hint: string`
Partially redacted hint for the API key.
- `status: "active" or "inactive" or "archived" or "expired"`
Status of the API key.
- `"active"`
- `"inactive"`
- `"archived"`
- `"expired"`
- `type: "api_key"`
Object type.
For API Keys, this is always `"api_key"`.
- `"api_key"`
- `workspace_id: string`
ID of the Workspace associated with the API key, or `null` if the API key belongs to the default Workspace.
### Example
```http
curl https://api.anthropic.com/v1/organizations/api_keys/$API_KEY_ID \
-H 'Content-Type: application/json' \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY" \
-d '{}'
```
# Usage Report
## Retrieve Messages
**get** `/v1/organizations/usage_report/messages`
Get Messages Usage Report
### Query Parameters
- `starting_at: string`
Time buckets that start on or after this RFC 3339 timestamp will be returned.
Each time bucket will be snapped to the start of the minute/hour/day in UTC.
- `account_ids: optional array of string`
Restrict usage returned to the specified user account ID(s).
- `api_key_ids: optional array of string`
Restrict usage returned to the specified API key ID(s).
- `bucket_width: optional "1d" or "1m" or "1h"`
Time granularity of the response data.
- `"1d"`
- `"1m"`
- `"1h"`
- `context_window: optional array of "0-200k" or "200k-1M"`
Restrict usage returned to the specified context window(s).
- `"0-200k"`
- `"200k-1M"`
- `ending_at: optional string`
Time buckets that end before this RFC 3339 timestamp will be returned.
- `group_by: optional array of "api_key_id" or "workspace_id" or "model" or 6 more`
Group by any subset of the available options. Grouping by `speed` requires the `fast-mode-2026-02-01` beta header.
- `"api_key_id"`
- `"workspace_id"`
- `"model"`
- `"service_tier"`
- `"context_window"`
- `"inference_geo"`
- `"speed"`
- `"account_id"`
- `"service_account_id"`
- `inference_geos: optional array of "global" or "us" or "not_available"`
Restrict usage returned to the specified inference geo(s). Use `not_available` for models that do not support specifying `inference_geo`.
- `"global"`
- `"us"`
- `"not_available"`
- `limit: optional number`
Maximum number of time buckets to return in the response.
The default and max limits depend on `bucket_width`:
• `"1d"`: Default of 7 days, maximum of 31 days
• `"1h"`: Default of 24 hours, maximum of 168 hours
• `"1m"`: Default of 60 minutes, maximum of 1440 minutes
- `models: optional array of string`
Restrict usage returned to the specified model(s).
- `page: optional string`
Optionally set to the `next_page` token from the previous response.
- `service_account_ids: optional array of string`
Restrict usage returned to the specified service account ID(s).
- `service_tiers: optional array of "standard" or "batch" or "priority" or 3 more`
Restrict usage returned to the specified service tier(s).
- `"standard"`
- `"batch"`
- `"priority"`
- `"priority_on_demand"`
- `"flex"`
- `"flex_discount"`
- `speeds: optional array of "standard" or "fast"`
Restrict usage returned to the specified speed(s) (Claude Code research preview).
Requires the `fast-mode-2026-02-01` beta header.
- `"standard"`
- `"fast"`
- `workspace_ids: optional array of string`
Restrict usage returned to the specified workspace ID(s).
### Header Parameters
- `"anthropic-beta": optional array of string`
Optional header to specify the beta version(s) you want to use.
To use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta.
### Returns
- `MessagesUsageReport = object { data, has_more, next_page }`
- `data: array of object { ending_at, results, starting_at }`
- `ending_at: string`
End of the time bucket (exclusive) in RFC 3339 format.
- `results: array of object { account_id, api_key_id, cache_creation, 10 more }`
List of usage items for this time bucket. There may be multiple items if one or more `group_by[]` parameters are specified.
- `account_id: string`
ID of the user account that made the request. `null` if not grouping by account or for non-OAuth requests.
- `api_key_id: string`
ID of the API key used. `null` if not grouping by API key or for usage in the Anthropic Console.
- `cache_creation: object { ephemeral_1h_input_tokens, ephemeral_5m_input_tokens }`
The number of input tokens for cache creation.
- `ephemeral_1h_input_tokens: number`
The number of input tokens used to create the 1 hour cache entry.
- `ephemeral_5m_input_tokens: number`
The number of input tokens used to create the 5 minute cache entry.
- `cache_read_input_tokens: number`
The number of input tokens read from the cache.
- `context_window: "0-200k" or "200k-1M"`
Context window used. `null` if not grouping by context window.
- `"0-200k"`
- `"200k-1M"`
- `inference_geo: string`
Inference geo used matching requests' `inference_geo` parameter if set, otherwise the workspace's `default_inference_geo`.
For models that do not support specifying `inference_geo` the value is `"not_available"`. Always `null` if not grouping by inference geo.
- `model: string`
Model used. `null` if not grouping by model.
- `output_tokens: number`
The number of output tokens generated.
- `server_tool_use: object { web_search_requests }`
Server-side tool usage metrics.
- `web_search_requests: number`
The number of web search requests made.
- `service_account_id: string`
ID of the service account that made the request. `null` if not grouping by service account or for non-OIDC-federation requests.
- `service_tier: "standard" or "batch" or "priority" or 3 more`
Service tier used. `null` if not grouping by service tier.
- `"standard"`
- `"batch"`
- `"priority"`
- `"priority_on_demand"`
- `"flex"`
- `"flex_discount"`
- `uncached_input_tokens: number`
The number of uncached input tokens processed.
- `workspace_id: string`
ID of the Workspace used. `null` if not grouping by workspace or for the default workspace.
- `starting_at: string`
Start of the time bucket (inclusive) in RFC 3339 format.
- `has_more: boolean`
Indicates if there are more results.
- `next_page: string`
Token to provide in as `page` in the subsequent request to retrieve the next page of data.
### Example
```http
curl https://api.anthropic.com/v1/organizations/usage_report/messages \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Retrieve Claude Code
**get** `/v1/organizations/usage_report/claude_code`
Retrieve daily aggregated usage metrics for Claude Code users.
Enables organizations to analyze developer productivity and build custom dashboards.
### Query Parameters
- `starting_at: string`
UTC date in YYYY-MM-DD format. Returns metrics for this single day only.
- `limit: optional number`
Number of records per page (default: 20, max: 1000).
- `page: optional string`
Opaque cursor token from previous response's `next_page` field.
### Returns
- `ClaudeCodeUsageReport = object { data, has_more, next_page }`
- `data: array of object { actor, core_metrics, customer_type, 6 more }`
List of Claude Code usage records for the requested date.
- `actor: object { email_address, type } or object { api_key_name, type }`
The user or API key that performed the Claude Code actions.
- `UserActor = object { email_address, type }`
- `email_address: string`
Email address of the user who performed Claude Code actions.
- `type: "user_actor"`
- `"user_actor"`
- `APIActor = object { api_key_name, type }`
- `api_key_name: string`
Name of the API key used to perform Claude Code actions.
- `type: "api_actor"`
- `"api_actor"`
- `core_metrics: object { commits_by_claude_code, lines_of_code, num_sessions, pull_requests_by_claude_code }`
Core productivity metrics measuring Claude Code usage and impact.
- `commits_by_claude_code: number`
Number of git commits created through Claude Code's commit functionality.
- `lines_of_code: object { added, removed }`
Statistics on code changes made through Claude Code.
- `added: number`
Total number of lines of code added across all files by Claude Code.
- `removed: number`
Total number of lines of code removed across all files by Claude Code.
- `num_sessions: number`
Number of distinct Claude Code sessions initiated by this actor.
- `pull_requests_by_claude_code: number`
Number of pull requests created through Claude Code's PR functionality.
- `customer_type: "api" or "subscription"`
Type of customer account (api for API customers, subscription for Pro/Team customers).
- `"api"`
- `"subscription"`
- `date: string`
UTC date for the usage metrics in YYYY-MM-DD format.
- `model_breakdown: array of object { estimated_cost, model, tokens }`
Token usage and cost breakdown by AI model used.
- `estimated_cost: object { amount, currency }`
Estimated cost for using this model
- `amount: number`
Estimated cost amount in minor currency units (e.g., cents for USD).
- `currency: string`
Currency code for the estimated cost (e.g., 'USD').
- `model: string`
Name of the AI model used for Claude Code interactions.
- `tokens: object { cache_creation, cache_read, input, output }`
Token usage breakdown for this model
- `cache_creation: number`
Number of cache creation tokens consumed by this model.
- `cache_read: number`
Number of cache read tokens consumed by this model.
- `input: number`
Number of input tokens consumed by this model.
- `output: number`
Number of output tokens generated by this model.
- `organization_id: string`
ID of the organization that owns the Claude Code usage.
- `terminal_type: string`
Type of terminal or environment where Claude Code was used.
- `tool_actions: map[object { accepted, rejected } ]`
Breakdown of tool action acceptance and rejection rates by tool type.
- `accepted: number`
Number of tool action proposals that the user accepted.
- `rejected: number`
Number of tool action proposals that the user rejected.
- `subscription_type: optional "enterprise" or "team"`
Subscription tier for subscription customers. `null` for API customers.
- `"enterprise"`
- `"team"`
- `has_more: boolean`
True if there are more records available beyond the current page.
- `next_page: string`
Opaque cursor token for fetching the next page of results, or null if no more pages are available.
### Example
```http
curl https://api.anthropic.com/v1/organizations/usage_report/claude_code \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Domain Types
### Claude Code Usage Report
- `ClaudeCodeUsageReport = object { data, has_more, next_page }`
- `data: array of object { actor, core_metrics, customer_type, 6 more }`
List of Claude Code usage records for the requested date.
- `actor: object { email_address, type } or object { api_key_name, type }`
The user or API key that performed the Claude Code actions.
- `UserActor = object { email_address, type }`
- `email_address: string`
Email address of the user who performed Claude Code actions.
- `type: "user_actor"`
- `"user_actor"`
- `APIActor = object { api_key_name, type }`
- `api_key_name: string`
Name of the API key used to perform Claude Code actions.
- `type: "api_actor"`
- `"api_actor"`
- `core_metrics: object { commits_by_claude_code, lines_of_code, num_sessions, pull_requests_by_claude_code }`
Core productivity metrics measuring Claude Code usage and impact.
- `commits_by_claude_code: number`
Number of git commits created through Claude Code's commit functionality.
- `lines_of_code: object { added, removed }`
Statistics on code changes made through Claude Code.
- `added: number`
Total number of lines of code added across all files by Claude Code.
- `removed: number`
Total number of lines of code removed across all files by Claude Code.
- `num_sessions: number`
Number of distinct Claude Code sessions initiated by this actor.
- `pull_requests_by_claude_code: number`
Number of pull requests created through Claude Code's PR functionality.
- `customer_type: "api" or "subscription"`
Type of customer account (api for API customers, subscription for Pro/Team customers).
- `"api"`
- `"subscription"`
- `date: string`
UTC date for the usage metrics in YYYY-MM-DD format.
- `model_breakdown: array of object { estimated_cost, model, tokens }`
Token usage and cost breakdown by AI model used.
- `estimated_cost: object { amount, currency }`
Estimated cost for using this model
- `amount: number`
Estimated cost amount in minor currency units (e.g., cents for USD).
- `currency: string`
Currency code for the estimated cost (e.g., 'USD').
- `model: string`
Name of the AI model used for Claude Code interactions.
- `tokens: object { cache_creation, cache_read, input, output }`
Token usage breakdown for this model
- `cache_creation: number`
Number of cache creation tokens consumed by this model.
- `cache_read: number`
Number of cache read tokens consumed by this model.
- `input: number`
Number of input tokens consumed by this model.
- `output: number`
Number of output tokens generated by this model.
- `organization_id: string`
ID of the organization that owns the Claude Code usage.
- `terminal_type: string`
Type of terminal or environment where Claude Code was used.
- `tool_actions: map[object { accepted, rejected } ]`
Breakdown of tool action acceptance and rejection rates by tool type.
- `accepted: number`
Number of tool action proposals that the user accepted.
- `rejected: number`
Number of tool action proposals that the user rejected.
- `subscription_type: optional "enterprise" or "team"`
Subscription tier for subscription customers. `null` for API customers.
- `"enterprise"`
- `"team"`
- `has_more: boolean`
True if there are more records available beyond the current page.
- `next_page: string`
Opaque cursor token for fetching the next page of results, or null if no more pages are available.
### Messages Usage Report
- `MessagesUsageReport = object { data, has_more, next_page }`
- `data: array of object { ending_at, results, starting_at }`
- `ending_at: string`
End of the time bucket (exclusive) in RFC 3339 format.
- `results: array of object { account_id, api_key_id, cache_creation, 10 more }`
List of usage items for this time bucket. There may be multiple items if one or more `group_by[]` parameters are specified.
- `account_id: string`
ID of the user account that made the request. `null` if not grouping by account or for non-OAuth requests.
- `api_key_id: string`
ID of the API key used. `null` if not grouping by API key or for usage in the Anthropic Console.
- `cache_creation: object { ephemeral_1h_input_tokens, ephemeral_5m_input_tokens }`
The number of input tokens for cache creation.
- `ephemeral_1h_input_tokens: number`
The number of input tokens used to create the 1 hour cache entry.
- `ephemeral_5m_input_tokens: number`
The number of input tokens used to create the 5 minute cache entry.
- `cache_read_input_tokens: number`
The number of input tokens read from the cache.
- `context_window: "0-200k" or "200k-1M"`
Context window used. `null` if not grouping by context window.
- `"0-200k"`
- `"200k-1M"`
- `inference_geo: string`
Inference geo used matching requests' `inference_geo` parameter if set, otherwise the workspace's `default_inference_geo`.
For models that do not support specifying `inference_geo` the value is `"not_available"`. Always `null` if not grouping by inference geo.
- `model: string`
Model used. `null` if not grouping by model.
- `output_tokens: number`
The number of output tokens generated.
- `server_tool_use: object { web_search_requests }`
Server-side tool usage metrics.
- `web_search_requests: number`
The number of web search requests made.
- `service_account_id: string`
ID of the service account that made the request. `null` if not grouping by service account or for non-OIDC-federation requests.
- `service_tier: "standard" or "batch" or "priority" or 3 more`
Service tier used. `null` if not grouping by service tier.
- `"standard"`
- `"batch"`
- `"priority"`
- `"priority_on_demand"`
- `"flex"`
- `"flex_discount"`
- `uncached_input_tokens: number`
The number of uncached input tokens processed.
- `workspace_id: string`
ID of the Workspace used. `null` if not grouping by workspace or for the default workspace.
- `starting_at: string`
Start of the time bucket (inclusive) in RFC 3339 format.
- `has_more: boolean`
Indicates if there are more results.
- `next_page: string`
Token to provide in as `page` in the subsequent request to retrieve the next page of data.
# Cost Report
## Retrieve
**get** `/v1/organizations/cost_report`
Get Cost Report
### Query Parameters
- `starting_at: string`
Time buckets that start on or after this RFC 3339 timestamp will be returned.
Each time bucket will be snapped to the start of the minute/hour/day in UTC.
- `bucket_width: optional "1d"`
Time granularity of the response data.
- `"1d"`
- `ending_at: optional string`
Time buckets that end before this RFC 3339 timestamp will be returned.
- `group_by: optional array of "workspace_id" or "description"`
Group by any subset of the available options.
- `"workspace_id"`
- `"description"`
- `limit: optional number`
Maximum number of time buckets to return in the response.
- `page: optional string`
Optionally set to the `next_page` token from the previous response.
### Header Parameters
- `"anthropic-beta": optional array of string`
Optional header to specify the beta version(s) you want to use.
To use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta.
### Returns
- `CostReport = object { data, has_more, next_page }`
- `data: array of object { ending_at, results, starting_at }`
- `ending_at: string`
End of the time bucket (exclusive) in RFC 3339 format.
- `results: array of object { amount, context_window, cost_type, 7 more }`
List of cost items for this time bucket. There may be multiple items if one or more `group_by[]` parameters are specified.
- `amount: string`
Cost amount in lowest currency units (e.g. cents) as a decimal string. For example, `"123.45"` in `"USD"` represents `$1.23`.
- `context_window: "0-200k" or "200k-1M"`
Input context window used. `null` if not grouping by description or for non-token costs.
- `"0-200k"`
- `"200k-1M"`
- `cost_type: "tokens" or "web_search" or "code_execution" or "session_usage"`
Type of cost. `null` if not grouping by description.
- `"tokens"`
- `"web_search"`
- `"code_execution"`
- `"session_usage"`
- `currency: string`
Currency code for the cost amount. Currently always `"USD"`.
- `description: string`
Description of the cost item. `null` if not grouping by description.
- `inference_geo: string`
Inference geo used matching requests' `inference_geo` parameter if set, otherwise the workspace's `default_inference_geo`.
For models that do not support specifying `inference_geo` the value is `"not_available"`. Always `null` if not grouping by inference geo.
- `model: string`
Model name used. `null` if not grouping by description or for non-token costs.
- `service_tier: "standard" or "batch"`
Service tier used. `null` if not grouping by description or for non-token costs.
- `"standard"`
- `"batch"`
- `token_type: "uncached_input_tokens" or "output_tokens" or "cache_read_input_tokens" or 2 more`
Type of token. `null` if not grouping by description or for non-token costs.
- `"uncached_input_tokens"`
- `"output_tokens"`
- `"cache_read_input_tokens"`
- `"cache_creation.ephemeral_1h_input_tokens"`
- `"cache_creation.ephemeral_5m_input_tokens"`
- `workspace_id: string`
ID of the Workspace this cost is associated with. `null` if not grouping by workspace or for the default workspace.
- `starting_at: string`
Start of the time bucket (inclusive) in RFC 3339 format.
- `has_more: boolean`
Indicates if there are more results.
- `next_page: string`
Token to provide in as `page` in the subsequent request to retrieve the next page of data.
### Example
```http
curl https://api.anthropic.com/v1/organizations/cost_report \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Domain Types
### Cost Report
- `CostReport = object { data, has_more, next_page }`
- `data: array of object { ending_at, results, starting_at }`
- `ending_at: string`
End of the time bucket (exclusive) in RFC 3339 format.
- `results: array of object { amount, context_window, cost_type, 7 more }`
List of cost items for this time bucket. There may be multiple items if one or more `group_by[]` parameters are specified.
- `amount: string`
Cost amount in lowest currency units (e.g. cents) as a decimal string. For example, `"123.45"` in `"USD"` represents `$1.23`.
- `context_window: "0-200k" or "200k-1M"`
Input context window used. `null` if not grouping by description or for non-token costs.
- `"0-200k"`
- `"200k-1M"`
- `cost_type: "tokens" or "web_search" or "code_execution" or "session_usage"`
Type of cost. `null` if not grouping by description.
- `"tokens"`
- `"web_search"`
- `"code_execution"`
- `"session_usage"`
- `currency: string`
Currency code for the cost amount. Currently always `"USD"`.
- `description: string`
Description of the cost item. `null` if not grouping by description.
- `inference_geo: string`
Inference geo used matching requests' `inference_geo` parameter if set, otherwise the workspace's `default_inference_geo`.
For models that do not support specifying `inference_geo` the value is `"not_available"`. Always `null` if not grouping by inference geo.
- `model: string`
Model name used. `null` if not grouping by description or for non-token costs.
- `service_tier: "standard" or "batch"`
Service tier used. `null` if not grouping by description or for non-token costs.
- `"standard"`
- `"batch"`
- `token_type: "uncached_input_tokens" or "output_tokens" or "cache_read_input_tokens" or 2 more`
Type of token. `null` if not grouping by description or for non-token costs.
- `"uncached_input_tokens"`
- `"output_tokens"`
- `"cache_read_input_tokens"`
- `"cache_creation.ephemeral_1h_input_tokens"`
- `"cache_creation.ephemeral_5m_input_tokens"`
- `workspace_id: string`
ID of the Workspace this cost is associated with. `null` if not grouping by workspace or for the default workspace.
- `starting_at: string`
Start of the time bucket (inclusive) in RFC 3339 format.
- `has_more: boolean`
Indicates if there are more results.
- `next_page: string`
Token to provide in as `page` in the subsequent request to retrieve the next page of data.
# Rate Limits
## List
**get** `/v1/organizations/rate_limits`
List Messages API rate limits for your organization.
Each entry corresponds to one rate-limit group (either a model family
or an API-surface category such as the Files API or Message Batches)
and contains the set of limiter values that apply to it.
### Query Parameters
- `group_type: optional "model_group" or "batch" or "token_count" or 3 more`
Filter by group type.
- `"model_group"`
- `"batch"`
- `"token_count"`
- `"files"`
- `"skills"`
- `"web_search"`
- `model: optional string`
Filter to the single entry containing this model. Accepts full model names and aliases. Returns 404 if the model is not found or has no rate limits for this organization.
- `page: optional string`
Opaque cursor from a previous response's `next_page`.
### Returns
- `data: array of object { group_type, limits, models, type }`
Rate-limit entries for the organization, one per group.
- `group_type: "model_group" or "batch" or "token_count" or 3 more`
The kind of rate-limit group this entry represents. `model_group` entries apply to a family of models (listed in `models`); other values apply to an API-surface category and have `models` set to `null`.
- `"model_group"`
- `"batch"`
- `"token_count"`
- `"files"`
- `"skills"`
- `"web_search"`
- `limits: array of object { type, value }`
The limiter values that apply to this group.
- `type: string`
The limiter type (for example, `requests_per_minute` or `input_tokens_per_minute`).
- `value: number`
The configured limit value for this limiter type.
- `models: array of string`
Model names this entry's limits apply to, including aliases. `null` when `group_type` is not `"model_group"`.
- `type: "rate_limit"`
Object type. Always `rate_limit` for organization rate-limit entries.
- `"rate_limit"`
- `next_page: string`
Token to provide in as `page` in the subsequent request to retrieve the next page of data.
### Example
```http
curl https://api.anthropic.com/v1/organizations/rate_limits \
-H 'anthropic-version: 2023-06-01' \
-H "X-Api-Key: $ANTHROPIC_ADMIN_API_KEY"
```
## Domain Types
### Rate Limit List Response
- `RateLimitListResponse = object { data, next_page }`
- `data: array of object { group_type, limits, models, type }`
Rate-limit entries for the organization, one per group.
- `group_type: "model_group" or "batch" or "token_count" or 3 more`
The kind of rate-limit group this entry represents. `model_group` entries apply to a family of models (listed in `models`); other values apply to an API-surface category and have `models` set to `null`.
- `"model_group"`
- `"batch"`
- `"token_count"`
- `"files"`
- `"skills"`
- `"web_search"`
- `limits: array of object { type, value }`
The limiter values that apply to this group.
- `type: string`
The limiter type (for example, `requests_per_minute` or `input_tokens_per_minute`).
- `value: number`
The configured limit value for this limiter type.
- `models: array of string`
Model names this entry's limits apply to, including aliases. `null` when `group_type` is not `"model_group"`.
- `type: "rate_limit"`
Object type. Always `rate_limit` for organization rate-limit entries.
- `"rate_limit"`
- `next_page: string`
Token to provide in as `page` in the subsequent request to retrieve the next page of data.
---
# Agents (Beta)
URL: https://platform.claude.com/docs/en/api/beta/agents
# Agents
## Create
**post** `/v1/agents`
Create Agent
### Header Parameters
- `"anthropic-beta": optional array of AnthropicBeta`
Optional header to specify the beta version(s) you want to use.
- `UnionMember0 = string`
- `UnionMember1 = "message-batches-2024-09-24" or "prompt-caching-2024-07-31" or "computer-use-2024-10-22" or 21 more`
- `"message-batches-2024-09-24"`
- `"prompt-caching-2024-07-31"`
- `"computer-use-2024-10-22"`
- `"computer-use-2025-01-24"`
- `"pdfs-2024-09-25"`
- `"token-counting-2024-11-01"`
- `"token-efficient-tools-2025-02-19"`
- `"output-128k-2025-02-19"`
- `"files-api-2025-04-14"`
- `"mcp-client-2025-04-04"`
- `"mcp-client-2025-11-20"`
- `"dev-full-thinking-2025-05-14"`
- `"interleaved-thinking-2025-05-14"`
- `"code-execution-2025-05-22"`
- `"extended-cache-ttl-2025-04-11"`
- `"context-1m-2025-08-07"`
- `"context-management-2025-06-27"`
- `"model-context-window-exceeded-2025-08-26"`
- `"skills-2025-10-02"`
- `"fast-mode-2026-02-01"`
- `"output-300k-2026-03-24"`
- `"user-profiles-2026-03-24"`
- `"advisor-tool-2026-03-01"`
- `"managed-agents-2026-04-01"`
### Body Parameters
- `model: BetaManagedAgentsModel or BetaManagedAgentsModelConfigParams`
Model identifier. Accepts the [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison), e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration control
- `BetaManagedAgentsModel = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more or string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `BetaManagedAgentsModelConfigParams = object { id, speed }`
An object that defines additional configuration control over model use
- `id: BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `name: string`
Human-readable name for the agent. 1-256 characters.
- `description: optional string`
Description of what the agent does. Up to 2048 characters.
- `mcp_servers: optional array of BetaManagedAgentsURLMCPServerParams`
MCP servers this agent connects to. Maximum 20. Names must be unique within the array.
- `name: string`
Unique name for this server, referenced by mcp_toolset configurations. 1-255 characters.
- `type: "url"`
- `"url"`
- `url: string`
Endpoint URL for the MCP server.
- `metadata: optional map[string]`
Arbitrary key-value metadata. Maximum 16 pairs, keys up to 64 chars, values up to 512 chars.
- `multiagent: optional BetaManagedAgentsMultiagentParams`
A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `agents: array of BetaManagedAgentsMultiagentRosterEntryParams`
Agents the coordinator may spawn as session threads. 1–20 entries. Each entry is an agent ID string, a versioned `{"type":"agent","id","version"}` reference, or `{"type":"self"}` to allow recursive self-invocation. Entries must reference distinct agents (after resolving `self` and string forms); at most one `self`. Referenced agents must exist, must not be archived, and must not themselves have `multiagent` set (depth limit 1).
- `UnionMember0 = string`
- `BetaManagedAgentsAgentParams = object { id, type, version }`
Specification for an Agent. Provide a specific `version` or use the short-form `agent="agent_id"` for the most recent version
- `id: string`
The `agent` ID.
- `type: "agent"`
- `"agent"`
- `version: optional number`
The specific `agent` version to use. Omit to use the latest version. Must be at least 1 if specified.
- `BetaManagedAgentsMultiagentSelfParams = object { type }`
Sentinel roster entry meaning "the agent that owns this configuration". Resolved server-side to a concrete agent reference.
- `type: "self"`
- `"self"`
- `type: "coordinator"`
- `"coordinator"`
- `skills: optional array of BetaManagedAgentsSkillParams`
Skills available to the agent. Maximum 20.
- `BetaManagedAgentsAnthropicSkillParams = object { skill_id, type, version }`
An Anthropic-managed skill.
- `skill_id: string`
Identifier of the Anthropic skill (e.g., "xlsx").
- `type: "anthropic"`
- `"anthropic"`
- `version: optional string`
Version to pin. Defaults to latest if omitted.
- `BetaManagedAgentsCustomSkillParams = object { skill_id, type, version }`
A user-created custom skill.
- `skill_id: string`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `type: "custom"`
- `"custom"`
- `version: optional string`
Version to pin. Defaults to latest if omitted.
- `system: optional string`
System prompt for the agent. Up to 100,000 characters.
- `tools: optional array of BetaManagedAgentsAgentToolset20260401Params or BetaManagedAgentsMCPToolsetParams or BetaManagedAgentsCustomToolParams`
Tool configurations available to the agent. Maximum of 128 tools across all toolsets allowed.
- `BetaManagedAgentsAgentToolset20260401Params = object { type, configs, default_config }`
Configuration for built-in agent tools. Use this to enable or disable groups of tools available to the agent.
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `configs: optional array of BetaManagedAgentsAgentToolConfigParams`
Per-tool configuration overrides.
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `enabled: optional boolean`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: optional BetaManagedAgentsAgentToolsetDefaultConfigParams`
Default configuration for all tools in a toolset.
- `enabled: optional boolean`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `BetaManagedAgentsMCPToolsetParams = object { mcp_server_name, type, configs, default_config }`
Configuration for tools from an MCP server defined in `mcp_servers`.
- `mcp_server_name: string`
Name of the MCP server. Must match a server name from the mcp_servers array. 1-255 characters.
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `configs: optional array of BetaManagedAgentsMCPToolConfigParams`
Per-tool configuration overrides.
- `name: string`
Name of the MCP tool to configure. 1-128 characters.
- `enabled: optional boolean`
Whether this tool is enabled. Overrides the `default_config` setting.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: optional BetaManagedAgentsMCPToolsetDefaultConfigParams`
Default configuration for all tools from an MCP server.
- `enabled: optional boolean`
Whether tools are enabled by default. Defaults to true if not specified.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `BetaManagedAgentsCustomToolParams = object { description, input_schema, name, type }`
A custom tool that is executed by the API client rather than the agent. When the agent calls this tool, an `agent.custom_tool_use` event is emitted and the session goes idle, waiting for the client to provide the result via a `user.custom_tool_result` event.
- `description: string`
Description of what the tool does, shown to the agent to help it decide when to use the tool. 1-1024 characters.
- `input_schema: BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
Unique name for the tool. 1-128 characters; letters, digits, underscores, and hyphens.
- `type: "custom"`
- `"custom"`
### Returns
- `BetaManagedAgentsAgent = object { id, archived_at, created_at, 12 more }`
A Managed Agents `agent`.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `id: BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `BetaManagedAgentsAnthropicSkill = object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `BetaManagedAgentsCustomSkill = object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `BetaManagedAgentsAgentToolset20260401 = object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `BetaManagedAgentsMCPToolset = object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `BetaManagedAgentsCustomTool = object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```http
curl https://api.anthropic.com/v1/agents \
-H 'Content-Type: application/json' \
-H 'anthropic-version: 2023-06-01' \
-H 'anthropic-beta: managed-agents-2026-04-01' \
-H "X-Api-Key: $ANTHROPIC_API_KEY" \
-d "{
\"model\": \"claude-sonnet-4-6\",
\"name\": \"My First Agent\",
\"description\": \"A general-purpose starter agent.\",
\"metadata\": {
\"foo\": \"bar\"
},
\"system\": \"You are a general-purpose agent that can research, write code, run commands, and use connected tools to complete the user's task end to end.\",
\"tools\": [
{
\"type\": \"agent_toolset_20260401\"
}
]
}"
```
## List
**get** `/v1/agents`
List Agents
### Query Parameters
- `"created_at[gte]": optional string`
Return agents created at or after this time (inclusive).
- `"created_at[lte]": optional string`
Return agents created at or before this time (inclusive).
- `include_archived: optional boolean`
Include archived agents in results. Defaults to false.
- `limit: optional number`
Maximum results per page. Default 20, maximum 100.
- `page: optional string`
Opaque pagination cursor from a previous response.
### Header Parameters
- `"anthropic-beta": optional array of AnthropicBeta`
Optional header to specify the beta version(s) you want to use.
- `UnionMember0 = string`
- `UnionMember1 = "message-batches-2024-09-24" or "prompt-caching-2024-07-31" or "computer-use-2024-10-22" or 21 more`
- `"message-batches-2024-09-24"`
- `"prompt-caching-2024-07-31"`
- `"computer-use-2024-10-22"`
- `"computer-use-2025-01-24"`
- `"pdfs-2024-09-25"`
- `"token-counting-2024-11-01"`
- `"token-efficient-tools-2025-02-19"`
- `"output-128k-2025-02-19"`
- `"files-api-2025-04-14"`
- `"mcp-client-2025-04-04"`
- `"mcp-client-2025-11-20"`
- `"dev-full-thinking-2025-05-14"`
- `"interleaved-thinking-2025-05-14"`
- `"code-execution-2025-05-22"`
- `"extended-cache-ttl-2025-04-11"`
- `"context-1m-2025-08-07"`
- `"context-management-2025-06-27"`
- `"model-context-window-exceeded-2025-08-26"`
- `"skills-2025-10-02"`
- `"fast-mode-2026-02-01"`
- `"output-300k-2026-03-24"`
- `"user-profiles-2026-03-24"`
- `"advisor-tool-2026-03-01"`
- `"managed-agents-2026-04-01"`
### Returns
- `data: optional array of BetaManagedAgentsAgent`
List of agents.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `id: BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `BetaManagedAgentsAnthropicSkill = object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `BetaManagedAgentsCustomSkill = object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `BetaManagedAgentsAgentToolset20260401 = object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `BetaManagedAgentsMCPToolset = object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `BetaManagedAgentsCustomTool = object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
- `next_page: optional string`
Opaque cursor for the next page. Null when no more results.
### Example
```http
curl https://api.anthropic.com/v1/agents \
-H 'anthropic-version: 2023-06-01' \
-H 'anthropic-beta: managed-agents-2026-04-01' \
-H "X-Api-Key: $ANTHROPIC_API_KEY"
```
## Retrieve
**get** `/v1/agents/{agent_id}`
Get Agent
### Path Parameters
- `agent_id: string`
### Query Parameters
- `version: optional number`
Agent version. Omit for the most recent version. Must be at least 1 if specified.
### Header Parameters
- `"anthropic-beta": optional array of AnthropicBeta`
Optional header to specify the beta version(s) you want to use.
- `UnionMember0 = string`
- `UnionMember1 = "message-batches-2024-09-24" or "prompt-caching-2024-07-31" or "computer-use-2024-10-22" or 21 more`
- `"message-batches-2024-09-24"`
- `"prompt-caching-2024-07-31"`
- `"computer-use-2024-10-22"`
- `"computer-use-2025-01-24"`
- `"pdfs-2024-09-25"`
- `"token-counting-2024-11-01"`
- `"token-efficient-tools-2025-02-19"`
- `"output-128k-2025-02-19"`
- `"files-api-2025-04-14"`
- `"mcp-client-2025-04-04"`
- `"mcp-client-2025-11-20"`
- `"dev-full-thinking-2025-05-14"`
- `"interleaved-thinking-2025-05-14"`
- `"code-execution-2025-05-22"`
- `"extended-cache-ttl-2025-04-11"`
- `"context-1m-2025-08-07"`
- `"context-management-2025-06-27"`
- `"model-context-window-exceeded-2025-08-26"`
- `"skills-2025-10-02"`
- `"fast-mode-2026-02-01"`
- `"output-300k-2026-03-24"`
- `"user-profiles-2026-03-24"`
- `"advisor-tool-2026-03-01"`
- `"managed-agents-2026-04-01"`
### Returns
- `BetaManagedAgentsAgent = object { id, archived_at, created_at, 12 more }`
A Managed Agents `agent`.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `id: BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `BetaManagedAgentsAnthropicSkill = object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `BetaManagedAgentsCustomSkill = object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `BetaManagedAgentsAgentToolset20260401 = object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `BetaManagedAgentsMCPToolset = object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `BetaManagedAgentsCustomTool = object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```http
curl https://api.anthropic.com/v1/agents/$AGENT_ID \
-H 'anthropic-version: 2023-06-01' \
-H 'anthropic-beta: managed-agents-2026-04-01' \
-H "X-Api-Key: $ANTHROPIC_API_KEY"
```
## Update
**post** `/v1/agents/{agent_id}`
Update Agent
### Path Parameters
- `agent_id: string`
### Header Parameters
- `"anthropic-beta": optional array of AnthropicBeta`
Optional header to specify the beta version(s) you want to use.
- `UnionMember0 = string`
- `UnionMember1 = "message-batches-2024-09-24" or "prompt-caching-2024-07-31" or "computer-use-2024-10-22" or 21 more`
- `"message-batches-2024-09-24"`
- `"prompt-caching-2024-07-31"`
- `"computer-use-2024-10-22"`
- `"computer-use-2025-01-24"`
- `"pdfs-2024-09-25"`
- `"token-counting-2024-11-01"`
- `"token-efficient-tools-2025-02-19"`
- `"output-128k-2025-02-19"`
- `"files-api-2025-04-14"`
- `"mcp-client-2025-04-04"`
- `"mcp-client-2025-11-20"`
- `"dev-full-thinking-2025-05-14"`
- `"interleaved-thinking-2025-05-14"`
- `"code-execution-2025-05-22"`
- `"extended-cache-ttl-2025-04-11"`
- `"context-1m-2025-08-07"`
- `"context-management-2025-06-27"`
- `"model-context-window-exceeded-2025-08-26"`
- `"skills-2025-10-02"`
- `"fast-mode-2026-02-01"`
- `"output-300k-2026-03-24"`
- `"user-profiles-2026-03-24"`
- `"advisor-tool-2026-03-01"`
- `"managed-agents-2026-04-01"`
### Body Parameters
- `version: number`
The agent's current version, used to prevent concurrent overwrites. Obtain this value from a create or retrieve response. The request fails if this does not match the server's current version.
- `description: optional string`
Description. Up to 2048 characters. Omit to preserve; send empty string or null to clear.
- `mcp_servers: optional array of BetaManagedAgentsURLMCPServerParams`
MCP servers. Full replacement. Omit to preserve; send empty array or null to clear. Names must be unique. Maximum 20.
- `name: string`
Unique name for this server, referenced by mcp_toolset configurations. 1-255 characters.
- `type: "url"`
- `"url"`
- `url: string`
Endpoint URL for the MCP server.
- `metadata: optional map[string]`
Metadata patch. Set a key to a string to upsert it, or to null to delete it. Omit the field to preserve. The stored bag is limited to 16 keys (up to 64 chars each) with values up to 512 chars.
- `model: optional BetaManagedAgentsModel or BetaManagedAgentsModelConfigParams`
Model identifier. Accepts the [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison), e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration control. Omit to preserve. Cannot be cleared.
- `BetaManagedAgentsModel = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more or string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `BetaManagedAgentsModelConfigParams = object { id, speed }`
An object that defines additional configuration control over model use
- `id: BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: optional BetaManagedAgentsMultiagentParams`
A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `agents: array of BetaManagedAgentsMultiagentRosterEntryParams`
Agents the coordinator may spawn as session threads. 1–20 entries. Each entry is an agent ID string, a versioned `{"type":"agent","id","version"}` reference, or `{"type":"self"}` to allow recursive self-invocation. Entries must reference distinct agents (after resolving `self` and string forms); at most one `self`. Referenced agents must exist, must not be archived, and must not themselves have `multiagent` set (depth limit 1).
- `UnionMember0 = string`
- `BetaManagedAgentsAgentParams = object { id, type, version }`
Specification for an Agent. Provide a specific `version` or use the short-form `agent="agent_id"` for the most recent version
- `id: string`
The `agent` ID.
- `type: "agent"`
- `"agent"`
- `version: optional number`
The specific `agent` version to use. Omit to use the latest version. Must be at least 1 if specified.
- `BetaManagedAgentsMultiagentSelfParams = object { type }`
Sentinel roster entry meaning "the agent that owns this configuration". Resolved server-side to a concrete agent reference.
- `type: "self"`
- `"self"`
- `type: "coordinator"`
- `"coordinator"`
- `name: optional string`
Human-readable name. 1-256 characters. Omit to preserve. Cannot be cleared.
- `skills: optional array of BetaManagedAgentsSkillParams`
Skills. Full replacement. Omit to preserve; send empty array or null to clear. Maximum 20.
- `BetaManagedAgentsAnthropicSkillParams = object { skill_id, type, version }`
An Anthropic-managed skill.
- `skill_id: string`
Identifier of the Anthropic skill (e.g., "xlsx").
- `type: "anthropic"`
- `"anthropic"`
- `version: optional string`
Version to pin. Defaults to latest if omitted.
- `BetaManagedAgentsCustomSkillParams = object { skill_id, type, version }`
A user-created custom skill.
- `skill_id: string`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `type: "custom"`
- `"custom"`
- `version: optional string`
Version to pin. Defaults to latest if omitted.
- `system: optional string`
System prompt. Up to 100,000 characters. Omit to preserve; send empty string or null to clear.
- `tools: optional array of BetaManagedAgentsAgentToolset20260401Params or BetaManagedAgentsMCPToolsetParams or BetaManagedAgentsCustomToolParams`
Tool configurations available to the agent. Full replacement. Omit to preserve; send empty array or null to clear. Maximum of 128 tools across all toolsets allowed.
- `BetaManagedAgentsAgentToolset20260401Params = object { type, configs, default_config }`
Configuration for built-in agent tools. Use this to enable or disable groups of tools available to the agent.
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `configs: optional array of BetaManagedAgentsAgentToolConfigParams`
Per-tool configuration overrides.
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `enabled: optional boolean`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: optional BetaManagedAgentsAgentToolsetDefaultConfigParams`
Default configuration for all tools in a toolset.
- `enabled: optional boolean`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `BetaManagedAgentsMCPToolsetParams = object { mcp_server_name, type, configs, default_config }`
Configuration for tools from an MCP server defined in `mcp_servers`.
- `mcp_server_name: string`
Name of the MCP server. Must match a server name from the mcp_servers array. 1-255 characters.
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `configs: optional array of BetaManagedAgentsMCPToolConfigParams`
Per-tool configuration overrides.
- `name: string`
Name of the MCP tool to configure. 1-128 characters.
- `enabled: optional boolean`
Whether this tool is enabled. Overrides the `default_config` setting.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: optional BetaManagedAgentsMCPToolsetDefaultConfigParams`
Default configuration for all tools from an MCP server.
- `enabled: optional boolean`
Whether tools are enabled by default. Defaults to true if not specified.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `BetaManagedAgentsCustomToolParams = object { description, input_schema, name, type }`
A custom tool that is executed by the API client rather than the agent. When the agent calls this tool, an `agent.custom_tool_use` event is emitted and the session goes idle, waiting for the client to provide the result via a `user.custom_tool_result` event.
- `description: string`
Description of what the tool does, shown to the agent to help it decide when to use the tool. 1-1024 characters.
- `input_schema: BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
Unique name for the tool. 1-128 characters; letters, digits, underscores, and hyphens.
- `type: "custom"`
- `"custom"`
### Returns
- `BetaManagedAgentsAgent = object { id, archived_at, created_at, 12 more }`
A Managed Agents `agent`.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `id: BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `BetaManagedAgentsAnthropicSkill = object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `BetaManagedAgentsCustomSkill = object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `BetaManagedAgentsAgentToolset20260401 = object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `BetaManagedAgentsMCPToolset = object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `BetaManagedAgentsCustomTool = object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```http
curl https://api.anthropic.com/v1/agents/$AGENT_ID \
-H 'Content-Type: application/json' \
-H 'anthropic-version: 2023-06-01' \
-H 'anthropic-beta: managed-agents-2026-04-01' \
-H "X-Api-Key: $ANTHROPIC_API_KEY" \
-d "{
\"version\": 1,
\"system\": \"You are a general-purpose agent that can research, write code, run commands, and use connected tools to complete the user's task end to end.\"
}"
```
## Archive
**post** `/v1/agents/{agent_id}/archive`
Archive Agent
### Path Parameters
- `agent_id: string`
### Header Parameters
- `"anthropic-beta": optional array of AnthropicBeta`
Optional header to specify the beta version(s) you want to use.
- `UnionMember0 = string`
- `UnionMember1 = "message-batches-2024-09-24" or "prompt-caching-2024-07-31" or "computer-use-2024-10-22" or 21 more`
- `"message-batches-2024-09-24"`
- `"prompt-caching-2024-07-31"`
- `"computer-use-2024-10-22"`
- `"computer-use-2025-01-24"`
- `"pdfs-2024-09-25"`
- `"token-counting-2024-11-01"`
- `"token-efficient-tools-2025-02-19"`
- `"output-128k-2025-02-19"`
- `"files-api-2025-04-14"`
- `"mcp-client-2025-04-04"`
- `"mcp-client-2025-11-20"`
- `"dev-full-thinking-2025-05-14"`
- `"interleaved-thinking-2025-05-14"`
- `"code-execution-2025-05-22"`
- `"extended-cache-ttl-2025-04-11"`
- `"context-1m-2025-08-07"`
- `"context-management-2025-06-27"`
- `"model-context-window-exceeded-2025-08-26"`
- `"skills-2025-10-02"`
- `"fast-mode-2026-02-01"`
- `"output-300k-2026-03-24"`
- `"user-profiles-2026-03-24"`
- `"advisor-tool-2026-03-01"`
- `"managed-agents-2026-04-01"`
### Returns
- `BetaManagedAgentsAgent = object { id, archived_at, created_at, 12 more }`
A Managed Agents `agent`.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `id: BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `BetaManagedAgentsAnthropicSkill = object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `BetaManagedAgentsCustomSkill = object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `BetaManagedAgentsAgentToolset20260401 = object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `BetaManagedAgentsMCPToolset = object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `BetaManagedAgentsCustomTool = object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```http
curl https://api.anthropic.com/v1/agents/$AGENT_ID/archive \
-X POST \
-H 'anthropic-version: 2023-06-01' \
-H 'anthropic-beta: managed-agents-2026-04-01' \
-H "X-Api-Key: $ANTHROPIC_API_KEY"
```
## Domain Types
### Beta Managed Agents Agent
- `BetaManagedAgentsAgent = object { id, archived_at, created_at, 12 more }`
A Managed Agents `agent`.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `id: BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `BetaManagedAgentsAnthropicSkill = object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `BetaManagedAgentsCustomSkill = object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `BetaManagedAgentsAgentToolset20260401 = object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `BetaManagedAgentsMCPToolset = object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `BetaManagedAgentsCustomTool = object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Beta Managed Agents Agent Reference
- `BetaManagedAgentsAgentReference = object { id, type, version }`
A resolved agent reference with a concrete version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
### Beta Managed Agents Agent Tool Config
- `BetaManagedAgentsAgentToolConfig = object { enabled, name, permission_policy }`
Configuration for a specific agent tool.
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Agent Tool Config Params
- `BetaManagedAgentsAgentToolConfigParams = object { name, enabled, permission_policy }`
Configuration override for a specific tool within a toolset.
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `enabled: optional boolean`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Agent Toolset Default Config
- `BetaManagedAgentsAgentToolsetDefaultConfig = object { enabled, permission_policy }`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Agent Toolset Default Config Params
- `BetaManagedAgentsAgentToolsetDefaultConfigParams = object { enabled, permission_policy }`
Default configuration for all tools in a toolset.
- `enabled: optional boolean`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Agent Toolset20260401
- `BetaManagedAgentsAgentToolset20260401 = object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
### Beta Managed Agents Agent Toolset20260401 Params
- `BetaManagedAgentsAgentToolset20260401Params = object { type, configs, default_config }`
Configuration for built-in agent tools. Use this to enable or disable groups of tools available to the agent.
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `configs: optional array of BetaManagedAgentsAgentToolConfigParams`
Per-tool configuration overrides.
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `enabled: optional boolean`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: optional BetaManagedAgentsAgentToolsetDefaultConfigParams`
Default configuration for all tools in a toolset.
- `enabled: optional boolean`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Always Allow Policy
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
### Beta Managed Agents Always Ask Policy
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Anthropic Skill
- `BetaManagedAgentsAnthropicSkill = object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
### Beta Managed Agents Anthropic Skill Params
- `BetaManagedAgentsAnthropicSkillParams = object { skill_id, type, version }`
An Anthropic-managed skill.
- `skill_id: string`
Identifier of the Anthropic skill (e.g., "xlsx").
- `type: "anthropic"`
- `"anthropic"`
- `version: optional string`
Version to pin. Defaults to latest if omitted.
### Beta Managed Agents Custom Skill
- `BetaManagedAgentsCustomSkill = object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
### Beta Managed Agents Custom Skill Params
- `BetaManagedAgentsCustomSkillParams = object { skill_id, type, version }`
A user-created custom skill.
- `skill_id: string`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `type: "custom"`
- `"custom"`
- `version: optional string`
Version to pin. Defaults to latest if omitted.
### Beta Managed Agents Custom Tool
- `BetaManagedAgentsCustomTool = object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
### Beta Managed Agents Custom Tool Input Schema
- `BetaManagedAgentsCustomToolInputSchema = object { properties, required, type }`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
### Beta Managed Agents Custom Tool Params
- `BetaManagedAgentsCustomToolParams = object { description, input_schema, name, type }`
A custom tool that is executed by the API client rather than the agent. When the agent calls this tool, an `agent.custom_tool_use` event is emitted and the session goes idle, waiting for the client to provide the result via a `user.custom_tool_result` event.
- `description: string`
Description of what the tool does, shown to the agent to help it decide when to use the tool. 1-1024 characters.
- `input_schema: BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
Unique name for the tool. 1-128 characters; letters, digits, underscores, and hyphens.
- `type: "custom"`
- `"custom"`
### Beta Managed Agents MCP Server URL Definition
- `BetaManagedAgentsMCPServerURLDefinition = object { name, type, url }`
URL-based MCP server connection as returned in API responses.
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
### Beta Managed Agents MCP Tool Config
- `BetaManagedAgentsMCPToolConfig = object { enabled, name, permission_policy }`
Resolved configuration for a specific MCP tool.
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents MCP Tool Config Params
- `BetaManagedAgentsMCPToolConfigParams = object { name, enabled, permission_policy }`
Configuration override for a specific MCP tool.
- `name: string`
Name of the MCP tool to configure. 1-128 characters.
- `enabled: optional boolean`
Whether this tool is enabled. Overrides the `default_config` setting.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents MCP Toolset
- `BetaManagedAgentsMCPToolset = object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
### Beta Managed Agents MCP Toolset Default Config
- `BetaManagedAgentsMCPToolsetDefaultConfig = object { enabled, permission_policy }`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents MCP Toolset Default Config Params
- `BetaManagedAgentsMCPToolsetDefaultConfigParams = object { enabled, permission_policy }`
Default configuration for all tools from an MCP server.
- `enabled: optional boolean`
Whether tools are enabled by default. Defaults to true if not specified.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents MCP Toolset Params
- `BetaManagedAgentsMCPToolsetParams = object { mcp_server_name, type, configs, default_config }`
Configuration for tools from an MCP server defined in `mcp_servers`.
- `mcp_server_name: string`
Name of the MCP server. Must match a server name from the mcp_servers array. 1-255 characters.
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `configs: optional array of BetaManagedAgentsMCPToolConfigParams`
Per-tool configuration overrides.
- `name: string`
Name of the MCP tool to configure. 1-128 characters.
- `enabled: optional boolean`
Whether this tool is enabled. Overrides the `default_config` setting.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: optional BetaManagedAgentsMCPToolsetDefaultConfigParams`
Default configuration for all tools from an MCP server.
- `enabled: optional boolean`
Whether tools are enabled by default. Defaults to true if not specified.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Model
- `BetaManagedAgentsModel = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more or string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
### Beta Managed Agents Model Config
- `BetaManagedAgentsModelConfig = object { id, speed }`
Model identifier and configuration.
- `id: BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
### Beta Managed Agents Model Config Params
- `BetaManagedAgentsModelConfigParams = object { id, speed }`
An object that defines additional configuration control over model use
- `id: BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
### Beta Managed Agents Multiagent Coordinator
- `BetaManagedAgentsMultiagentCoordinator = object { agents, type }`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
### Beta Managed Agents Multiagent Coordinator Params
- `BetaManagedAgentsMultiagentCoordinatorParams = object { agents, type }`
A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `agents: array of BetaManagedAgentsMultiagentRosterEntryParams`
Agents the coordinator may spawn as session threads. 1–20 entries. Each entry is an agent ID string, a versioned `{"type":"agent","id","version"}` reference, or `{"type":"self"}` to allow recursive self-invocation. Entries must reference distinct agents (after resolving `self` and string forms); at most one `self`. Referenced agents must exist, must not be archived, and must not themselves have `multiagent` set (depth limit 1).
- `UnionMember0 = string`
- `BetaManagedAgentsAgentParams = object { id, type, version }`
Specification for an Agent. Provide a specific `version` or use the short-form `agent="agent_id"` for the most recent version
- `id: string`
The `agent` ID.
- `type: "agent"`
- `"agent"`
- `version: optional number`
The specific `agent` version to use. Omit to use the latest version. Must be at least 1 if specified.
- `BetaManagedAgentsMultiagentSelfParams = object { type }`
Sentinel roster entry meaning "the agent that owns this configuration". Resolved server-side to a concrete agent reference.
- `type: "self"`
- `"self"`
- `type: "coordinator"`
- `"coordinator"`
### Beta Managed Agents Multiagent Self Params
- `BetaManagedAgentsMultiagentSelfParams = object { type }`
Sentinel roster entry meaning "the agent that owns this configuration". Resolved server-side to a concrete agent reference.
- `type: "self"`
- `"self"`
### Beta Managed Agents Skill Params
- `BetaManagedAgentsSkillParams = BetaManagedAgentsAnthropicSkillParams or BetaManagedAgentsCustomSkillParams`
Skill to load in the session container.
- `BetaManagedAgentsAnthropicSkillParams = object { skill_id, type, version }`
An Anthropic-managed skill.
- `skill_id: string`
Identifier of the Anthropic skill (e.g., "xlsx").
- `type: "anthropic"`
- `"anthropic"`
- `version: optional string`
Version to pin. Defaults to latest if omitted.
- `BetaManagedAgentsCustomSkillParams = object { skill_id, type, version }`
A user-created custom skill.
- `skill_id: string`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `type: "custom"`
- `"custom"`
- `version: optional string`
Version to pin. Defaults to latest if omitted.
### Beta Managed Agents URL MCP Server Params
- `BetaManagedAgentsURLMCPServerParams = object { name, type, url }`
URL-based MCP server connection.
- `name: string`
Unique name for this server, referenced by mcp_toolset configurations. 1-255 characters.
- `type: "url"`
- `"url"`
- `url: string`
Endpoint URL for the MCP server.
# Versions
## List
**get** `/v1/agents/{agent_id}/versions`
List Agent Versions
### Path Parameters
- `agent_id: string`
### Query Parameters
- `limit: optional number`
Maximum results per page. Default 20, maximum 100.
- `page: optional string`
Opaque pagination cursor.
### Header Parameters
- `"anthropic-beta": optional array of AnthropicBeta`
Optional header to specify the beta version(s) you want to use.
- `UnionMember0 = string`
- `UnionMember1 = "message-batches-2024-09-24" or "prompt-caching-2024-07-31" or "computer-use-2024-10-22" or 21 more`
- `"message-batches-2024-09-24"`
- `"prompt-caching-2024-07-31"`
- `"computer-use-2024-10-22"`
- `"computer-use-2025-01-24"`
- `"pdfs-2024-09-25"`
- `"token-counting-2024-11-01"`
- `"token-efficient-tools-2025-02-19"`
- `"output-128k-2025-02-19"`
- `"files-api-2025-04-14"`
- `"mcp-client-2025-04-04"`
- `"mcp-client-2025-11-20"`
- `"dev-full-thinking-2025-05-14"`
- `"interleaved-thinking-2025-05-14"`
- `"code-execution-2025-05-22"`
- `"extended-cache-ttl-2025-04-11"`
- `"context-1m-2025-08-07"`
- `"context-management-2025-06-27"`
- `"model-context-window-exceeded-2025-08-26"`
- `"skills-2025-10-02"`
- `"fast-mode-2026-02-01"`
- `"output-300k-2026-03-24"`
- `"user-profiles-2026-03-24"`
- `"advisor-tool-2026-03-01"`
- `"managed-agents-2026-04-01"`
### Returns
- `data: optional array of BetaManagedAgentsAgent`
Agent versions.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `id: BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `UnionMember0 = "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `UnionMember1 = string`
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `BetaManagedAgentsAnthropicSkill = object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `BetaManagedAgentsCustomSkill = object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `BetaManagedAgentsAgentToolset20260401 = object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `BetaManagedAgentsMCPToolset = object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `BetaManagedAgentsAlwaysAllowPolicy = object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `BetaManagedAgentsAlwaysAskPolicy = object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `BetaManagedAgentsCustomTool = object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
- `next_page: optional string`
Opaque cursor for the next page. Null when no more results.
### Example
```http
curl https://api.anthropic.com/v1/agents/$AGENT_ID/versions \
-H 'anthropic-version: 2023-06-01' \
-H 'anthropic-beta: managed-agents-2026-04-01' \
-H "X-Api-Key: $ANTHROPIC_API_KEY"
```
---
# Agents (Beta) (cli)
URL: https://platform.claude.com/docs/en/api/cli/beta/agents
# Agents
## Create
`$ ant beta:agents create`
**post** `/v1/agents`
Create Agent
### Parameters
- `--model: BetaManagedAgentsModelConfigParams`
Body param: Model identifier. Accepts the [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison), e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration control
- `--name: string`
Body param: Human-readable name for the agent. 1-256 characters.
- `--description: optional string`
Body param: Description of what the agent does. Up to 2048 characters.
- `--mcp-server: optional array of BetaManagedAgentsURLMCPServerParams`
Body param: MCP servers this agent connects to. Maximum 20. Names must be unique within the array.
- `--metadata: optional map[string]`
Body param: Arbitrary key-value metadata. Maximum 16 pairs, keys up to 64 chars, values up to 512 chars.
- `--multiagent: optional object { agents, type }`
Body param: A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `--skill: optional array of BetaManagedAgentsSkillParams`
Body param: Skills available to the agent. Maximum 20.
- `--system: optional string`
Body param: System prompt for the agent. Up to 100,000 characters.
- `--tool: optional array of BetaManagedAgentsAgentToolset20260401Params or BetaManagedAgentsMCPToolsetParams or BetaManagedAgentsCustomToolParams`
Body param: Tool configurations available to the agent. Maximum of 128 tools across all toolsets allowed.
- `--beta: optional array of AnthropicBeta`
Header param: Optional header to specify the beta version(s) you want to use.
### Returns
- `beta_managed_agents_agent: object { id, archived_at, created_at, 12 more }`
A Managed Agents `agent`.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: object { id, speed }`
Model identifier and configuration.
- `id: "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more or string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: object { agents, type }`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `beta_managed_agents_anthropic_skill: object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `beta_managed_agents_custom_skill: object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `beta_managed_agents_agent_toolset20260401: object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `beta_managed_agents_mcp_toolset: object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `beta_managed_agents_custom_tool: object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: object { properties, required, type }`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```cli
ant beta:agents create \
--api-key my-anthropic-api-key \
--model '{id: claude-opus-4-6}' \
--name 'My First Agent'
```
## List
`$ ant beta:agents list`
**get** `/v1/agents`
List Agents
### Parameters
- `--created-at-gte: optional string`
Query param: Return agents created at or after this time (inclusive).
- `--created-at-lte: optional string`
Query param: Return agents created at or before this time (inclusive).
- `--include-archived: optional boolean`
Query param: Include archived agents in results. Defaults to false.
- `--limit: optional number`
Query param: Maximum results per page. Default 20, maximum 100.
- `--page: optional string`
Query param: Opaque pagination cursor from a previous response.
- `--beta: optional array of AnthropicBeta`
Header param: Optional header to specify the beta version(s) you want to use.
### Returns
- `BetaManagedAgentsListAgents: object { data, next_page }`
Paginated list of agents.
- `data: optional array of BetaManagedAgentsAgent`
List of agents.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: object { id, speed }`
Model identifier and configuration.
- `id: "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more or string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: object { agents, type }`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `beta_managed_agents_anthropic_skill: object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `beta_managed_agents_custom_skill: object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `beta_managed_agents_agent_toolset20260401: object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `beta_managed_agents_mcp_toolset: object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `beta_managed_agents_custom_tool: object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: object { properties, required, type }`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
- `next_page: optional string`
Opaque cursor for the next page. Null when no more results.
### Example
```cli
ant beta:agents list \
--api-key my-anthropic-api-key
```
## Retrieve
`$ ant beta:agents retrieve`
**get** `/v1/agents/{agent_id}`
Get Agent
### Parameters
- `--agent-id: string`
Path param: Path parameter agent_id
- `--version: optional number`
Query param: Agent version. Omit for the most recent version. Must be at least 1 if specified.
- `--beta: optional array of AnthropicBeta`
Header param: Optional header to specify the beta version(s) you want to use.
### Returns
- `beta_managed_agents_agent: object { id, archived_at, created_at, 12 more }`
A Managed Agents `agent`.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: object { id, speed }`
Model identifier and configuration.
- `id: "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more or string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: object { agents, type }`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `beta_managed_agents_anthropic_skill: object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `beta_managed_agents_custom_skill: object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `beta_managed_agents_agent_toolset20260401: object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `beta_managed_agents_mcp_toolset: object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `beta_managed_agents_custom_tool: object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: object { properties, required, type }`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```cli
ant beta:agents retrieve \
--api-key my-anthropic-api-key \
--agent-id agent_011CZkYpogX7uDKUyvBTophP
```
## Update
`$ ant beta:agents update`
**post** `/v1/agents/{agent_id}`
Update Agent
### Parameters
- `--agent-id: string`
Path param: Path parameter agent_id
- `--version: number`
Body param: The agent's current version, used to prevent concurrent overwrites. Obtain this value from a create or retrieve response. The request fails if this does not match the server's current version.
- `--description: optional string`
Body param: Description. Up to 2048 characters. Omit to preserve; send empty string or null to clear.
- `--mcp-server: optional array of BetaManagedAgentsURLMCPServerParams`
Body param: MCP servers. Full replacement. Omit to preserve; send empty array or null to clear. Names must be unique. Maximum 20.
- `--metadata: optional map[string]`
Body param: Metadata patch. Set a key to a string to upsert it, or to null to delete it. Omit the field to preserve. The stored bag is limited to 16 keys (up to 64 chars each) with values up to 512 chars.
- `--model: optional BetaManagedAgentsModelConfigParams`
Body param: Model identifier. Accepts the [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison), e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration control. Omit to preserve. Cannot be cleared.
- `--multiagent: optional object { agents, type }`
Body param: A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `--name: optional string`
Body param: Human-readable name. 1-256 characters. Omit to preserve. Cannot be cleared.
- `--skill: optional array of BetaManagedAgentsSkillParams`
Body param: Skills. Full replacement. Omit to preserve; send empty array or null to clear. Maximum 20.
- `--system: optional string`
Body param: System prompt. Up to 100,000 characters. Omit to preserve; send empty string or null to clear.
- `--tool: optional array of BetaManagedAgentsAgentToolset20260401Params or BetaManagedAgentsMCPToolsetParams or BetaManagedAgentsCustomToolParams`
Body param: Tool configurations available to the agent. Full replacement. Omit to preserve; send empty array or null to clear. Maximum of 128 tools across all toolsets allowed.
- `--beta: optional array of AnthropicBeta`
Header param: Optional header to specify the beta version(s) you want to use.
### Returns
- `beta_managed_agents_agent: object { id, archived_at, created_at, 12 more }`
A Managed Agents `agent`.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: object { id, speed }`
Model identifier and configuration.
- `id: "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more or string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: object { agents, type }`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `beta_managed_agents_anthropic_skill: object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `beta_managed_agents_custom_skill: object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `beta_managed_agents_agent_toolset20260401: object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `beta_managed_agents_mcp_toolset: object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `beta_managed_agents_custom_tool: object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: object { properties, required, type }`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```cli
ant beta:agents update \
--api-key my-anthropic-api-key \
--agent-id agent_011CZkYpogX7uDKUyvBTophP \
--version 1
```
## Archive
`$ ant beta:agents archive`
**post** `/v1/agents/{agent_id}/archive`
Archive Agent
### Parameters
- `--agent-id: string`
Path parameter agent_id
- `--beta: optional array of AnthropicBeta`
Optional header to specify the beta version(s) you want to use.
### Returns
- `beta_managed_agents_agent: object { id, archived_at, created_at, 12 more }`
A Managed Agents `agent`.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: object { id, speed }`
Model identifier and configuration.
- `id: "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more or string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: object { agents, type }`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `beta_managed_agents_anthropic_skill: object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `beta_managed_agents_custom_skill: object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `beta_managed_agents_agent_toolset20260401: object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `beta_managed_agents_mcp_toolset: object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `beta_managed_agents_custom_tool: object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: object { properties, required, type }`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```cli
ant beta:agents archive \
--api-key my-anthropic-api-key \
--agent-id agent_011CZkYpogX7uDKUyvBTophP
```
## Domain Types
### Beta Managed Agents Agent
- `beta_managed_agents_agent: object { id, archived_at, created_at, 12 more }`
A Managed Agents `agent`.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: object { id, speed }`
Model identifier and configuration.
- `id: "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more or string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: object { agents, type }`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `beta_managed_agents_anthropic_skill: object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `beta_managed_agents_custom_skill: object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `beta_managed_agents_agent_toolset20260401: object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `beta_managed_agents_mcp_toolset: object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `beta_managed_agents_custom_tool: object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: object { properties, required, type }`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Beta Managed Agents Agent Reference
- `beta_managed_agents_agent_reference: object { id, type, version }`
A resolved agent reference with a concrete version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
### Beta Managed Agents Agent Tool Config
- `beta_managed_agents_agent_tool_config: object { enabled, name, permission_policy }`
Configuration for a specific agent tool.
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Agent Tool Config Params
- `beta_managed_agents_agent_tool_config_params: object { name, enabled, permission_policy }`
Configuration override for a specific tool within a toolset.
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `enabled: optional boolean`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Agent Toolset Default Config
- `beta_managed_agents_agent_toolset_default_config: object { enabled, permission_policy }`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Agent Toolset Default Config Params
- `beta_managed_agents_agent_toolset_default_config_params: object { enabled, permission_policy }`
Default configuration for all tools in a toolset.
- `enabled: optional boolean`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Agent Toolset20260401
- `beta_managed_agents_agent_toolset20260401: object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
### Beta Managed Agents Agent Toolset20260401 Params
- `beta_managed_agents_agent_toolset20260401_params: object { type, configs, default_config }`
Configuration for built-in agent tools. Use this to enable or disable groups of tools available to the agent.
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `configs: optional array of BetaManagedAgentsAgentToolConfigParams`
Per-tool configuration overrides.
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `enabled: optional boolean`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: optional object { enabled, permission_policy }`
Default configuration for all tools in a toolset.
- `enabled: optional boolean`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Always Allow Policy
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
### Beta Managed Agents Always Ask Policy
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Anthropic Skill
- `beta_managed_agents_anthropic_skill: object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
### Beta Managed Agents Anthropic Skill Params
- `beta_managed_agents_anthropic_skill_params: object { skill_id, type, version }`
An Anthropic-managed skill.
- `skill_id: string`
Identifier of the Anthropic skill (e.g., "xlsx").
- `type: "anthropic"`
- `"anthropic"`
- `version: optional string`
Version to pin. Defaults to latest if omitted.
### Beta Managed Agents Custom Skill
- `beta_managed_agents_custom_skill: object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
### Beta Managed Agents Custom Skill Params
- `beta_managed_agents_custom_skill_params: object { skill_id, type, version }`
A user-created custom skill.
- `skill_id: string`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `type: "custom"`
- `"custom"`
- `version: optional string`
Version to pin. Defaults to latest if omitted.
### Beta Managed Agents Custom Tool
- `beta_managed_agents_custom_tool: object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: object { properties, required, type }`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
### Beta Managed Agents Custom Tool Input Schema
- `beta_managed_agents_custom_tool_input_schema: object { properties, required, type }`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
### Beta Managed Agents Custom Tool Params
- `beta_managed_agents_custom_tool_params: object { description, input_schema, name, type }`
A custom tool that is executed by the API client rather than the agent. When the agent calls this tool, an `agent.custom_tool_use` event is emitted and the session goes idle, waiting for the client to provide the result via a `user.custom_tool_result` event.
- `description: string`
Description of what the tool does, shown to the agent to help it decide when to use the tool. 1-1024 characters.
- `input_schema: object { properties, required, type }`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
Unique name for the tool. 1-128 characters; letters, digits, underscores, and hyphens.
- `type: "custom"`
- `"custom"`
### Beta Managed Agents MCP Server URL Definition
- `beta_managed_agents_mcp_server_url_definition: object { name, type, url }`
URL-based MCP server connection as returned in API responses.
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
### Beta Managed Agents MCP Tool Config
- `beta_managed_agents_mcp_tool_config: object { enabled, name, permission_policy }`
Resolved configuration for a specific MCP tool.
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents MCP Tool Config Params
- `beta_managed_agents_mcp_tool_config_params: object { name, enabled, permission_policy }`
Configuration override for a specific MCP tool.
- `name: string`
Name of the MCP tool to configure. 1-128 characters.
- `enabled: optional boolean`
Whether this tool is enabled. Overrides the `default_config` setting.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents MCP Toolset
- `beta_managed_agents_mcp_toolset: object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
### Beta Managed Agents MCP Toolset Default Config
- `beta_managed_agents_mcp_toolset_default_config: object { enabled, permission_policy }`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents MCP Toolset Default Config Params
- `beta_managed_agents_mcp_toolset_default_config_params: object { enabled, permission_policy }`
Default configuration for all tools from an MCP server.
- `enabled: optional boolean`
Whether tools are enabled by default. Defaults to true if not specified.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents MCP Toolset Params
- `beta_managed_agents_mcp_toolset_params: object { mcp_server_name, type, configs, default_config }`
Configuration for tools from an MCP server defined in `mcp_servers`.
- `mcp_server_name: string`
Name of the MCP server. Must match a server name from the mcp_servers array. 1-255 characters.
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `configs: optional array of BetaManagedAgentsMCPToolConfigParams`
Per-tool configuration overrides.
- `name: string`
Name of the MCP tool to configure. 1-128 characters.
- `enabled: optional boolean`
Whether this tool is enabled. Overrides the `default_config` setting.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: optional object { enabled, permission_policy }`
Default configuration for all tools from an MCP server.
- `enabled: optional boolean`
Whether tools are enabled by default. Defaults to true if not specified.
- `permission_policy: optional BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
### Beta Managed Agents Model Config
- `beta_managed_agents_model_config: object { id, speed }`
Model identifier and configuration.
- `id: "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more or string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
### Beta Managed Agents Model Config Params
- `beta_managed_agents_model_config_params: object { id, speed }`
An object that defines additional configuration control over model use
- `id: "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more or string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
### Beta Managed Agents Multiagent Coordinator
- `beta_managed_agents_multiagent_coordinator: object { agents, type }`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
### Beta Managed Agents Multiagent Coordinator Params
- `beta_managed_agents_multiagent_coordinator_params: object { agents, type }`
A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `agents: array of BetaManagedAgentsMultiagentRosterEntryParams`
Agents the coordinator may spawn as session threads. 1–20 entries. Each entry is an agent ID string, a versioned `{"type":"agent","id","version"}` reference, or `{"type":"self"}` to allow recursive self-invocation. Entries must reference distinct agents (after resolving `self` and string forms); at most one `self`. Referenced agents must exist, must not be archived, and must not themselves have `multiagent` set (depth limit 1).
- `union_member_0: string`
- `beta_managed_agents_agent_params: object { id, type, version }`
Specification for an Agent. Provide a specific `version` or use the short-form `agent="agent_id"` for the most recent version
- `id: string`
The `agent` ID.
- `type: "agent"`
- `"agent"`
- `version: optional number`
The specific `agent` version to use. Omit to use the latest version. Must be at least 1 if specified.
- `beta_managed_agents_multiagent_self_params: object { type }`
Sentinel roster entry meaning "the agent that owns this configuration". Resolved server-side to a concrete agent reference.
- `type: "self"`
- `"self"`
- `type: "coordinator"`
- `"coordinator"`
### Beta Managed Agents Multiagent Self Params
- `beta_managed_agents_multiagent_self_params: object { type }`
Sentinel roster entry meaning "the agent that owns this configuration". Resolved server-side to a concrete agent reference.
- `type: "self"`
- `"self"`
### Beta Managed Agents Skill Params
- `beta_managed_agents_skill_params: BetaManagedAgentsAnthropicSkillParams or BetaManagedAgentsCustomSkillParams`
Skill to load in the session container.
- `beta_managed_agents_anthropic_skill_params: object { skill_id, type, version }`
An Anthropic-managed skill.
- `skill_id: string`
Identifier of the Anthropic skill (e.g., "xlsx").
- `type: "anthropic"`
- `"anthropic"`
- `version: optional string`
Version to pin. Defaults to latest if omitted.
- `beta_managed_agents_custom_skill_params: object { skill_id, type, version }`
A user-created custom skill.
- `skill_id: string`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `type: "custom"`
- `"custom"`
- `version: optional string`
Version to pin. Defaults to latest if omitted.
### Beta Managed Agents URL MCP Server Params
- `beta_managed_agents_url_mcp_server_params: object { name, type, url }`
URL-based MCP server connection.
- `name: string`
Unique name for this server, referenced by mcp_toolset configurations. 1-255 characters.
- `type: "url"`
- `"url"`
- `url: string`
Endpoint URL for the MCP server.
# Versions
## List
`$ ant beta:agents:versions list`
**get** `/v1/agents/{agent_id}/versions`
List Agent Versions
### Parameters
- `--agent-id: string`
Path param: Path parameter agent_id
- `--limit: optional number`
Query param: Maximum results per page. Default 20, maximum 100.
- `--page: optional string`
Query param: Opaque pagination cursor.
- `--beta: optional array of AnthropicBeta`
Header param: Optional header to specify the beta version(s) you want to use.
### Returns
- `BetaManagedAgentsListAgentVersions: object { data, next_page }`
Paginated list of agent versions.
- `data: optional array of BetaManagedAgentsAgent`
Agent versions.
- `id: string`
- `archived_at: string`
A timestamp in RFC 3339 format
- `created_at: string`
A timestamp in RFC 3339 format
- `description: string`
- `mcp_servers: array of BetaManagedAgentsMCPServerURLDefinition`
- `name: string`
- `type: "url"`
- `"url"`
- `url: string`
- `metadata: map[string]`
- `model: object { id, speed }`
Model identifier and configuration.
- `id: "claude-opus-4-7" or "claude-opus-4-6" or "claude-sonnet-4-6" or 6 more or string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"`
Best combination of speed and intelligence
- `"claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `speed: optional "standard" or "fast"`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"`
- `"fast"`
- `multiagent: object { agents, type }`
Resolved coordinator topology with a concrete agent roster.
- `agents: array of BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `id: string`
- `type: "agent"`
- `"agent"`
- `version: number`
- `type: "coordinator"`
- `"coordinator"`
- `name: string`
- `skills: array of BetaManagedAgentsAnthropicSkill or BetaManagedAgentsCustomSkill`
- `beta_managed_agents_anthropic_skill: object { skill_id, type, version }`
A resolved Anthropic-managed skill.
- `skill_id: string`
- `type: "anthropic"`
- `"anthropic"`
- `version: string`
- `beta_managed_agents_custom_skill: object { skill_id, type, version }`
A resolved user-created custom skill.
- `skill_id: string`
- `type: "custom"`
- `"custom"`
- `version: string`
- `system: string`
- `tools: array of BetaManagedAgentsAgentToolset20260401 or BetaManagedAgentsMCPToolset or BetaManagedAgentsCustomTool`
- `beta_managed_agents_agent_toolset20260401: object { configs, default_config, type }`
- `configs: array of BetaManagedAgentsAgentToolConfig`
- `enabled: boolean`
- `name: "bash" or "edit" or "read" or 5 more`
Built-in agent tool identifier.
- `"bash"`
- `"edit"`
- `"read"`
- `"write"`
- `"glob"`
- `"grep"`
- `"web_fetch"`
- `"web_search"`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for agent tools.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `type: "agent_toolset_20260401"`
- `"agent_toolset_20260401"`
- `beta_managed_agents_mcp_toolset: object { configs, default_config, mcp_server_name, type }`
- `configs: array of BetaManagedAgentsMCPToolConfig`
- `enabled: boolean`
- `name: string`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `default_config: object { enabled, permission_policy }`
Resolved default configuration for all tools from an MCP server.
- `enabled: boolean`
- `permission_policy: BetaManagedAgentsAlwaysAllowPolicy or BetaManagedAgentsAlwaysAskPolicy`
Permission policy for tool execution.
- `beta_managed_agents_always_allow_policy: object { type }`
Tool calls are automatically approved without user confirmation.
- `type: "always_allow"`
- `"always_allow"`
- `beta_managed_agents_always_ask_policy: object { type }`
Tool calls require user confirmation before execution.
- `type: "always_ask"`
- `"always_ask"`
- `mcp_server_name: string`
- `type: "mcp_toolset"`
- `"mcp_toolset"`
- `beta_managed_agents_custom_tool: object { description, input_schema, name, type }`
A custom tool as returned in API responses.
- `description: string`
- `input_schema: object { properties, required, type }`
JSON Schema for custom tool input parameters.
- `properties: optional map[unknown]`
JSON Schema properties defining the tool's input parameters.
- `required: optional array of string`
List of required property names.
- `type: optional "object"`
Must be 'object' for tool input schemas.
- `"object"`
- `name: string`
- `type: "custom"`
- `"custom"`
- `type: "agent"`
- `"agent"`
- `updated_at: string`
A timestamp in RFC 3339 format
- `version: number`
The agent's current version. Starts at 1 and increments when the agent is modified.
- `next_page: optional string`
Opaque cursor for the next page. Null when no more results.
### Example
```cli
ant beta:agents:versions list \
--api-key my-anthropic-api-key \
--agent-id agent_011CZkYpogX7uDKUyvBTophP
```
---
# Agents (Beta) (csharp)
URL: https://platform.claude.com/docs/en/api/csharp/beta/agents
# Agents
## Create
`BetaManagedAgentsAgent Beta.Agents.Create(AgentCreateParamsparameters, CancellationTokencancellationToken = default)`
**post** `/v1/agents`
Create Agent
### Parameters
- `AgentCreateParams parameters`
- `required Model model`
Body param: Model identifier. Accepts the [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison), e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration control
- `enum BetaManagedAgentsModel:`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `class BetaManagedAgentsModelConfigParams:`
An object that defines additional configuration control over model use
- `required BetaManagedAgentsModel ID`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `Speed? Speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"Standard`
- `"fast"Fast`
- `required string name`
Body param: Human-readable name for the agent. 1-256 characters.
- `string? description`
Body param: Description of what the agent does. Up to 2048 characters.
- `IReadOnlyList mcpServers`
Body param: MCP servers this agent connects to. Maximum 20. Names must be unique within the array.
- `required string Name`
Unique name for this server, referenced by mcp_toolset configurations. 1-255 characters.
- `required Type Type`
- `"url"Url`
- `required string Url`
Endpoint URL for the MCP server.
- `IReadOnlyDictionary metadata`
Body param: Arbitrary key-value metadata. Maximum 16 pairs, keys up to 64 chars, values up to 512 chars.
- `BetaManagedAgentsMultiagentParams? multiagent`
Body param: A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `IReadOnlyList skills`
Body param: Skills available to the agent. Maximum 20.
- `class BetaManagedAgentsAnthropicSkillParams:`
An Anthropic-managed skill.
- `required string SkillID`
Identifier of the Anthropic skill (e.g., "xlsx").
- `required Type Type`
- `"anthropic"Anthropic`
- `string? Version`
Version to pin. Defaults to latest if omitted.
- `class BetaManagedAgentsCustomSkillParams:`
A user-created custom skill.
- `required string SkillID`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `required Type Type`
- `"custom"Custom`
- `string? Version`
Version to pin. Defaults to latest if omitted.
- `string? system`
Body param: System prompt for the agent. Up to 100,000 characters.
- `IReadOnlyList tools`
Body param: Tool configurations available to the agent. Maximum of 128 tools across all toolsets allowed.
- `class BetaManagedAgentsAgentToolset20260401Params:`
Configuration for built-in agent tools. Use this to enable or disable groups of tools available to the agent.
- `required Type Type`
- `"agent_toolset_20260401"AgentToolset20260401`
- `IReadOnlyList Configs`
Per-tool configuration overrides.
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `Boolean? Enabled`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `BetaManagedAgentsAgentToolsetDefaultConfigParams? DefaultConfig`
Default configuration for all tools in a toolset.
- `Boolean? Enabled`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `class BetaManagedAgentsMcpToolsetParams:`
Configuration for tools from an MCP server defined in `mcp_servers`.
- `required string McpServerName`
Name of the MCP server. Must match a server name from the mcp_servers array. 1-255 characters.
- `required Type Type`
- `"mcp_toolset"McpToolset`
- `IReadOnlyList Configs`
Per-tool configuration overrides.
- `required string Name`
Name of the MCP tool to configure. 1-128 characters.
- `Boolean? Enabled`
Whether this tool is enabled. Overrides the `default_config` setting.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `BetaManagedAgentsMcpToolsetDefaultConfigParams? DefaultConfig`
Default configuration for all tools from an MCP server.
- `Boolean? Enabled`
Whether tools are enabled by default. Defaults to true if not specified.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `class BetaManagedAgentsCustomToolParams:`
A custom tool that is executed by the API client rather than the agent. When the agent calls this tool, an `agent.custom_tool_use` event is emitted and the session goes idle, waiting for the client to provide the result via a `user.custom_tool_result` event.
- `required string Description`
Description of what the tool does, shown to the agent to help it decide when to use the tool. 1-1024 characters.
- `required BetaManagedAgentsCustomToolInputSchema InputSchema`
JSON Schema for custom tool input parameters.
- `IReadOnlyDictionary? Properties`
JSON Schema properties defining the tool's input parameters.
- `IReadOnlyList Required`
List of required property names.
- `Type Type`
Must be 'object' for tool input schemas.
- `"object"Object`
- `required string Name`
Unique name for the tool. 1-128 characters; letters, digits, underscores, and hyphens.
- `required Type Type`
- `"custom"Custom`
- `IReadOnlyList betas`
Header param: Optional header to specify the beta version(s) you want to use.
- `"message-batches-2024-09-24"MessageBatches2024_09_24`
- `"prompt-caching-2024-07-31"PromptCaching2024_07_31`
- `"computer-use-2024-10-22"ComputerUse2024_10_22`
- `"computer-use-2025-01-24"ComputerUse2025_01_24`
- `"pdfs-2024-09-25"Pdfs2024_09_25`
- `"token-counting-2024-11-01"TokenCounting2024_11_01`
- `"token-efficient-tools-2025-02-19"TokenEfficientTools2025_02_19`
- `"output-128k-2025-02-19"Output128k2025_02_19`
- `"files-api-2025-04-14"FilesApi2025_04_14`
- `"mcp-client-2025-04-04"McpClient2025_04_04`
- `"mcp-client-2025-11-20"McpClient2025_11_20`
- `"dev-full-thinking-2025-05-14"DevFullThinking2025_05_14`
- `"interleaved-thinking-2025-05-14"InterleavedThinking2025_05_14`
- `"code-execution-2025-05-22"CodeExecution2025_05_22`
- `"extended-cache-ttl-2025-04-11"ExtendedCacheTtl2025_04_11`
- `"context-1m-2025-08-07"Context1m2025_08_07`
- `"context-management-2025-06-27"ContextManagement2025_06_27`
- `"model-context-window-exceeded-2025-08-26"ModelContextWindowExceeded2025_08_26`
- `"skills-2025-10-02"Skills2025_10_02`
- `"fast-mode-2026-02-01"FastMode2026_02_01`
- `"output-300k-2026-03-24"Output300k2026_03_24`
- `"user-profiles-2026-03-24"UserProfiles2026_03_24`
- `"advisor-tool-2026-03-01"AdvisorTool2026_03_01`
- `"managed-agents-2026-04-01"ManagedAgents2026_04_01`
### Returns
- `class BetaManagedAgentsAgent:`
A Managed Agents `agent`.
- `required string ID`
- `required DateTimeOffset? ArchivedAt`
A timestamp in RFC 3339 format
- `required DateTimeOffset CreatedAt`
A timestamp in RFC 3339 format
- `required string? Description`
- `required IReadOnlyList McpServers`
- `required string Name`
- `required Type Type`
- `"url"Url`
- `required string Url`
- `required IReadOnlyDictionary Metadata`
- `required BetaManagedAgentsModelConfig Model`
Model identifier and configuration.
- `required BetaManagedAgentsModel ID`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `Speed Speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"Standard`
- `"fast"Fast`
- `required BetaManagedAgentsMultiagent? Multiagent`
Resolved coordinator topology with a concrete agent roster.
- `required IReadOnlyList Agents`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `required string ID`
- `required Type Type`
- `"agent"Agent`
- `required Int Version`
- `required Type Type`
- `"coordinator"Coordinator`
- `required string Name`
- `required IReadOnlyList Skills`
- `class BetaManagedAgentsAnthropicSkill:`
A resolved Anthropic-managed skill.
- `required string SkillID`
- `required Type Type`
- `"anthropic"Anthropic`
- `required string Version`
- `class BetaManagedAgentsCustomSkill:`
A resolved user-created custom skill.
- `required string SkillID`
- `required Type Type`
- `"custom"Custom`
- `required string Version`
- `required string? System`
- `required IReadOnlyList Tools`
- `class BetaManagedAgentsAgentToolset20260401:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsAgentToolsetDefaultConfig DefaultConfig`
Resolved default configuration for agent tools.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required Type Type`
- `"agent_toolset_20260401"AgentToolset20260401`
- `class BetaManagedAgentsMcpToolset:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required string Name`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsMcpToolsetDefaultConfig DefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required string McpServerName`
- `required Type Type`
- `"mcp_toolset"McpToolset`
- `class BetaManagedAgentsCustomTool:`
A custom tool as returned in API responses.
- `required string Description`
- `required BetaManagedAgentsCustomToolInputSchema InputSchema`
JSON Schema for custom tool input parameters.
- `IReadOnlyDictionary? Properties`
JSON Schema properties defining the tool's input parameters.
- `IReadOnlyList Required`
List of required property names.
- `Type Type`
Must be 'object' for tool input schemas.
- `"object"Object`
- `required string Name`
- `required Type Type`
- `"custom"Custom`
- `required Type Type`
- `"agent"Agent`
- `required DateTimeOffset UpdatedAt`
A timestamp in RFC 3339 format
- `required Int Version`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```csharp
AgentCreateParams parameters = new()
{
Model = BetaManagedAgentsModel.ClaudeSonnet4_6,
Name = "My First Agent",
};
var betaManagedAgentsAgent = await client.Beta.Agents.Create(parameters);
Console.WriteLine(betaManagedAgentsAgent);
```
## List
`AgentListPageResponse Beta.Agents.List(AgentListParams?parameters, CancellationTokencancellationToken = default)`
**get** `/v1/agents`
List Agents
### Parameters
- `AgentListParams parameters`
- `DateTimeOffset createdAtGte`
Query param: Return agents created at or after this time (inclusive).
- `DateTimeOffset createdAtLte`
Query param: Return agents created at or before this time (inclusive).
- `Boolean includeArchived`
Query param: Include archived agents in results. Defaults to false.
- `Int limit`
Query param: Maximum results per page. Default 20, maximum 100.
- `string page`
Query param: Opaque pagination cursor from a previous response.
- `IReadOnlyList betas`
Header param: Optional header to specify the beta version(s) you want to use.
- `"message-batches-2024-09-24"MessageBatches2024_09_24`
- `"prompt-caching-2024-07-31"PromptCaching2024_07_31`
- `"computer-use-2024-10-22"ComputerUse2024_10_22`
- `"computer-use-2025-01-24"ComputerUse2025_01_24`
- `"pdfs-2024-09-25"Pdfs2024_09_25`
- `"token-counting-2024-11-01"TokenCounting2024_11_01`
- `"token-efficient-tools-2025-02-19"TokenEfficientTools2025_02_19`
- `"output-128k-2025-02-19"Output128k2025_02_19`
- `"files-api-2025-04-14"FilesApi2025_04_14`
- `"mcp-client-2025-04-04"McpClient2025_04_04`
- `"mcp-client-2025-11-20"McpClient2025_11_20`
- `"dev-full-thinking-2025-05-14"DevFullThinking2025_05_14`
- `"interleaved-thinking-2025-05-14"InterleavedThinking2025_05_14`
- `"code-execution-2025-05-22"CodeExecution2025_05_22`
- `"extended-cache-ttl-2025-04-11"ExtendedCacheTtl2025_04_11`
- `"context-1m-2025-08-07"Context1m2025_08_07`
- `"context-management-2025-06-27"ContextManagement2025_06_27`
- `"model-context-window-exceeded-2025-08-26"ModelContextWindowExceeded2025_08_26`
- `"skills-2025-10-02"Skills2025_10_02`
- `"fast-mode-2026-02-01"FastMode2026_02_01`
- `"output-300k-2026-03-24"Output300k2026_03_24`
- `"user-profiles-2026-03-24"UserProfiles2026_03_24`
- `"advisor-tool-2026-03-01"AdvisorTool2026_03_01`
- `"managed-agents-2026-04-01"ManagedAgents2026_04_01`
### Returns
- `class AgentListPageResponse:`
Paginated list of agents.
- `IReadOnlyList Data`
List of agents.
- `required string ID`
- `required DateTimeOffset? ArchivedAt`
A timestamp in RFC 3339 format
- `required DateTimeOffset CreatedAt`
A timestamp in RFC 3339 format
- `required string? Description`
- `required IReadOnlyList McpServers`
- `required string Name`
- `required Type Type`
- `"url"Url`
- `required string Url`
- `required IReadOnlyDictionary Metadata`
- `required BetaManagedAgentsModelConfig Model`
Model identifier and configuration.
- `required BetaManagedAgentsModel ID`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `Speed Speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"Standard`
- `"fast"Fast`
- `required BetaManagedAgentsMultiagent? Multiagent`
Resolved coordinator topology with a concrete agent roster.
- `required IReadOnlyList Agents`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `required string ID`
- `required Type Type`
- `"agent"Agent`
- `required Int Version`
- `required Type Type`
- `"coordinator"Coordinator`
- `required string Name`
- `required IReadOnlyList Skills`
- `class BetaManagedAgentsAnthropicSkill:`
A resolved Anthropic-managed skill.
- `required string SkillID`
- `required Type Type`
- `"anthropic"Anthropic`
- `required string Version`
- `class BetaManagedAgentsCustomSkill:`
A resolved user-created custom skill.
- `required string SkillID`
- `required Type Type`
- `"custom"Custom`
- `required string Version`
- `required string? System`
- `required IReadOnlyList Tools`
- `class BetaManagedAgentsAgentToolset20260401:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsAgentToolsetDefaultConfig DefaultConfig`
Resolved default configuration for agent tools.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required Type Type`
- `"agent_toolset_20260401"AgentToolset20260401`
- `class BetaManagedAgentsMcpToolset:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required string Name`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsMcpToolsetDefaultConfig DefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required string McpServerName`
- `required Type Type`
- `"mcp_toolset"McpToolset`
- `class BetaManagedAgentsCustomTool:`
A custom tool as returned in API responses.
- `required string Description`
- `required BetaManagedAgentsCustomToolInputSchema InputSchema`
JSON Schema for custom tool input parameters.
- `IReadOnlyDictionary? Properties`
JSON Schema properties defining the tool's input parameters.
- `IReadOnlyList Required`
List of required property names.
- `Type Type`
Must be 'object' for tool input schemas.
- `"object"Object`
- `required string Name`
- `required Type Type`
- `"custom"Custom`
- `required Type Type`
- `"agent"Agent`
- `required DateTimeOffset UpdatedAt`
A timestamp in RFC 3339 format
- `required Int Version`
The agent's current version. Starts at 1 and increments when the agent is modified.
- `string? NextPage`
Opaque cursor for the next page. Null when no more results.
### Example
```csharp
AgentListParams parameters = new();
var page = await client.Beta.Agents.List(parameters);
await foreach (var item in page.Paginate())
{
Console.WriteLine(item);
}
```
## Retrieve
`BetaManagedAgentsAgent Beta.Agents.Retrieve(AgentRetrieveParamsparameters, CancellationTokencancellationToken = default)`
**get** `/v1/agents/{agent_id}`
Get Agent
### Parameters
- `AgentRetrieveParams parameters`
- `required string agentID`
Path param: Path parameter agent_id
- `Int version`
Query param: Agent version. Omit for the most recent version. Must be at least 1 if specified.
- `IReadOnlyList betas`
Header param: Optional header to specify the beta version(s) you want to use.
- `"message-batches-2024-09-24"MessageBatches2024_09_24`
- `"prompt-caching-2024-07-31"PromptCaching2024_07_31`
- `"computer-use-2024-10-22"ComputerUse2024_10_22`
- `"computer-use-2025-01-24"ComputerUse2025_01_24`
- `"pdfs-2024-09-25"Pdfs2024_09_25`
- `"token-counting-2024-11-01"TokenCounting2024_11_01`
- `"token-efficient-tools-2025-02-19"TokenEfficientTools2025_02_19`
- `"output-128k-2025-02-19"Output128k2025_02_19`
- `"files-api-2025-04-14"FilesApi2025_04_14`
- `"mcp-client-2025-04-04"McpClient2025_04_04`
- `"mcp-client-2025-11-20"McpClient2025_11_20`
- `"dev-full-thinking-2025-05-14"DevFullThinking2025_05_14`
- `"interleaved-thinking-2025-05-14"InterleavedThinking2025_05_14`
- `"code-execution-2025-05-22"CodeExecution2025_05_22`
- `"extended-cache-ttl-2025-04-11"ExtendedCacheTtl2025_04_11`
- `"context-1m-2025-08-07"Context1m2025_08_07`
- `"context-management-2025-06-27"ContextManagement2025_06_27`
- `"model-context-window-exceeded-2025-08-26"ModelContextWindowExceeded2025_08_26`
- `"skills-2025-10-02"Skills2025_10_02`
- `"fast-mode-2026-02-01"FastMode2026_02_01`
- `"output-300k-2026-03-24"Output300k2026_03_24`
- `"user-profiles-2026-03-24"UserProfiles2026_03_24`
- `"advisor-tool-2026-03-01"AdvisorTool2026_03_01`
- `"managed-agents-2026-04-01"ManagedAgents2026_04_01`
### Returns
- `class BetaManagedAgentsAgent:`
A Managed Agents `agent`.
- `required string ID`
- `required DateTimeOffset? ArchivedAt`
A timestamp in RFC 3339 format
- `required DateTimeOffset CreatedAt`
A timestamp in RFC 3339 format
- `required string? Description`
- `required IReadOnlyList McpServers`
- `required string Name`
- `required Type Type`
- `"url"Url`
- `required string Url`
- `required IReadOnlyDictionary Metadata`
- `required BetaManagedAgentsModelConfig Model`
Model identifier and configuration.
- `required BetaManagedAgentsModel ID`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `Speed Speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"Standard`
- `"fast"Fast`
- `required BetaManagedAgentsMultiagent? Multiagent`
Resolved coordinator topology with a concrete agent roster.
- `required IReadOnlyList Agents`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `required string ID`
- `required Type Type`
- `"agent"Agent`
- `required Int Version`
- `required Type Type`
- `"coordinator"Coordinator`
- `required string Name`
- `required IReadOnlyList Skills`
- `class BetaManagedAgentsAnthropicSkill:`
A resolved Anthropic-managed skill.
- `required string SkillID`
- `required Type Type`
- `"anthropic"Anthropic`
- `required string Version`
- `class BetaManagedAgentsCustomSkill:`
A resolved user-created custom skill.
- `required string SkillID`
- `required Type Type`
- `"custom"Custom`
- `required string Version`
- `required string? System`
- `required IReadOnlyList Tools`
- `class BetaManagedAgentsAgentToolset20260401:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsAgentToolsetDefaultConfig DefaultConfig`
Resolved default configuration for agent tools.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required Type Type`
- `"agent_toolset_20260401"AgentToolset20260401`
- `class BetaManagedAgentsMcpToolset:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required string Name`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsMcpToolsetDefaultConfig DefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required string McpServerName`
- `required Type Type`
- `"mcp_toolset"McpToolset`
- `class BetaManagedAgentsCustomTool:`
A custom tool as returned in API responses.
- `required string Description`
- `required BetaManagedAgentsCustomToolInputSchema InputSchema`
JSON Schema for custom tool input parameters.
- `IReadOnlyDictionary? Properties`
JSON Schema properties defining the tool's input parameters.
- `IReadOnlyList Required`
List of required property names.
- `Type Type`
Must be 'object' for tool input schemas.
- `"object"Object`
- `required string Name`
- `required Type Type`
- `"custom"Custom`
- `required Type Type`
- `"agent"Agent`
- `required DateTimeOffset UpdatedAt`
A timestamp in RFC 3339 format
- `required Int Version`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```csharp
AgentRetrieveParams parameters = new()
{
AgentID = "agent_011CZkYpogX7uDKUyvBTophP"
};
var betaManagedAgentsAgent = await client.Beta.Agents.Retrieve(parameters);
Console.WriteLine(betaManagedAgentsAgent);
```
## Update
`BetaManagedAgentsAgent Beta.Agents.Update(AgentUpdateParamsparameters, CancellationTokencancellationToken = default)`
**post** `/v1/agents/{agent_id}`
Update Agent
### Parameters
- `AgentUpdateParams parameters`
- `required string agentID`
Path param: Path parameter agent_id
- `required Int version`
Body param: The agent's current version, used to prevent concurrent overwrites. Obtain this value from a create or retrieve response. The request fails if this does not match the server's current version.
- `string? description`
Body param: Description. Up to 2048 characters. Omit to preserve; send empty string or null to clear.
- `IReadOnlyList? mcpServers`
Body param: MCP servers. Full replacement. Omit to preserve; send empty array or null to clear. Names must be unique. Maximum 20.
- `required string Name`
Unique name for this server, referenced by mcp_toolset configurations. 1-255 characters.
- `required Type Type`
- `"url"Url`
- `required string Url`
Endpoint URL for the MCP server.
- `IReadOnlyDictionary? metadata`
Body param: Metadata patch. Set a key to a string to upsert it, or to null to delete it. Omit the field to preserve. The stored bag is limited to 16 keys (up to 64 chars each) with values up to 512 chars.
- `Model model`
Body param: Model identifier. Accepts the [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison), e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration control. Omit to preserve. Cannot be cleared.
- `enum BetaManagedAgentsModel:`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `class BetaManagedAgentsModelConfigParams:`
An object that defines additional configuration control over model use
- `required BetaManagedAgentsModel ID`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `Speed? Speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"Standard`
- `"fast"Fast`
- `BetaManagedAgentsMultiagentParams? multiagent`
Body param: A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `string name`
Body param: Human-readable name. 1-256 characters. Omit to preserve. Cannot be cleared.
- `IReadOnlyList? skills`
Body param: Skills. Full replacement. Omit to preserve; send empty array or null to clear. Maximum 20.
- `class BetaManagedAgentsAnthropicSkillParams:`
An Anthropic-managed skill.
- `required string SkillID`
Identifier of the Anthropic skill (e.g., "xlsx").
- `required Type Type`
- `"anthropic"Anthropic`
- `string? Version`
Version to pin. Defaults to latest if omitted.
- `class BetaManagedAgentsCustomSkillParams:`
A user-created custom skill.
- `required string SkillID`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `required Type Type`
- `"custom"Custom`
- `string? Version`
Version to pin. Defaults to latest if omitted.
- `string? system`
Body param: System prompt. Up to 100,000 characters. Omit to preserve; send empty string or null to clear.
- `IReadOnlyList? tools`
Body param: Tool configurations available to the agent. Full replacement. Omit to preserve; send empty array or null to clear. Maximum of 128 tools across all toolsets allowed.
- `class BetaManagedAgentsAgentToolset20260401Params:`
Configuration for built-in agent tools. Use this to enable or disable groups of tools available to the agent.
- `required Type Type`
- `"agent_toolset_20260401"AgentToolset20260401`
- `IReadOnlyList Configs`
Per-tool configuration overrides.
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `Boolean? Enabled`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `BetaManagedAgentsAgentToolsetDefaultConfigParams? DefaultConfig`
Default configuration for all tools in a toolset.
- `Boolean? Enabled`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `class BetaManagedAgentsMcpToolsetParams:`
Configuration for tools from an MCP server defined in `mcp_servers`.
- `required string McpServerName`
Name of the MCP server. Must match a server name from the mcp_servers array. 1-255 characters.
- `required Type Type`
- `"mcp_toolset"McpToolset`
- `IReadOnlyList Configs`
Per-tool configuration overrides.
- `required string Name`
Name of the MCP tool to configure. 1-128 characters.
- `Boolean? Enabled`
Whether this tool is enabled. Overrides the `default_config` setting.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `BetaManagedAgentsMcpToolsetDefaultConfigParams? DefaultConfig`
Default configuration for all tools from an MCP server.
- `Boolean? Enabled`
Whether tools are enabled by default. Defaults to true if not specified.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `class BetaManagedAgentsCustomToolParams:`
A custom tool that is executed by the API client rather than the agent. When the agent calls this tool, an `agent.custom_tool_use` event is emitted and the session goes idle, waiting for the client to provide the result via a `user.custom_tool_result` event.
- `required string Description`
Description of what the tool does, shown to the agent to help it decide when to use the tool. 1-1024 characters.
- `required BetaManagedAgentsCustomToolInputSchema InputSchema`
JSON Schema for custom tool input parameters.
- `IReadOnlyDictionary? Properties`
JSON Schema properties defining the tool's input parameters.
- `IReadOnlyList Required`
List of required property names.
- `Type Type`
Must be 'object' for tool input schemas.
- `"object"Object`
- `required string Name`
Unique name for the tool. 1-128 characters; letters, digits, underscores, and hyphens.
- `required Type Type`
- `"custom"Custom`
- `IReadOnlyList betas`
Header param: Optional header to specify the beta version(s) you want to use.
- `"message-batches-2024-09-24"MessageBatches2024_09_24`
- `"prompt-caching-2024-07-31"PromptCaching2024_07_31`
- `"computer-use-2024-10-22"ComputerUse2024_10_22`
- `"computer-use-2025-01-24"ComputerUse2025_01_24`
- `"pdfs-2024-09-25"Pdfs2024_09_25`
- `"token-counting-2024-11-01"TokenCounting2024_11_01`
- `"token-efficient-tools-2025-02-19"TokenEfficientTools2025_02_19`
- `"output-128k-2025-02-19"Output128k2025_02_19`
- `"files-api-2025-04-14"FilesApi2025_04_14`
- `"mcp-client-2025-04-04"McpClient2025_04_04`
- `"mcp-client-2025-11-20"McpClient2025_11_20`
- `"dev-full-thinking-2025-05-14"DevFullThinking2025_05_14`
- `"interleaved-thinking-2025-05-14"InterleavedThinking2025_05_14`
- `"code-execution-2025-05-22"CodeExecution2025_05_22`
- `"extended-cache-ttl-2025-04-11"ExtendedCacheTtl2025_04_11`
- `"context-1m-2025-08-07"Context1m2025_08_07`
- `"context-management-2025-06-27"ContextManagement2025_06_27`
- `"model-context-window-exceeded-2025-08-26"ModelContextWindowExceeded2025_08_26`
- `"skills-2025-10-02"Skills2025_10_02`
- `"fast-mode-2026-02-01"FastMode2026_02_01`
- `"output-300k-2026-03-24"Output300k2026_03_24`
- `"user-profiles-2026-03-24"UserProfiles2026_03_24`
- `"advisor-tool-2026-03-01"AdvisorTool2026_03_01`
- `"managed-agents-2026-04-01"ManagedAgents2026_04_01`
### Returns
- `class BetaManagedAgentsAgent:`
A Managed Agents `agent`.
- `required string ID`
- `required DateTimeOffset? ArchivedAt`
A timestamp in RFC 3339 format
- `required DateTimeOffset CreatedAt`
A timestamp in RFC 3339 format
- `required string? Description`
- `required IReadOnlyList McpServers`
- `required string Name`
- `required Type Type`
- `"url"Url`
- `required string Url`
- `required IReadOnlyDictionary Metadata`
- `required BetaManagedAgentsModelConfig Model`
Model identifier and configuration.
- `required BetaManagedAgentsModel ID`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `Speed Speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"Standard`
- `"fast"Fast`
- `required BetaManagedAgentsMultiagent? Multiagent`
Resolved coordinator topology with a concrete agent roster.
- `required IReadOnlyList Agents`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `required string ID`
- `required Type Type`
- `"agent"Agent`
- `required Int Version`
- `required Type Type`
- `"coordinator"Coordinator`
- `required string Name`
- `required IReadOnlyList Skills`
- `class BetaManagedAgentsAnthropicSkill:`
A resolved Anthropic-managed skill.
- `required string SkillID`
- `required Type Type`
- `"anthropic"Anthropic`
- `required string Version`
- `class BetaManagedAgentsCustomSkill:`
A resolved user-created custom skill.
- `required string SkillID`
- `required Type Type`
- `"custom"Custom`
- `required string Version`
- `required string? System`
- `required IReadOnlyList Tools`
- `class BetaManagedAgentsAgentToolset20260401:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsAgentToolsetDefaultConfig DefaultConfig`
Resolved default configuration for agent tools.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required Type Type`
- `"agent_toolset_20260401"AgentToolset20260401`
- `class BetaManagedAgentsMcpToolset:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required string Name`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsMcpToolsetDefaultConfig DefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required string McpServerName`
- `required Type Type`
- `"mcp_toolset"McpToolset`
- `class BetaManagedAgentsCustomTool:`
A custom tool as returned in API responses.
- `required string Description`
- `required BetaManagedAgentsCustomToolInputSchema InputSchema`
JSON Schema for custom tool input parameters.
- `IReadOnlyDictionary? Properties`
JSON Schema properties defining the tool's input parameters.
- `IReadOnlyList Required`
List of required property names.
- `Type Type`
Must be 'object' for tool input schemas.
- `"object"Object`
- `required string Name`
- `required Type Type`
- `"custom"Custom`
- `required Type Type`
- `"agent"Agent`
- `required DateTimeOffset UpdatedAt`
A timestamp in RFC 3339 format
- `required Int Version`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```csharp
AgentUpdateParams parameters = new()
{
AgentID = "agent_011CZkYpogX7uDKUyvBTophP",
Version = 1,
};
var betaManagedAgentsAgent = await client.Beta.Agents.Update(parameters);
Console.WriteLine(betaManagedAgentsAgent);
```
## Archive
`BetaManagedAgentsAgent Beta.Agents.Archive(AgentArchiveParamsparameters, CancellationTokencancellationToken = default)`
**post** `/v1/agents/{agent_id}/archive`
Archive Agent
### Parameters
- `AgentArchiveParams parameters`
- `required string agentID`
Path parameter agent_id
- `IReadOnlyList betas`
Optional header to specify the beta version(s) you want to use.
- `"message-batches-2024-09-24"MessageBatches2024_09_24`
- `"prompt-caching-2024-07-31"PromptCaching2024_07_31`
- `"computer-use-2024-10-22"ComputerUse2024_10_22`
- `"computer-use-2025-01-24"ComputerUse2025_01_24`
- `"pdfs-2024-09-25"Pdfs2024_09_25`
- `"token-counting-2024-11-01"TokenCounting2024_11_01`
- `"token-efficient-tools-2025-02-19"TokenEfficientTools2025_02_19`
- `"output-128k-2025-02-19"Output128k2025_02_19`
- `"files-api-2025-04-14"FilesApi2025_04_14`
- `"mcp-client-2025-04-04"McpClient2025_04_04`
- `"mcp-client-2025-11-20"McpClient2025_11_20`
- `"dev-full-thinking-2025-05-14"DevFullThinking2025_05_14`
- `"interleaved-thinking-2025-05-14"InterleavedThinking2025_05_14`
- `"code-execution-2025-05-22"CodeExecution2025_05_22`
- `"extended-cache-ttl-2025-04-11"ExtendedCacheTtl2025_04_11`
- `"context-1m-2025-08-07"Context1m2025_08_07`
- `"context-management-2025-06-27"ContextManagement2025_06_27`
- `"model-context-window-exceeded-2025-08-26"ModelContextWindowExceeded2025_08_26`
- `"skills-2025-10-02"Skills2025_10_02`
- `"fast-mode-2026-02-01"FastMode2026_02_01`
- `"output-300k-2026-03-24"Output300k2026_03_24`
- `"user-profiles-2026-03-24"UserProfiles2026_03_24`
- `"advisor-tool-2026-03-01"AdvisorTool2026_03_01`
- `"managed-agents-2026-04-01"ManagedAgents2026_04_01`
### Returns
- `class BetaManagedAgentsAgent:`
A Managed Agents `agent`.
- `required string ID`
- `required DateTimeOffset? ArchivedAt`
A timestamp in RFC 3339 format
- `required DateTimeOffset CreatedAt`
A timestamp in RFC 3339 format
- `required string? Description`
- `required IReadOnlyList McpServers`
- `required string Name`
- `required Type Type`
- `"url"Url`
- `required string Url`
- `required IReadOnlyDictionary Metadata`
- `required BetaManagedAgentsModelConfig Model`
Model identifier and configuration.
- `required BetaManagedAgentsModel ID`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `Speed Speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"Standard`
- `"fast"Fast`
- `required BetaManagedAgentsMultiagent? Multiagent`
Resolved coordinator topology with a concrete agent roster.
- `required IReadOnlyList Agents`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `required string ID`
- `required Type Type`
- `"agent"Agent`
- `required Int Version`
- `required Type Type`
- `"coordinator"Coordinator`
- `required string Name`
- `required IReadOnlyList Skills`
- `class BetaManagedAgentsAnthropicSkill:`
A resolved Anthropic-managed skill.
- `required string SkillID`
- `required Type Type`
- `"anthropic"Anthropic`
- `required string Version`
- `class BetaManagedAgentsCustomSkill:`
A resolved user-created custom skill.
- `required string SkillID`
- `required Type Type`
- `"custom"Custom`
- `required string Version`
- `required string? System`
- `required IReadOnlyList Tools`
- `class BetaManagedAgentsAgentToolset20260401:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsAgentToolsetDefaultConfig DefaultConfig`
Resolved default configuration for agent tools.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required Type Type`
- `"agent_toolset_20260401"AgentToolset20260401`
- `class BetaManagedAgentsMcpToolset:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required string Name`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsMcpToolsetDefaultConfig DefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required string McpServerName`
- `required Type Type`
- `"mcp_toolset"McpToolset`
- `class BetaManagedAgentsCustomTool:`
A custom tool as returned in API responses.
- `required string Description`
- `required BetaManagedAgentsCustomToolInputSchema InputSchema`
JSON Schema for custom tool input parameters.
- `IReadOnlyDictionary? Properties`
JSON Schema properties defining the tool's input parameters.
- `IReadOnlyList Required`
List of required property names.
- `Type Type`
Must be 'object' for tool input schemas.
- `"object"Object`
- `required string Name`
- `required Type Type`
- `"custom"Custom`
- `required Type Type`
- `"agent"Agent`
- `required DateTimeOffset UpdatedAt`
A timestamp in RFC 3339 format
- `required Int Version`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```csharp
AgentArchiveParams parameters = new()
{
AgentID = "agent_011CZkYpogX7uDKUyvBTophP"
};
var betaManagedAgentsAgent = await client.Beta.Agents.Archive(parameters);
Console.WriteLine(betaManagedAgentsAgent);
```
## Domain Types
### Beta Managed Agents Agent
- `class BetaManagedAgentsAgent:`
A Managed Agents `agent`.
- `required string ID`
- `required DateTimeOffset? ArchivedAt`
A timestamp in RFC 3339 format
- `required DateTimeOffset CreatedAt`
A timestamp in RFC 3339 format
- `required string? Description`
- `required IReadOnlyList McpServers`
- `required string Name`
- `required Type Type`
- `"url"Url`
- `required string Url`
- `required IReadOnlyDictionary Metadata`
- `required BetaManagedAgentsModelConfig Model`
Model identifier and configuration.
- `required BetaManagedAgentsModel ID`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `Speed Speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"Standard`
- `"fast"Fast`
- `required BetaManagedAgentsMultiagent? Multiagent`
Resolved coordinator topology with a concrete agent roster.
- `required IReadOnlyList Agents`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `required string ID`
- `required Type Type`
- `"agent"Agent`
- `required Int Version`
- `required Type Type`
- `"coordinator"Coordinator`
- `required string Name`
- `required IReadOnlyList Skills`
- `class BetaManagedAgentsAnthropicSkill:`
A resolved Anthropic-managed skill.
- `required string SkillID`
- `required Type Type`
- `"anthropic"Anthropic`
- `required string Version`
- `class BetaManagedAgentsCustomSkill:`
A resolved user-created custom skill.
- `required string SkillID`
- `required Type Type`
- `"custom"Custom`
- `required string Version`
- `required string? System`
- `required IReadOnlyList Tools`
- `class BetaManagedAgentsAgentToolset20260401:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsAgentToolsetDefaultConfig DefaultConfig`
Resolved default configuration for agent tools.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required Type Type`
- `"agent_toolset_20260401"AgentToolset20260401`
- `class BetaManagedAgentsMcpToolset:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required string Name`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsMcpToolsetDefaultConfig DefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required string McpServerName`
- `required Type Type`
- `"mcp_toolset"McpToolset`
- `class BetaManagedAgentsCustomTool:`
A custom tool as returned in API responses.
- `required string Description`
- `required BetaManagedAgentsCustomToolInputSchema InputSchema`
JSON Schema for custom tool input parameters.
- `IReadOnlyDictionary? Properties`
JSON Schema properties defining the tool's input parameters.
- `IReadOnlyList Required`
List of required property names.
- `Type Type`
Must be 'object' for tool input schemas.
- `"object"Object`
- `required string Name`
- `required Type Type`
- `"custom"Custom`
- `required Type Type`
- `"agent"Agent`
- `required DateTimeOffset UpdatedAt`
A timestamp in RFC 3339 format
- `required Int Version`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Beta Managed Agents Agent Reference
- `class BetaManagedAgentsAgentReference:`
A resolved agent reference with a concrete version.
- `required string ID`
- `required Type Type`
- `"agent"Agent`
- `required Int Version`
### Beta Managed Agents Agent Tool Config
- `class BetaManagedAgentsAgentToolConfig:`
Configuration for a specific agent tool.
- `required Boolean Enabled`
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
### Beta Managed Agents Agent Tool Config Params
- `class BetaManagedAgentsAgentToolConfigParams:`
Configuration override for a specific tool within a toolset.
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `Boolean? Enabled`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
### Beta Managed Agents Agent Toolset Default Config
- `class BetaManagedAgentsAgentToolsetDefaultConfig:`
Resolved default configuration for agent tools.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
### Beta Managed Agents Agent Toolset Default Config Params
- `class BetaManagedAgentsAgentToolsetDefaultConfigParams:`
Default configuration for all tools in a toolset.
- `Boolean? Enabled`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
### Beta Managed Agents Agent Toolset20260401
- `class BetaManagedAgentsAgentToolset20260401:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsAgentToolsetDefaultConfig DefaultConfig`
Resolved default configuration for agent tools.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required Type Type`
- `"agent_toolset_20260401"AgentToolset20260401`
### Beta Managed Agents Agent Toolset20260401 Params
- `class BetaManagedAgentsAgentToolset20260401Params:`
Configuration for built-in agent tools. Use this to enable or disable groups of tools available to the agent.
- `required Type Type`
- `"agent_toolset_20260401"AgentToolset20260401`
- `IReadOnlyList Configs`
Per-tool configuration overrides.
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `Boolean? Enabled`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `BetaManagedAgentsAgentToolsetDefaultConfigParams? DefaultConfig`
Default configuration for all tools in a toolset.
- `Boolean? Enabled`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
### Beta Managed Agents Always Allow Policy
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
### Beta Managed Agents Always Ask Policy
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
### Beta Managed Agents Anthropic Skill
- `class BetaManagedAgentsAnthropicSkill:`
A resolved Anthropic-managed skill.
- `required string SkillID`
- `required Type Type`
- `"anthropic"Anthropic`
- `required string Version`
### Beta Managed Agents Anthropic Skill Params
- `class BetaManagedAgentsAnthropicSkillParams:`
An Anthropic-managed skill.
- `required string SkillID`
Identifier of the Anthropic skill (e.g., "xlsx").
- `required Type Type`
- `"anthropic"Anthropic`
- `string? Version`
Version to pin. Defaults to latest if omitted.
### Beta Managed Agents Custom Skill
- `class BetaManagedAgentsCustomSkill:`
A resolved user-created custom skill.
- `required string SkillID`
- `required Type Type`
- `"custom"Custom`
- `required string Version`
### Beta Managed Agents Custom Skill Params
- `class BetaManagedAgentsCustomSkillParams:`
A user-created custom skill.
- `required string SkillID`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `required Type Type`
- `"custom"Custom`
- `string? Version`
Version to pin. Defaults to latest if omitted.
### Beta Managed Agents Custom Tool
- `class BetaManagedAgentsCustomTool:`
A custom tool as returned in API responses.
- `required string Description`
- `required BetaManagedAgentsCustomToolInputSchema InputSchema`
JSON Schema for custom tool input parameters.
- `IReadOnlyDictionary? Properties`
JSON Schema properties defining the tool's input parameters.
- `IReadOnlyList Required`
List of required property names.
- `Type Type`
Must be 'object' for tool input schemas.
- `"object"Object`
- `required string Name`
- `required Type Type`
- `"custom"Custom`
### Beta Managed Agents Custom Tool Input Schema
- `class BetaManagedAgentsCustomToolInputSchema:`
JSON Schema for custom tool input parameters.
- `IReadOnlyDictionary? Properties`
JSON Schema properties defining the tool's input parameters.
- `IReadOnlyList Required`
List of required property names.
- `Type Type`
Must be 'object' for tool input schemas.
- `"object"Object`
### Beta Managed Agents Custom Tool Params
- `class BetaManagedAgentsCustomToolParams:`
A custom tool that is executed by the API client rather than the agent. When the agent calls this tool, an `agent.custom_tool_use` event is emitted and the session goes idle, waiting for the client to provide the result via a `user.custom_tool_result` event.
- `required string Description`
Description of what the tool does, shown to the agent to help it decide when to use the tool. 1-1024 characters.
- `required BetaManagedAgentsCustomToolInputSchema InputSchema`
JSON Schema for custom tool input parameters.
- `IReadOnlyDictionary? Properties`
JSON Schema properties defining the tool's input parameters.
- `IReadOnlyList Required`
List of required property names.
- `Type Type`
Must be 'object' for tool input schemas.
- `"object"Object`
- `required string Name`
Unique name for the tool. 1-128 characters; letters, digits, underscores, and hyphens.
- `required Type Type`
- `"custom"Custom`
### Beta Managed Agents MCP Server URL Definition
- `class BetaManagedAgentsMcpServerUrlDefinition:`
URL-based MCP server connection as returned in API responses.
- `required string Name`
- `required Type Type`
- `"url"Url`
- `required string Url`
### Beta Managed Agents MCP Tool Config
- `class BetaManagedAgentsMcpToolConfig:`
Resolved configuration for a specific MCP tool.
- `required Boolean Enabled`
- `required string Name`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
### Beta Managed Agents MCP Tool Config Params
- `class BetaManagedAgentsMcpToolConfigParams:`
Configuration override for a specific MCP tool.
- `required string Name`
Name of the MCP tool to configure. 1-128 characters.
- `Boolean? Enabled`
Whether this tool is enabled. Overrides the `default_config` setting.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
### Beta Managed Agents MCP Toolset
- `class BetaManagedAgentsMcpToolset:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required string Name`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsMcpToolsetDefaultConfig DefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required string McpServerName`
- `required Type Type`
- `"mcp_toolset"McpToolset`
### Beta Managed Agents MCP Toolset Default Config
- `class BetaManagedAgentsMcpToolsetDefaultConfig:`
Resolved default configuration for all tools from an MCP server.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
### Beta Managed Agents MCP Toolset Default Config Params
- `class BetaManagedAgentsMcpToolsetDefaultConfigParams:`
Default configuration for all tools from an MCP server.
- `Boolean? Enabled`
Whether tools are enabled by default. Defaults to true if not specified.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
### Beta Managed Agents MCP Toolset Params
- `class BetaManagedAgentsMcpToolsetParams:`
Configuration for tools from an MCP server defined in `mcp_servers`.
- `required string McpServerName`
Name of the MCP server. Must match a server name from the mcp_servers array. 1-255 characters.
- `required Type Type`
- `"mcp_toolset"McpToolset`
- `IReadOnlyList Configs`
Per-tool configuration overrides.
- `required string Name`
Name of the MCP tool to configure. 1-128 characters.
- `Boolean? Enabled`
Whether this tool is enabled. Overrides the `default_config` setting.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `BetaManagedAgentsMcpToolsetDefaultConfigParams? DefaultConfig`
Default configuration for all tools from an MCP server.
- `Boolean? Enabled`
Whether tools are enabled by default. Defaults to true if not specified.
- `PermissionPolicy? PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
### Beta Managed Agents Model Config
- `class BetaManagedAgentsModelConfig:`
Model identifier and configuration.
- `required BetaManagedAgentsModel ID`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `Speed Speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"Standard`
- `"fast"Fast`
### Beta Managed Agents Model Config Params
- `class BetaManagedAgentsModelConfigParams:`
An object that defines additional configuration control over model use
- `required BetaManagedAgentsModel ID`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `Speed? Speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"Standard`
- `"fast"Fast`
### Beta Managed Agents Multiagent Coordinator
- `class BetaManagedAgentsMultiagentCoordinator:`
Resolved coordinator topology with a concrete agent roster.
- `required IReadOnlyList Agents`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `required string ID`
- `required Type Type`
- `"agent"Agent`
- `required Int Version`
- `required Type Type`
- `"coordinator"Coordinator`
### Beta Managed Agents Multiagent Coordinator Params
- `class BetaManagedAgentsMultiagentCoordinatorParams:`
A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `required IReadOnlyList Agents`
Agents the coordinator may spawn as session threads. 1–20 entries. Each entry is an agent ID string, a versioned `{"type":"agent","id","version"}` reference, or `{"type":"self"}` to allow recursive self-invocation. Entries must reference distinct agents (after resolving `self` and string forms); at most one `self`. Referenced agents must exist, must not be archived, and must not themselves have `multiagent` set (depth limit 1).
- `string`
- `class BetaManagedAgentsAgentParams:`
Specification for an Agent. Provide a specific `version` or use the short-form `agent="agent_id"` for the most recent version
- `required string ID`
The `agent` ID.
- `required Type Type`
- `"agent"Agent`
- `Int Version`
The specific `agent` version to use. Omit to use the latest version. Must be at least 1 if specified.
- `class BetaManagedAgentsMultiagentSelfParams:`
Sentinel roster entry meaning "the agent that owns this configuration". Resolved server-side to a concrete agent reference.
- `required Type Type`
- `"self"Self`
- `required Type Type`
- `"coordinator"Coordinator`
### Beta Managed Agents Multiagent Self Params
- `class BetaManagedAgentsMultiagentSelfParams:`
Sentinel roster entry meaning "the agent that owns this configuration". Resolved server-side to a concrete agent reference.
- `required Type Type`
- `"self"Self`
### Beta Managed Agents Skill Params
- `class BetaManagedAgentsSkillParams: A class that can be one of several variants.union`
Skill to load in the session container.
- `class BetaManagedAgentsAnthropicSkillParams:`
An Anthropic-managed skill.
- `required string SkillID`
Identifier of the Anthropic skill (e.g., "xlsx").
- `required Type Type`
- `"anthropic"Anthropic`
- `string? Version`
Version to pin. Defaults to latest if omitted.
- `class BetaManagedAgentsCustomSkillParams:`
A user-created custom skill.
- `required string SkillID`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `required Type Type`
- `"custom"Custom`
- `string? Version`
Version to pin. Defaults to latest if omitted.
### Beta Managed Agents URL MCP Server Params
- `class BetaManagedAgentsUrlMcpServerParams:`
URL-based MCP server connection.
- `required string Name`
Unique name for this server, referenced by mcp_toolset configurations. 1-255 characters.
- `required Type Type`
- `"url"Url`
- `required string Url`
Endpoint URL for the MCP server.
# Versions
## List
`VersionListPageResponse Beta.Agents.Versions.List(VersionListParamsparameters, CancellationTokencancellationToken = default)`
**get** `/v1/agents/{agent_id}/versions`
List Agent Versions
### Parameters
- `VersionListParams parameters`
- `required string agentID`
Path param: Path parameter agent_id
- `Int limit`
Query param: Maximum results per page. Default 20, maximum 100.
- `string page`
Query param: Opaque pagination cursor.
- `IReadOnlyList betas`
Header param: Optional header to specify the beta version(s) you want to use.
- `"message-batches-2024-09-24"MessageBatches2024_09_24`
- `"prompt-caching-2024-07-31"PromptCaching2024_07_31`
- `"computer-use-2024-10-22"ComputerUse2024_10_22`
- `"computer-use-2025-01-24"ComputerUse2025_01_24`
- `"pdfs-2024-09-25"Pdfs2024_09_25`
- `"token-counting-2024-11-01"TokenCounting2024_11_01`
- `"token-efficient-tools-2025-02-19"TokenEfficientTools2025_02_19`
- `"output-128k-2025-02-19"Output128k2025_02_19`
- `"files-api-2025-04-14"FilesApi2025_04_14`
- `"mcp-client-2025-04-04"McpClient2025_04_04`
- `"mcp-client-2025-11-20"McpClient2025_11_20`
- `"dev-full-thinking-2025-05-14"DevFullThinking2025_05_14`
- `"interleaved-thinking-2025-05-14"InterleavedThinking2025_05_14`
- `"code-execution-2025-05-22"CodeExecution2025_05_22`
- `"extended-cache-ttl-2025-04-11"ExtendedCacheTtl2025_04_11`
- `"context-1m-2025-08-07"Context1m2025_08_07`
- `"context-management-2025-06-27"ContextManagement2025_06_27`
- `"model-context-window-exceeded-2025-08-26"ModelContextWindowExceeded2025_08_26`
- `"skills-2025-10-02"Skills2025_10_02`
- `"fast-mode-2026-02-01"FastMode2026_02_01`
- `"output-300k-2026-03-24"Output300k2026_03_24`
- `"user-profiles-2026-03-24"UserProfiles2026_03_24`
- `"advisor-tool-2026-03-01"AdvisorTool2026_03_01`
- `"managed-agents-2026-04-01"ManagedAgents2026_04_01`
### Returns
- `class VersionListPageResponse:`
Paginated list of agent versions.
- `IReadOnlyList Data`
Agent versions.
- `required string ID`
- `required DateTimeOffset? ArchivedAt`
A timestamp in RFC 3339 format
- `required DateTimeOffset CreatedAt`
A timestamp in RFC 3339 format
- `required string? Description`
- `required IReadOnlyList McpServers`
- `required string Name`
- `required Type Type`
- `"url"Url`
- `required string Url`
- `required IReadOnlyDictionary Metadata`
- `required BetaManagedAgentsModelConfig Model`
Model identifier and configuration.
- `required BetaManagedAgentsModel ID`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `"claude-opus-4-7"ClaudeOpus4_7`
Frontier intelligence for long-running agents and coding
- `"claude-opus-4-6"ClaudeOpus4_6`
Most intelligent model for building agents and coding
- `"claude-sonnet-4-6"ClaudeSonnet4_6`
Best combination of speed and intelligence
- `"claude-haiku-4-5"ClaudeHaiku4_5`
Fastest model with near-frontier intelligence
- `"claude-haiku-4-5-20251001"ClaudeHaiku4_5_20251001`
Fastest model with near-frontier intelligence
- `"claude-opus-4-5"ClaudeOpus4_5`
Premium model combining maximum intelligence with practical performance
- `"claude-opus-4-5-20251101"ClaudeOpus4_5_20251101`
Premium model combining maximum intelligence with practical performance
- `"claude-sonnet-4-5"ClaudeSonnet4_5`
High-performance model for agents and coding
- `"claude-sonnet-4-5-20250929"ClaudeSonnet4_5_20250929`
High-performance model for agents and coding
- `Speed Speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `"standard"Standard`
- `"fast"Fast`
- `required BetaManagedAgentsMultiagent? Multiagent`
Resolved coordinator topology with a concrete agent roster.
- `required IReadOnlyList Agents`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `required string ID`
- `required Type Type`
- `"agent"Agent`
- `required Int Version`
- `required Type Type`
- `"coordinator"Coordinator`
- `required string Name`
- `required IReadOnlyList Skills`
- `class BetaManagedAgentsAnthropicSkill:`
A resolved Anthropic-managed skill.
- `required string SkillID`
- `required Type Type`
- `"anthropic"Anthropic`
- `required string Version`
- `class BetaManagedAgentsCustomSkill:`
A resolved user-created custom skill.
- `required string SkillID`
- `required Type Type`
- `"custom"Custom`
- `required string Version`
- `required string? System`
- `required IReadOnlyList Tools`
- `class BetaManagedAgentsAgentToolset20260401:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required Name Name`
Built-in agent tool identifier.
- `"bash"Bash`
- `"edit"Edit`
- `"read"Read`
- `"write"Write`
- `"glob"Glob`
- `"grep"Grep`
- `"web_fetch"WebFetch`
- `"web_search"WebSearch`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsAgentToolsetDefaultConfig DefaultConfig`
Resolved default configuration for agent tools.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required Type Type`
- `"agent_toolset_20260401"AgentToolset20260401`
- `class BetaManagedAgentsMcpToolset:`
- `required IReadOnlyList Configs`
- `required Boolean Enabled`
- `required string Name`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required BetaManagedAgentsMcpToolsetDefaultConfig DefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `required Boolean Enabled`
- `required PermissionPolicy PermissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `required Type Type`
- `"always_allow"AlwaysAllow`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `required Type Type`
- `"always_ask"AlwaysAsk`
- `required string McpServerName`
- `required Type Type`
- `"mcp_toolset"McpToolset`
- `class BetaManagedAgentsCustomTool:`
A custom tool as returned in API responses.
- `required string Description`
- `required BetaManagedAgentsCustomToolInputSchema InputSchema`
JSON Schema for custom tool input parameters.
- `IReadOnlyDictionary? Properties`
JSON Schema properties defining the tool's input parameters.
- `IReadOnlyList Required`
List of required property names.
- `Type Type`
Must be 'object' for tool input schemas.
- `"object"Object`
- `required string Name`
- `required Type Type`
- `"custom"Custom`
- `required Type Type`
- `"agent"Agent`
- `required DateTimeOffset UpdatedAt`
A timestamp in RFC 3339 format
- `required Int Version`
The agent's current version. Starts at 1 and increments when the agent is modified.
- `string? NextPage`
Opaque cursor for the next page. Null when no more results.
### Example
```csharp
VersionListParams parameters = new()
{
AgentID = "agent_011CZkYpogX7uDKUyvBTophP"
};
var page = await client.Beta.Agents.Versions.List(parameters);
await foreach (var item in page.Paginate())
{
Console.WriteLine(item);
}
```
---
# Agents (Beta) (Go)
URL: https://platform.claude.com/docs/en/api/go/beta/agents
# Agents
## Create
`client.Beta.Agents.New(ctx, params) (*BetaManagedAgentsAgent, error)`
**post** `/v1/agents`
Create Agent
### Parameters
- `params BetaAgentNewParams`
- `Model param.Field[BetaManagedAgentsModelConfigParamsResp]`
Body param: Model identifier. Accepts the [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison), e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration control
- `type BetaManagedAgentsModelConfigParamsResp struct{…}`
An object that defines additional configuration control over model use
- `ID BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `type BetaManagedAgentsModel string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `const BetaManagedAgentsModelClaudeOpus4_7 BetaManagedAgentsModel = "claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `const BetaManagedAgentsModelClaudeOpus4_6 BetaManagedAgentsModel = "claude-opus-4-6"`
Most intelligent model for building agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_6 BetaManagedAgentsModel = "claude-sonnet-4-6"`
Best combination of speed and intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5 BetaManagedAgentsModel = "claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5_20251001 BetaManagedAgentsModel = "claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeOpus4_5 BetaManagedAgentsModel = "claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeOpus4_5_20251101 BetaManagedAgentsModel = "claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeSonnet4_5 BetaManagedAgentsModel = "claude-sonnet-4-5"`
High-performance model for agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_5_20250929 BetaManagedAgentsModel = "claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `string`
- `Speed BetaManagedAgentsModelConfigParamsSpeed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `const BetaManagedAgentsModelConfigParamsSpeedStandard BetaManagedAgentsModelConfigParamsSpeed = "standard"`
- `const BetaManagedAgentsModelConfigParamsSpeedFast BetaManagedAgentsModelConfigParamsSpeed = "fast"`
- `Name param.Field[string]`
Body param: Human-readable name for the agent. 1-256 characters.
- `Description param.Field[string]`
Body param: Description of what the agent does. Up to 2048 characters.
- `MCPServers param.Field[[]BetaManagedAgentsURLMCPServerParamsResp]`
Body param: MCP servers this agent connects to. Maximum 20. Names must be unique within the array.
- `Name string`
Unique name for this server, referenced by mcp_toolset configurations. 1-255 characters.
- `Type BetaManagedAgentsURLMCPServerParamsType`
- `const BetaManagedAgentsURLMCPServerParamsTypeURL BetaManagedAgentsURLMCPServerParamsType = "url"`
- `URL string`
Endpoint URL for the MCP server.
- `Metadata param.Field[map[string, string]]`
Body param: Arbitrary key-value metadata. Maximum 16 pairs, keys up to 64 chars, values up to 512 chars.
- `Multiagent param.Field[BetaManagedAgentsMultiagentParamsResp]`
Body param: A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `Skills param.Field[[]BetaManagedAgentsSkillParamsUnionResp]`
Body param: Skills available to the agent. Maximum 20.
- `type BetaManagedAgentsAnthropicSkillParamsResp struct{…}`
An Anthropic-managed skill.
- `SkillID string`
Identifier of the Anthropic skill (e.g., "xlsx").
- `Type BetaManagedAgentsAnthropicSkillParamsType`
- `const BetaManagedAgentsAnthropicSkillParamsTypeAnthropic BetaManagedAgentsAnthropicSkillParamsType = "anthropic"`
- `Version string`
Version to pin. Defaults to latest if omitted.
- `type BetaManagedAgentsCustomSkillParamsResp struct{…}`
A user-created custom skill.
- `SkillID string`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `Type BetaManagedAgentsCustomSkillParamsType`
- `const BetaManagedAgentsCustomSkillParamsTypeCustom BetaManagedAgentsCustomSkillParamsType = "custom"`
- `Version string`
Version to pin. Defaults to latest if omitted.
- `System param.Field[string]`
Body param: System prompt for the agent. Up to 100,000 characters.
- `Tools param.Field[[]BetaAgentNewParamsToolUnion]`
Body param: Tool configurations available to the agent. Maximum of 128 tools across all toolsets allowed.
- `type BetaManagedAgentsAgentToolset20260401ParamsResp struct{…}`
Configuration for built-in agent tools. Use this to enable or disable groups of tools available to the agent.
- `Type BetaManagedAgentsAgentToolset20260401ParamsType`
- `const BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401 BetaManagedAgentsAgentToolset20260401ParamsType = "agent_toolset_20260401"`
- `Configs []BetaManagedAgentsAgentToolConfigParamsResp`
Per-tool configuration overrides.
- `Name BetaManagedAgentsAgentToolConfigParamsName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigParamsNameBash BetaManagedAgentsAgentToolConfigParamsName = "bash"`
- `const BetaManagedAgentsAgentToolConfigParamsNameEdit BetaManagedAgentsAgentToolConfigParamsName = "edit"`
- `const BetaManagedAgentsAgentToolConfigParamsNameRead BetaManagedAgentsAgentToolConfigParamsName = "read"`
- `const BetaManagedAgentsAgentToolConfigParamsNameWrite BetaManagedAgentsAgentToolConfigParamsName = "write"`
- `const BetaManagedAgentsAgentToolConfigParamsNameGlob BetaManagedAgentsAgentToolConfigParamsName = "glob"`
- `const BetaManagedAgentsAgentToolConfigParamsNameGrep BetaManagedAgentsAgentToolConfigParamsName = "grep"`
- `const BetaManagedAgentsAgentToolConfigParamsNameWebFetch BetaManagedAgentsAgentToolConfigParamsName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigParamsNameWebSearch BetaManagedAgentsAgentToolConfigParamsName = "web_search"`
- `Enabled bool`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `PermissionPolicy BetaManagedAgentsAgentToolConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsAgentToolsetDefaultConfigParamsResp`
Default configuration for all tools in a toolset.
- `Enabled bool`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `type BetaManagedAgentsMCPToolsetParamsResp struct{…}`
Configuration for tools from an MCP server defined in `mcp_servers`.
- `MCPServerName string`
Name of the MCP server. Must match a server name from the mcp_servers array. 1-255 characters.
- `Type BetaManagedAgentsMCPToolsetParamsType`
- `const BetaManagedAgentsMCPToolsetParamsTypeMCPToolset BetaManagedAgentsMCPToolsetParamsType = "mcp_toolset"`
- `Configs []BetaManagedAgentsMCPToolConfigParamsResp`
Per-tool configuration overrides.
- `Name string`
Name of the MCP tool to configure. 1-128 characters.
- `Enabled bool`
Whether this tool is enabled. Overrides the `default_config` setting.
- `PermissionPolicy BetaManagedAgentsMCPToolConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsMCPToolsetDefaultConfigParamsResp`
Default configuration for all tools from an MCP server.
- `Enabled bool`
Whether tools are enabled by default. Defaults to true if not specified.
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `type BetaManagedAgentsCustomToolParamsResp struct{…}`
A custom tool that is executed by the API client rather than the agent. When the agent calls this tool, an `agent.custom_tool_use` event is emitted and the session goes idle, waiting for the client to provide the result via a `user.custom_tool_result` event.
- `Description string`
Description of what the tool does, shown to the agent to help it decide when to use the tool. 1-1024 characters.
- `InputSchema BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `Properties map[string, any]`
JSON Schema properties defining the tool's input parameters.
- `Required []string`
List of required property names.
- `Type BetaManagedAgentsCustomToolInputSchemaType`
Must be 'object' for tool input schemas.
- `const BetaManagedAgentsCustomToolInputSchemaTypeObject BetaManagedAgentsCustomToolInputSchemaType = "object"`
- `Name string`
Unique name for the tool. 1-128 characters; letters, digits, underscores, and hyphens.
- `Type BetaManagedAgentsCustomToolParamsType`
- `const BetaManagedAgentsCustomToolParamsTypeCustom BetaManagedAgentsCustomToolParamsType = "custom"`
- `Betas param.Field[[]AnthropicBeta]`
Header param: Optional header to specify the beta version(s) you want to use.
- `string`
- `type AnthropicBeta string`
- `const AnthropicBetaMessageBatches2024_09_24 AnthropicBeta = "message-batches-2024-09-24"`
- `const AnthropicBetaPromptCaching2024_07_31 AnthropicBeta = "prompt-caching-2024-07-31"`
- `const AnthropicBetaComputerUse2024_10_22 AnthropicBeta = "computer-use-2024-10-22"`
- `const AnthropicBetaComputerUse2025_01_24 AnthropicBeta = "computer-use-2025-01-24"`
- `const AnthropicBetaPDFs2024_09_25 AnthropicBeta = "pdfs-2024-09-25"`
- `const AnthropicBetaTokenCounting2024_11_01 AnthropicBeta = "token-counting-2024-11-01"`
- `const AnthropicBetaTokenEfficientTools2025_02_19 AnthropicBeta = "token-efficient-tools-2025-02-19"`
- `const AnthropicBetaOutput128k2025_02_19 AnthropicBeta = "output-128k-2025-02-19"`
- `const AnthropicBetaFilesAPI2025_04_14 AnthropicBeta = "files-api-2025-04-14"`
- `const AnthropicBetaMCPClient2025_04_04 AnthropicBeta = "mcp-client-2025-04-04"`
- `const AnthropicBetaMCPClient2025_11_20 AnthropicBeta = "mcp-client-2025-11-20"`
- `const AnthropicBetaDevFullThinking2025_05_14 AnthropicBeta = "dev-full-thinking-2025-05-14"`
- `const AnthropicBetaInterleavedThinking2025_05_14 AnthropicBeta = "interleaved-thinking-2025-05-14"`
- `const AnthropicBetaCodeExecution2025_05_22 AnthropicBeta = "code-execution-2025-05-22"`
- `const AnthropicBetaExtendedCacheTTL2025_04_11 AnthropicBeta = "extended-cache-ttl-2025-04-11"`
- `const AnthropicBetaContext1m2025_08_07 AnthropicBeta = "context-1m-2025-08-07"`
- `const AnthropicBetaContextManagement2025_06_27 AnthropicBeta = "context-management-2025-06-27"`
- `const AnthropicBetaModelContextWindowExceeded2025_08_26 AnthropicBeta = "model-context-window-exceeded-2025-08-26"`
- `const AnthropicBetaSkills2025_10_02 AnthropicBeta = "skills-2025-10-02"`
- `const AnthropicBetaFastMode2026_02_01 AnthropicBeta = "fast-mode-2026-02-01"`
- `const AnthropicBetaOutput300k2026_03_24 AnthropicBeta = "output-300k-2026-03-24"`
- `const AnthropicBetaUserProfiles2026_03_24 AnthropicBeta = "user-profiles-2026-03-24"`
- `const AnthropicBetaAdvisorTool2026_03_01 AnthropicBeta = "advisor-tool-2026-03-01"`
- `const AnthropicBetaManagedAgents2026_04_01 AnthropicBeta = "managed-agents-2026-04-01"`
### Returns
- `type BetaManagedAgentsAgent struct{…}`
A Managed Agents `agent`.
- `ID string`
- `ArchivedAt Time`
A timestamp in RFC 3339 format
- `CreatedAt Time`
A timestamp in RFC 3339 format
- `Description string`
- `MCPServers []BetaManagedAgentsMCPServerURLDefinition`
- `Name string`
- `Type BetaManagedAgentsMCPServerURLDefinitionType`
- `const BetaManagedAgentsMCPServerURLDefinitionTypeURL BetaManagedAgentsMCPServerURLDefinitionType = "url"`
- `URL string`
- `Metadata map[string, string]`
- `Model BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `ID BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `type BetaManagedAgentsModel string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `const BetaManagedAgentsModelClaudeOpus4_7 BetaManagedAgentsModel = "claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `const BetaManagedAgentsModelClaudeOpus4_6 BetaManagedAgentsModel = "claude-opus-4-6"`
Most intelligent model for building agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_6 BetaManagedAgentsModel = "claude-sonnet-4-6"`
Best combination of speed and intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5 BetaManagedAgentsModel = "claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5_20251001 BetaManagedAgentsModel = "claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeOpus4_5 BetaManagedAgentsModel = "claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeOpus4_5_20251101 BetaManagedAgentsModel = "claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeSonnet4_5 BetaManagedAgentsModel = "claude-sonnet-4-5"`
High-performance model for agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_5_20250929 BetaManagedAgentsModel = "claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `string`
- `Speed BetaManagedAgentsModelConfigSpeed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `const BetaManagedAgentsModelConfigSpeedStandard BetaManagedAgentsModelConfigSpeed = "standard"`
- `const BetaManagedAgentsModelConfigSpeedFast BetaManagedAgentsModelConfigSpeed = "fast"`
- `Multiagent BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `Agents []BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `ID string`
- `Type BetaManagedAgentsAgentReferenceType`
- `const BetaManagedAgentsAgentReferenceTypeAgent BetaManagedAgentsAgentReferenceType = "agent"`
- `Version int64`
- `Type BetaManagedAgentsMultiagentType`
- `const BetaManagedAgentsMultiagentTypeCoordinator BetaManagedAgentsMultiagentType = "coordinator"`
- `Name string`
- `Skills []BetaManagedAgentsAgentSkillUnion`
- `type BetaManagedAgentsAnthropicSkill struct{…}`
A resolved Anthropic-managed skill.
- `SkillID string`
- `Type BetaManagedAgentsAnthropicSkillType`
- `const BetaManagedAgentsAnthropicSkillTypeAnthropic BetaManagedAgentsAnthropicSkillType = "anthropic"`
- `Version string`
- `type BetaManagedAgentsCustomSkill struct{…}`
A resolved user-created custom skill.
- `SkillID string`
- `Type BetaManagedAgentsCustomSkillType`
- `const BetaManagedAgentsCustomSkillTypeCustom BetaManagedAgentsCustomSkillType = "custom"`
- `Version string`
- `System string`
- `Tools []BetaManagedAgentsAgentToolUnion`
- `type BetaManagedAgentsAgentToolset20260401 struct{…}`
- `Configs []BetaManagedAgentsAgentToolConfig`
- `Enabled bool`
- `Name BetaManagedAgentsAgentToolConfigName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigNameBash BetaManagedAgentsAgentToolConfigName = "bash"`
- `const BetaManagedAgentsAgentToolConfigNameEdit BetaManagedAgentsAgentToolConfigName = "edit"`
- `const BetaManagedAgentsAgentToolConfigNameRead BetaManagedAgentsAgentToolConfigName = "read"`
- `const BetaManagedAgentsAgentToolConfigNameWrite BetaManagedAgentsAgentToolConfigName = "write"`
- `const BetaManagedAgentsAgentToolConfigNameGlob BetaManagedAgentsAgentToolConfigName = "glob"`
- `const BetaManagedAgentsAgentToolConfigNameGrep BetaManagedAgentsAgentToolConfigName = "grep"`
- `const BetaManagedAgentsAgentToolConfigNameWebFetch BetaManagedAgentsAgentToolConfigName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigNameWebSearch BetaManagedAgentsAgentToolConfigName = "web_search"`
- `PermissionPolicy BetaManagedAgentsAgentToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `Type BetaManagedAgentsAgentToolset20260401Type`
- `const BetaManagedAgentsAgentToolset20260401TypeAgentToolset20260401 BetaManagedAgentsAgentToolset20260401Type = "agent_toolset_20260401"`
- `type BetaManagedAgentsMCPToolset struct{…}`
- `Configs []BetaManagedAgentsMCPToolConfig`
- `Enabled bool`
- `Name string`
- `PermissionPolicy BetaManagedAgentsMCPToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `MCPServerName string`
- `Type BetaManagedAgentsMCPToolsetType`
- `const BetaManagedAgentsMCPToolsetTypeMCPToolset BetaManagedAgentsMCPToolsetType = "mcp_toolset"`
- `type BetaManagedAgentsCustomTool struct{…}`
A custom tool as returned in API responses.
- `Description string`
- `InputSchema BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `Properties map[string, any]`
JSON Schema properties defining the tool's input parameters.
- `Required []string`
List of required property names.
- `Type BetaManagedAgentsCustomToolInputSchemaType`
Must be 'object' for tool input schemas.
- `const BetaManagedAgentsCustomToolInputSchemaTypeObject BetaManagedAgentsCustomToolInputSchemaType = "object"`
- `Name string`
- `Type BetaManagedAgentsCustomToolType`
- `const BetaManagedAgentsCustomToolTypeCustom BetaManagedAgentsCustomToolType = "custom"`
- `Type BetaManagedAgentsAgentType`
- `const BetaManagedAgentsAgentTypeAgent BetaManagedAgentsAgentType = "agent"`
- `UpdatedAt Time`
A timestamp in RFC 3339 format
- `Version int64`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```go
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func main() {
client := anthropic.NewClient(
option.WithAPIKey("my-anthropic-api-key"),
)
betaManagedAgentsAgent, err := client.Beta.Agents.New(context.TODO(), anthropic.BetaAgentNewParams{
Model: anthropic.BetaManagedAgentsModelConfigParams{
ID: anthropic.BetaManagedAgentsModelClaudeOpus4_6,
},
Name: "My First Agent",
})
if err != nil {
panic(err.Error())
}
fmt.Printf("%+v\n", betaManagedAgentsAgent.ID)
}
```
## List
`client.Beta.Agents.List(ctx, params) (*PageCursor[BetaManagedAgentsAgent], error)`
**get** `/v1/agents`
List Agents
### Parameters
- `params BetaAgentListParams`
- `CreatedAtGte param.Field[Time]`
Query param: Return agents created at or after this time (inclusive).
- `CreatedAtLte param.Field[Time]`
Query param: Return agents created at or before this time (inclusive).
- `IncludeArchived param.Field[bool]`
Query param: Include archived agents in results. Defaults to false.
- `Limit param.Field[int64]`
Query param: Maximum results per page. Default 20, maximum 100.
- `Page param.Field[string]`
Query param: Opaque pagination cursor from a previous response.
- `Betas param.Field[[]AnthropicBeta]`
Header param: Optional header to specify the beta version(s) you want to use.
- `string`
- `type AnthropicBeta string`
- `const AnthropicBetaMessageBatches2024_09_24 AnthropicBeta = "message-batches-2024-09-24"`
- `const AnthropicBetaPromptCaching2024_07_31 AnthropicBeta = "prompt-caching-2024-07-31"`
- `const AnthropicBetaComputerUse2024_10_22 AnthropicBeta = "computer-use-2024-10-22"`
- `const AnthropicBetaComputerUse2025_01_24 AnthropicBeta = "computer-use-2025-01-24"`
- `const AnthropicBetaPDFs2024_09_25 AnthropicBeta = "pdfs-2024-09-25"`
- `const AnthropicBetaTokenCounting2024_11_01 AnthropicBeta = "token-counting-2024-11-01"`
- `const AnthropicBetaTokenEfficientTools2025_02_19 AnthropicBeta = "token-efficient-tools-2025-02-19"`
- `const AnthropicBetaOutput128k2025_02_19 AnthropicBeta = "output-128k-2025-02-19"`
- `const AnthropicBetaFilesAPI2025_04_14 AnthropicBeta = "files-api-2025-04-14"`
- `const AnthropicBetaMCPClient2025_04_04 AnthropicBeta = "mcp-client-2025-04-04"`
- `const AnthropicBetaMCPClient2025_11_20 AnthropicBeta = "mcp-client-2025-11-20"`
- `const AnthropicBetaDevFullThinking2025_05_14 AnthropicBeta = "dev-full-thinking-2025-05-14"`
- `const AnthropicBetaInterleavedThinking2025_05_14 AnthropicBeta = "interleaved-thinking-2025-05-14"`
- `const AnthropicBetaCodeExecution2025_05_22 AnthropicBeta = "code-execution-2025-05-22"`
- `const AnthropicBetaExtendedCacheTTL2025_04_11 AnthropicBeta = "extended-cache-ttl-2025-04-11"`
- `const AnthropicBetaContext1m2025_08_07 AnthropicBeta = "context-1m-2025-08-07"`
- `const AnthropicBetaContextManagement2025_06_27 AnthropicBeta = "context-management-2025-06-27"`
- `const AnthropicBetaModelContextWindowExceeded2025_08_26 AnthropicBeta = "model-context-window-exceeded-2025-08-26"`
- `const AnthropicBetaSkills2025_10_02 AnthropicBeta = "skills-2025-10-02"`
- `const AnthropicBetaFastMode2026_02_01 AnthropicBeta = "fast-mode-2026-02-01"`
- `const AnthropicBetaOutput300k2026_03_24 AnthropicBeta = "output-300k-2026-03-24"`
- `const AnthropicBetaUserProfiles2026_03_24 AnthropicBeta = "user-profiles-2026-03-24"`
- `const AnthropicBetaAdvisorTool2026_03_01 AnthropicBeta = "advisor-tool-2026-03-01"`
- `const AnthropicBetaManagedAgents2026_04_01 AnthropicBeta = "managed-agents-2026-04-01"`
### Returns
- `type BetaManagedAgentsAgent struct{…}`
A Managed Agents `agent`.
- `ID string`
- `ArchivedAt Time`
A timestamp in RFC 3339 format
- `CreatedAt Time`
A timestamp in RFC 3339 format
- `Description string`
- `MCPServers []BetaManagedAgentsMCPServerURLDefinition`
- `Name string`
- `Type BetaManagedAgentsMCPServerURLDefinitionType`
- `const BetaManagedAgentsMCPServerURLDefinitionTypeURL BetaManagedAgentsMCPServerURLDefinitionType = "url"`
- `URL string`
- `Metadata map[string, string]`
- `Model BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `ID BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `type BetaManagedAgentsModel string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `const BetaManagedAgentsModelClaudeOpus4_7 BetaManagedAgentsModel = "claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `const BetaManagedAgentsModelClaudeOpus4_6 BetaManagedAgentsModel = "claude-opus-4-6"`
Most intelligent model for building agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_6 BetaManagedAgentsModel = "claude-sonnet-4-6"`
Best combination of speed and intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5 BetaManagedAgentsModel = "claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5_20251001 BetaManagedAgentsModel = "claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeOpus4_5 BetaManagedAgentsModel = "claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeOpus4_5_20251101 BetaManagedAgentsModel = "claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeSonnet4_5 BetaManagedAgentsModel = "claude-sonnet-4-5"`
High-performance model for agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_5_20250929 BetaManagedAgentsModel = "claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `string`
- `Speed BetaManagedAgentsModelConfigSpeed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `const BetaManagedAgentsModelConfigSpeedStandard BetaManagedAgentsModelConfigSpeed = "standard"`
- `const BetaManagedAgentsModelConfigSpeedFast BetaManagedAgentsModelConfigSpeed = "fast"`
- `Multiagent BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `Agents []BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `ID string`
- `Type BetaManagedAgentsAgentReferenceType`
- `const BetaManagedAgentsAgentReferenceTypeAgent BetaManagedAgentsAgentReferenceType = "agent"`
- `Version int64`
- `Type BetaManagedAgentsMultiagentType`
- `const BetaManagedAgentsMultiagentTypeCoordinator BetaManagedAgentsMultiagentType = "coordinator"`
- `Name string`
- `Skills []BetaManagedAgentsAgentSkillUnion`
- `type BetaManagedAgentsAnthropicSkill struct{…}`
A resolved Anthropic-managed skill.
- `SkillID string`
- `Type BetaManagedAgentsAnthropicSkillType`
- `const BetaManagedAgentsAnthropicSkillTypeAnthropic BetaManagedAgentsAnthropicSkillType = "anthropic"`
- `Version string`
- `type BetaManagedAgentsCustomSkill struct{…}`
A resolved user-created custom skill.
- `SkillID string`
- `Type BetaManagedAgentsCustomSkillType`
- `const BetaManagedAgentsCustomSkillTypeCustom BetaManagedAgentsCustomSkillType = "custom"`
- `Version string`
- `System string`
- `Tools []BetaManagedAgentsAgentToolUnion`
- `type BetaManagedAgentsAgentToolset20260401 struct{…}`
- `Configs []BetaManagedAgentsAgentToolConfig`
- `Enabled bool`
- `Name BetaManagedAgentsAgentToolConfigName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigNameBash BetaManagedAgentsAgentToolConfigName = "bash"`
- `const BetaManagedAgentsAgentToolConfigNameEdit BetaManagedAgentsAgentToolConfigName = "edit"`
- `const BetaManagedAgentsAgentToolConfigNameRead BetaManagedAgentsAgentToolConfigName = "read"`
- `const BetaManagedAgentsAgentToolConfigNameWrite BetaManagedAgentsAgentToolConfigName = "write"`
- `const BetaManagedAgentsAgentToolConfigNameGlob BetaManagedAgentsAgentToolConfigName = "glob"`
- `const BetaManagedAgentsAgentToolConfigNameGrep BetaManagedAgentsAgentToolConfigName = "grep"`
- `const BetaManagedAgentsAgentToolConfigNameWebFetch BetaManagedAgentsAgentToolConfigName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigNameWebSearch BetaManagedAgentsAgentToolConfigName = "web_search"`
- `PermissionPolicy BetaManagedAgentsAgentToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `Type BetaManagedAgentsAgentToolset20260401Type`
- `const BetaManagedAgentsAgentToolset20260401TypeAgentToolset20260401 BetaManagedAgentsAgentToolset20260401Type = "agent_toolset_20260401"`
- `type BetaManagedAgentsMCPToolset struct{…}`
- `Configs []BetaManagedAgentsMCPToolConfig`
- `Enabled bool`
- `Name string`
- `PermissionPolicy BetaManagedAgentsMCPToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `MCPServerName string`
- `Type BetaManagedAgentsMCPToolsetType`
- `const BetaManagedAgentsMCPToolsetTypeMCPToolset BetaManagedAgentsMCPToolsetType = "mcp_toolset"`
- `type BetaManagedAgentsCustomTool struct{…}`
A custom tool as returned in API responses.
- `Description string`
- `InputSchema BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `Properties map[string, any]`
JSON Schema properties defining the tool's input parameters.
- `Required []string`
List of required property names.
- `Type BetaManagedAgentsCustomToolInputSchemaType`
Must be 'object' for tool input schemas.
- `const BetaManagedAgentsCustomToolInputSchemaTypeObject BetaManagedAgentsCustomToolInputSchemaType = "object"`
- `Name string`
- `Type BetaManagedAgentsCustomToolType`
- `const BetaManagedAgentsCustomToolTypeCustom BetaManagedAgentsCustomToolType = "custom"`
- `Type BetaManagedAgentsAgentType`
- `const BetaManagedAgentsAgentTypeAgent BetaManagedAgentsAgentType = "agent"`
- `UpdatedAt Time`
A timestamp in RFC 3339 format
- `Version int64`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```go
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func main() {
client := anthropic.NewClient(
option.WithAPIKey("my-anthropic-api-key"),
)
page, err := client.Beta.Agents.List(context.TODO(), anthropic.BetaAgentListParams{
})
if err != nil {
panic(err.Error())
}
fmt.Printf("%+v\n", page)
}
```
## Retrieve
`client.Beta.Agents.Get(ctx, agentID, params) (*BetaManagedAgentsAgent, error)`
**get** `/v1/agents/{agent_id}`
Get Agent
### Parameters
- `agentID string`
- `params BetaAgentGetParams`
- `Version param.Field[int64]`
Query param: Agent version. Omit for the most recent version. Must be at least 1 if specified.
- `Betas param.Field[[]AnthropicBeta]`
Header param: Optional header to specify the beta version(s) you want to use.
- `string`
- `type AnthropicBeta string`
- `const AnthropicBetaMessageBatches2024_09_24 AnthropicBeta = "message-batches-2024-09-24"`
- `const AnthropicBetaPromptCaching2024_07_31 AnthropicBeta = "prompt-caching-2024-07-31"`
- `const AnthropicBetaComputerUse2024_10_22 AnthropicBeta = "computer-use-2024-10-22"`
- `const AnthropicBetaComputerUse2025_01_24 AnthropicBeta = "computer-use-2025-01-24"`
- `const AnthropicBetaPDFs2024_09_25 AnthropicBeta = "pdfs-2024-09-25"`
- `const AnthropicBetaTokenCounting2024_11_01 AnthropicBeta = "token-counting-2024-11-01"`
- `const AnthropicBetaTokenEfficientTools2025_02_19 AnthropicBeta = "token-efficient-tools-2025-02-19"`
- `const AnthropicBetaOutput128k2025_02_19 AnthropicBeta = "output-128k-2025-02-19"`
- `const AnthropicBetaFilesAPI2025_04_14 AnthropicBeta = "files-api-2025-04-14"`
- `const AnthropicBetaMCPClient2025_04_04 AnthropicBeta = "mcp-client-2025-04-04"`
- `const AnthropicBetaMCPClient2025_11_20 AnthropicBeta = "mcp-client-2025-11-20"`
- `const AnthropicBetaDevFullThinking2025_05_14 AnthropicBeta = "dev-full-thinking-2025-05-14"`
- `const AnthropicBetaInterleavedThinking2025_05_14 AnthropicBeta = "interleaved-thinking-2025-05-14"`
- `const AnthropicBetaCodeExecution2025_05_22 AnthropicBeta = "code-execution-2025-05-22"`
- `const AnthropicBetaExtendedCacheTTL2025_04_11 AnthropicBeta = "extended-cache-ttl-2025-04-11"`
- `const AnthropicBetaContext1m2025_08_07 AnthropicBeta = "context-1m-2025-08-07"`
- `const AnthropicBetaContextManagement2025_06_27 AnthropicBeta = "context-management-2025-06-27"`
- `const AnthropicBetaModelContextWindowExceeded2025_08_26 AnthropicBeta = "model-context-window-exceeded-2025-08-26"`
- `const AnthropicBetaSkills2025_10_02 AnthropicBeta = "skills-2025-10-02"`
- `const AnthropicBetaFastMode2026_02_01 AnthropicBeta = "fast-mode-2026-02-01"`
- `const AnthropicBetaOutput300k2026_03_24 AnthropicBeta = "output-300k-2026-03-24"`
- `const AnthropicBetaUserProfiles2026_03_24 AnthropicBeta = "user-profiles-2026-03-24"`
- `const AnthropicBetaAdvisorTool2026_03_01 AnthropicBeta = "advisor-tool-2026-03-01"`
- `const AnthropicBetaManagedAgents2026_04_01 AnthropicBeta = "managed-agents-2026-04-01"`
### Returns
- `type BetaManagedAgentsAgent struct{…}`
A Managed Agents `agent`.
- `ID string`
- `ArchivedAt Time`
A timestamp in RFC 3339 format
- `CreatedAt Time`
A timestamp in RFC 3339 format
- `Description string`
- `MCPServers []BetaManagedAgentsMCPServerURLDefinition`
- `Name string`
- `Type BetaManagedAgentsMCPServerURLDefinitionType`
- `const BetaManagedAgentsMCPServerURLDefinitionTypeURL BetaManagedAgentsMCPServerURLDefinitionType = "url"`
- `URL string`
- `Metadata map[string, string]`
- `Model BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `ID BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `type BetaManagedAgentsModel string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `const BetaManagedAgentsModelClaudeOpus4_7 BetaManagedAgentsModel = "claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `const BetaManagedAgentsModelClaudeOpus4_6 BetaManagedAgentsModel = "claude-opus-4-6"`
Most intelligent model for building agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_6 BetaManagedAgentsModel = "claude-sonnet-4-6"`
Best combination of speed and intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5 BetaManagedAgentsModel = "claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5_20251001 BetaManagedAgentsModel = "claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeOpus4_5 BetaManagedAgentsModel = "claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeOpus4_5_20251101 BetaManagedAgentsModel = "claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeSonnet4_5 BetaManagedAgentsModel = "claude-sonnet-4-5"`
High-performance model for agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_5_20250929 BetaManagedAgentsModel = "claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `string`
- `Speed BetaManagedAgentsModelConfigSpeed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `const BetaManagedAgentsModelConfigSpeedStandard BetaManagedAgentsModelConfigSpeed = "standard"`
- `const BetaManagedAgentsModelConfigSpeedFast BetaManagedAgentsModelConfigSpeed = "fast"`
- `Multiagent BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `Agents []BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `ID string`
- `Type BetaManagedAgentsAgentReferenceType`
- `const BetaManagedAgentsAgentReferenceTypeAgent BetaManagedAgentsAgentReferenceType = "agent"`
- `Version int64`
- `Type BetaManagedAgentsMultiagentType`
- `const BetaManagedAgentsMultiagentTypeCoordinator BetaManagedAgentsMultiagentType = "coordinator"`
- `Name string`
- `Skills []BetaManagedAgentsAgentSkillUnion`
- `type BetaManagedAgentsAnthropicSkill struct{…}`
A resolved Anthropic-managed skill.
- `SkillID string`
- `Type BetaManagedAgentsAnthropicSkillType`
- `const BetaManagedAgentsAnthropicSkillTypeAnthropic BetaManagedAgentsAnthropicSkillType = "anthropic"`
- `Version string`
- `type BetaManagedAgentsCustomSkill struct{…}`
A resolved user-created custom skill.
- `SkillID string`
- `Type BetaManagedAgentsCustomSkillType`
- `const BetaManagedAgentsCustomSkillTypeCustom BetaManagedAgentsCustomSkillType = "custom"`
- `Version string`
- `System string`
- `Tools []BetaManagedAgentsAgentToolUnion`
- `type BetaManagedAgentsAgentToolset20260401 struct{…}`
- `Configs []BetaManagedAgentsAgentToolConfig`
- `Enabled bool`
- `Name BetaManagedAgentsAgentToolConfigName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigNameBash BetaManagedAgentsAgentToolConfigName = "bash"`
- `const BetaManagedAgentsAgentToolConfigNameEdit BetaManagedAgentsAgentToolConfigName = "edit"`
- `const BetaManagedAgentsAgentToolConfigNameRead BetaManagedAgentsAgentToolConfigName = "read"`
- `const BetaManagedAgentsAgentToolConfigNameWrite BetaManagedAgentsAgentToolConfigName = "write"`
- `const BetaManagedAgentsAgentToolConfigNameGlob BetaManagedAgentsAgentToolConfigName = "glob"`
- `const BetaManagedAgentsAgentToolConfigNameGrep BetaManagedAgentsAgentToolConfigName = "grep"`
- `const BetaManagedAgentsAgentToolConfigNameWebFetch BetaManagedAgentsAgentToolConfigName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigNameWebSearch BetaManagedAgentsAgentToolConfigName = "web_search"`
- `PermissionPolicy BetaManagedAgentsAgentToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `Type BetaManagedAgentsAgentToolset20260401Type`
- `const BetaManagedAgentsAgentToolset20260401TypeAgentToolset20260401 BetaManagedAgentsAgentToolset20260401Type = "agent_toolset_20260401"`
- `type BetaManagedAgentsMCPToolset struct{…}`
- `Configs []BetaManagedAgentsMCPToolConfig`
- `Enabled bool`
- `Name string`
- `PermissionPolicy BetaManagedAgentsMCPToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `MCPServerName string`
- `Type BetaManagedAgentsMCPToolsetType`
- `const BetaManagedAgentsMCPToolsetTypeMCPToolset BetaManagedAgentsMCPToolsetType = "mcp_toolset"`
- `type BetaManagedAgentsCustomTool struct{…}`
A custom tool as returned in API responses.
- `Description string`
- `InputSchema BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `Properties map[string, any]`
JSON Schema properties defining the tool's input parameters.
- `Required []string`
List of required property names.
- `Type BetaManagedAgentsCustomToolInputSchemaType`
Must be 'object' for tool input schemas.
- `const BetaManagedAgentsCustomToolInputSchemaTypeObject BetaManagedAgentsCustomToolInputSchemaType = "object"`
- `Name string`
- `Type BetaManagedAgentsCustomToolType`
- `const BetaManagedAgentsCustomToolTypeCustom BetaManagedAgentsCustomToolType = "custom"`
- `Type BetaManagedAgentsAgentType`
- `const BetaManagedAgentsAgentTypeAgent BetaManagedAgentsAgentType = "agent"`
- `UpdatedAt Time`
A timestamp in RFC 3339 format
- `Version int64`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```go
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func main() {
client := anthropic.NewClient(
option.WithAPIKey("my-anthropic-api-key"),
)
betaManagedAgentsAgent, err := client.Beta.Agents.Get(
context.TODO(),
"agent_011CZkYpogX7uDKUyvBTophP",
anthropic.BetaAgentGetParams{
},
)
if err != nil {
panic(err.Error())
}
fmt.Printf("%+v\n", betaManagedAgentsAgent.ID)
}
```
## Update
`client.Beta.Agents.Update(ctx, agentID, params) (*BetaManagedAgentsAgent, error)`
**post** `/v1/agents/{agent_id}`
Update Agent
### Parameters
- `agentID string`
- `params BetaAgentUpdateParams`
- `Version param.Field[int64]`
Body param: The agent's current version, used to prevent concurrent overwrites. Obtain this value from a create or retrieve response. The request fails if this does not match the server's current version.
- `Description param.Field[string]`
Body param: Description. Up to 2048 characters. Omit to preserve; send empty string or null to clear.
- `MCPServers param.Field[[]BetaManagedAgentsURLMCPServerParamsResp]`
Body param: MCP servers. Full replacement. Omit to preserve; send empty array or null to clear. Names must be unique. Maximum 20.
- `Name string`
Unique name for this server, referenced by mcp_toolset configurations. 1-255 characters.
- `Type BetaManagedAgentsURLMCPServerParamsType`
- `const BetaManagedAgentsURLMCPServerParamsTypeURL BetaManagedAgentsURLMCPServerParamsType = "url"`
- `URL string`
Endpoint URL for the MCP server.
- `Metadata param.Field[map[string, string]]`
Body param: Metadata patch. Set a key to a string to upsert it, or to null to delete it. Omit the field to preserve. The stored bag is limited to 16 keys (up to 64 chars each) with values up to 512 chars.
- `Model param.Field[BetaManagedAgentsModelConfigParamsResp]`
Body param: Model identifier. Accepts the [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison), e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration control. Omit to preserve. Cannot be cleared.
- `type BetaManagedAgentsModelConfigParamsResp struct{…}`
An object that defines additional configuration control over model use
- `ID BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `type BetaManagedAgentsModel string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `const BetaManagedAgentsModelClaudeOpus4_7 BetaManagedAgentsModel = "claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `const BetaManagedAgentsModelClaudeOpus4_6 BetaManagedAgentsModel = "claude-opus-4-6"`
Most intelligent model for building agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_6 BetaManagedAgentsModel = "claude-sonnet-4-6"`
Best combination of speed and intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5 BetaManagedAgentsModel = "claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5_20251001 BetaManagedAgentsModel = "claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeOpus4_5 BetaManagedAgentsModel = "claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeOpus4_5_20251101 BetaManagedAgentsModel = "claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeSonnet4_5 BetaManagedAgentsModel = "claude-sonnet-4-5"`
High-performance model for agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_5_20250929 BetaManagedAgentsModel = "claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `string`
- `Speed BetaManagedAgentsModelConfigParamsSpeed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `const BetaManagedAgentsModelConfigParamsSpeedStandard BetaManagedAgentsModelConfigParamsSpeed = "standard"`
- `const BetaManagedAgentsModelConfigParamsSpeedFast BetaManagedAgentsModelConfigParamsSpeed = "fast"`
- `Multiagent param.Field[BetaManagedAgentsMultiagentParamsResp]`
Body param: A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `Name param.Field[string]`
Body param: Human-readable name. 1-256 characters. Omit to preserve. Cannot be cleared.
- `Skills param.Field[[]BetaManagedAgentsSkillParamsUnionResp]`
Body param: Skills. Full replacement. Omit to preserve; send empty array or null to clear. Maximum 20.
- `type BetaManagedAgentsAnthropicSkillParamsResp struct{…}`
An Anthropic-managed skill.
- `SkillID string`
Identifier of the Anthropic skill (e.g., "xlsx").
- `Type BetaManagedAgentsAnthropicSkillParamsType`
- `const BetaManagedAgentsAnthropicSkillParamsTypeAnthropic BetaManagedAgentsAnthropicSkillParamsType = "anthropic"`
- `Version string`
Version to pin. Defaults to latest if omitted.
- `type BetaManagedAgentsCustomSkillParamsResp struct{…}`
A user-created custom skill.
- `SkillID string`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `Type BetaManagedAgentsCustomSkillParamsType`
- `const BetaManagedAgentsCustomSkillParamsTypeCustom BetaManagedAgentsCustomSkillParamsType = "custom"`
- `Version string`
Version to pin. Defaults to latest if omitted.
- `System param.Field[string]`
Body param: System prompt. Up to 100,000 characters. Omit to preserve; send empty string or null to clear.
- `Tools param.Field[[]BetaAgentUpdateParamsToolUnion]`
Body param: Tool configurations available to the agent. Full replacement. Omit to preserve; send empty array or null to clear. Maximum of 128 tools across all toolsets allowed.
- `type BetaManagedAgentsAgentToolset20260401ParamsResp struct{…}`
Configuration for built-in agent tools. Use this to enable or disable groups of tools available to the agent.
- `Type BetaManagedAgentsAgentToolset20260401ParamsType`
- `const BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401 BetaManagedAgentsAgentToolset20260401ParamsType = "agent_toolset_20260401"`
- `Configs []BetaManagedAgentsAgentToolConfigParamsResp`
Per-tool configuration overrides.
- `Name BetaManagedAgentsAgentToolConfigParamsName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigParamsNameBash BetaManagedAgentsAgentToolConfigParamsName = "bash"`
- `const BetaManagedAgentsAgentToolConfigParamsNameEdit BetaManagedAgentsAgentToolConfigParamsName = "edit"`
- `const BetaManagedAgentsAgentToolConfigParamsNameRead BetaManagedAgentsAgentToolConfigParamsName = "read"`
- `const BetaManagedAgentsAgentToolConfigParamsNameWrite BetaManagedAgentsAgentToolConfigParamsName = "write"`
- `const BetaManagedAgentsAgentToolConfigParamsNameGlob BetaManagedAgentsAgentToolConfigParamsName = "glob"`
- `const BetaManagedAgentsAgentToolConfigParamsNameGrep BetaManagedAgentsAgentToolConfigParamsName = "grep"`
- `const BetaManagedAgentsAgentToolConfigParamsNameWebFetch BetaManagedAgentsAgentToolConfigParamsName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigParamsNameWebSearch BetaManagedAgentsAgentToolConfigParamsName = "web_search"`
- `Enabled bool`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `PermissionPolicy BetaManagedAgentsAgentToolConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsAgentToolsetDefaultConfigParamsResp`
Default configuration for all tools in a toolset.
- `Enabled bool`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `type BetaManagedAgentsMCPToolsetParamsResp struct{…}`
Configuration for tools from an MCP server defined in `mcp_servers`.
- `MCPServerName string`
Name of the MCP server. Must match a server name from the mcp_servers array. 1-255 characters.
- `Type BetaManagedAgentsMCPToolsetParamsType`
- `const BetaManagedAgentsMCPToolsetParamsTypeMCPToolset BetaManagedAgentsMCPToolsetParamsType = "mcp_toolset"`
- `Configs []BetaManagedAgentsMCPToolConfigParamsResp`
Per-tool configuration overrides.
- `Name string`
Name of the MCP tool to configure. 1-128 characters.
- `Enabled bool`
Whether this tool is enabled. Overrides the `default_config` setting.
- `PermissionPolicy BetaManagedAgentsMCPToolConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsMCPToolsetDefaultConfigParamsResp`
Default configuration for all tools from an MCP server.
- `Enabled bool`
Whether tools are enabled by default. Defaults to true if not specified.
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `type BetaManagedAgentsCustomToolParamsResp struct{…}`
A custom tool that is executed by the API client rather than the agent. When the agent calls this tool, an `agent.custom_tool_use` event is emitted and the session goes idle, waiting for the client to provide the result via a `user.custom_tool_result` event.
- `Description string`
Description of what the tool does, shown to the agent to help it decide when to use the tool. 1-1024 characters.
- `InputSchema BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `Properties map[string, any]`
JSON Schema properties defining the tool's input parameters.
- `Required []string`
List of required property names.
- `Type BetaManagedAgentsCustomToolInputSchemaType`
Must be 'object' for tool input schemas.
- `const BetaManagedAgentsCustomToolInputSchemaTypeObject BetaManagedAgentsCustomToolInputSchemaType = "object"`
- `Name string`
Unique name for the tool. 1-128 characters; letters, digits, underscores, and hyphens.
- `Type BetaManagedAgentsCustomToolParamsType`
- `const BetaManagedAgentsCustomToolParamsTypeCustom BetaManagedAgentsCustomToolParamsType = "custom"`
- `Betas param.Field[[]AnthropicBeta]`
Header param: Optional header to specify the beta version(s) you want to use.
- `string`
- `type AnthropicBeta string`
- `const AnthropicBetaMessageBatches2024_09_24 AnthropicBeta = "message-batches-2024-09-24"`
- `const AnthropicBetaPromptCaching2024_07_31 AnthropicBeta = "prompt-caching-2024-07-31"`
- `const AnthropicBetaComputerUse2024_10_22 AnthropicBeta = "computer-use-2024-10-22"`
- `const AnthropicBetaComputerUse2025_01_24 AnthropicBeta = "computer-use-2025-01-24"`
- `const AnthropicBetaPDFs2024_09_25 AnthropicBeta = "pdfs-2024-09-25"`
- `const AnthropicBetaTokenCounting2024_11_01 AnthropicBeta = "token-counting-2024-11-01"`
- `const AnthropicBetaTokenEfficientTools2025_02_19 AnthropicBeta = "token-efficient-tools-2025-02-19"`
- `const AnthropicBetaOutput128k2025_02_19 AnthropicBeta = "output-128k-2025-02-19"`
- `const AnthropicBetaFilesAPI2025_04_14 AnthropicBeta = "files-api-2025-04-14"`
- `const AnthropicBetaMCPClient2025_04_04 AnthropicBeta = "mcp-client-2025-04-04"`
- `const AnthropicBetaMCPClient2025_11_20 AnthropicBeta = "mcp-client-2025-11-20"`
- `const AnthropicBetaDevFullThinking2025_05_14 AnthropicBeta = "dev-full-thinking-2025-05-14"`
- `const AnthropicBetaInterleavedThinking2025_05_14 AnthropicBeta = "interleaved-thinking-2025-05-14"`
- `const AnthropicBetaCodeExecution2025_05_22 AnthropicBeta = "code-execution-2025-05-22"`
- `const AnthropicBetaExtendedCacheTTL2025_04_11 AnthropicBeta = "extended-cache-ttl-2025-04-11"`
- `const AnthropicBetaContext1m2025_08_07 AnthropicBeta = "context-1m-2025-08-07"`
- `const AnthropicBetaContextManagement2025_06_27 AnthropicBeta = "context-management-2025-06-27"`
- `const AnthropicBetaModelContextWindowExceeded2025_08_26 AnthropicBeta = "model-context-window-exceeded-2025-08-26"`
- `const AnthropicBetaSkills2025_10_02 AnthropicBeta = "skills-2025-10-02"`
- `const AnthropicBetaFastMode2026_02_01 AnthropicBeta = "fast-mode-2026-02-01"`
- `const AnthropicBetaOutput300k2026_03_24 AnthropicBeta = "output-300k-2026-03-24"`
- `const AnthropicBetaUserProfiles2026_03_24 AnthropicBeta = "user-profiles-2026-03-24"`
- `const AnthropicBetaAdvisorTool2026_03_01 AnthropicBeta = "advisor-tool-2026-03-01"`
- `const AnthropicBetaManagedAgents2026_04_01 AnthropicBeta = "managed-agents-2026-04-01"`
### Returns
- `type BetaManagedAgentsAgent struct{…}`
A Managed Agents `agent`.
- `ID string`
- `ArchivedAt Time`
A timestamp in RFC 3339 format
- `CreatedAt Time`
A timestamp in RFC 3339 format
- `Description string`
- `MCPServers []BetaManagedAgentsMCPServerURLDefinition`
- `Name string`
- `Type BetaManagedAgentsMCPServerURLDefinitionType`
- `const BetaManagedAgentsMCPServerURLDefinitionTypeURL BetaManagedAgentsMCPServerURLDefinitionType = "url"`
- `URL string`
- `Metadata map[string, string]`
- `Model BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `ID BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `type BetaManagedAgentsModel string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `const BetaManagedAgentsModelClaudeOpus4_7 BetaManagedAgentsModel = "claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `const BetaManagedAgentsModelClaudeOpus4_6 BetaManagedAgentsModel = "claude-opus-4-6"`
Most intelligent model for building agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_6 BetaManagedAgentsModel = "claude-sonnet-4-6"`
Best combination of speed and intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5 BetaManagedAgentsModel = "claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5_20251001 BetaManagedAgentsModel = "claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeOpus4_5 BetaManagedAgentsModel = "claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeOpus4_5_20251101 BetaManagedAgentsModel = "claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeSonnet4_5 BetaManagedAgentsModel = "claude-sonnet-4-5"`
High-performance model for agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_5_20250929 BetaManagedAgentsModel = "claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `string`
- `Speed BetaManagedAgentsModelConfigSpeed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `const BetaManagedAgentsModelConfigSpeedStandard BetaManagedAgentsModelConfigSpeed = "standard"`
- `const BetaManagedAgentsModelConfigSpeedFast BetaManagedAgentsModelConfigSpeed = "fast"`
- `Multiagent BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `Agents []BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `ID string`
- `Type BetaManagedAgentsAgentReferenceType`
- `const BetaManagedAgentsAgentReferenceTypeAgent BetaManagedAgentsAgentReferenceType = "agent"`
- `Version int64`
- `Type BetaManagedAgentsMultiagentType`
- `const BetaManagedAgentsMultiagentTypeCoordinator BetaManagedAgentsMultiagentType = "coordinator"`
- `Name string`
- `Skills []BetaManagedAgentsAgentSkillUnion`
- `type BetaManagedAgentsAnthropicSkill struct{…}`
A resolved Anthropic-managed skill.
- `SkillID string`
- `Type BetaManagedAgentsAnthropicSkillType`
- `const BetaManagedAgentsAnthropicSkillTypeAnthropic BetaManagedAgentsAnthropicSkillType = "anthropic"`
- `Version string`
- `type BetaManagedAgentsCustomSkill struct{…}`
A resolved user-created custom skill.
- `SkillID string`
- `Type BetaManagedAgentsCustomSkillType`
- `const BetaManagedAgentsCustomSkillTypeCustom BetaManagedAgentsCustomSkillType = "custom"`
- `Version string`
- `System string`
- `Tools []BetaManagedAgentsAgentToolUnion`
- `type BetaManagedAgentsAgentToolset20260401 struct{…}`
- `Configs []BetaManagedAgentsAgentToolConfig`
- `Enabled bool`
- `Name BetaManagedAgentsAgentToolConfigName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigNameBash BetaManagedAgentsAgentToolConfigName = "bash"`
- `const BetaManagedAgentsAgentToolConfigNameEdit BetaManagedAgentsAgentToolConfigName = "edit"`
- `const BetaManagedAgentsAgentToolConfigNameRead BetaManagedAgentsAgentToolConfigName = "read"`
- `const BetaManagedAgentsAgentToolConfigNameWrite BetaManagedAgentsAgentToolConfigName = "write"`
- `const BetaManagedAgentsAgentToolConfigNameGlob BetaManagedAgentsAgentToolConfigName = "glob"`
- `const BetaManagedAgentsAgentToolConfigNameGrep BetaManagedAgentsAgentToolConfigName = "grep"`
- `const BetaManagedAgentsAgentToolConfigNameWebFetch BetaManagedAgentsAgentToolConfigName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigNameWebSearch BetaManagedAgentsAgentToolConfigName = "web_search"`
- `PermissionPolicy BetaManagedAgentsAgentToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `Type BetaManagedAgentsAgentToolset20260401Type`
- `const BetaManagedAgentsAgentToolset20260401TypeAgentToolset20260401 BetaManagedAgentsAgentToolset20260401Type = "agent_toolset_20260401"`
- `type BetaManagedAgentsMCPToolset struct{…}`
- `Configs []BetaManagedAgentsMCPToolConfig`
- `Enabled bool`
- `Name string`
- `PermissionPolicy BetaManagedAgentsMCPToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `MCPServerName string`
- `Type BetaManagedAgentsMCPToolsetType`
- `const BetaManagedAgentsMCPToolsetTypeMCPToolset BetaManagedAgentsMCPToolsetType = "mcp_toolset"`
- `type BetaManagedAgentsCustomTool struct{…}`
A custom tool as returned in API responses.
- `Description string`
- `InputSchema BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `Properties map[string, any]`
JSON Schema properties defining the tool's input parameters.
- `Required []string`
List of required property names.
- `Type BetaManagedAgentsCustomToolInputSchemaType`
Must be 'object' for tool input schemas.
- `const BetaManagedAgentsCustomToolInputSchemaTypeObject BetaManagedAgentsCustomToolInputSchemaType = "object"`
- `Name string`
- `Type BetaManagedAgentsCustomToolType`
- `const BetaManagedAgentsCustomToolTypeCustom BetaManagedAgentsCustomToolType = "custom"`
- `Type BetaManagedAgentsAgentType`
- `const BetaManagedAgentsAgentTypeAgent BetaManagedAgentsAgentType = "agent"`
- `UpdatedAt Time`
A timestamp in RFC 3339 format
- `Version int64`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```go
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func main() {
client := anthropic.NewClient(
option.WithAPIKey("my-anthropic-api-key"),
)
betaManagedAgentsAgent, err := client.Beta.Agents.Update(
context.TODO(),
"agent_011CZkYpogX7uDKUyvBTophP",
anthropic.BetaAgentUpdateParams{
Version: 1,
},
)
if err != nil {
panic(err.Error())
}
fmt.Printf("%+v\n", betaManagedAgentsAgent.ID)
}
```
## Archive
`client.Beta.Agents.Archive(ctx, agentID, body) (*BetaManagedAgentsAgent, error)`
**post** `/v1/agents/{agent_id}/archive`
Archive Agent
### Parameters
- `agentID string`
- `body BetaAgentArchiveParams`
- `Betas param.Field[[]AnthropicBeta]`
Optional header to specify the beta version(s) you want to use.
- `string`
- `type AnthropicBeta string`
- `const AnthropicBetaMessageBatches2024_09_24 AnthropicBeta = "message-batches-2024-09-24"`
- `const AnthropicBetaPromptCaching2024_07_31 AnthropicBeta = "prompt-caching-2024-07-31"`
- `const AnthropicBetaComputerUse2024_10_22 AnthropicBeta = "computer-use-2024-10-22"`
- `const AnthropicBetaComputerUse2025_01_24 AnthropicBeta = "computer-use-2025-01-24"`
- `const AnthropicBetaPDFs2024_09_25 AnthropicBeta = "pdfs-2024-09-25"`
- `const AnthropicBetaTokenCounting2024_11_01 AnthropicBeta = "token-counting-2024-11-01"`
- `const AnthropicBetaTokenEfficientTools2025_02_19 AnthropicBeta = "token-efficient-tools-2025-02-19"`
- `const AnthropicBetaOutput128k2025_02_19 AnthropicBeta = "output-128k-2025-02-19"`
- `const AnthropicBetaFilesAPI2025_04_14 AnthropicBeta = "files-api-2025-04-14"`
- `const AnthropicBetaMCPClient2025_04_04 AnthropicBeta = "mcp-client-2025-04-04"`
- `const AnthropicBetaMCPClient2025_11_20 AnthropicBeta = "mcp-client-2025-11-20"`
- `const AnthropicBetaDevFullThinking2025_05_14 AnthropicBeta = "dev-full-thinking-2025-05-14"`
- `const AnthropicBetaInterleavedThinking2025_05_14 AnthropicBeta = "interleaved-thinking-2025-05-14"`
- `const AnthropicBetaCodeExecution2025_05_22 AnthropicBeta = "code-execution-2025-05-22"`
- `const AnthropicBetaExtendedCacheTTL2025_04_11 AnthropicBeta = "extended-cache-ttl-2025-04-11"`
- `const AnthropicBetaContext1m2025_08_07 AnthropicBeta = "context-1m-2025-08-07"`
- `const AnthropicBetaContextManagement2025_06_27 AnthropicBeta = "context-management-2025-06-27"`
- `const AnthropicBetaModelContextWindowExceeded2025_08_26 AnthropicBeta = "model-context-window-exceeded-2025-08-26"`
- `const AnthropicBetaSkills2025_10_02 AnthropicBeta = "skills-2025-10-02"`
- `const AnthropicBetaFastMode2026_02_01 AnthropicBeta = "fast-mode-2026-02-01"`
- `const AnthropicBetaOutput300k2026_03_24 AnthropicBeta = "output-300k-2026-03-24"`
- `const AnthropicBetaUserProfiles2026_03_24 AnthropicBeta = "user-profiles-2026-03-24"`
- `const AnthropicBetaAdvisorTool2026_03_01 AnthropicBeta = "advisor-tool-2026-03-01"`
- `const AnthropicBetaManagedAgents2026_04_01 AnthropicBeta = "managed-agents-2026-04-01"`
### Returns
- `type BetaManagedAgentsAgent struct{…}`
A Managed Agents `agent`.
- `ID string`
- `ArchivedAt Time`
A timestamp in RFC 3339 format
- `CreatedAt Time`
A timestamp in RFC 3339 format
- `Description string`
- `MCPServers []BetaManagedAgentsMCPServerURLDefinition`
- `Name string`
- `Type BetaManagedAgentsMCPServerURLDefinitionType`
- `const BetaManagedAgentsMCPServerURLDefinitionTypeURL BetaManagedAgentsMCPServerURLDefinitionType = "url"`
- `URL string`
- `Metadata map[string, string]`
- `Model BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `ID BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `type BetaManagedAgentsModel string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `const BetaManagedAgentsModelClaudeOpus4_7 BetaManagedAgentsModel = "claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `const BetaManagedAgentsModelClaudeOpus4_6 BetaManagedAgentsModel = "claude-opus-4-6"`
Most intelligent model for building agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_6 BetaManagedAgentsModel = "claude-sonnet-4-6"`
Best combination of speed and intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5 BetaManagedAgentsModel = "claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5_20251001 BetaManagedAgentsModel = "claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeOpus4_5 BetaManagedAgentsModel = "claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeOpus4_5_20251101 BetaManagedAgentsModel = "claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeSonnet4_5 BetaManagedAgentsModel = "claude-sonnet-4-5"`
High-performance model for agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_5_20250929 BetaManagedAgentsModel = "claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `string`
- `Speed BetaManagedAgentsModelConfigSpeed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `const BetaManagedAgentsModelConfigSpeedStandard BetaManagedAgentsModelConfigSpeed = "standard"`
- `const BetaManagedAgentsModelConfigSpeedFast BetaManagedAgentsModelConfigSpeed = "fast"`
- `Multiagent BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `Agents []BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `ID string`
- `Type BetaManagedAgentsAgentReferenceType`
- `const BetaManagedAgentsAgentReferenceTypeAgent BetaManagedAgentsAgentReferenceType = "agent"`
- `Version int64`
- `Type BetaManagedAgentsMultiagentType`
- `const BetaManagedAgentsMultiagentTypeCoordinator BetaManagedAgentsMultiagentType = "coordinator"`
- `Name string`
- `Skills []BetaManagedAgentsAgentSkillUnion`
- `type BetaManagedAgentsAnthropicSkill struct{…}`
A resolved Anthropic-managed skill.
- `SkillID string`
- `Type BetaManagedAgentsAnthropicSkillType`
- `const BetaManagedAgentsAnthropicSkillTypeAnthropic BetaManagedAgentsAnthropicSkillType = "anthropic"`
- `Version string`
- `type BetaManagedAgentsCustomSkill struct{…}`
A resolved user-created custom skill.
- `SkillID string`
- `Type BetaManagedAgentsCustomSkillType`
- `const BetaManagedAgentsCustomSkillTypeCustom BetaManagedAgentsCustomSkillType = "custom"`
- `Version string`
- `System string`
- `Tools []BetaManagedAgentsAgentToolUnion`
- `type BetaManagedAgentsAgentToolset20260401 struct{…}`
- `Configs []BetaManagedAgentsAgentToolConfig`
- `Enabled bool`
- `Name BetaManagedAgentsAgentToolConfigName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigNameBash BetaManagedAgentsAgentToolConfigName = "bash"`
- `const BetaManagedAgentsAgentToolConfigNameEdit BetaManagedAgentsAgentToolConfigName = "edit"`
- `const BetaManagedAgentsAgentToolConfigNameRead BetaManagedAgentsAgentToolConfigName = "read"`
- `const BetaManagedAgentsAgentToolConfigNameWrite BetaManagedAgentsAgentToolConfigName = "write"`
- `const BetaManagedAgentsAgentToolConfigNameGlob BetaManagedAgentsAgentToolConfigName = "glob"`
- `const BetaManagedAgentsAgentToolConfigNameGrep BetaManagedAgentsAgentToolConfigName = "grep"`
- `const BetaManagedAgentsAgentToolConfigNameWebFetch BetaManagedAgentsAgentToolConfigName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigNameWebSearch BetaManagedAgentsAgentToolConfigName = "web_search"`
- `PermissionPolicy BetaManagedAgentsAgentToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `Type BetaManagedAgentsAgentToolset20260401Type`
- `const BetaManagedAgentsAgentToolset20260401TypeAgentToolset20260401 BetaManagedAgentsAgentToolset20260401Type = "agent_toolset_20260401"`
- `type BetaManagedAgentsMCPToolset struct{…}`
- `Configs []BetaManagedAgentsMCPToolConfig`
- `Enabled bool`
- `Name string`
- `PermissionPolicy BetaManagedAgentsMCPToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `MCPServerName string`
- `Type BetaManagedAgentsMCPToolsetType`
- `const BetaManagedAgentsMCPToolsetTypeMCPToolset BetaManagedAgentsMCPToolsetType = "mcp_toolset"`
- `type BetaManagedAgentsCustomTool struct{…}`
A custom tool as returned in API responses.
- `Description string`
- `InputSchema BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `Properties map[string, any]`
JSON Schema properties defining the tool's input parameters.
- `Required []string`
List of required property names.
- `Type BetaManagedAgentsCustomToolInputSchemaType`
Must be 'object' for tool input schemas.
- `const BetaManagedAgentsCustomToolInputSchemaTypeObject BetaManagedAgentsCustomToolInputSchemaType = "object"`
- `Name string`
- `Type BetaManagedAgentsCustomToolType`
- `const BetaManagedAgentsCustomToolTypeCustom BetaManagedAgentsCustomToolType = "custom"`
- `Type BetaManagedAgentsAgentType`
- `const BetaManagedAgentsAgentTypeAgent BetaManagedAgentsAgentType = "agent"`
- `UpdatedAt Time`
A timestamp in RFC 3339 format
- `Version int64`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```go
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func main() {
client := anthropic.NewClient(
option.WithAPIKey("my-anthropic-api-key"),
)
betaManagedAgentsAgent, err := client.Beta.Agents.Archive(
context.TODO(),
"agent_011CZkYpogX7uDKUyvBTophP",
anthropic.BetaAgentArchiveParams{
},
)
if err != nil {
panic(err.Error())
}
fmt.Printf("%+v\n", betaManagedAgentsAgent.ID)
}
```
## Domain Types
### Beta Managed Agents Agent
- `type BetaManagedAgentsAgent struct{…}`
A Managed Agents `agent`.
- `ID string`
- `ArchivedAt Time`
A timestamp in RFC 3339 format
- `CreatedAt Time`
A timestamp in RFC 3339 format
- `Description string`
- `MCPServers []BetaManagedAgentsMCPServerURLDefinition`
- `Name string`
- `Type BetaManagedAgentsMCPServerURLDefinitionType`
- `const BetaManagedAgentsMCPServerURLDefinitionTypeURL BetaManagedAgentsMCPServerURLDefinitionType = "url"`
- `URL string`
- `Metadata map[string, string]`
- `Model BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `ID BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `type BetaManagedAgentsModel string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `const BetaManagedAgentsModelClaudeOpus4_7 BetaManagedAgentsModel = "claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `const BetaManagedAgentsModelClaudeOpus4_6 BetaManagedAgentsModel = "claude-opus-4-6"`
Most intelligent model for building agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_6 BetaManagedAgentsModel = "claude-sonnet-4-6"`
Best combination of speed and intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5 BetaManagedAgentsModel = "claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5_20251001 BetaManagedAgentsModel = "claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeOpus4_5 BetaManagedAgentsModel = "claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeOpus4_5_20251101 BetaManagedAgentsModel = "claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeSonnet4_5 BetaManagedAgentsModel = "claude-sonnet-4-5"`
High-performance model for agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_5_20250929 BetaManagedAgentsModel = "claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `string`
- `Speed BetaManagedAgentsModelConfigSpeed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `const BetaManagedAgentsModelConfigSpeedStandard BetaManagedAgentsModelConfigSpeed = "standard"`
- `const BetaManagedAgentsModelConfigSpeedFast BetaManagedAgentsModelConfigSpeed = "fast"`
- `Multiagent BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `Agents []BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `ID string`
- `Type BetaManagedAgentsAgentReferenceType`
- `const BetaManagedAgentsAgentReferenceTypeAgent BetaManagedAgentsAgentReferenceType = "agent"`
- `Version int64`
- `Type BetaManagedAgentsMultiagentType`
- `const BetaManagedAgentsMultiagentTypeCoordinator BetaManagedAgentsMultiagentType = "coordinator"`
- `Name string`
- `Skills []BetaManagedAgentsAgentSkillUnion`
- `type BetaManagedAgentsAnthropicSkill struct{…}`
A resolved Anthropic-managed skill.
- `SkillID string`
- `Type BetaManagedAgentsAnthropicSkillType`
- `const BetaManagedAgentsAnthropicSkillTypeAnthropic BetaManagedAgentsAnthropicSkillType = "anthropic"`
- `Version string`
- `type BetaManagedAgentsCustomSkill struct{…}`
A resolved user-created custom skill.
- `SkillID string`
- `Type BetaManagedAgentsCustomSkillType`
- `const BetaManagedAgentsCustomSkillTypeCustom BetaManagedAgentsCustomSkillType = "custom"`
- `Version string`
- `System string`
- `Tools []BetaManagedAgentsAgentToolUnion`
- `type BetaManagedAgentsAgentToolset20260401 struct{…}`
- `Configs []BetaManagedAgentsAgentToolConfig`
- `Enabled bool`
- `Name BetaManagedAgentsAgentToolConfigName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigNameBash BetaManagedAgentsAgentToolConfigName = "bash"`
- `const BetaManagedAgentsAgentToolConfigNameEdit BetaManagedAgentsAgentToolConfigName = "edit"`
- `const BetaManagedAgentsAgentToolConfigNameRead BetaManagedAgentsAgentToolConfigName = "read"`
- `const BetaManagedAgentsAgentToolConfigNameWrite BetaManagedAgentsAgentToolConfigName = "write"`
- `const BetaManagedAgentsAgentToolConfigNameGlob BetaManagedAgentsAgentToolConfigName = "glob"`
- `const BetaManagedAgentsAgentToolConfigNameGrep BetaManagedAgentsAgentToolConfigName = "grep"`
- `const BetaManagedAgentsAgentToolConfigNameWebFetch BetaManagedAgentsAgentToolConfigName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigNameWebSearch BetaManagedAgentsAgentToolConfigName = "web_search"`
- `PermissionPolicy BetaManagedAgentsAgentToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `Type BetaManagedAgentsAgentToolset20260401Type`
- `const BetaManagedAgentsAgentToolset20260401TypeAgentToolset20260401 BetaManagedAgentsAgentToolset20260401Type = "agent_toolset_20260401"`
- `type BetaManagedAgentsMCPToolset struct{…}`
- `Configs []BetaManagedAgentsMCPToolConfig`
- `Enabled bool`
- `Name string`
- `PermissionPolicy BetaManagedAgentsMCPToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `MCPServerName string`
- `Type BetaManagedAgentsMCPToolsetType`
- `const BetaManagedAgentsMCPToolsetTypeMCPToolset BetaManagedAgentsMCPToolsetType = "mcp_toolset"`
- `type BetaManagedAgentsCustomTool struct{…}`
A custom tool as returned in API responses.
- `Description string`
- `InputSchema BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `Properties map[string, any]`
JSON Schema properties defining the tool's input parameters.
- `Required []string`
List of required property names.
- `Type BetaManagedAgentsCustomToolInputSchemaType`
Must be 'object' for tool input schemas.
- `const BetaManagedAgentsCustomToolInputSchemaTypeObject BetaManagedAgentsCustomToolInputSchemaType = "object"`
- `Name string`
- `Type BetaManagedAgentsCustomToolType`
- `const BetaManagedAgentsCustomToolTypeCustom BetaManagedAgentsCustomToolType = "custom"`
- `Type BetaManagedAgentsAgentType`
- `const BetaManagedAgentsAgentTypeAgent BetaManagedAgentsAgentType = "agent"`
- `UpdatedAt Time`
A timestamp in RFC 3339 format
- `Version int64`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Beta Managed Agents Agent Reference
- `type BetaManagedAgentsAgentReference struct{…}`
A resolved agent reference with a concrete version.
- `ID string`
- `Type BetaManagedAgentsAgentReferenceType`
- `const BetaManagedAgentsAgentReferenceTypeAgent BetaManagedAgentsAgentReferenceType = "agent"`
- `Version int64`
### Beta Managed Agents Agent Tool Config
- `type BetaManagedAgentsAgentToolConfig struct{…}`
Configuration for a specific agent tool.
- `Enabled bool`
- `Name BetaManagedAgentsAgentToolConfigName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigNameBash BetaManagedAgentsAgentToolConfigName = "bash"`
- `const BetaManagedAgentsAgentToolConfigNameEdit BetaManagedAgentsAgentToolConfigName = "edit"`
- `const BetaManagedAgentsAgentToolConfigNameRead BetaManagedAgentsAgentToolConfigName = "read"`
- `const BetaManagedAgentsAgentToolConfigNameWrite BetaManagedAgentsAgentToolConfigName = "write"`
- `const BetaManagedAgentsAgentToolConfigNameGlob BetaManagedAgentsAgentToolConfigName = "glob"`
- `const BetaManagedAgentsAgentToolConfigNameGrep BetaManagedAgentsAgentToolConfigName = "grep"`
- `const BetaManagedAgentsAgentToolConfigNameWebFetch BetaManagedAgentsAgentToolConfigName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigNameWebSearch BetaManagedAgentsAgentToolConfigName = "web_search"`
- `PermissionPolicy BetaManagedAgentsAgentToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
### Beta Managed Agents Agent Tool Config Params
- `type BetaManagedAgentsAgentToolConfigParamsResp struct{…}`
Configuration override for a specific tool within a toolset.
- `Name BetaManagedAgentsAgentToolConfigParamsName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigParamsNameBash BetaManagedAgentsAgentToolConfigParamsName = "bash"`
- `const BetaManagedAgentsAgentToolConfigParamsNameEdit BetaManagedAgentsAgentToolConfigParamsName = "edit"`
- `const BetaManagedAgentsAgentToolConfigParamsNameRead BetaManagedAgentsAgentToolConfigParamsName = "read"`
- `const BetaManagedAgentsAgentToolConfigParamsNameWrite BetaManagedAgentsAgentToolConfigParamsName = "write"`
- `const BetaManagedAgentsAgentToolConfigParamsNameGlob BetaManagedAgentsAgentToolConfigParamsName = "glob"`
- `const BetaManagedAgentsAgentToolConfigParamsNameGrep BetaManagedAgentsAgentToolConfigParamsName = "grep"`
- `const BetaManagedAgentsAgentToolConfigParamsNameWebFetch BetaManagedAgentsAgentToolConfigParamsName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigParamsNameWebSearch BetaManagedAgentsAgentToolConfigParamsName = "web_search"`
- `Enabled bool`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `PermissionPolicy BetaManagedAgentsAgentToolConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
### Beta Managed Agents Agent Toolset Default Config
- `type BetaManagedAgentsAgentToolsetDefaultConfig struct{…}`
Resolved default configuration for agent tools.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
### Beta Managed Agents Agent Toolset Default Config Params
- `type BetaManagedAgentsAgentToolsetDefaultConfigParamsResp struct{…}`
Default configuration for all tools in a toolset.
- `Enabled bool`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
### Beta Managed Agents Agent Toolset20260401
- `type BetaManagedAgentsAgentToolset20260401 struct{…}`
- `Configs []BetaManagedAgentsAgentToolConfig`
- `Enabled bool`
- `Name BetaManagedAgentsAgentToolConfigName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigNameBash BetaManagedAgentsAgentToolConfigName = "bash"`
- `const BetaManagedAgentsAgentToolConfigNameEdit BetaManagedAgentsAgentToolConfigName = "edit"`
- `const BetaManagedAgentsAgentToolConfigNameRead BetaManagedAgentsAgentToolConfigName = "read"`
- `const BetaManagedAgentsAgentToolConfigNameWrite BetaManagedAgentsAgentToolConfigName = "write"`
- `const BetaManagedAgentsAgentToolConfigNameGlob BetaManagedAgentsAgentToolConfigName = "glob"`
- `const BetaManagedAgentsAgentToolConfigNameGrep BetaManagedAgentsAgentToolConfigName = "grep"`
- `const BetaManagedAgentsAgentToolConfigNameWebFetch BetaManagedAgentsAgentToolConfigName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigNameWebSearch BetaManagedAgentsAgentToolConfigName = "web_search"`
- `PermissionPolicy BetaManagedAgentsAgentToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `Type BetaManagedAgentsAgentToolset20260401Type`
- `const BetaManagedAgentsAgentToolset20260401TypeAgentToolset20260401 BetaManagedAgentsAgentToolset20260401Type = "agent_toolset_20260401"`
### Beta Managed Agents Agent Toolset20260401 Params
- `type BetaManagedAgentsAgentToolset20260401ParamsResp struct{…}`
Configuration for built-in agent tools. Use this to enable or disable groups of tools available to the agent.
- `Type BetaManagedAgentsAgentToolset20260401ParamsType`
- `const BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401 BetaManagedAgentsAgentToolset20260401ParamsType = "agent_toolset_20260401"`
- `Configs []BetaManagedAgentsAgentToolConfigParamsResp`
Per-tool configuration overrides.
- `Name BetaManagedAgentsAgentToolConfigParamsName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigParamsNameBash BetaManagedAgentsAgentToolConfigParamsName = "bash"`
- `const BetaManagedAgentsAgentToolConfigParamsNameEdit BetaManagedAgentsAgentToolConfigParamsName = "edit"`
- `const BetaManagedAgentsAgentToolConfigParamsNameRead BetaManagedAgentsAgentToolConfigParamsName = "read"`
- `const BetaManagedAgentsAgentToolConfigParamsNameWrite BetaManagedAgentsAgentToolConfigParamsName = "write"`
- `const BetaManagedAgentsAgentToolConfigParamsNameGlob BetaManagedAgentsAgentToolConfigParamsName = "glob"`
- `const BetaManagedAgentsAgentToolConfigParamsNameGrep BetaManagedAgentsAgentToolConfigParamsName = "grep"`
- `const BetaManagedAgentsAgentToolConfigParamsNameWebFetch BetaManagedAgentsAgentToolConfigParamsName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigParamsNameWebSearch BetaManagedAgentsAgentToolConfigParamsName = "web_search"`
- `Enabled bool`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `PermissionPolicy BetaManagedAgentsAgentToolConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsAgentToolsetDefaultConfigParamsResp`
Default configuration for all tools in a toolset.
- `Enabled bool`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
### Beta Managed Agents Always Allow Policy
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
### Beta Managed Agents Always Ask Policy
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
### Beta Managed Agents Anthropic Skill
- `type BetaManagedAgentsAnthropicSkill struct{…}`
A resolved Anthropic-managed skill.
- `SkillID string`
- `Type BetaManagedAgentsAnthropicSkillType`
- `const BetaManagedAgentsAnthropicSkillTypeAnthropic BetaManagedAgentsAnthropicSkillType = "anthropic"`
- `Version string`
### Beta Managed Agents Anthropic Skill Params
- `type BetaManagedAgentsAnthropicSkillParamsResp struct{…}`
An Anthropic-managed skill.
- `SkillID string`
Identifier of the Anthropic skill (e.g., "xlsx").
- `Type BetaManagedAgentsAnthropicSkillParamsType`
- `const BetaManagedAgentsAnthropicSkillParamsTypeAnthropic BetaManagedAgentsAnthropicSkillParamsType = "anthropic"`
- `Version string`
Version to pin. Defaults to latest if omitted.
### Beta Managed Agents Custom Skill
- `type BetaManagedAgentsCustomSkill struct{…}`
A resolved user-created custom skill.
- `SkillID string`
- `Type BetaManagedAgentsCustomSkillType`
- `const BetaManagedAgentsCustomSkillTypeCustom BetaManagedAgentsCustomSkillType = "custom"`
- `Version string`
### Beta Managed Agents Custom Skill Params
- `type BetaManagedAgentsCustomSkillParamsResp struct{…}`
A user-created custom skill.
- `SkillID string`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `Type BetaManagedAgentsCustomSkillParamsType`
- `const BetaManagedAgentsCustomSkillParamsTypeCustom BetaManagedAgentsCustomSkillParamsType = "custom"`
- `Version string`
Version to pin. Defaults to latest if omitted.
### Beta Managed Agents Custom Tool
- `type BetaManagedAgentsCustomTool struct{…}`
A custom tool as returned in API responses.
- `Description string`
- `InputSchema BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `Properties map[string, any]`
JSON Schema properties defining the tool's input parameters.
- `Required []string`
List of required property names.
- `Type BetaManagedAgentsCustomToolInputSchemaType`
Must be 'object' for tool input schemas.
- `const BetaManagedAgentsCustomToolInputSchemaTypeObject BetaManagedAgentsCustomToolInputSchemaType = "object"`
- `Name string`
- `Type BetaManagedAgentsCustomToolType`
- `const BetaManagedAgentsCustomToolTypeCustom BetaManagedAgentsCustomToolType = "custom"`
### Beta Managed Agents Custom Tool Input Schema
- `type BetaManagedAgentsCustomToolInputSchema struct{…}`
JSON Schema for custom tool input parameters.
- `Properties map[string, any]`
JSON Schema properties defining the tool's input parameters.
- `Required []string`
List of required property names.
- `Type BetaManagedAgentsCustomToolInputSchemaType`
Must be 'object' for tool input schemas.
- `const BetaManagedAgentsCustomToolInputSchemaTypeObject BetaManagedAgentsCustomToolInputSchemaType = "object"`
### Beta Managed Agents Custom Tool Params
- `type BetaManagedAgentsCustomToolParamsResp struct{…}`
A custom tool that is executed by the API client rather than the agent. When the agent calls this tool, an `agent.custom_tool_use` event is emitted and the session goes idle, waiting for the client to provide the result via a `user.custom_tool_result` event.
- `Description string`
Description of what the tool does, shown to the agent to help it decide when to use the tool. 1-1024 characters.
- `InputSchema BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `Properties map[string, any]`
JSON Schema properties defining the tool's input parameters.
- `Required []string`
List of required property names.
- `Type BetaManagedAgentsCustomToolInputSchemaType`
Must be 'object' for tool input schemas.
- `const BetaManagedAgentsCustomToolInputSchemaTypeObject BetaManagedAgentsCustomToolInputSchemaType = "object"`
- `Name string`
Unique name for the tool. 1-128 characters; letters, digits, underscores, and hyphens.
- `Type BetaManagedAgentsCustomToolParamsType`
- `const BetaManagedAgentsCustomToolParamsTypeCustom BetaManagedAgentsCustomToolParamsType = "custom"`
### Beta Managed Agents MCP Server URL Definition
- `type BetaManagedAgentsMCPServerURLDefinition struct{…}`
URL-based MCP server connection as returned in API responses.
- `Name string`
- `Type BetaManagedAgentsMCPServerURLDefinitionType`
- `const BetaManagedAgentsMCPServerURLDefinitionTypeURL BetaManagedAgentsMCPServerURLDefinitionType = "url"`
- `URL string`
### Beta Managed Agents MCP Tool Config
- `type BetaManagedAgentsMCPToolConfig struct{…}`
Resolved configuration for a specific MCP tool.
- `Enabled bool`
- `Name string`
- `PermissionPolicy BetaManagedAgentsMCPToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
### Beta Managed Agents MCP Tool Config Params
- `type BetaManagedAgentsMCPToolConfigParamsResp struct{…}`
Configuration override for a specific MCP tool.
- `Name string`
Name of the MCP tool to configure. 1-128 characters.
- `Enabled bool`
Whether this tool is enabled. Overrides the `default_config` setting.
- `PermissionPolicy BetaManagedAgentsMCPToolConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
### Beta Managed Agents MCP Toolset
- `type BetaManagedAgentsMCPToolset struct{…}`
- `Configs []BetaManagedAgentsMCPToolConfig`
- `Enabled bool`
- `Name string`
- `PermissionPolicy BetaManagedAgentsMCPToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `MCPServerName string`
- `Type BetaManagedAgentsMCPToolsetType`
- `const BetaManagedAgentsMCPToolsetTypeMCPToolset BetaManagedAgentsMCPToolsetType = "mcp_toolset"`
### Beta Managed Agents MCP Toolset Default Config
- `type BetaManagedAgentsMCPToolsetDefaultConfig struct{…}`
Resolved default configuration for all tools from an MCP server.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
### Beta Managed Agents MCP Toolset Default Config Params
- `type BetaManagedAgentsMCPToolsetDefaultConfigParamsResp struct{…}`
Default configuration for all tools from an MCP server.
- `Enabled bool`
Whether tools are enabled by default. Defaults to true if not specified.
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
### Beta Managed Agents MCP Toolset Params
- `type BetaManagedAgentsMCPToolsetParamsResp struct{…}`
Configuration for tools from an MCP server defined in `mcp_servers`.
- `MCPServerName string`
Name of the MCP server. Must match a server name from the mcp_servers array. 1-255 characters.
- `Type BetaManagedAgentsMCPToolsetParamsType`
- `const BetaManagedAgentsMCPToolsetParamsTypeMCPToolset BetaManagedAgentsMCPToolsetParamsType = "mcp_toolset"`
- `Configs []BetaManagedAgentsMCPToolConfigParamsResp`
Per-tool configuration overrides.
- `Name string`
Name of the MCP tool to configure. 1-128 characters.
- `Enabled bool`
Whether this tool is enabled. Overrides the `default_config` setting.
- `PermissionPolicy BetaManagedAgentsMCPToolConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsMCPToolsetDefaultConfigParamsResp`
Default configuration for all tools from an MCP server.
- `Enabled bool`
Whether tools are enabled by default. Defaults to true if not specified.
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigParamsPermissionPolicyUnionResp`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
### Beta Managed Agents Model
- `type BetaManagedAgentsModel interface{…}`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `type BetaManagedAgentsModel string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `const BetaManagedAgentsModelClaudeOpus4_7 BetaManagedAgentsModel = "claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `const BetaManagedAgentsModelClaudeOpus4_6 BetaManagedAgentsModel = "claude-opus-4-6"`
Most intelligent model for building agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_6 BetaManagedAgentsModel = "claude-sonnet-4-6"`
Best combination of speed and intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5 BetaManagedAgentsModel = "claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5_20251001 BetaManagedAgentsModel = "claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeOpus4_5 BetaManagedAgentsModel = "claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeOpus4_5_20251101 BetaManagedAgentsModel = "claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeSonnet4_5 BetaManagedAgentsModel = "claude-sonnet-4-5"`
High-performance model for agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_5_20250929 BetaManagedAgentsModel = "claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `string`
### Beta Managed Agents Model Config
- `type BetaManagedAgentsModelConfig struct{…}`
Model identifier and configuration.
- `ID BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `type BetaManagedAgentsModel string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `const BetaManagedAgentsModelClaudeOpus4_7 BetaManagedAgentsModel = "claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `const BetaManagedAgentsModelClaudeOpus4_6 BetaManagedAgentsModel = "claude-opus-4-6"`
Most intelligent model for building agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_6 BetaManagedAgentsModel = "claude-sonnet-4-6"`
Best combination of speed and intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5 BetaManagedAgentsModel = "claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5_20251001 BetaManagedAgentsModel = "claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeOpus4_5 BetaManagedAgentsModel = "claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeOpus4_5_20251101 BetaManagedAgentsModel = "claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeSonnet4_5 BetaManagedAgentsModel = "claude-sonnet-4-5"`
High-performance model for agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_5_20250929 BetaManagedAgentsModel = "claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `string`
- `Speed BetaManagedAgentsModelConfigSpeed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `const BetaManagedAgentsModelConfigSpeedStandard BetaManagedAgentsModelConfigSpeed = "standard"`
- `const BetaManagedAgentsModelConfigSpeedFast BetaManagedAgentsModelConfigSpeed = "fast"`
### Beta Managed Agents Model Config Params
- `type BetaManagedAgentsModelConfigParamsResp struct{…}`
An object that defines additional configuration control over model use
- `ID BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `type BetaManagedAgentsModel string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `const BetaManagedAgentsModelClaudeOpus4_7 BetaManagedAgentsModel = "claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `const BetaManagedAgentsModelClaudeOpus4_6 BetaManagedAgentsModel = "claude-opus-4-6"`
Most intelligent model for building agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_6 BetaManagedAgentsModel = "claude-sonnet-4-6"`
Best combination of speed and intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5 BetaManagedAgentsModel = "claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5_20251001 BetaManagedAgentsModel = "claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeOpus4_5 BetaManagedAgentsModel = "claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeOpus4_5_20251101 BetaManagedAgentsModel = "claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeSonnet4_5 BetaManagedAgentsModel = "claude-sonnet-4-5"`
High-performance model for agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_5_20250929 BetaManagedAgentsModel = "claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `string`
- `Speed BetaManagedAgentsModelConfigParamsSpeed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `const BetaManagedAgentsModelConfigParamsSpeedStandard BetaManagedAgentsModelConfigParamsSpeed = "standard"`
- `const BetaManagedAgentsModelConfigParamsSpeedFast BetaManagedAgentsModelConfigParamsSpeed = "fast"`
### Beta Managed Agents Multiagent Coordinator
- `type BetaManagedAgentsMultiagentCoordinator struct{…}`
Resolved coordinator topology with a concrete agent roster.
- `Agents []BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `ID string`
- `Type BetaManagedAgentsAgentReferenceType`
- `const BetaManagedAgentsAgentReferenceTypeAgent BetaManagedAgentsAgentReferenceType = "agent"`
- `Version int64`
- `Type BetaManagedAgentsMultiagentCoordinatorType`
- `const BetaManagedAgentsMultiagentCoordinatorTypeCoordinator BetaManagedAgentsMultiagentCoordinatorType = "coordinator"`
### Beta Managed Agents Multiagent Coordinator Params
- `type BetaManagedAgentsMultiagentCoordinatorParamsResp struct{…}`
A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `Agents []BetaManagedAgentsMultiagentRosterEntryParamsUnionResp`
Agents the coordinator may spawn as session threads. 1–20 entries. Each entry is an agent ID string, a versioned `{"type":"agent","id","version"}` reference, or `{"type":"self"}` to allow recursive self-invocation. Entries must reference distinct agents (after resolving `self` and string forms); at most one `self`. Referenced agents must exist, must not be archived, and must not themselves have `multiagent` set (depth limit 1).
- `string`
- `type BetaManagedAgentsAgentParamsResp struct{…}`
Specification for an Agent. Provide a specific `version` or use the short-form `agent="agent_id"` for the most recent version
- `ID string`
The `agent` ID.
- `Type BetaManagedAgentsAgentParamsType`
- `const BetaManagedAgentsAgentParamsTypeAgent BetaManagedAgentsAgentParamsType = "agent"`
- `Version int64`
The specific `agent` version to use. Omit to use the latest version. Must be at least 1 if specified.
- `type BetaManagedAgentsMultiagentSelfParamsResp struct{…}`
Sentinel roster entry meaning "the agent that owns this configuration". Resolved server-side to a concrete agent reference.
- `Type BetaManagedAgentsMultiagentSelfParamsType`
- `const BetaManagedAgentsMultiagentSelfParamsTypeSelf BetaManagedAgentsMultiagentSelfParamsType = "self"`
- `Type BetaManagedAgentsMultiagentCoordinatorParamsType`
- `const BetaManagedAgentsMultiagentCoordinatorParamsTypeCoordinator BetaManagedAgentsMultiagentCoordinatorParamsType = "coordinator"`
### Beta Managed Agents Multiagent Self Params
- `type BetaManagedAgentsMultiagentSelfParamsResp struct{…}`
Sentinel roster entry meaning "the agent that owns this configuration". Resolved server-side to a concrete agent reference.
- `Type BetaManagedAgentsMultiagentSelfParamsType`
- `const BetaManagedAgentsMultiagentSelfParamsTypeSelf BetaManagedAgentsMultiagentSelfParamsType = "self"`
### Beta Managed Agents Skill Params
- `type BetaManagedAgentsSkillParamsUnionResp interface{…}`
Skill to load in the session container.
- `type BetaManagedAgentsAnthropicSkillParamsResp struct{…}`
An Anthropic-managed skill.
- `SkillID string`
Identifier of the Anthropic skill (e.g., "xlsx").
- `Type BetaManagedAgentsAnthropicSkillParamsType`
- `const BetaManagedAgentsAnthropicSkillParamsTypeAnthropic BetaManagedAgentsAnthropicSkillParamsType = "anthropic"`
- `Version string`
Version to pin. Defaults to latest if omitted.
- `type BetaManagedAgentsCustomSkillParamsResp struct{…}`
A user-created custom skill.
- `SkillID string`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `Type BetaManagedAgentsCustomSkillParamsType`
- `const BetaManagedAgentsCustomSkillParamsTypeCustom BetaManagedAgentsCustomSkillParamsType = "custom"`
- `Version string`
Version to pin. Defaults to latest if omitted.
### Beta Managed Agents URL MCP Server Params
- `type BetaManagedAgentsURLMCPServerParamsResp struct{…}`
URL-based MCP server connection.
- `Name string`
Unique name for this server, referenced by mcp_toolset configurations. 1-255 characters.
- `Type BetaManagedAgentsURLMCPServerParamsType`
- `const BetaManagedAgentsURLMCPServerParamsTypeURL BetaManagedAgentsURLMCPServerParamsType = "url"`
- `URL string`
Endpoint URL for the MCP server.
# Versions
## List
`client.Beta.Agents.Versions.List(ctx, agentID, params) (*PageCursor[BetaManagedAgentsAgent], error)`
**get** `/v1/agents/{agent_id}/versions`
List Agent Versions
### Parameters
- `agentID string`
- `params BetaAgentVersionListParams`
- `Limit param.Field[int64]`
Query param: Maximum results per page. Default 20, maximum 100.
- `Page param.Field[string]`
Query param: Opaque pagination cursor.
- `Betas param.Field[[]AnthropicBeta]`
Header param: Optional header to specify the beta version(s) you want to use.
- `string`
- `type AnthropicBeta string`
- `const AnthropicBetaMessageBatches2024_09_24 AnthropicBeta = "message-batches-2024-09-24"`
- `const AnthropicBetaPromptCaching2024_07_31 AnthropicBeta = "prompt-caching-2024-07-31"`
- `const AnthropicBetaComputerUse2024_10_22 AnthropicBeta = "computer-use-2024-10-22"`
- `const AnthropicBetaComputerUse2025_01_24 AnthropicBeta = "computer-use-2025-01-24"`
- `const AnthropicBetaPDFs2024_09_25 AnthropicBeta = "pdfs-2024-09-25"`
- `const AnthropicBetaTokenCounting2024_11_01 AnthropicBeta = "token-counting-2024-11-01"`
- `const AnthropicBetaTokenEfficientTools2025_02_19 AnthropicBeta = "token-efficient-tools-2025-02-19"`
- `const AnthropicBetaOutput128k2025_02_19 AnthropicBeta = "output-128k-2025-02-19"`
- `const AnthropicBetaFilesAPI2025_04_14 AnthropicBeta = "files-api-2025-04-14"`
- `const AnthropicBetaMCPClient2025_04_04 AnthropicBeta = "mcp-client-2025-04-04"`
- `const AnthropicBetaMCPClient2025_11_20 AnthropicBeta = "mcp-client-2025-11-20"`
- `const AnthropicBetaDevFullThinking2025_05_14 AnthropicBeta = "dev-full-thinking-2025-05-14"`
- `const AnthropicBetaInterleavedThinking2025_05_14 AnthropicBeta = "interleaved-thinking-2025-05-14"`
- `const AnthropicBetaCodeExecution2025_05_22 AnthropicBeta = "code-execution-2025-05-22"`
- `const AnthropicBetaExtendedCacheTTL2025_04_11 AnthropicBeta = "extended-cache-ttl-2025-04-11"`
- `const AnthropicBetaContext1m2025_08_07 AnthropicBeta = "context-1m-2025-08-07"`
- `const AnthropicBetaContextManagement2025_06_27 AnthropicBeta = "context-management-2025-06-27"`
- `const AnthropicBetaModelContextWindowExceeded2025_08_26 AnthropicBeta = "model-context-window-exceeded-2025-08-26"`
- `const AnthropicBetaSkills2025_10_02 AnthropicBeta = "skills-2025-10-02"`
- `const AnthropicBetaFastMode2026_02_01 AnthropicBeta = "fast-mode-2026-02-01"`
- `const AnthropicBetaOutput300k2026_03_24 AnthropicBeta = "output-300k-2026-03-24"`
- `const AnthropicBetaUserProfiles2026_03_24 AnthropicBeta = "user-profiles-2026-03-24"`
- `const AnthropicBetaAdvisorTool2026_03_01 AnthropicBeta = "advisor-tool-2026-03-01"`
- `const AnthropicBetaManagedAgents2026_04_01 AnthropicBeta = "managed-agents-2026-04-01"`
### Returns
- `type BetaManagedAgentsAgent struct{…}`
A Managed Agents `agent`.
- `ID string`
- `ArchivedAt Time`
A timestamp in RFC 3339 format
- `CreatedAt Time`
A timestamp in RFC 3339 format
- `Description string`
- `MCPServers []BetaManagedAgentsMCPServerURLDefinition`
- `Name string`
- `Type BetaManagedAgentsMCPServerURLDefinitionType`
- `const BetaManagedAgentsMCPServerURLDefinitionTypeURL BetaManagedAgentsMCPServerURLDefinitionType = "url"`
- `URL string`
- `Metadata map[string, string]`
- `Model BetaManagedAgentsModelConfig`
Model identifier and configuration.
- `ID BetaManagedAgentsModel`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `type BetaManagedAgentsModel string`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `const BetaManagedAgentsModelClaudeOpus4_7 BetaManagedAgentsModel = "claude-opus-4-7"`
Frontier intelligence for long-running agents and coding
- `const BetaManagedAgentsModelClaudeOpus4_6 BetaManagedAgentsModel = "claude-opus-4-6"`
Most intelligent model for building agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_6 BetaManagedAgentsModel = "claude-sonnet-4-6"`
Best combination of speed and intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5 BetaManagedAgentsModel = "claude-haiku-4-5"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeHaiku4_5_20251001 BetaManagedAgentsModel = "claude-haiku-4-5-20251001"`
Fastest model with near-frontier intelligence
- `const BetaManagedAgentsModelClaudeOpus4_5 BetaManagedAgentsModel = "claude-opus-4-5"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeOpus4_5_20251101 BetaManagedAgentsModel = "claude-opus-4-5-20251101"`
Premium model combining maximum intelligence with practical performance
- `const BetaManagedAgentsModelClaudeSonnet4_5 BetaManagedAgentsModel = "claude-sonnet-4-5"`
High-performance model for agents and coding
- `const BetaManagedAgentsModelClaudeSonnet4_5_20250929 BetaManagedAgentsModel = "claude-sonnet-4-5-20250929"`
High-performance model for agents and coding
- `string`
- `Speed BetaManagedAgentsModelConfigSpeed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `const BetaManagedAgentsModelConfigSpeedStandard BetaManagedAgentsModelConfigSpeed = "standard"`
- `const BetaManagedAgentsModelConfigSpeedFast BetaManagedAgentsModelConfigSpeed = "fast"`
- `Multiagent BetaManagedAgentsMultiagent`
Resolved coordinator topology with a concrete agent roster.
- `Agents []BetaManagedAgentsAgentReference`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `ID string`
- `Type BetaManagedAgentsAgentReferenceType`
- `const BetaManagedAgentsAgentReferenceTypeAgent BetaManagedAgentsAgentReferenceType = "agent"`
- `Version int64`
- `Type BetaManagedAgentsMultiagentType`
- `const BetaManagedAgentsMultiagentTypeCoordinator BetaManagedAgentsMultiagentType = "coordinator"`
- `Name string`
- `Skills []BetaManagedAgentsAgentSkillUnion`
- `type BetaManagedAgentsAnthropicSkill struct{…}`
A resolved Anthropic-managed skill.
- `SkillID string`
- `Type BetaManagedAgentsAnthropicSkillType`
- `const BetaManagedAgentsAnthropicSkillTypeAnthropic BetaManagedAgentsAnthropicSkillType = "anthropic"`
- `Version string`
- `type BetaManagedAgentsCustomSkill struct{…}`
A resolved user-created custom skill.
- `SkillID string`
- `Type BetaManagedAgentsCustomSkillType`
- `const BetaManagedAgentsCustomSkillTypeCustom BetaManagedAgentsCustomSkillType = "custom"`
- `Version string`
- `System string`
- `Tools []BetaManagedAgentsAgentToolUnion`
- `type BetaManagedAgentsAgentToolset20260401 struct{…}`
- `Configs []BetaManagedAgentsAgentToolConfig`
- `Enabled bool`
- `Name BetaManagedAgentsAgentToolConfigName`
Built-in agent tool identifier.
- `const BetaManagedAgentsAgentToolConfigNameBash BetaManagedAgentsAgentToolConfigName = "bash"`
- `const BetaManagedAgentsAgentToolConfigNameEdit BetaManagedAgentsAgentToolConfigName = "edit"`
- `const BetaManagedAgentsAgentToolConfigNameRead BetaManagedAgentsAgentToolConfigName = "read"`
- `const BetaManagedAgentsAgentToolConfigNameWrite BetaManagedAgentsAgentToolConfigName = "write"`
- `const BetaManagedAgentsAgentToolConfigNameGlob BetaManagedAgentsAgentToolConfigName = "glob"`
- `const BetaManagedAgentsAgentToolConfigNameGrep BetaManagedAgentsAgentToolConfigName = "grep"`
- `const BetaManagedAgentsAgentToolConfigNameWebFetch BetaManagedAgentsAgentToolConfigName = "web_fetch"`
- `const BetaManagedAgentsAgentToolConfigNameWebSearch BetaManagedAgentsAgentToolConfigName = "web_search"`
- `PermissionPolicy BetaManagedAgentsAgentToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsAgentToolsetDefaultConfig`
Resolved default configuration for agent tools.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsAgentToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `Type BetaManagedAgentsAgentToolset20260401Type`
- `const BetaManagedAgentsAgentToolset20260401TypeAgentToolset20260401 BetaManagedAgentsAgentToolset20260401Type = "agent_toolset_20260401"`
- `type BetaManagedAgentsMCPToolset struct{…}`
- `Configs []BetaManagedAgentsMCPToolConfig`
- `Enabled bool`
- `Name string`
- `PermissionPolicy BetaManagedAgentsMCPToolConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `DefaultConfig BetaManagedAgentsMCPToolsetDefaultConfig`
Resolved default configuration for all tools from an MCP server.
- `Enabled bool`
- `PermissionPolicy BetaManagedAgentsMCPToolsetDefaultConfigPermissionPolicyUnion`
Permission policy for tool execution.
- `type BetaManagedAgentsAlwaysAllowPolicy struct{…}`
Tool calls are automatically approved without user confirmation.
- `Type BetaManagedAgentsAlwaysAllowPolicyType`
- `const BetaManagedAgentsAlwaysAllowPolicyTypeAlwaysAllow BetaManagedAgentsAlwaysAllowPolicyType = "always_allow"`
- `type BetaManagedAgentsAlwaysAskPolicy struct{…}`
Tool calls require user confirmation before execution.
- `Type BetaManagedAgentsAlwaysAskPolicyType`
- `const BetaManagedAgentsAlwaysAskPolicyTypeAlwaysAsk BetaManagedAgentsAlwaysAskPolicyType = "always_ask"`
- `MCPServerName string`
- `Type BetaManagedAgentsMCPToolsetType`
- `const BetaManagedAgentsMCPToolsetTypeMCPToolset BetaManagedAgentsMCPToolsetType = "mcp_toolset"`
- `type BetaManagedAgentsCustomTool struct{…}`
A custom tool as returned in API responses.
- `Description string`
- `InputSchema BetaManagedAgentsCustomToolInputSchema`
JSON Schema for custom tool input parameters.
- `Properties map[string, any]`
JSON Schema properties defining the tool's input parameters.
- `Required []string`
List of required property names.
- `Type BetaManagedAgentsCustomToolInputSchemaType`
Must be 'object' for tool input schemas.
- `const BetaManagedAgentsCustomToolInputSchemaTypeObject BetaManagedAgentsCustomToolInputSchemaType = "object"`
- `Name string`
- `Type BetaManagedAgentsCustomToolType`
- `const BetaManagedAgentsCustomToolTypeCustom BetaManagedAgentsCustomToolType = "custom"`
- `Type BetaManagedAgentsAgentType`
- `const BetaManagedAgentsAgentTypeAgent BetaManagedAgentsAgentType = "agent"`
- `UpdatedAt Time`
A timestamp in RFC 3339 format
- `Version int64`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```go
package main
import (
"context"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
func main() {
client := anthropic.NewClient(
option.WithAPIKey("my-anthropic-api-key"),
)
page, err := client.Beta.Agents.Versions.List(
context.TODO(),
"agent_011CZkYpogX7uDKUyvBTophP",
anthropic.BetaAgentVersionListParams{
},
)
if err != nil {
panic(err.Error())
}
fmt.Printf("%+v\n", page)
}
```
---
# Agents (Beta) (Java)
URL: https://platform.claude.com/docs/en/api/java/beta/agents
# Agents
## Create
`BetaManagedAgentsAgent beta().agents().create(AgentCreateParamsparams, RequestOptionsrequestOptions = RequestOptions.none())`
**post** `/v1/agents`
Create Agent
### Parameters
- `AgentCreateParams params`
- `Optional> betas`
Optional header to specify the beta version(s) you want to use.
- `MESSAGE_BATCHES_2024_09_24("message-batches-2024-09-24")`
- `PROMPT_CACHING_2024_07_31("prompt-caching-2024-07-31")`
- `COMPUTER_USE_2024_10_22("computer-use-2024-10-22")`
- `COMPUTER_USE_2025_01_24("computer-use-2025-01-24")`
- `PDFS_2024_09_25("pdfs-2024-09-25")`
- `TOKEN_COUNTING_2024_11_01("token-counting-2024-11-01")`
- `TOKEN_EFFICIENT_TOOLS_2025_02_19("token-efficient-tools-2025-02-19")`
- `OUTPUT_128K_2025_02_19("output-128k-2025-02-19")`
- `FILES_API_2025_04_14("files-api-2025-04-14")`
- `MCP_CLIENT_2025_04_04("mcp-client-2025-04-04")`
- `MCP_CLIENT_2025_11_20("mcp-client-2025-11-20")`
- `DEV_FULL_THINKING_2025_05_14("dev-full-thinking-2025-05-14")`
- `INTERLEAVED_THINKING_2025_05_14("interleaved-thinking-2025-05-14")`
- `CODE_EXECUTION_2025_05_22("code-execution-2025-05-22")`
- `EXTENDED_CACHE_TTL_2025_04_11("extended-cache-ttl-2025-04-11")`
- `CONTEXT_1M_2025_08_07("context-1m-2025-08-07")`
- `CONTEXT_MANAGEMENT_2025_06_27("context-management-2025-06-27")`
- `MODEL_CONTEXT_WINDOW_EXCEEDED_2025_08_26("model-context-window-exceeded-2025-08-26")`
- `SKILLS_2025_10_02("skills-2025-10-02")`
- `FAST_MODE_2026_02_01("fast-mode-2026-02-01")`
- `OUTPUT_300K_2026_03_24("output-300k-2026-03-24")`
- `USER_PROFILES_2026_03_24("user-profiles-2026-03-24")`
- `ADVISOR_TOOL_2026_03_01("advisor-tool-2026-03-01")`
- `MANAGED_AGENTS_2026_04_01("managed-agents-2026-04-01")`
- `Model model`
Model identifier. Accepts the [model string](https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison), e.g. `claude-opus-4-6`, or a `model_config` object for additional configuration control
- `enum BetaManagedAgentsModel:`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `CLAUDE_OPUS_4_7("claude-opus-4-7")`
Frontier intelligence for long-running agents and coding
- `CLAUDE_OPUS_4_6("claude-opus-4-6")`
Most intelligent model for building agents and coding
- `CLAUDE_SONNET_4_6("claude-sonnet-4-6")`
Best combination of speed and intelligence
- `CLAUDE_HAIKU_4_5("claude-haiku-4-5")`
Fastest model with near-frontier intelligence
- `CLAUDE_HAIKU_4_5_20251001("claude-haiku-4-5-20251001")`
Fastest model with near-frontier intelligence
- `CLAUDE_OPUS_4_5("claude-opus-4-5")`
Premium model combining maximum intelligence with practical performance
- `CLAUDE_OPUS_4_5_20251101("claude-opus-4-5-20251101")`
Premium model combining maximum intelligence with practical performance
- `CLAUDE_SONNET_4_5("claude-sonnet-4-5")`
High-performance model for agents and coding
- `CLAUDE_SONNET_4_5_20250929("claude-sonnet-4-5-20250929")`
High-performance model for agents and coding
- `class BetaManagedAgentsModelConfigParams:`
An object that defines additional configuration control over model use
- `BetaManagedAgentsModel id`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `CLAUDE_OPUS_4_7("claude-opus-4-7")`
Frontier intelligence for long-running agents and coding
- `CLAUDE_OPUS_4_6("claude-opus-4-6")`
Most intelligent model for building agents and coding
- `CLAUDE_SONNET_4_6("claude-sonnet-4-6")`
Best combination of speed and intelligence
- `CLAUDE_HAIKU_4_5("claude-haiku-4-5")`
Fastest model with near-frontier intelligence
- `CLAUDE_HAIKU_4_5_20251001("claude-haiku-4-5-20251001")`
Fastest model with near-frontier intelligence
- `CLAUDE_OPUS_4_5("claude-opus-4-5")`
Premium model combining maximum intelligence with practical performance
- `CLAUDE_OPUS_4_5_20251101("claude-opus-4-5-20251101")`
Premium model combining maximum intelligence with practical performance
- `CLAUDE_SONNET_4_5("claude-sonnet-4-5")`
High-performance model for agents and coding
- `CLAUDE_SONNET_4_5_20250929("claude-sonnet-4-5-20250929")`
High-performance model for agents and coding
- `Optional speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `STANDARD("standard")`
- `FAST("fast")`
- `String name`
Human-readable name for the agent. 1-256 characters.
- `Optional description`
Description of what the agent does. Up to 2048 characters.
- `Optional> mcpServers`
MCP servers this agent connects to. Maximum 20. Names must be unique within the array.
- `String name`
Unique name for this server, referenced by mcp_toolset configurations. 1-255 characters.
- `Type type`
- `URL("url")`
- `String url`
Endpoint URL for the MCP server.
- `Optional metadata`
Arbitrary key-value metadata. Maximum 16 pairs, keys up to 64 chars, values up to 512 chars.
- `Optional multiagent`
A coordinator topology: the session's primary thread orchestrates work by spawning session threads, each running an agent drawn from the `agents` roster.
- `Optional> skills`
Skills available to the agent. Maximum 20.
- `class BetaManagedAgentsAnthropicSkillParams:`
An Anthropic-managed skill.
- `String skillId`
Identifier of the Anthropic skill (e.g., "xlsx").
- `Type type`
- `ANTHROPIC("anthropic")`
- `Optional version`
Version to pin. Defaults to latest if omitted.
- `class BetaManagedAgentsCustomSkillParams:`
A user-created custom skill.
- `String skillId`
Tagged ID of the custom skill (e.g., "skill_01XJ5...").
- `Type type`
- `CUSTOM("custom")`
- `Optional version`
Version to pin. Defaults to latest if omitted.
- `Optional system`
System prompt for the agent. Up to 100,000 characters.
- `Optional> tools`
Tool configurations available to the agent. Maximum of 128 tools across all toolsets allowed.
- `class BetaManagedAgentsAgentToolset20260401Params:`
Configuration for built-in agent tools. Use this to enable or disable groups of tools available to the agent.
- `Type type`
- `AGENT_TOOLSET_20260401("agent_toolset_20260401")`
- `Optional> configs`
Per-tool configuration overrides.
- `Name name`
Built-in agent tool identifier.
- `BASH("bash")`
- `EDIT("edit")`
- `READ("read")`
- `WRITE("write")`
- `GLOB("glob")`
- `GREP("grep")`
- `WEB_FETCH("web_fetch")`
- `WEB_SEARCH("web_search")`
- `Optional enabled`
Whether this tool is enabled and available to Claude. Overrides the default_config setting.
- `Optional permissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `Type type`
- `ALWAYS_ALLOW("always_allow")`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `Type type`
- `ALWAYS_ASK("always_ask")`
- `Optional defaultConfig`
Default configuration for all tools in a toolset.
- `Optional enabled`
Whether tools are enabled and available to Claude by default. Defaults to true if not specified.
- `Optional permissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `Type type`
- `ALWAYS_ALLOW("always_allow")`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `Type type`
- `ALWAYS_ASK("always_ask")`
- `class BetaManagedAgentsMcpToolsetParams:`
Configuration for tools from an MCP server defined in `mcp_servers`.
- `String mcpServerName`
Name of the MCP server. Must match a server name from the mcp_servers array. 1-255 characters.
- `Type type`
- `MCP_TOOLSET("mcp_toolset")`
- `Optional> configs`
Per-tool configuration overrides.
- `String name`
Name of the MCP tool to configure. 1-128 characters.
- `Optional enabled`
Whether this tool is enabled. Overrides the `default_config` setting.
- `Optional permissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `Type type`
- `ALWAYS_ALLOW("always_allow")`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `Type type`
- `ALWAYS_ASK("always_ask")`
- `Optional defaultConfig`
Default configuration for all tools from an MCP server.
- `Optional enabled`
Whether tools are enabled by default. Defaults to true if not specified.
- `Optional permissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `Type type`
- `ALWAYS_ALLOW("always_allow")`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `Type type`
- `ALWAYS_ASK("always_ask")`
- `class BetaManagedAgentsCustomToolParams:`
A custom tool that is executed by the API client rather than the agent. When the agent calls this tool, an `agent.custom_tool_use` event is emitted and the session goes idle, waiting for the client to provide the result via a `user.custom_tool_result` event.
- `String description`
Description of what the tool does, shown to the agent to help it decide when to use the tool. 1-1024 characters.
- `BetaManagedAgentsCustomToolInputSchema inputSchema`
JSON Schema for custom tool input parameters.
- `Optional properties`
JSON Schema properties defining the tool's input parameters.
- `Optional> required`
List of required property names.
- `Optional type`
Must be 'object' for tool input schemas.
- `OBJECT("object")`
- `String name`
Unique name for the tool. 1-128 characters; letters, digits, underscores, and hyphens.
- `Type type`
- `CUSTOM("custom")`
### Returns
- `class BetaManagedAgentsAgent:`
A Managed Agents `agent`.
- `String id`
- `Optional archivedAt`
A timestamp in RFC 3339 format
- `LocalDateTime createdAt`
A timestamp in RFC 3339 format
- `Optional description`
- `List mcpServers`
- `String name`
- `Type type`
- `URL("url")`
- `String url`
- `Metadata metadata`
- `BetaManagedAgentsModelConfig model`
Model identifier and configuration.
- `BetaManagedAgentsModel id`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `CLAUDE_OPUS_4_7("claude-opus-4-7")`
Frontier intelligence for long-running agents and coding
- `CLAUDE_OPUS_4_6("claude-opus-4-6")`
Most intelligent model for building agents and coding
- `CLAUDE_SONNET_4_6("claude-sonnet-4-6")`
Best combination of speed and intelligence
- `CLAUDE_HAIKU_4_5("claude-haiku-4-5")`
Fastest model with near-frontier intelligence
- `CLAUDE_HAIKU_4_5_20251001("claude-haiku-4-5-20251001")`
Fastest model with near-frontier intelligence
- `CLAUDE_OPUS_4_5("claude-opus-4-5")`
Premium model combining maximum intelligence with practical performance
- `CLAUDE_OPUS_4_5_20251101("claude-opus-4-5-20251101")`
Premium model combining maximum intelligence with practical performance
- `CLAUDE_SONNET_4_5("claude-sonnet-4-5")`
High-performance model for agents and coding
- `CLAUDE_SONNET_4_5_20250929("claude-sonnet-4-5-20250929")`
High-performance model for agents and coding
- `Optional speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `STANDARD("standard")`
- `FAST("fast")`
- `Optional multiagent`
Resolved coordinator topology with a concrete agent roster.
- `List agents`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `String id`
- `Type type`
- `AGENT("agent")`
- `long version`
- `Type type`
- `COORDINATOR("coordinator")`
- `String name`
- `List skills`
- `class BetaManagedAgentsAnthropicSkill:`
A resolved Anthropic-managed skill.
- `String skillId`
- `Type type`
- `ANTHROPIC("anthropic")`
- `String version`
- `class BetaManagedAgentsCustomSkill:`
A resolved user-created custom skill.
- `String skillId`
- `Type type`
- `CUSTOM("custom")`
- `String version`
- `Optional system`
- `List tools`
- `class BetaManagedAgentsAgentToolset20260401:`
- `List configs`
- `boolean enabled`
- `Name name`
Built-in agent tool identifier.
- `BASH("bash")`
- `EDIT("edit")`
- `READ("read")`
- `WRITE("write")`
- `GLOB("glob")`
- `GREP("grep")`
- `WEB_FETCH("web_fetch")`
- `WEB_SEARCH("web_search")`
- `PermissionPolicy permissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `Type type`
- `ALWAYS_ALLOW("always_allow")`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `Type type`
- `ALWAYS_ASK("always_ask")`
- `BetaManagedAgentsAgentToolsetDefaultConfig defaultConfig`
Resolved default configuration for agent tools.
- `boolean enabled`
- `PermissionPolicy permissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `Type type`
- `ALWAYS_ALLOW("always_allow")`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `Type type`
- `ALWAYS_ASK("always_ask")`
- `Type type`
- `AGENT_TOOLSET_20260401("agent_toolset_20260401")`
- `class BetaManagedAgentsMcpToolset:`
- `List configs`
- `boolean enabled`
- `String name`
- `PermissionPolicy permissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `Type type`
- `ALWAYS_ALLOW("always_allow")`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `Type type`
- `ALWAYS_ASK("always_ask")`
- `BetaManagedAgentsMcpToolsetDefaultConfig defaultConfig`
Resolved default configuration for all tools from an MCP server.
- `boolean enabled`
- `PermissionPolicy permissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `Type type`
- `ALWAYS_ALLOW("always_allow")`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `Type type`
- `ALWAYS_ASK("always_ask")`
- `String mcpServerName`
- `Type type`
- `MCP_TOOLSET("mcp_toolset")`
- `class BetaManagedAgentsCustomTool:`
A custom tool as returned in API responses.
- `String description`
- `BetaManagedAgentsCustomToolInputSchema inputSchema`
JSON Schema for custom tool input parameters.
- `Optional properties`
JSON Schema properties defining the tool's input parameters.
- `Optional> required`
List of required property names.
- `Optional type`
Must be 'object' for tool input schemas.
- `OBJECT("object")`
- `String name`
- `Type type`
- `CUSTOM("custom")`
- `Type type`
- `AGENT("agent")`
- `LocalDateTime updatedAt`
A timestamp in RFC 3339 format
- `long version`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```java
package com.anthropic.example;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.agents.AgentCreateParams;
import com.anthropic.models.beta.agents.BetaManagedAgentsAgent;
import com.anthropic.models.beta.agents.BetaManagedAgentsModel;
public final class Main {
private Main() {}
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
AgentCreateParams params = AgentCreateParams.builder()
.model(BetaManagedAgentsModel.CLAUDE_SONNET_4_6)
.name("My First Agent")
.build();
BetaManagedAgentsAgent betaManagedAgentsAgent = client.beta().agents().create(params);
}
}
```
## List
`AgentListPage beta().agents().list(AgentListParamsparams = AgentListParams.none(), RequestOptionsrequestOptions = RequestOptions.none())`
**get** `/v1/agents`
List Agents
### Parameters
- `AgentListParams params`
- `Optional createdAtGte`
Return agents created at or after this time (inclusive).
- `Optional createdAtLte`
Return agents created at or before this time (inclusive).
- `Optional includeArchived`
Include archived agents in results. Defaults to false.
- `Optional limit`
Maximum results per page. Default 20, maximum 100.
- `Optional page`
Opaque pagination cursor from a previous response.
- `Optional> betas`
Optional header to specify the beta version(s) you want to use.
- `MESSAGE_BATCHES_2024_09_24("message-batches-2024-09-24")`
- `PROMPT_CACHING_2024_07_31("prompt-caching-2024-07-31")`
- `COMPUTER_USE_2024_10_22("computer-use-2024-10-22")`
- `COMPUTER_USE_2025_01_24("computer-use-2025-01-24")`
- `PDFS_2024_09_25("pdfs-2024-09-25")`
- `TOKEN_COUNTING_2024_11_01("token-counting-2024-11-01")`
- `TOKEN_EFFICIENT_TOOLS_2025_02_19("token-efficient-tools-2025-02-19")`
- `OUTPUT_128K_2025_02_19("output-128k-2025-02-19")`
- `FILES_API_2025_04_14("files-api-2025-04-14")`
- `MCP_CLIENT_2025_04_04("mcp-client-2025-04-04")`
- `MCP_CLIENT_2025_11_20("mcp-client-2025-11-20")`
- `DEV_FULL_THINKING_2025_05_14("dev-full-thinking-2025-05-14")`
- `INTERLEAVED_THINKING_2025_05_14("interleaved-thinking-2025-05-14")`
- `CODE_EXECUTION_2025_05_22("code-execution-2025-05-22")`
- `EXTENDED_CACHE_TTL_2025_04_11("extended-cache-ttl-2025-04-11")`
- `CONTEXT_1M_2025_08_07("context-1m-2025-08-07")`
- `CONTEXT_MANAGEMENT_2025_06_27("context-management-2025-06-27")`
- `MODEL_CONTEXT_WINDOW_EXCEEDED_2025_08_26("model-context-window-exceeded-2025-08-26")`
- `SKILLS_2025_10_02("skills-2025-10-02")`
- `FAST_MODE_2026_02_01("fast-mode-2026-02-01")`
- `OUTPUT_300K_2026_03_24("output-300k-2026-03-24")`
- `USER_PROFILES_2026_03_24("user-profiles-2026-03-24")`
- `ADVISOR_TOOL_2026_03_01("advisor-tool-2026-03-01")`
- `MANAGED_AGENTS_2026_04_01("managed-agents-2026-04-01")`
### Returns
- `class BetaManagedAgentsAgent:`
A Managed Agents `agent`.
- `String id`
- `Optional archivedAt`
A timestamp in RFC 3339 format
- `LocalDateTime createdAt`
A timestamp in RFC 3339 format
- `Optional description`
- `List mcpServers`
- `String name`
- `Type type`
- `URL("url")`
- `String url`
- `Metadata metadata`
- `BetaManagedAgentsModelConfig model`
Model identifier and configuration.
- `BetaManagedAgentsModel id`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `CLAUDE_OPUS_4_7("claude-opus-4-7")`
Frontier intelligence for long-running agents and coding
- `CLAUDE_OPUS_4_6("claude-opus-4-6")`
Most intelligent model for building agents and coding
- `CLAUDE_SONNET_4_6("claude-sonnet-4-6")`
Best combination of speed and intelligence
- `CLAUDE_HAIKU_4_5("claude-haiku-4-5")`
Fastest model with near-frontier intelligence
- `CLAUDE_HAIKU_4_5_20251001("claude-haiku-4-5-20251001")`
Fastest model with near-frontier intelligence
- `CLAUDE_OPUS_4_5("claude-opus-4-5")`
Premium model combining maximum intelligence with practical performance
- `CLAUDE_OPUS_4_5_20251101("claude-opus-4-5-20251101")`
Premium model combining maximum intelligence with practical performance
- `CLAUDE_SONNET_4_5("claude-sonnet-4-5")`
High-performance model for agents and coding
- `CLAUDE_SONNET_4_5_20250929("claude-sonnet-4-5-20250929")`
High-performance model for agents and coding
- `Optional speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `STANDARD("standard")`
- `FAST("fast")`
- `Optional multiagent`
Resolved coordinator topology with a concrete agent roster.
- `List agents`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `String id`
- `Type type`
- `AGENT("agent")`
- `long version`
- `Type type`
- `COORDINATOR("coordinator")`
- `String name`
- `List skills`
- `class BetaManagedAgentsAnthropicSkill:`
A resolved Anthropic-managed skill.
- `String skillId`
- `Type type`
- `ANTHROPIC("anthropic")`
- `String version`
- `class BetaManagedAgentsCustomSkill:`
A resolved user-created custom skill.
- `String skillId`
- `Type type`
- `CUSTOM("custom")`
- `String version`
- `Optional system`
- `List tools`
- `class BetaManagedAgentsAgentToolset20260401:`
- `List configs`
- `boolean enabled`
- `Name name`
Built-in agent tool identifier.
- `BASH("bash")`
- `EDIT("edit")`
- `READ("read")`
- `WRITE("write")`
- `GLOB("glob")`
- `GREP("grep")`
- `WEB_FETCH("web_fetch")`
- `WEB_SEARCH("web_search")`
- `PermissionPolicy permissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `Type type`
- `ALWAYS_ALLOW("always_allow")`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `Type type`
- `ALWAYS_ASK("always_ask")`
- `BetaManagedAgentsAgentToolsetDefaultConfig defaultConfig`
Resolved default configuration for agent tools.
- `boolean enabled`
- `PermissionPolicy permissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `Type type`
- `ALWAYS_ALLOW("always_allow")`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `Type type`
- `ALWAYS_ASK("always_ask")`
- `Type type`
- `AGENT_TOOLSET_20260401("agent_toolset_20260401")`
- `class BetaManagedAgentsMcpToolset:`
- `List configs`
- `boolean enabled`
- `String name`
- `PermissionPolicy permissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `Type type`
- `ALWAYS_ALLOW("always_allow")`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `Type type`
- `ALWAYS_ASK("always_ask")`
- `BetaManagedAgentsMcpToolsetDefaultConfig defaultConfig`
Resolved default configuration for all tools from an MCP server.
- `boolean enabled`
- `PermissionPolicy permissionPolicy`
Permission policy for tool execution.
- `class BetaManagedAgentsAlwaysAllowPolicy:`
Tool calls are automatically approved without user confirmation.
- `Type type`
- `ALWAYS_ALLOW("always_allow")`
- `class BetaManagedAgentsAlwaysAskPolicy:`
Tool calls require user confirmation before execution.
- `Type type`
- `ALWAYS_ASK("always_ask")`
- `String mcpServerName`
- `Type type`
- `MCP_TOOLSET("mcp_toolset")`
- `class BetaManagedAgentsCustomTool:`
A custom tool as returned in API responses.
- `String description`
- `BetaManagedAgentsCustomToolInputSchema inputSchema`
JSON Schema for custom tool input parameters.
- `Optional properties`
JSON Schema properties defining the tool's input parameters.
- `Optional> required`
List of required property names.
- `Optional type`
Must be 'object' for tool input schemas.
- `OBJECT("object")`
- `String name`
- `Type type`
- `CUSTOM("custom")`
- `Type type`
- `AGENT("agent")`
- `LocalDateTime updatedAt`
A timestamp in RFC 3339 format
- `long version`
The agent's current version. Starts at 1 and increments when the agent is modified.
### Example
```java
package com.anthropic.example;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.agents.AgentListPage;
import com.anthropic.models.beta.agents.AgentListParams;
public final class Main {
private Main() {}
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
AgentListPage page = client.beta().agents().list();
}
}
```
## Retrieve
`BetaManagedAgentsAgent beta().agents().retrieve(AgentRetrieveParamsparams = AgentRetrieveParams.none(), RequestOptionsrequestOptions = RequestOptions.none())`
**get** `/v1/agents/{agent_id}`
Get Agent
### Parameters
- `AgentRetrieveParams params`
- `Optional agentId`
- `Optional version`
Agent version. Omit for the most recent version. Must be at least 1 if specified.
- `Optional> betas`
Optional header to specify the beta version(s) you want to use.
- `MESSAGE_BATCHES_2024_09_24("message-batches-2024-09-24")`
- `PROMPT_CACHING_2024_07_31("prompt-caching-2024-07-31")`
- `COMPUTER_USE_2024_10_22("computer-use-2024-10-22")`
- `COMPUTER_USE_2025_01_24("computer-use-2025-01-24")`
- `PDFS_2024_09_25("pdfs-2024-09-25")`
- `TOKEN_COUNTING_2024_11_01("token-counting-2024-11-01")`
- `TOKEN_EFFICIENT_TOOLS_2025_02_19("token-efficient-tools-2025-02-19")`
- `OUTPUT_128K_2025_02_19("output-128k-2025-02-19")`
- `FILES_API_2025_04_14("files-api-2025-04-14")`
- `MCP_CLIENT_2025_04_04("mcp-client-2025-04-04")`
- `MCP_CLIENT_2025_11_20("mcp-client-2025-11-20")`
- `DEV_FULL_THINKING_2025_05_14("dev-full-thinking-2025-05-14")`
- `INTERLEAVED_THINKING_2025_05_14("interleaved-thinking-2025-05-14")`
- `CODE_EXECUTION_2025_05_22("code-execution-2025-05-22")`
- `EXTENDED_CACHE_TTL_2025_04_11("extended-cache-ttl-2025-04-11")`
- `CONTEXT_1M_2025_08_07("context-1m-2025-08-07")`
- `CONTEXT_MANAGEMENT_2025_06_27("context-management-2025-06-27")`
- `MODEL_CONTEXT_WINDOW_EXCEEDED_2025_08_26("model-context-window-exceeded-2025-08-26")`
- `SKILLS_2025_10_02("skills-2025-10-02")`
- `FAST_MODE_2026_02_01("fast-mode-2026-02-01")`
- `OUTPUT_300K_2026_03_24("output-300k-2026-03-24")`
- `USER_PROFILES_2026_03_24("user-profiles-2026-03-24")`
- `ADVISOR_TOOL_2026_03_01("advisor-tool-2026-03-01")`
- `MANAGED_AGENTS_2026_04_01("managed-agents-2026-04-01")`
### Returns
- `class BetaManagedAgentsAgent:`
A Managed Agents `agent`.
- `String id`
- `Optional archivedAt`
A timestamp in RFC 3339 format
- `LocalDateTime createdAt`
A timestamp in RFC 3339 format
- `Optional description`
- `List mcpServers`
- `String name`
- `Type type`
- `URL("url")`
- `String url`
- `Metadata metadata`
- `BetaManagedAgentsModelConfig model`
Model identifier and configuration.
- `BetaManagedAgentsModel id`
The model that will power your agent.
See [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options.
- `CLAUDE_OPUS_4_7("claude-opus-4-7")`
Frontier intelligence for long-running agents and coding
- `CLAUDE_OPUS_4_6("claude-opus-4-6")`
Most intelligent model for building agents and coding
- `CLAUDE_SONNET_4_6("claude-sonnet-4-6")`
Best combination of speed and intelligence
- `CLAUDE_HAIKU_4_5("claude-haiku-4-5")`
Fastest model with near-frontier intelligence
- `CLAUDE_HAIKU_4_5_20251001("claude-haiku-4-5-20251001")`
Fastest model with near-frontier intelligence
- `CLAUDE_OPUS_4_5("claude-opus-4-5")`
Premium model combining maximum intelligence with practical performance
- `CLAUDE_OPUS_4_5_20251101("claude-opus-4-5-20251101")`
Premium model combining maximum intelligence with practical performance
- `CLAUDE_SONNET_4_5("claude-sonnet-4-5")`
High-performance model for agents and coding
- `CLAUDE_SONNET_4_5_20250929("claude-sonnet-4-5-20250929")`
High-performance model for agents and coding
- `Optional speed`
Inference speed mode. `fast` provides significantly faster output token generation at premium pricing. Not all models support `fast`; invalid combinations are rejected at create time.
- `STANDARD("standard")`
- `FAST("fast")`
- `Optional multiagent`
Resolved coordinator topology with a concrete agent roster.
- `List agents`
Agents the coordinator may spawn as session threads, each resolved to a specific version.
- `String id`
- `Type type`
- `AGENT("agent")`
- `long version`
- `Type type`
- `COORDINATOR("coordinator")`
- `String name`
- `List skills`
- `class BetaManagedAgentsAnthropicSkill:`
A resolved Anthropic-managed skill.
- `String skillId`
- `Type type`
- `ANTHROPIC("anthropic")`
- `String version`
- `class BetaManagedAgentsCustomSkill:`
A resolved user-created custom skill.
- `String skillId`
- `Type type`
- `CUSTOM("custom")`
- `String version`
- `Optional system`
- `List