Claude API JSON Schema Function Calling Example
Function calling with Claude works by describing a "tool" using a JSON Schema object, sending it alongside your prompt, and letting Claude decide when to call it. Claude responds with a structured tool_use block containing arguments that match your schema, which your code then parses and executes. This article walks through a complete, working example from schema definition to handling the response.
The core pattern is always the same: define the tool's input_schema using standard JSON Schema (types, required fields, enums, nested objects), pass it in the tools array of your request, and check the response for a tool_use content block instead of plain text.
A minimal function calling example
Suppose you want Claude to extract structured flight search parameters from a user's natural language request. Here's the tool definition:
{
"name": "search_flights",
"description": "Search for available flights between two cities",
"input_schema": {
"type": "object",
"properties": {
"origin": {
"type": "string",
"description": "IATA airport code, e.g. JFK"
},
"destination": {
"type": "string",
"description": "IATA airport code, e.g. LAX"
},
"departure_date": {
"type": "string",
"format": "date",
"description": "Date in YYYY-MM-DD format"
},
"passengers": {
"type": "integer",
"minimum": 1,
"default": 1
},
"cabin_class": {
"type": "string",
"enum": ["economy", "premium_economy", "business", "first"]
}
},
"required": ["origin", "destination", "departure_date"]
}
}
A full request including this tool, using curl against a Messages-style endpoint:
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": "search_flights",
"description": "Search for available flights between two cities",
"input_schema": { ... }
}],
"messages": [
{"role": "user", "content": "Find me a business class flight from JFK to LAX on March 15th for 2 people"}
]
}'
Claude's response includes a tool_use block like this:
{
"type": "tool_use",
"id": "toolu_01A2b3",
"name": "search_flights",
"input": {
"origin": "JFK",
"destination": "LAX",
"departure_date": "2025-03-15",
"passengers": 2,
"cabin_class": "business"
}
}
Your application reads input, which already conforms to the schema — no regex parsing, no "please return valid JSON" prompting tricks required.
Returning the result to Claude
After executing the actual flight search, you send the result back as a tool_result block in the next message so Claude can summarize it for the user:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A2b3",
"content": "[{\"flight\":\"AA123\",\"price\":412}]"
}
]
}
Claude then produces a natural-language reply based on that data, maintaining the conversation loop.
Schema design tips that reduce errors
A few practical rules make function calling noticeably more reliable:
- Use
enumwherever possible. Free-text string fields invite inconsistent values; enums constrain Claude to exact matches. - Write detailed
descriptionfields. Claude relies heavily on them to decide what value belongs where, especially for ambiguous fields like dates or IDs. - Mark fields
requireddeliberately. Only require what your downstream code truly needs — overly strict schemas cause more clarification turns. - Nest objects for grouped data. If you're capturing an address or a date range, nest it as a sub-object rather than flattening into prefixed field names.
- Avoid deeply nested arrays of objects with many optional fields. Simpler schemas get filled in more consistently.
Forcing a specific tool
Sometimes you don't want Claude to decide whether to call a tool — you want it to always call one, especially for structured extraction tasks. You can force this with tool_choice:
"tool_choice": {"type": "tool", "name": "search_flights"}
This is useful when the entire point of the request is structured output, not conversation — effectively turning Claude into a JSON extraction engine.
Multiple tools in one request
You can register several tools and let Claude pick the right one(s) based on the user's message:
"tools": [
{"name": "search_flights", "input_schema": {...}},
{"name": "get_weather", "input_schema": {...}},
{"name": "convert_currency", "input_schema": {...}}
]
Claude may call zero, one, or multiple tools in sequence across a conversation, depending on what the user asks for. Your code is responsible for dispatching based on name and validating input against the schema before executing anything with side effects — treat tool arguments as untrusted input from a model, not as pre-validated data.
Simplifying this in production
If you're building this pattern repeatedly across services or teams, the overhead isn't the schema itself — it's managing API keys, streaming tool-call deltas correctly, logging usage per tool, and giving teammates controlled access without sharing a raw provider key. SubToAPI wraps your existing Claude access behind a standard HTTPS API with sub_live_... application keys, so you can issue separate keys per service that all call tools the same way, with usage visible in one dashboard. Tool definitions and tool_use responses work the same as shown above — see the tools docs for request/response shapes, and streaming docs if you need incremental tool-call deltas rather than waiting for the full response. You can get a key from the signup page and compare plans starting at Solo €9.
FAQ
Does Claude support JSON Schema natively for function calling?
Yes. The input_schema field in a tool definition accepts standard JSON Schema, including types, enums, required fields, and nested objects, which Claude uses to validate and structure its tool_use output.
Can I force Claude to always call a specific tool?
Yes, using tool_choice with {"type": "tool", "name": "<tool_name>"}. This is common when using Claude purely for structured data extraction rather than open-ended conversation.
What happens if Claude's tool input doesn't match my schema?
Claude generally produces well-formed input matching the schema, but you should still validate it in your code before executing side effects, since model outputs should never be treated as fully trusted input.