Programmatic tool calling allows Claude to write code that calls your tools programmatically within a code execution 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.
Programmatic tool calling is currently in public beta.
To use this feature, add the "advanced-tool-use-2025-11-20" beta header to your API requests.
This feature requires the code execution tool to be enabled.
Please reach out through our feedback form to share your feedback on this feature.
Programmatic tool calling is available on the following models:
| Model | Tool Version |
|---|---|
Claude Opus 4.5 (claude-opus-4-5-20251101) | code_execution_20250825 |
Claude Sonnet 4.5 (claude-sonnet-4-5-20250929) | code_execution_20250825 |
Programmatic tool calling is available via the Claude API and Microsoft Foundry.
Here's a simple example where Claude programmatically queries a database multiple times and aggregates results:
When you configure a tool to be callable from code execution and Claude decides to use that tool:
tool_use blockThis approach is particularly useful for:
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("<sql>")) and automatically includes the appropriate async wrapper function.
The async wrapper is omitted from code examples in this documentation for clarity.
allowed_callers fieldThe allowed_callers field specifies which contexts can invoke a tool:
{
"name": "query_database",
"description": "Execute a SQL query against the database",
"input_schema": {...},
"allowed_callers": ["code_execution_20250825"]
}Possible values:
["direct"] - Only Claude can call this tool directly (default if omitted)["code_execution_20250825"] - Only callable from within code execution["direct", "code_execution_20250825"] - Callable both directly and from code executionWe recommend choosing either ["direct"] or ["code_execution_20250825"] for each tool rather than enabling both, as this provides clearer guidance to Claude for how best to use the tool.
caller field in responsesEvery tool use block includes a caller field indicating how it was invoked:
Direct invocation (traditional tool use):
{
"type": "tool_use",
"id": "toolu_abc123",
"name": "query_database",
"input": {"sql": "<sql>"},
"caller": {"type": "direct"}
}Programmatic invocation:
{
"type": "tool_use",
"id": "toolu_xyz789",
"name": "query_database",
"input": {"sql": "<sql>"},
"caller": {
"type": "code_execution_20250825",
"tool_id": "srvtoolu_abc123"
}
}The tool_id references the code execution tool that made the programmatic call.
Programmatic tool calling uses the same containers as code execution:
container fieldWhen 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.
Here's how a complete programmatic tool calling flow works:
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 will attempt 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.
Claude writes code that calls your tool. The API pauses and returns:
{
"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('<sql>')\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": "<sql>"},
"caller": {
"type": "code_execution_20250825",
"tool_id": "srvtoolu_abc123"
}
}
],
"container": {
"id": "container_xyz789",
"expires_at": "2025-01-15T14:30:00Z"
},
"stop_reason": "tool_use"
}Include the full conversation history plus your tool result:
The code execution continues and processes the results. If additional tool calls are needed, repeat Step 3 until all tool calls are satisfied.
Once the code execution completes, Claude provides the final response:
{
"content": [
{
"type": "code_execution_tool_result",
"tool_use_id": "srvtoolu_abc123",
"content": {
"type": "code_execution_result",
"stdout": "Top 5 customers by revenue:\n1. Customer C1: $45,000\n2. Customer C2: $38,000\n3. Customer C5: $32,000\n4. Customer C8: $28,500\n5. Customer C3: $24,000",
"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"
}Claude can write code that processes multiple items efficiently:
# async wrapper omitted for clarity
regions = ["West", "East", "Central", "North", "South"]
results = {}
for region in regions:
data = await query_database(f"<sql for {region}>")
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")This pattern:
Claude can stop processing as soon as success criteria are met:
# async wrapper omitted for clarity
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# async wrapper omitted for clarity
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)# async wrapper omitted for clarity
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)When code execution calls a tool:
{
"type": "tool_use",
"id": "toolu_abc123",
"name": "query_database",
"input": {"sql": "<sql>"},
"caller": {
"type": "code_execution_20250825",
"tool_id": "srvtoolu_xyz789"
}
}Your tool result is passed back to the running code:
{
"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}, ...]"
}
]
}When all tool calls are satisfied and code completes:
{
"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 | 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 |
missing_beta_header | PTC beta header not provided | Add both beta headers to your request |
If your tool takes too long to respond, the code execution will receive a TimeoutError. Claude sees this in stderr and will typically retry:
{
"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:
expires_at field in responsesIf your tool returns an error:
# Provide error information in the tool result
{
"type": "tool_result",
"tool_use_id": "toolu_abc123",
"content": "Error: Query timeout - table lock exceeded 30 seconds"
}Claude's code will receive this error and can handle it appropriately.
strict: true are not supported with programmatic callingtool_choicedisable_parallel_tool_use: true is not supported with programmatic callingThe following tools cannot currently be called programmatically, but support may be added in future releases:
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
{
"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?"} // This will cause an error
]
}
// ✅ VALID - Only tool results when responding to programmatic tool calls
{
"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.
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.
When implementing custom tools that will be called programmatically:
Programmatic tool calling can significantly reduce token consumption:
For example, calling 10 tools directly uses ~10x the tokens of calling them programmatically and returning a summary.
Programmatic tool calling uses the same pricing as code execution. See the code execution 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.
Good use cases:
Less ideal use cases:
"Tool not allowed" error
"allowed_callers": ["code_execution_20250825"]Container expiration
expires_at field in responsesBeta header issues
"advanced-tool-use-2025-11-20"Tool result not parsed correctly
caller field to confirm programmatic invocationClaude'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:
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.
Programmatic tool calling is a generalizable pattern that can be implemented outside of Anthropic's managed code execution. Here's an overview of the approaches:
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:
Disadvantages:
Use when: Your application can safely execute arbitrary code, you want a simple solution, and Anthropic's managed offering doesn't fit your needs.
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:
Disadvantages:
Use when: Security is critical and Anthropic's managed solution doesn't fit your requirements.
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:
We recommend using Anthropic's managed solution if you're using the Claude API.
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "anthropic-beta: advanced-tool-use-2025-11-20" \
--header "content-type: application/json" \
--data '{
"model": "claude-sonnet-4-5",
"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_20250825",
"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_20250825"]
}
]
}'response = client.beta.messages.create(
model="claude-sonnet-4-5",
betas=["advanced-tool-use-2025-11-20"],
max_tokens=4096,
messages=[{
"role": "user",
"content": "Query customer purchase history from the last quarter and identify our top 5 customers by revenue"
}],
tools=[
{
"type": "code_execution_20250825",
"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": {...},
"allowed_callers": ["code_execution_20250825"]
}
]
)response = client.beta.messages.create(
model="claude-sonnet-4-5",
betas=["advanced-tool-use-2025-11-20"],
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": "<sql>"},
"caller": {
"type": "code_execution_20250825",
"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=[...]
)Understand the fundamentals of tool use with Claude.