Building with the API

Claude API Tool Use

4 min read

Claude never executes your code. It returns a structured request to call a function; your app runs it and sends the answer back.

1. Define the tool

tools = [{
    "name": "get_weather",
    "description": "Get the current weather for a given location.",
    "input_schema": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "City and state, e.g. San Francisco, CA",
            }
        },
        "required": ["location"],
    },
}]

name, description and input_schema are the only required fields. The description is part of the prompt — write it like documentation, not like a variable name.

2. What comes back

{
  "stop_reason": "tool_use",
  "content": [
    { "type": "text", "text": "Let me check." },
    {
      "type": "tool_use",
      "id": "toolu_01A09q90qw90lq917835lq9",
      "name": "get_weather",
      "input": { "location": "San Francisco, CA" }
    }
  ]
}

stop_reason: "tool_use" is the signal to run something. Keep the id — your result has to reference it.

3. The loop

import json
import anthropic

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY

def get_weather(location):
    return {"location": location, "temp_c": 15, "sky": "partly cloudy"}

messages = [{"role": "user", "content": "What's the weather in San Francisco?"}]

while True:
    response = client.messages.create(
        model="claude-opus-5",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )
    if response.stop_reason != "tool_use":
        break

    messages.append({"role": "assistant", "content": response.content})
    results = []
    for block in response.content:
        if block.type == "tool_use":
            results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": json.dumps(get_weather(**block.input)),
            })
    messages.append({"role": "user", "content": results})

print(next(b.text for b in response.content if b.type == "text"))

Runs until Claude stops asking for tools. Two rules that bite people: tool results go back in a user message, and every tool_use block from one turn must be answered in a single message — splitting them teaches the model to stop calling tools in parallel.

Errors and guarantees

{
    "type": "tool_result",
    "tool_use_id": block.id,
    "content": "Error: unknown city 'xyz'. Ask the user for a valid city.",
    "is_error": True,
}

Reports a failed call instead of dropping it, so Claude can retry or ask a follow-up question. Add "strict": True to a tool definition (with required and "additionalProperties": false) to guarantee the arguments validate against your schema.

block.input is already a parsed dict — use it as data, never string-match the serialized JSON.


Next: reliable JSON output · your first Claude API request.

Open the full interactive version (with copy buttons) ↗

← All guides