Claude API Custom Tool Definition Example
A custom tool definition in the Claude API is a JSON object that describes a function Claude can choose to call: its name, a description of what it does, and a JSON Schema specifying its input parameters. You pass an array of these definitions in the tools field of your request, and Claude decides whether to respond with text or with a structured tool_use block asking you to run one of them.
This article walks through a full, working example — from schema definition to handling the tool call and returning the result — so you can copy the pattern directly into your own code.
The anatomy of a tool definition
Every tool definition has three required parts:
{
"name": "get_order_status",
"description": "Look up the current status and tracking info for a customer order by order ID.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The unique order identifier, e.g. ORD-48213."
}
},
"required": ["order_id"]
}
}
A few details that matter more than people expect:
descriptionis the prompt for the model, not documentation for your team. Claude reads this to decide when to call the tool. Vague descriptions ("gets order info") lead to missed or wrong calls. Specific descriptions with examples of valid input work much better.input_schemais standard JSON Schema, so you can useenum,minimum/maximum, nested objects, arrays, andrequiredlists exactly as you would anywhere else.- Keep parameter names self-explanatory. Claude infers meaning partly from the names themselves, so
order_idis better thanidoroid.
A complete request/response example
Here's the full round trip using curl against the Claude API directly:
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-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 and tracking info for a customer order by order ID.",
"input_schema": {
"type": "object",
"properties": {
"order_id": { "type": "string", "description": "e.g. ORD-48213" }
},
"required": ["order_id"]
}
}
],
"messages": [
{ "role": "user", "content": "Where is my order ORD-48213?" }
]
}'
Claude responds with a tool_use content block instead of plain text:
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_01A2b3",
"name": "get_order_status",
"input": { "order_id": "ORD-48213" }
}
],
"stop_reason": "tool_use"
}
Your code runs the actual lookup, then sends the result back in a follow-up message with a tool_result block:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A2b3",
"content": "Order ORD-48213: shipped, arriving Thursday."
}
]
}
Claude then produces the final natural-language reply using that result. This three-step loop — definition, tool call, tool result — is the entire pattern, no matter how complex the tool.
Common mistakes in custom tool definitions
- Making every field optional. If
order_idisn't truly optional, mark itrequired. An under-specified schema causes Claude to guess or call the tool with missing data. - Overlapping tool purposes. If two tools both plausibly handle "check order," Claude may pick the wrong one or hesitate. Keep tool responsibilities distinct and name them accordingly.
- Returning raw, unstructured data in
tool_result. A giant JSON dump works, but a concise, pre-formatted string usually produces better final answers since Claude has to parse less noise. - Forgetting
stop_reasonhandling. Always check for"stop_reason": "tool_use"in your loop logic — if you only check for text content, your integration will silently break whenever Claude decides to call a tool.
Building tool-using apps without managing raw SDK plumbing
The pattern above is the same whether you're calling Claude directly or through a proxy. If you're building a product on top of Claude's tool-use feature and want application-level API keys, usage tracking per key, and streaming support without reimplementing auth and billing yourself, SubToAPI (https://subtoapi.app) wraps your existing Claude access in a standard HTTPS API.
Tool definitions work exactly as shown above — you send the same tools array and get the same tool_use/tool_result flow — but requests go through https://api.subtoapi.app/v1/messages with your own sub_live_... key:
const res = 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 tool definitions as above */ ],
messages: [{ role: "user", content: "Where is my order ORD-48213?" }]
})
});
This is useful if you're shipping a product to multiple customers or teammates and need separate keys per app or seat, usage metadata per request, and a dashboard — rather than one shared raw API key. See /docs/tools for the full tool-use reference and /docs/quickstart to get a key in minutes. Plans start at €9/month on the Solo tier, with Team and Scale tiers for multi-seat setups — details at /pricing.
Questions
Can a single request define multiple tools? Yes. Pass as many tool objects as you need in the tools array. Claude picks which one (if any) to call based on the user's message and each tool's description. See /docs/tools for examples with more than one function.
What happens if Claude's tool input doesn't match my schema? Claude generally respects required fields and types, but you should still validate input server-side before executing anything, especially for destructive actions, since schema adherence isn't a hard guarantee.
Do I need a different tool definition format for streaming responses? No — tool definitions are identical whether you use standard or streaming requests. The difference is that tool_use blocks arrive incrementally as events rather than in one payload. See /docs/streaming for handling streamed tool-use events.