← Blog

Claude API Multi-Step Tool Use: A Full Example

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

What "multi-step tool use" actually means

Multi-step tool use is when Claude needs more than one round trip through your tools before it can answer a question. A single tool_use call is easy: Claude asks for a function, you run it, you send back the result, Claude answers. Multi-step means Claude calls one tool, looks at the result, decides it needs another tool (possibly with different arguments based on what it just learned), and repeats that loop until it has enough information to produce a final text answer.

This pattern shows up constantly in real applications: look up a customer by email, then fetch their orders, then check refund eligibility for the most recent one. Each step depends on the output of the previous step, so you can't just fire all three tools in parallel — you need a loop that keeps feeding results back to Claude until it stops asking for tools and returns end_turn.

Below is a full working example, the loop structure that makes it reliable, and the mistakes that break it in production.

The scenario

We'll build a support assistant that answers "what's the status of my last order and can I get a refund?" using three tools:

Claude has to call these in sequence, not in parallel, because each call needs data from the one before it.

Tool definitions

[
  {
    "name": "lookup_customer",
    "description": "Find a customer ID by email address",
    "input_schema": {
      "type": "object",
      "properties": { "email": { "type": "string" } },
      "required": ["email"]
    }
  },
  {
    "name": "get_orders",
    "description": "Get all orders for a customer ID",
    "input_schema": {
      "type": "object",
      "properties": { "customer_id": { "type": "string" } },
      "required": ["customer_id"]
    }
  },
  {
    "name": "check_refund_eligibility",
    "description": "Check if an order qualifies for a refund",
    "input_schema": {
      "type": "object",
      "properties": { "order_id": { "type": "string" } },
      "required": ["order_id"]
    }
  }
]

The agent loop

The core idea: keep calling the Messages API, and every time the response's stop_reason is tool_use, execute the requested tool(s), append a tool_result block, and send the whole conversation back. Stop when stop_reason is end_turn.

const tools = [/* definitions above */];

async function runTool(name, input) {
  if (name === "lookup_customer") return lookupCustomer(input.email);
  if (name === "get_orders") return getOrders(input.customer_id);
  if (name === "check_refund_eligibility") return checkEligibility(input.order_id);
  throw new Error(`Unknown tool: ${name}`);
}

async function callClaude(messages) {
  const res = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "x-api-key": process.env.ANTHROPIC_API_KEY,
      "anthropic-version": "2023-06-01",
      "content-type": "application/json"
    },
    body: JSON.stringify({
      model: "claude-sonnet-4-5",
      max_tokens: 1024,
      tools,
      messages
    })
  });
  return res.json();
}

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

  for (let step = 0; step < 6; step++) {
    const response = await callClaude(messages);
    messages.push({ role: "assistant", content: response.content });

    if (response.stop_reason !== "tool_use") {
      return response.content; // final answer
    }

    const toolResults = [];
    for (const block of response.content) {
      if (block.type === "tool_use") {
        const result = await runTool(block.name, block.input);
        toolResults.push({
          type: "tool_result",
          tool_use_id: block.id,
          content: JSON.stringify(result)
        });
      }
    }

    messages.push({ role: "user", content: toolResults });
  }

  throw new Error("Exceeded max tool-use steps");
}

Three things make this loop correct:

  1. Match tool_use_id. Every tool_result block must reference the exact id of the tool_use block it answers. Mixing these up (common when handling multiple calls in one turn) causes Claude to lose track of which result belongs to which call.
  2. Preserve the full assistant turn. You must push the entire response.content array back into history, including any text Claude generated alongside the tool call, not just the tool_use block.
  3. Cap the loop. Always bound the number of iterations. A malformed tool or a bad prompt can otherwise cause Claude to loop indefinitely, burning tokens on every turn.

Handling parallel calls inside one step

Claude can request multiple tools in a single response (e.g., get_orders and a separate get_loyalty_points call that don't depend on each other). That's not really "multi-step" — it's parallel tool use within one step. Your loop should still iterate over every tool_use block in response.content and return all results together, as shown above. True multi-step behavior happens when the next response contains a new tool_use that depends on data you just returned.

Handling tool errors

If a tool call fails, don't throw — return the error as the tool result so Claude can react:

toolResults.push({
  type: "tool_result",
  tool_use_id: block.id,
  content: `Error: order not found for customer ${block.input.customer_id}`,
  is_error: true
});

Claude will typically adjust its next tool call or explain the failure to the user instead of crashing the whole flow.

Running this through SubToAPI

If you're already exposing this as an HTTPS endpoint for your app or team, SubToAPI wraps the same Messages-style API with sub_live_ keys, streaming, and usage metadata per request, so the agent loop above works unchanged — swap the endpoint and auth header. That's useful when several people on a team need to call the same multi-step tool flow without sharing one raw credential; see team seats on the pricing page. Tool definitions and the tool_use / tool_result cycle are documented at /docs/tools, and general request/response shape is in /docs/messages.

questions

How many tool-use steps can a single Claude conversation have? There's no hard platform limit — it's bounded by your max_tokens, the loop you write, and practical cost/latency. Most production agents cap it at 5–10 steps and treat exceeding that as a failure state.

Can Claude call two tools in parallel during multi-step tool use? Yes. A single response can contain multiple tool_use blocks for independent calls. Return all their results in one tool_result batch before the next step, matching each by tool_use_id.

What's the difference between tool_use and tool_result in a multi-step flow? tool_use is Claude's request to run a specific tool with specific arguments, sent in the assistant turn. tool_result is your response containing the tool's output, sent in the next user turn — Claude reads it before deciding whether to call another tool or answer.

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 →