Claude API Tool Use Schema Validation Guide
Why schema validation matters for tool use
When you give Claude a set of tools, you define each one with a JSON Schema describing its expected inputs. Claude uses that schema to decide which tool to call and what arguments to populate — but Claude does not guarantee the output will always pass strict validation against your schema. It's a language model generating structured text, not a schema-enforcing compiler. Malformed enums, missing required fields, wrong types, and extra properties all happen in production, especially with complex nested schemas or ambiguous field names.
Schema validation in this context means two things: (1) writing tool schemas that constrain Claude's output as tightly as possible, and (2) validating the tool_use block Claude returns before you execute anything with it. Skipping the second step is how you end up running a shell command with null as the path argument, or calling a payment API with a string where you expected an integer. This guide covers both halves.
Writing schemas Claude actually respects
Claude's tool-calling reliability improves significantly when your schema removes ambiguity rather than just describing a shape. A few practices that measurably reduce invalid output:
- Use
enuminstead of free-text strings wherever the set of valid values is finite."status": {"type": "string", "enum": ["open", "closed", "pending"]}is far more reliable than a description saying "one of open, closed, or pending." - Mark fields
requiredexplicitly. Claude is more likely to omit optional-looking fields than ones flagged as required, especially in schemas with ten or more properties. - Avoid deeply nested objects when a flatter structure works. Each level of nesting is another place for Claude to drop a key or misplace a value.
- Add
descriptionto every field, not just the top-level tool. A field namedrefwith no description gets inconsistent values;refdescribed as "the git commit SHA, 40 hex characters" gets consistent ones. - Set
additionalProperties: falseon object schemas when you control the full field list. This doesn't stop Claude from generating extra keys, but it gives your downstream validator a clean failure signal when it does.
Here's a schema for a tool that searches internal tickets, written defensively:
{
"name": "search_tickets",
"description": "Search support tickets by status and priority.",
"input_schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["open", "closed", "pending"],
"description": "Ticket status to filter by."
},
"priority": {
"type": "integer",
"minimum": 1,
"maximum": 5,
"description": "Priority from 1 (low) to 5 (urgent)."
},
"query": {
"type": "string",
"description": "Free-text search term, max 200 characters."
}
},
"required": ["status", "query"],
"additionalProperties": false
}
}
Numeric bounds (minimum, maximum) and string length constraints are part of JSON Schema and worth including even though Claude doesn't enforce them at generation time — your validation layer will.
Validating the tool_use block before execution
Claude's response for a tool call looks like this:
{
"type": "tool_use",
"id": "toolu_01A...",
"name": "search_tickets",
"input": {
"status": "open",
"query": "login failure"
}
}
Never pass input straight into your function. Run it through a real validator — ajv for JavaScript, jsonschema or pydantic for Python — against the same schema you sent to Claude. If validation fails, don't throw away the turn; send a tool_result back with is_error: true and a message describing what was wrong. Claude will usually self-correct on the next turn.
import Ajv from "ajv";
const ajv = new Ajv();
const validate = ajv.compile(searchTicketsSchema.input_schema);
function handleToolUse(block) {
const valid = validate(block.input);
if (!valid) {
return {
type: "tool_result",
tool_use_id: block.id,
content: `Invalid input: ${ajv.errorsText(validate.errors)}`,
is_error: true,
};
}
return runSearchTickets(block.input);
}
This retry loop is the single biggest reliability improvement you can make to a tool-use pipeline. Models that look "unreliable" at tool calling are often just not being given a chance to correct malformed output.
Common validation failures and their fixes
- Enum mismatch (
"status": "Open"vs. schema's"open"): case-sensitivity trips this up constantly. Normalize case in your validator rather than fighting the model over it, or list both cases in the enum if truly unavoidable. - Stringified numbers (
"priority": "3"instead of3): happens more often in multi-turn conversations after a tool result contained a string. Coerce types in your validator before failing hard, but only for scalar types where the conversion is unambiguous. - Missing required fields on the first call, correct on retry: this is expected behavior, not a bug — build the retry path rather than trying to eliminate it entirely with prompt tweaks.
- Extra fields not in the schema: usually harmless if
additionalPropertiesisn't set tofalse, but strip them before passing to internal functions that use keyword arguments, since unexpected kwargs cause Python/Node errors unrelated to Claude.
Where SubToAPI fits in
If you're exposing Claude's tool-use results through your own internal API or to a team, SubToAPI gives you a stable HTTPS endpoint with per-application keys (sub_live_...) sitting in front of your Claude access, so you can centralize schema validation, logging, and retry logic in one place instead of duplicating it across services. The tool use docs cover request and response formats in detail, and the messages API reference documents the full tool_result and is_error contract. Start with the quickstart if you're wiring this up for the first time.
Questions
Does Claude guarantee its tool_use output matches my JSON Schema? No. Claude is strongly guided by the schema but can still produce missing fields, wrong types, or invalid enum values. Always validate the input object server-side before executing it.
What's the best way to recover from invalid tool input? Return a tool_result with is_error: true and a specific error message describing the validation failure. Claude will typically correct its next attempt rather than repeating the same mistake.
Should I use additionalProperties: false in every tool schema? Use it when you control the full set of valid fields and want a clean validation failure signal for unexpected keys. Skip it for schemas that intentionally allow flexible or evolving input shapes.