← Blog

Claude API Agent With Tool Calling: A Working Example

2026-10-17 · 5 min read · SubToAPI Team

Building a Claude API agent with tool calling means writing code that lets Claude decide when to call a function you've defined — like get_weather or search_database — then feeding the result back so Claude can finish its answer. This article walks through a complete, runnable example: the request structure, the response parsing, and the loop that ties it together.

If you've only used Claude for single-turn text generation, tool calling is the piece that turns it into an agent: something that can take actions, observe results, and decide what to do next instead of just producing one block of text.

What "tool calling" actually is

You send Claude a list of tools (name, description, JSON schema for inputs) alongside your prompt. Claude doesn't execute anything — it returns a response that says "call this tool with these arguments." Your code runs the actual function, then sends the result back to Claude in a follow-up message. Claude reads that result and either calls another tool or returns a final answer.

This request/response/execute/respond cycle is the entire mechanism behind every Claude-based agent, no matter how complex the end product looks.

Defining a tool

A tool is a JSON Schema object describing what Claude can call:

{
  "name": "get_weather",
  "description": "Get current weather for a city",
  "input_schema": {
    "type": "object",
    "properties": {
      "city": { "type": "string", "description": "City name" },
      "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
    },
    "required": ["city"]
  }
}

The description matters more than most people expect — Claude uses it to decide whether to call the tool at all, not just how to fill in the arguments. Vague descriptions lead to tools being ignored or misused.

The request

Here's a minimal request to the Claude Messages API with one tool attached:

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-sonnet-4-5",
    "max_tokens": 1024,
    "tools": [{
      "name": "get_weather",
      "description": "Get current weather for a city",
      "input_schema": {
        "type": "object",
        "properties": {
          "city": {"type": "string"},
          "unit": {"type": "string", "enum": ["celsius","fahrenheit"]}
        },
        "required": ["city"]
      }
    }],
    "messages": [
      {"role": "user", "content": "What is the weather in Lisbon right now?"}
    ]
  }'

If Claude decides to use the tool, the response contains a content block with "type": "tool_use", a name, an id, and input (the arguments it filled in). There's no plain text to show the user yet — you need to run the tool and reply.

The agent loop

The core of any tool-calling agent is a loop: send messages, check if Claude wants a tool, run it, append the result, repeat until Claude returns plain text.

async function runAgent(userMessage, tools, executeTool) {
  let messages = [{ role: "user", content: userMessage }];

  while (true) {
    const response = await callClaude(messages, tools);
    const toolUses = response.content.filter(b => b.type === "tool_use");

    if (toolUses.length === 0) {
      return response.content.find(b => b.type === "text")?.text;
    }

    messages.push({ role: "assistant", content: response.content });

    const toolResults = [];
    for (const call of toolUses) {
      const result = await executeTool(call.name, call.input);
      toolResults.push({
        type: "tool_result",
        tool_use_id: call.id,
        content: JSON.stringify(result)
      });
    }
    messages.push({ role: "user", content: toolResults });
  }
}

A few details matter here:

Handling multiple tool calls in one turn

Claude can request several tools at once — for example, checking weather in three cities in parallel. Your toolUses array will have multiple entries, and you must return a result for every one of them before the next request, even if some fail:

const result = await executeTool(call.name, call.input).catch(err => ({
  error: err.message
}));

Returning an error object as the tool result (rather than throwing) lets Claude see the failure and decide whether to retry, try a different approach, or tell the user it can't complete the request. This is usually better than crashing the loop.

Running this through SubToAPI

If you're already using Claude through SubToAPI, the agent loop above works unchanged — you just swap the endpoint and auth header. SubToAPI exposes the same Messages-style API with sub_live_ application keys instead of raw Anthropic credentials, which matters once you have more than one app or teammate hitting Claude:

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 1024,
    "tools": [ /* same tool schema */ ],
    "messages": [ /* same messages */ ]
  }'

Tool definitions, tool_use blocks, and tool_result messages all follow the same shape — see /docs/tools for the full reference. The practical benefit is usage metadata per application key and per seat, so if your agent is shared across a team, you can see exactly which key is driving tool calls and token usage without scraping logs yourself.

Streaming tool-calling agents

Tool use works with streaming too, but you need to buffer input_json deltas until the tool_use block closes before you have a complete, parseable input object. Partial JSON isn't safe to execute against. See /docs/streaming for the event sequence if you want to stream intermediate text while a tool call is still being assembled.

Common mistakes to avoid

Start with /docs/quickstart if you want the full request/response contract, or /docs/messages for everything the Messages endpoint accepts beyond tools (system prompts, stop sequences, multi-turn history). A free trial is available at /signup, and plan details — including team seats if multiple developers will be building against the same agent — are on /pricing.

Questions

Does Claude execute the tool itself? No. Claude only returns the tool name and arguments it wants to use. Your application code is responsible for actually running the function and sending the result back.

Can an agent call multiple tools in a single turn? Yes. Claude can return several tool_use blocks in one response, and you must return a tool_result for each one, matched by tool_use_id, before continuing.

How do I stop an agent loop from running forever? Add a hard iteration limit (5–8 is typical) and treat hitting the limit as a failure case — return a message to the user rather than looping indefinitely on ambiguous tool results.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →