Claude API Tool Use: A Python Example
If you're searching for a working Claude API tool use Python example, you want code you can run, not just theory. This article walks through a complete script: defining a tool, sending a request, handling Claude's tool_use response, executing the function, and returning the result so Claude can finish its answer.
Tool use (also called function calling) lets Claude call code you control — a weather lookup, a database query, a calculator — instead of guessing an answer. Claude doesn't execute anything itself; it tells you which tool to call and with what arguments, and your Python code runs it and sends the result back.
How Tool Use Works, in Practice
The flow is always the same four steps:
- You send a message plus a list of available tools (name, description, JSON schema for inputs).
- Claude replies with
stop_reason: "tool_use"and a block containing the tool name and input arguments. - Your code runs the actual function and packages the output as a
tool_result. - You send that result back in a new message; Claude uses it to produce the final answer.
This is a multi-turn exchange even for a single user question — Claude's first reply isn't the final answer, it's a request for data.
Full Python Example
This example defines a get_stock_price tool and walks through the full loop using the requests library and the Anthropic Messages API format.
import requests
import json
API_KEY = "your-api-key"
URL = "https://api.anthropic.com/v1/messages"
HEADERS = {
"x-api-key": API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
}
tools = [
{
"name": "get_stock_price",
"description": "Get the current price of a stock by ticker symbol.",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "Stock ticker symbol, e.g. AAPL",
}
},
"required": ["ticker"],
},
}
]
def get_stock_price(ticker: str) -> dict:
# Replace with a real API call in production
fake_prices = {"AAPL": 227.14, "MSFT": 418.30}
return {"ticker": ticker, "price": fake_prices.get(ticker, 0.0)}
messages = [
{"role": "user", "content": "What's the current price of AAPL?"}
]
response = requests.post(
URL,
headers=HEADERS,
json={
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"tools": tools,
"messages": messages,
},
).json()
if response["stop_reason"] == "tool_use":
tool_block = next(
block for block in response["content"] if block["type"] == "tool_use"
)
tool_name = tool_block["name"]
tool_input = tool_block["input"]
tool_use_id = tool_block["id"]
if tool_name == "get_stock_price":
result = get_stock_price(**tool_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),
}
],
}
)
final = requests.post(
URL,
headers=HEADERS,
json={
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"tools": tools,
"messages": messages,
},
).json()
print(final["content"][0]["text"])
A few details that trip people up:
- The assistant message must be echoed back exactly. Append the full
contentarray from Claude's tool-use response before adding the tool result, or the conversation history breaks. tool_resultcontent must match thetool_use_id. If you have multiple tools called in parallel, each needs its own matching result block.- Claude may call multiple tools in one turn. Always loop over
contentlooking fortype == "tool_use"rather than assuming there's exactly one. stop_reasontells you what happened."tool_use"means Claude wants a function call;"end_turn"means it's done; anything else (like"max_tokens") needs its own handling.
Running This Against SubToAPI
If you're already using SubToAPI to turn your Claude subscription into an API key, the same script works with almost no changes — just swap the endpoint and auth header:
URL = "https://api.subtoapi.app/v1/messages"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"content-type": "application/json",
}
The request and response shapes for tool use — tools, tool_use blocks, tool_result messages — are unchanged, since SubToAPI passes through the same Messages API format. This is useful if you want a single dashboard for API keys, streaming, and usage metadata across a team, without managing separate Anthropic billing per developer. Details on the request format are in the docs and the tool use docs specifically; general message handling is covered in the Messages API docs. You can try it with a free trial at signup, and plans start at €9/month on the pricing page.
Common Mistakes to Avoid
- Forgetting
input_schemais JSON Schema, not a Python type hint. Use"type": "string","type": "number","type": "array", etc., with properrequiredfields. - Not validating tool input before executing it. Claude generates arguments based on its understanding of the schema, but you should still validate types and ranges before calling real functions, especially anything that touches a database or external API.
- Treating the first response as final. A
tool_usestop reason always means another round-trip is needed — there's no shortcut. - Mixing up text and tool_use blocks. A single response can contain both a text block (Claude explaining what it's doing) and a tool_use block. Iterate over
contentrather than assuming a fixed structure.
Once this loop works for one tool, adding more is just appending to the tools list and extending the dispatch logic (an if/elif or a dict of function name to callable) that routes tool_name to the right Python function.
questions
Can Claude call more than one tool in a single response? Yes. Claude can return multiple tool_use blocks in one content array when a task needs several function calls. Loop through all blocks, execute each, and return all corresponding tool_result blocks in the next message.
Does tool use work with streaming responses? Yes, but the tool input arguments arrive as accumulated JSON deltas rather than a single block, so you need to buffer the partial JSON until the block closes before parsing it.
What happens if my function throws an error? Return the error as the tool_result content (as a string) and set "is_error": true on that block. Claude will see the failure and can retry, ask for clarification, or explain the problem to the user instead of crashing your application.