Claude API Function Calling Schema Examples That Work
Claude's version of function calling is called tool use, and it works by passing a JSON Schema description of each function you want Claude to be able to call. Claude doesn't execute anything itself — it reads your schemas, decides when a tool is relevant, and returns a structured tool_use block with the arguments filled in. Your code runs the actual function and sends the result back.
This article focuses specifically on the schema part: how to write tool definitions that Claude parses correctly, with real examples for common cases (strings, enums, numbers, nested objects, arrays, optional fields) and the mistakes that cause Claude to send malformed or incomplete arguments.
The basic shape of a tool schema
Every tool you give Claude needs three things: a name, a description, and an input_schema written in JSON Schema. Here's the minimal structure:
{
"name": "get_weather",
"description": "Get the current weather for a specific city.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g. 'Berlin' or 'Tokyo'."
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit to return."
}
},
"required": ["city"]
}
}
A few details that matter more than they look:
descriptionon the tool itself is what Claude uses to decide whether to call it at all — vague descriptions cause missed or wrong calls.descriptionon each property helps Claude fill in values correctly, especially for ambiguous fields like dates or IDs.requiredmust list only the fields that truly need a value. Marking everything required makes Claude guess values it doesn't have.
Enums for constrained choices
If a parameter only accepts a fixed set of values, always use enum instead of a free-text description. This is the single biggest accuracy improvement for structured outputs:
{
"name": "set_order_status",
"description": "Update the status of an existing order.",
"input_schema": {
"type": "object",
"properties": {
"order_id": { "type": "string" },
"status": {
"type": "string",
"enum": ["pending", "shipped", "delivered", "cancelled"]
}
},
"required": ["order_id", "status"]
}
}
Without the enum, Claude might return "Shipped", "SHIPPED", or "on the way" — all plausible English, none of which match your backend's expected values.
Nested objects and arrays
Real-world functions often need structured, not flat, input. Claude handles nested JSON Schema objects fine as long as the nesting is explicit:
{
"name": "create_invoice",
"description": "Create an invoice for a customer with one or more line items.",
"input_schema": {
"type": "object",
"properties": {
"customer_email": { "type": "string" },
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": { "type": "string" },
"quantity": { "type": "integer", "minimum": 1 },
"unit_price": { "type": "number" }
},
"required": ["description", "quantity", "unit_price"]
}
}
},
"required": ["customer_email", "line_items"]
}
}
This is the pattern to use for anything resembling a form, order, or multi-field record. Avoid flattening nested data into a single string field like "line_items": "2x Widget @5, 1x Gadget @12" — Claude can produce it, but your parsing code has to reverse-engineer a format that was never strictly defined.
Optional fields and defaults
JSON Schema doesn't have a native "default" that Claude will reliably apply, so be explicit in the description instead of relying on omission:
{
"name": "search_products",
"input_schema": {
"type": "object",
"properties": {
"query": { "type": "string" },
"max_results": {
"type": "integer",
"description": "Number of results to return. Defaults to 10 if not specified."
}
},
"required": ["query"]
}
}
Your application code should still apply the actual default when the field is missing from the returned arguments — don't assume Claude will inject it.
Putting it together in a request
Tool schemas go in the tools array of your Messages request, and Claude's response includes a tool_use content block with the matching input object:
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": [ /* schema from above */ ],
"messages": [
{"role": "user", "content": "Ship order #4821 and mark it delivered"}
]
}'
If you're routing Claude access through SubToAPI, the request shape is identical — you call https://api.subtoapi.app/v1/messages with Authorization: Bearer $SUBTOAPI_KEY instead of the Anthropic key, and the same tools and input_schema fields apply unchanged. That makes it easy to run the same tool-use code in dev against your personal Claude plan and in production against a dashboard-managed key with per-team usage tracking. Full request format is in the docs and Messages reference.
Common schema mistakes
- Missing
descriptionfields. Claude's tool selection and argument filling both depend heavily on text, not just types. - Everything typed as
string. Useinteger,number, andbooleanwhere they apply — it reduces parsing errors on your side. - No
enumfor finite option sets. This is the fastest way to get inconsistent casing and wording back. - Over-nesting. Three or four levels deep is fine; beyond that, consider splitting into multiple tools.
- Forgetting
required. Without it, Claude may omit fields it's unsure about instead of asking for clarification.
Questions
Does Claude support OpenAI-style function calling syntax directly? No. Claude uses its own tools / input_schema format based on standard JSON Schema. The concepts are similar, but field names and response structure differ, so schemas need to be rewritten, not just copied.
Can one request include multiple tool schemas? Yes. The tools array can contain several tool definitions, and Claude will choose which one (if any) to call based on each tool's name and description.
What happens if Claude's returned arguments don't match my schema exactly? Claude generally respects required fields and types, but your code should still validate the input object before executing anything — schemas guide Claude's output, they don't enforce it at runtime.