Claude API Tool Use: JSON Schema Validation Guide
When you give Claude a tool, you're not handing it a function — you're handing it a JSON Schema. Claude reads that schema to decide what arguments to produce, and the API (or your own code) uses it to check whether the arguments Claude generated are actually valid. If you've landed here, you're probably dealing with one of two problems: Claude is returning tool calls with missing or malformed fields, or you want to validate tool inputs before executing them so a bad call doesn't crash your backend.
The short answer: Claude's tool use follows standard JSON Schema (draft-07-ish subset) for the input_schema field on each tool definition. Claude uses this schema to constrain its output, but it does not guarantee 100% schema-perfect JSON every time — especially with complex nested objects, enums, or optional fields. That means you still need to validate the tool_use payload yourself before you execute anything downstream. Below is how the schema works, what typically goes wrong, and how to build a validation layer that catches issues before they become bugs or security problems.
How Claude Uses input_schema
Every tool you pass to the Messages API looks like this:
{
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": { "type": "string", "description": "City and country" },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
},
"required": ["location"]
}
}
Claude reads input_schema the same way a developer would: type, properties, required, enum, and nested object/array structures are all respected in how it constructs the arguments. When Claude decides to call the tool, it returns a tool_use content block with an input object that should conform to that schema.
In practice, Claude is very good at following simple schemas (flat objects, a handful of string/number/boolean fields) and less reliable as schemas get deeply nested, have many optional fields, or mix types loosely. The model isn't running a JSON Schema validator internally — it's generating tokens that are statistically likely to match the shape you described. That's the gap you need to close in your own code.
Common Validation Failures
A few patterns show up repeatedly when teams build tool-calling agents:
- Missing required fields — Claude calls the tool but omits a field you marked as
required, usually because the description was ambiguous or the model didn't have enough context to fill it. - Wrong type coercion — a
numberfield comes back as a string ("42" instead of 42), or a boolean comes back as "true" as text. - Enum drift — Claude returns a value close to but not exactly matching an
enum, e.g."Celsius"instead of"celsius". - Extra fields — Claude adds fields you didn't define, which is harmless if you ignore unknown keys but can break strict validators with
additionalProperties: false. - Nested array/object mismatches — multi-level schemas (arrays of objects, objects with optional nested objects) are where Claude is most likely to produce structurally invalid JSON.
None of these are bugs in Claude per se — they're the expected behavior of a language model approximating a schema. The fix is the same one you'd use for any LLM-generated structured data: validate, then branch.
Building a Validation Layer
The standard approach is to run every tool_use.input through a real JSON Schema validator (Ajv for JavaScript, jsonschema or pydantic for Python) using the exact same schema you sent to Claude. This gives you a deterministic pass/fail instead of trusting the model's output blindly.
import Ajv from "ajv";
const ajv = new Ajv();
const schema = {
type: "object",
properties: {
location: { type: "string" },
unit: { type: "string", enum: ["celsius", "fahrenheit"] }
},
required: ["location"],
additionalProperties: false
};
const validate = ajv.compile(schema);
function handleToolUse(toolUseBlock) {
const valid = validate(toolUseBlock.input);
if (!valid) {
// return a tool_result with is_error: true and ajv.errors
return {
type: "tool_result",
tool_use_id: toolUseBlock.id,
content: JSON.stringify(validate.errors),
is_error: true
};
}
// proceed to execute the real function
}
Feeding validation errors back to Claude as a tool_result with is_error: true is the key move most teams miss. Claude will read the error and typically self-correct on the next turn — re-issuing the tool call with the fixed arguments — instead of you having to retry blindly or fail the whole request.
Schema Design Tips That Reduce Errors
- Keep schemas flat where possible. Flatten nested objects into prefixed fields (
shipping_city,shipping_zip) instead of deep nesting when the tool is simple enough to allow it. - Use
enumaggressively for any field with a fixed set of valid values — it narrows Claude's output space and reduces free-text drift. - Write precise
descriptionfields. Claude relies on descriptions almost as much as types to decide what to put in a field. "Date in YYYY-MM-DD format" produces far more consistent output than "the date". - Mark everything that matters as
required. Optional fields are where omissions happen most; if a field is truly optional, give it a sensible default in your execution code rather than assuming Claude will always include it. - Avoid
additionalProperties: falseunless you validate immediately after. It's useful for strictness but will reject otherwise-fine tool calls that include a stray field, so pair it with the retry-on-error pattern above.
Where SubToAPI Fits
If you're calling Claude's Messages API through SubToAPI, tool definitions and the input_schema format work exactly as documented — SubToAPI is a pass-through layer that gives you an application API key (sub_live_...), streaming, and usage metadata on top of your existing Claude access, not a different tool-calling implementation. You still own the schema design and the validation layer described above; SubToAPI just gives you a stable HTTPS endpoint and dashboard visibility into how often tool calls happen and what they cost. See the tool use docs and the general API docs for request formats, or start from the quickstart if you're setting this up for the first time.
Questions
Does Claude guarantee valid JSON for tool calls? No. Claude is very reliable with simple schemas but can produce malformed or incomplete JSON with complex nested structures. Always validate tool_use.input against your schema before executing.
What JSON Schema features does Claude's input_schema support? A practical subset: type, properties, required, enum, items, nested object/array, and description. Advanced constructs like oneOf/anyOf work less reliably and are best avoided for critical fields.
What should I do when validation fails? Return a tool_result block with is_error: true and the validation error details. Claude will usually read the error and re-issue a corrected tool call on the next turn instead of failing outright.