Claude API JSON Schema Tool Definitions, Explained
When you give Claude access to tools (also called function calling), each tool needs a JSON schema that describes its name, purpose, and input parameters. Claude reads this schema to decide when to call the tool and what arguments to pass. Getting the schema right is the difference between reliable tool use and Claude guessing at malformed arguments or refusing to call anything at all.
This article covers how Claude API tool definitions are structured, the JSON Schema subset Claude actually supports, common mistakes, and a working example you can adapt directly.
What a tool definition looks like
A tool definition is a plain JSON object with three required fields: name, description, and input_schema. The input_schema field is standard JSON Schema, restricted to the subset Claude's models are trained to parse reliably.
{
"name": "get_weather",
"description": "Get the current weather for a given city and unit of measurement.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g. 'Berlin' or 'Austin'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit to return"
}
},
"required": ["city"]
}
}
You pass an array of these tool objects in the tools field of your request, alongside your messages. Claude decides on its own whether a tool call is needed, which tool to use, and what arguments to populate — you don't force it unless you use tool_choice.
The JSON Schema subset that actually works well
Claude supports most of core JSON Schema, but a few patterns produce far better results than others:
- Keep types simple.
string,number,integer,boolean,array, andobjectall work reliably. Deeply nested unions oranyOf/oneOfcombinations increase the chance of malformed output. - Use
enumfor constrained choices. If a parameter only accepts a fixed set of values, declare it withenumrather than describing the options in prose. Claude respects enums far more consistently than free-text instructions. - Mark required fields explicitly. The
requiredarray tells Claude which parameters it must populate before calling the tool. Anything not listed is treated as optional. - Write descriptions for the model, not for humans skimming your code.
descriptionfields are part of the prompt Claude sees. "City name" is weaker than "The city name as a string, e.g. 'Lisbon' — do not include country codes." - Avoid
$refand external schema files. Claude tool schemas are self-contained JSON sent inline with each request. There's no schema registry or$refresolution across files — define everything in the oneinput_schemaobject. - Arrays need an
itemsschema. If a parameter is an array, always specify what each array item looks like:
{
"name": "tags",
"type": "array",
"items": { "type": "string" }
}
Leaving items out is a common source of Claude passing malformed array arguments.
A multi-parameter example
Here's a more realistic tool definition for a function that creates calendar events:
{
"name": "create_event",
"description": "Create a calendar event with a title, start time, and optional attendees.",
"input_schema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Short title of the event"
},
"start_time": {
"type": "string",
"description": "ISO 8601 datetime, e.g. 2024-06-01T14:00:00Z"
},
"duration_minutes": {
"type": "integer",
"description": "Event length in minutes",
"minimum": 5
},
"attendees": {
"type": "array",
"items": { "type": "string" },
"description": "Email addresses of attendees"
}
},
"required": ["title", "start_time"]
}
}
Notice duration_minutes uses minimum as a constraint hint and attendees declares its items type. Claude will generally respect numeric constraints like minimum and maximum as guidance, though it's still good practice to validate the returned arguments server-side before executing anything — the schema shapes Claude's output, it doesn't guarantee it.
Common mistakes to avoid
- Vague descriptions. "The input" tells Claude nothing. Be specific about format, units, and edge cases.
- Missing
requiredarrays. Without it, Claude may omit fields you actually need. - Overloaded tools. A single tool with 15 optional parameters covering five different actions performs worse than five focused tools. Split by use case.
- Forgetting to handle
tool_usestop reasons. When Claude decides to call a tool, the response hasstop_reason: "tool_use"and atool_usecontent block with the arguments. Your code needs to parse that, execute the tool, and send the result back in atool_resultblock to continue the conversation. - Not testing with edge-case inputs. Try ambiguous prompts to see if Claude picks the right tool and fills in sensible defaults for optional fields.
Testing your schemas without a full backend
If you're prototyping and don't want to stand up a complete Anthropic integration (billing, key rotation, usage dashboards) just to test tool schemas, SubToAPI gives you an HTTPS endpoint — sub_live_... keys — that wraps Claude access, including tool use and streaming. You send the same tools array and input_schema structure described above:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-3-5-sonnet",
"max_tokens": 1024,
"tools": [{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}],
"messages": [{"role": "user", "content": "What is the weather in Porto?"}]
}'
See the tools documentation for the full request/response shape, or start with the quickstart if you're setting up your first API key. Plans start at Solo (€9) with a free trial at signup; pricing details are on the pricing page.
questions
Do I need to use strict JSON Schema validators, or does Claude handle loose schemas? Claude parses standard JSON Schema but performs best with simple, flat structures. Avoid $ref, complex oneOf/allOf combinations, and undocumented fields — stick to type, properties, required, enum, and items.
Can I force Claude to always call a specific tool? Yes, using tool_choice you can require a specific tool or force any tool call instead of a free-text response. Without it, Claude decides autonomously based on the user's message and the tool descriptions.
What happens if Claude's tool call arguments don't match my schema? Claude is trained to follow the schema closely, but you should still validate the parsed arguments server-side before executing the tool, since no model guarantees 100% schema compliance on every call.