← Blog

Claude Tool Use Workflow: From Request to Result

2026-09-15 · 5 min read · SubToAPI Team

What the Claude Tool Use Workflow Actually Looks Like

If you're searching for "claude tool use workflow," you probably want a concrete sequence of steps you can implement: how a request goes from your app, to Claude, into a tool call, back out to your code, and finally into a finished answer. That loop — not the underlying architecture — is what trips up most developers building their first tool-using agent.

The short version: you send Claude a message plus a list of tool definitions. Claude decides whether it needs a tool, and if so, replies with a tool_use block instead of (or alongside) plain text. Your code executes the actual tool — a database query, an API call, a calculation — and sends the result back as a tool_result. Claude then continues the conversation, either asking for another tool or producing a final answer. This can repeat multiple times in a single turn. The rest of this article walks through each step with working code.

Step 1: Define Your Tools

Every tool is a JSON schema describing its name, purpose, and expected input. Claude uses this schema to decide when and how to call the tool — it never executes anything itself.

{
  "name": "get_order_status",
  "description": "Look up the current status of a customer order by ID",
  "input_schema": {
    "type": "object",
    "properties": {
      "order_id": { "type": "string", "description": "The order ID, e.g. ORD-4821" }
    },
    "required": ["order_id"]
  }
}

Keep descriptions specific. Vague descriptions like "gets data" lead to Claude either avoiding the tool or misusing it. Name your parameters clearly and mark only truly required fields as required — over-requiring fields causes unnecessary back-and-forth.

Step 2: Send the Request with Tools Attached

You attach the tools array to a normal messages request. Nothing else changes about how you call the API.

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": [
      {
        "name": "get_order_status",
        "description": "Look up the current status of a customer order by ID",
        "input_schema": {
          "type": "object",
          "properties": { "order_id": { "type": "string" } },
          "required": ["order_id"]
        }
      }
    ],
    "messages": [
      { "role": "user", "content": "Where is order ORD-4821?" }
    ]
  }'

If you're wiring this into an existing SaaS or internal tool, SubToAPI exposes this exact endpoint shape over standard HTTPS with an application key (sub_live_...), so you don't need direct model access to build tool-using features — see the tool use docs for the full request/response reference.

Step 3: Handle the tool_use Response

When Claude decides to call a tool, the response's stop_reason will be tool_use, and the content array will contain a block like this:

{
  "type": "tool_use",
  "id": "toolu_01A2B3",
  "name": "get_order_status",
  "input": { "order_id": "ORD-4821" }
}

Your code is responsible for detecting this block, extracting name and input, and running the corresponding function. This is a plain conditional check — nothing magical:

const toolUseBlock = response.content.find(b => b.type === "tool_use");

if (toolUseBlock) {
  const result = await runTool(toolUseBlock.name, toolUseBlock.input);
  // proceed to step 4
}

Step 4: Return the Tool Result

You send the result back as a new user message containing a tool_result block, referencing the original id. The conversation history — including the assistant's tool_use message — must be preserved and sent back in full, exactly like any other multi-turn conversation.

const followUp = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    model: "claude-sonnet-4-5",
    max_tokens: 1024,
    tools: [/* same tools array as before */],
    messages: [
      { role: "user", content: "Where is order ORD-4821?" },
      { role: "assistant", content: response.content },
      {
        role: "user",
        content: [
          {
            type: "tool_result",
            tool_use_id: toolUseBlock.id,
            content: JSON.stringify(result)
          }
        ]
      }
    ]
  })
});

If the tool call failed — bad input, timeout, permission error — you can still return a tool_result with "is_error": true and a short explanation. Claude will typically retry with corrected input or explain the failure to the user rather than crashing the conversation.

Step 5: Loop Until You Get a Final Answer

Claude might need several tools in sequence — check inventory, then check shipping, then compose a summary. Structurally, this means running steps 3 and 4 in a loop until stop_reason comes back as end_turn instead of tool_use.

async function runAgentLoop(messages, tools) {
  let response = await callClaude(messages, tools);

  while (response.stop_reason === "tool_use") {
    const toolUseBlock = response.content.find(b => b.type === "tool_use");
    const result = await runTool(toolUseBlock.name, toolUseBlock.input);

    messages.push({ role: "assistant", content: response.content });
    messages.push({
      role: "user",
      content: [{ type: "tool_result", tool_use_id: toolUseBlock.id, content: JSON.stringify(result) }]
    });

    response = await callClaude(messages, tools);
  }

  return response;
}

Set a hard cap on loop iterations in production — a misconfigured tool or ambiguous prompt can cause repeated calls, and you don't want an unbounded loop burning tokens.

Step 6: Stream If the Final Answer Matters for UX

Once tool calls resolve, the final response is often long-form text. If you're building a chat UI, stream that last step so users see tokens as they arrive rather than waiting on a spinner. This is a separate concern from tool handling — see streaming for the SSE event format and how it interacts with tool_use blocks mid-stream.

Where SubToAPI Fits

If you already have Claude access through an existing subscription and want to build the workflow above without managing separate API billing, key rotation, or usage tracking yourself, SubToAPI turns that access into a standard HTTPS API with sub_live_... keys, per-key usage metadata, and team seats. Plans start at €9/month for solo use, with team and scale tiers at €19 and €49 per seat — a free trial is available if you want to test the tool use loop against your own use case before committing. Full request/response formats are in the quickstart and messages docs.

Questions

Do I need to resend the entire conversation on every tool call? Yes. Claude's API is stateless — each request must include the full message history, including prior tool_use and tool_result blocks, so the model has context for its next decision.

Can Claude call multiple tools in one response? Yes, a single response can contain several tool_use blocks if the task requires parallel independent lookups. You return one tool_result per tool_use_id, all in the same follow-up message.

What happens if I don't provide a tool Claude wants to use? Claude only calls tools you've explicitly defined in the tools array. If no suitable tool exists, it will answer using available context or state that it lacks the information, rather than inventing a tool call.

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 →