← Blog

Claude API Tool Calling: Parallel Execution Explained

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

When Claude decides it needs more than one tool to answer a request, it doesn't ask for them one at a time — it can return multiple tool_use blocks in a single response. "Parallel execution" refers to running those tool calls concurrently on your side instead of looping through them sequentially, then sending all the results back to Claude in one follow-up message.

This matters because sequential execution adds latency for no reason. If Claude asks for a weather lookup and a currency conversion in the same turn, there's no dependency between them — running them in parallel can cut your round-trip time significantly, especially when individual tools take 200–800ms to respond (database queries, external APIs, search calls). Below is how parallel tool calling actually works at the message level, and how to implement it correctly.

How Claude Signals Multiple Tool Calls

When Claude's response includes more than one tool it wants to invoke, the content array in the API response contains multiple blocks with type: "tool_use", each with its own id, name, and input:

{
  "content": [
    { "type": "text", "text": "I'll check both for you." },
    { "type": "tool_use", "id": "toolu_01A", "name": "get_weather", "input": { "city": "Lisbon" } },
    { "type": "tool_use", "id": "toolu_01B", "name": "convert_currency", "input": { "from": "EUR", "to": "USD", "amount": 100 } }
  ],
  "stop_reason": "tool_use"
}

There's no ordering guarantee you should rely on for execution — each tool_use block is independent unless your own prompt or tool definitions imply a dependency (e.g., tool B needs the output of tool A). Claude doesn't know whether your tools depend on each other; that logic lives in your application.

Executing Tool Calls Concurrently

The practical pattern is: extract every tool_use block, map each one to the right handler, run them with Promise.all (or your language's equivalent), and collect tool_result blocks in the same order as the inputs.

async function runTools(toolUseBlocks) {
  const handlers = {
    get_weather: getWeather,
    convert_currency: convertCurrency,
  };

  const results = await Promise.all(
    toolUseBlocks.map(async (block) => {
      try {
        const output = await handlers[block.name](block.input);
        return {
          type: "tool_result",
          tool_use_id: block.id,
          content: JSON.stringify(output),
        };
      } catch (err) {
        return {
          type: "tool_result",
          tool_use_id: block.id,
          content: JSON.stringify({ error: err.message }),
          is_error: true,
        };
      }
    })
  );

  return results;
}

A few things to get right here:

Returning Results to Claude

Once you have all tool_result blocks, send them back as a single user message containing all of them, not one message per result:

const response = await client.messages.create({
  model: "claude-opus-4",
  max_tokens: 1024,
  messages: [
    ...previousMessages,
    { role: "assistant", content: assistantBlocks },
    { role: "user", content: toolResults }, // all results together
  ],
  tools: toolDefinitions,
});

Sending results in separate messages breaks the conversation structure Claude expects and will often produce a 400 error or degraded responses. One assistant turn with N tool calls gets exactly one user turn with N tool results.

Common Pitfalls

Rate limits on the tool side, not the model side. Running five tool calls in parallel is great until four of them hit the same third-party API and you get throttled. Add a concurrency cap (e.g., p-limit in Node) if your tools call rate-limited services.

Retrying the whole batch instead of the failed call. If one tool times out, don't resend all five — retry just that one and keep the others' results as-is.

Ignoring stop_reason. Only treat the response as a tool-calling turn if stop_reason is "tool_use". Text-only responses won't have tool blocks to execute.

Token overhead from verbose tool results. Large JSON payloads in tool_result content count toward your context window. Trim unnecessary fields before sending results back — Claude doesn't need your entire API response, just what's relevant to the question.

Streaming With Parallel Tools

If you're streaming responses, tool_use blocks arrive incrementally across content_block_start, content_block_delta, and content_block_stop events, and multiple blocks can be in flight for the same response. You need to buffer each block by index until it's complete before you have a valid input to execute. This is one of the trickier parts of implementing tool calling correctly — see /docs/streaming for the event sequence and /docs/tools for the full tool-use message contract if you're building this directly against Claude's API.

Using SubToAPI for Tool Calling at Scale

If you're already managing multiple services that call Claude with tools — a support bot, an internal data agent, a few scripts — SubToAPI sits in front of your existing Claude access and gives each service its own sub_live_... key, so you can trace which key is firing parallel tool calls and how much usage each one generates without sharing one shared credential across your team.

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-4",
    "max_tokens": 1024,
    "tools": [...],
    "messages": [...]
  }'

The request/response shape for tool use is unchanged, so your Promise.all execution logic above works exactly the same way. Usage metadata per key shows up in your dashboard, which is useful when you want to know whether a specific agent is making an unusual number of parallel tool calls. Start with a free trial at /signup, check /pricing for seat-based plans, and see /docs/quickstart for the full setup.

questions

Does Claude always call tools in parallel when it can? No. Claude decides per-turn whether tools are independent enough to request together. Ambiguous prompts, or tool definitions that imply sequencing, often result in one tool call per turn instead.

Can I force Claude to call tools sequentially? Not directly through an API parameter, but you can shape it by writing tool descriptions and system prompts that make dependencies explicit, or by not exposing a second tool until the first one's result is available.

What happens if I return tool results out of order? Order in the array doesn't matter as long as each tool_result block's tool_use_id correctly matches its corresponding tool_use block — Claude matches by ID, not position.

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 →