Claude API Custom Function Calling Setup Guide
Setting up custom function calling with the Claude API means defining one or more "tools" — JSON-described functions Claude can decide to call — then writing the code that executes those functions and feeds results back into the conversation. This is how you connect Claude to your own database lookups, internal APIs, calculators, or any action that lives outside the model itself.
The core workflow is always the same regardless of which language or client you use: you send a request with a tools array describing what's available, Claude responds with a tool_use block if it wants to call one, your code runs the actual function, and you send the result back as a tool_result so Claude can continue the conversation with that new information. Below is a complete walkthrough of building this from scratch.
Step 1: Define Your Tool Schema
Each tool needs a name, a description, and an input schema. The description matters more than most developers expect — Claude uses it to decide when to call the function, not just how.
{
"name": "get_order_status",
"description": "Look up the current shipping status of a customer order by order ID. Use this whenever a user asks about delivery, tracking, or order progress.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The order ID, e.g. ORD-20394"
}
},
"required": ["order_id"]
}
}
Keep schemas minimal. Every extra optional field increases the chance Claude guesses wrong values instead of asking for clarification.
Step 2: Send the Request With Tools Attached
Pass your tool definitions alongside the normal messages payload. Here's a raw HTTPS example using curl:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 1024,
"tools": [
{
"name": "get_order_status",
"description": "Look up shipping status by order ID.",
"input_schema": {
"type": "object",
"properties": {
"order_id": { "type": "string" }
},
"required": ["order_id"]
}
}
],
"messages": [
{ "role": "user", "content": "Where is my order ORD-20394?" }
]
}'
If Claude decides it needs the function, the response will include a content block with "type": "tool_use", the tool name, a unique id, and the parsed input arguments instead of a plain text answer.
Step 3: Execute the Function Yourself
Claude never actually runs your code — it only tells you what it wants called and with what arguments. Your backend is responsible for executing the real logic:
async function handleToolUse(toolUseBlock) {
if (toolUseBlock.name === "get_order_status") {
const { order_id } = toolUseBlock.input;
const status = await db.orders.findStatus(order_id);
return { tool_use_id: toolUseBlock.id, content: status };
}
throw new Error(`Unknown tool: ${toolUseBlock.name}`);
}
This is the part developers most often get wrong when first wiring up custom function calling: they expect the API to call their function automatically. It doesn't — function calling in Claude is really "function calling intent detection," and the execution loop is entirely yours to build.
Step 4: Return the Result in a Follow-Up Message
Send a new request that includes the original assistant message (with the tool_use block) plus a user message containing a tool_result block referencing the same tool_use_id:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01abc123",
"content": "Order ORD-20394 shipped on March 3rd, arriving March 6th."
}
]
}
Claude then generates a final natural-language response using that result. You can chain multiple tool calls in a single turn if the model needs several pieces of data before answering — just keep looping through steps 2–4 until the response comes back as plain text with no further tool_use blocks.
Step 5: Handle Multi-Tool and Parallel Calls
In more complex setups, Claude may request multiple tools in the same response. Loop over all tool_use blocks, execute each one (in parallel if they're independent), and return all corresponding tool_result blocks in a single follow-up message. Mixing up the order or omitting a result for any requested tool will cause the next turn to fail or produce a confused response.
Common Setup Mistakes
- Vague descriptions. "Gets data" tells Claude nothing about when to use the tool. Be specific about triggers and expected inputs.
- Overloaded schemas. One tool that tries to do five things is harder for the model to use correctly than five narrow tools.
- Forgetting the
tool_use_idmatch. Everytool_resultmust reference the exact ID from the correspondingtool_useblock, or the conversation state breaks. - Not handling the "no tool needed" case. Claude might answer directly without calling anything — your code should always check the response type before assuming a tool call happened.
Where SubToAPI Fits In
If you're integrating custom function calling into a product and want a clean path from prototype to production, SubToAPI turns your existing Claude access into a standard HTTPS API with application-scoped keys (sub_live_...), streaming support, and usage metadata per request — all the pieces you need around the tool-calling loop itself. You can test tool definitions directly against the Messages endpoint, read the full tool use reference, or just start with the quickstart guide and plug your existing function schemas straight in. Plans start at the Solo tier for individual developers, scale to Team and Scale tiers with per-seat billing, and every plan includes a free trial — see pricing for details.
Questions
Does Claude execute my functions automatically? No. Claude only identifies which function to call and with what arguments. Your application code is responsible for actually running the function and returning the result.
Can I define multiple tools in one request? Yes. Pass an array of tool definitions in the tools field. Claude will choose the appropriate one (or none) based on the conversation and may request several in sequence or in parallel.
What happens if I don't return a tool_result for a requested tool_use block? The conversation will likely error out or produce an incomplete response on the next turn — every tool_use block in an assistant message needs a matching tool_result before you continue.