# Claude API Tool Use

> Define a tool with a JSON schema, catch the tool_use block, run your function, and send back a tool_result — in a minimal Python loop.

- Canonical: https://guides-ai.pages.dev/guides/claude-api-tool-use/
- Plate 15.02 · Topic: Building with the API (https://guides-ai.pages.dev/topics/api/)
- Published: 06 Sept 2026 · 4 min read
- Source site: guides-ai — https://guides-ai.pages.dev/

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

```python
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

```json
{
  "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

```python
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

```python
{
    "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](/guides/claude-api-structured-output/) · [your first Claude API request](/guides/claude-api-first-request/).
