Function Calling with Claude: A Complete Setup Guide
Function calling with Claude lets you give the model a set of tools — described as JSON schemas — that it can choose to invoke instead of just replying with text. Claude decides when a function call is appropriate, returns structured arguments matching your schema, and pauses so your code can run the actual function and send the result back. This is how you connect Claude to databases, internal APIs, calculators, search engines, or any system that needs precise, structured input rather than free-form prose.
The mechanics are the same regardless of which client you use: you send a list of tool definitions with your request, Claude responds with a tool_use block containing the function name and arguments, your application executes that function, and you send the result back as a tool_result so Claude can continue the conversation with that information in hand. This article walks through the full loop, common pitfalls, and how to keep it reliable in production.
How Claude's Tool Use Format Works
Each tool you define needs three things: a name, a description, and an input_schema written as JSON Schema. The description matters more than 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 missed calls or wrong tool selection when you have several similar tools.
{
"name": "get_order_status",
"description": "Look up the current status of a customer order by order ID. Use this whenever the user asks about an existing order.",
"input_schema": {
"type": "object",
"properties": {
"order_id": { "type": "string", "description": "The order ID, e.g. ORD-4821" }
},
"required": ["order_id"]
}
}
When Claude decides to use this tool, the response contains a content block like:
{
"type": "tool_use",
"id": "toolu_01A2b3",
"name": "get_order_status",
"input": { "order_id": "ORD-4821" }
}
Your code executes the real lookup, then sends a follow-up message containing a tool_result block referencing that same id, with the output. Claude then produces a final natural-language answer incorporating the result. If Claude needs another tool call — say, checking inventory after confirming the order — it will emit another tool_use block instead of a final answer, and the loop repeats.
Building a Reliable Tool-Calling Loop
Most implementation bugs come from the surrounding loop, not from Claude's tool selection itself. A few things worth getting right from the start:
- Always check
stop_reason. If it'stool_use, you must handle the tool call before Claude will produce a final response. Treating every response as final is the most common source of "Claude ignored my instructions" bug reports. - Match tool_result IDs exactly. The
tool_resultblock must reference the sameidfrom thetool_useblock. Mismatched IDs cause the API to reject the follow-up turn. - Handle multiple parallel tool calls. Claude can request several tools in a single turn. Your loop needs to execute all of them and return all corresponding
tool_resultblocks before continuing. - Return errors as tool results, not exceptions. If a function call fails, send back a
tool_resultwithis_error: trueand a short explanation. Claude will usually retry with corrected arguments or explain the failure to the user, instead of the conversation breaking. - Cap the loop. Set a max number of tool-call rounds (5–10 is typical) to avoid runaway loops if a tool keeps returning ambiguous results.
let messages = [{ role: "user", content: userInput }];
for (let i = 0; i < MAX_ROUNDS; i++) {
const response = await callClaude(messages, tools);
messages.push({ role: "assistant", content: response.content });
if (response.stop_reason !== "tool_use") break;
const toolResults = [];
for (const block of response.content) {
if (block.type === "tool_use") {
const output = await runTool(block.name, block.input);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: JSON.stringify(output)
});
}
}
messages.push({ role: "user", content: toolResults });
}
This pattern works whether you're calling Claude directly or through a proxy layer — the tool-use protocol itself doesn't change.
Keeping Tool Definitions Tight
Function calling gets unreliable fast when tool sets grow sprawling. A few practices help:
- Keep tool names short and unambiguous —
search_productsbeatsproduct_search_v2_final. - Don't overload one tool with a dozen optional parameters. Split into separate tools if the use cases diverge significantly.
- Use
enumin your schema wherever the valid values are a fixed set — it cuts down on malformed arguments. - Test with adversarial or vague prompts, not just clean happy-path inputs. That's usually where tool selection breaks down first.
Running Function Calling Through an API Key
If you're building on Claude through a subscription rather than a metered API account, you still need a stable way to issue application keys, stream responses, and see what tool calls are actually costing you in tokens. SubToAPI turns your existing Claude access into a standard HTTPS API — you get sub_live_... keys, streaming support, and full tool-use compatibility, so the same request format shown above works without changes. Usage metadata and team seats are handled in one dashboard, which is useful if more than one developer on your team needs to build against the same account.
Getting started takes the same shape as any other integration: create a key, point your requests at https://api.subtoapi.app/v1/messages, and pass your tools array exactly as documented. See the quickstart and the messages and tools reference pages for the exact request/response shapes, or check streaming if you want tool-use events delivered incrementally. Plans start at €9/month on the Solo tier, with team pricing on the pricing page and a free trial at signup.
Questions
Does Claude support parallel function calls in a single response? Yes. Claude can emit multiple tool_use blocks in one turn, and your code should execute all of them and return matching tool_result blocks before continuing the conversation.
What happens if Claude calls a function with invalid arguments? It usually won't if your input_schema is well-defined, but if it does, return a tool_result with is_error: true and a clear message — Claude will typically correct itself on the next call rather than failing outright.
Can I force Claude to always use a specific tool? Yes, most implementations support a tool_choice parameter to require a specific tool or force some tool use on a given turn, instead of leaving it to Claude's judgment.