← Blog

Claude API Parallel Tool Calls: Implementation Guide

2026-11-06 · 4 min read · SubToAPI Team

What "parallel tool calls" actually means in the Claude API

When you give Claude more than one tool and a request that needs several independent lookups — say, weather for three cities, or a database query plus a price check — Claude can return multiple tool_use blocks in a single assistant turn instead of calling them one at a time. This is what people mean by "parallel tool calls": not that Claude executes the tools itself, but that it asks for several tool invocations at once, and your code is responsible for running them (ideally concurrently) and sending back all the results together.

The implementation challenge isn't the Claude API call itself — it's the orchestration on your side: detecting multiple tool_use blocks, executing them concurrently, matching each result back to the correct tool_use_id, handling partial failures, and sending a single user message back with all tool_result blocks in the right order. This article walks through that implementation end to end.

How Claude signals a parallel tool call request

When Claude decides to call tools, the response content array can contain more than one tool_use object alongside any text blocks. A typical response looks like this:

{
  "role": "assistant",
  "content": [
    { "type": "text", "text": "Let me check both." },
    { "type": "tool_use", "id": "toolu_01A", "name": "get_weather", "input": { "city": "Lisbon" } },
    { "type": "tool_use", "id": "toolu_01B", "name": "get_weather", "input": { "city": "Porto" } }
  ],
  "stop_reason": "tool_use"
}

Two things matter here: stop_reason is "tool_use" (not "end_turn"), and there are two separate id values. Your job is to execute both, then return two tool_result blocks referencing those exact IDs.

Step 1: detect and collect all tool_use blocks

Don't assume there's only one. Filter the content array for every block with type === "tool_use":

const toolUseBlocks = response.content.filter(
  (block) => block.type === "tool_use"
);

If toolUseBlocks.length is zero, Claude finished normally. If it's one or more, you need to execute all of them before continuing the conversation.

Step 2: execute them concurrently

This is the part most implementations get wrong by looping with await sequentially. Use Promise.all (or Promise.allSettled if you want to handle failures gracefully) so independent tool calls actually run in parallel:

const toolHandlers = {
  get_weather: async (input) => fetchWeather(input.city),
  get_price: async (input) => fetchPrice(input.sku),
};

const results = await Promise.allSettled(
  toolUseBlocks.map(async (block) => {
    const handler = toolHandlers[block.name];
    if (!handler) throw new Error(`Unknown tool: ${block.name}`);
    const output = await handler(block.input);
    return { id: block.id, output };
  })
);

Promise.allSettled is the safer choice because one failing tool call shouldn't crash the rest. Each tool should fail independently and report its own error back to Claude.

Step 3: build matching tool_result blocks

Each tool_result must carry the exact tool_use_id it answers. Order within the array doesn't strictly matter to Claude, but mismatched IDs will break the turn:

const toolResults = results.map((result, i) => {
  const toolId = toolUseBlocks[i].id;
  if (result.status === "fulfilled") {
    return {
      type: "tool_result",
      tool_use_id: toolId,
      content: JSON.stringify(result.value.output),
    };
  }
  return {
    type: "tool_result",
    tool_use_id: toolId,
    content: result.reason.message,
    is_error: true,
  };
});

Then send a new user message containing all toolResults as its content, appended to the conversation history, and call the API again. Claude will read every result and continue the turn — often producing a final text answer, but sometimes requesting another round of parallel tool calls if the first results revealed more work to do. Loop this pattern until stop_reason is "end_turn".

Common implementation mistakes

Simplifying this with SubToAPI

If you're building this on top of your own Claude access rather than a raw Anthropic account, SubToAPI turns that access into a standard HTTPS endpoint with application API keys, so the parallel tool call logic above is the only orchestration layer you need to write — no separate auth or key rotation concerns. The request and response shapes for tool use match what's described in the tool use docs and the messages docs, so the Promise.allSettled pattern works unchanged against sub_live_... keys. Streaming responses that include tool calls are covered in the streaming docs, and a minimal end-to-end setup is in the quickstart. Plans start at the pricing page, with a free trial at signup.

Questions

Does Claude execute tools in parallel on Anthropic's servers? No. Claude only decides which tools to call and returns multiple tool_use blocks in one response. Your application executes the actual tool functions, and you choose whether to run them concurrently.

What happens if one tool call fails while others succeed? Return a tool_result for every tool_use_id, marking the failed one with is_error: true and a short message. Claude will see which calls succeeded and which failed, and can decide whether to retry, ask for clarification, or proceed with partial data.

Can Claude request another round of parallel tool calls after receiving results? Yes. After you send back tool_result blocks, Claude may respond with stop_reason: "tool_use" again if it needs more information. Keep looping the execute-and-respond cycle until you get stop_reason: "end_turn".

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 →