Claude API Structured Output Validation with JSON Schema
Claude does not have a native "JSON mode" flag like some other model APIs. To get structured output validation with a JSON schema, you define a tool with an input_schema and force Claude to call it, then validate the result on your side with a schema library. This gets you reliable, typed JSON output without parsing free-text responses or hoping the model remembers to wrap things in code fences.
The short answer: use tool use (function calling) as your schema enforcement mechanism, not prompt instructions like "respond only in JSON." Prompt instructions get you close most of the time, but they break under edge cases — the model adds a sentence before the JSON, wraps it in markdown, or drops a required field. Tool calling constrains the model's output shape at generation time and gives you a parseable object directly in the response, which you then re-validate with a proper JSON Schema validator for belt-and-suspenders correctness.
Why prompting alone isn't enough
Asking Claude to "return valid JSON matching this schema" works often enough in a demo and fails often enough in production to be a problem. Common failure modes:
- Trailing commentary before or after the JSON block
- Markdown code fences (
`json) that yourJSON.parse()chokes on - Optional fields omitted when they should be
null - Numbers returned as strings, or vice versa
- Nested objects flattened or restructured when the model "helps"
None of these are catastrophic on their own, but at scale (thousands of requests a day) a 2-3% failure rate on parsing is a steady stream of production errors. Structured output validation needs to happen at the API boundary, not just in the prompt.
Use tool definitions as your schema
Claude's tool use feature accepts a JSON Schema-style input_schema for each tool. When you force Claude to use a specific tool via tool_choice, the model's response comes back as a structured tool_use block with arguments that match your schema far more reliably than free-text generation.
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": "extract_invoice",
"description": "Extract structured invoice data",
"input_schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"total_amount": {"type": "number"},
"currency": {"type": "string"},
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": {"type": "string"},
"quantity": {"type": "integer"},
"unit_price": {"type": "number"}
},
"required": ["description", "quantity", "unit_price"]
}
}
},
"required": ["invoice_number", "total_amount", "currency"]
}
}],
"tool_choice": {"type": "tool", "name": "extract_invoice"},
"messages": [{"role": "user", "content": "Invoice #4471, total $1,240.00 USD, 2x widget at $500, 1x install fee at $240."}]
}'
The response contains a tool_use content block with input already shaped as an object matching your schema — no markdown fences, no preamble text to strip out.
Validate again on your side
Tool schemas constrain generation, but they are not a hard guarantee. The model can still occasionally omit a field it judges irrelevant, misformat an enum value, or hit an edge case your schema didn't anticipate. Treat the tool output as "likely correct" and run it through a real validator before trusting it downstream.
import Ajv from "ajv";
const ajv = new Ajv();
const validate = ajv.compile(invoiceSchema);
const toolUseBlock = response.content.find(b => b.type === "tool_use");
const valid = validate(toolUseBlock.input);
if (!valid) {
console.error(validate.errors);
// retry with error feedback, or fall back to a repair prompt
}
If validation fails, the cheapest fix is usually a single retry where you append the Ajv error messages to the conversation and ask Claude to correct the specific fields that failed — this is far more reliable than re-running the whole prompt from scratch.
Keep schemas simple and explicit
A few patterns that reduce validation failures:
- Avoid deeply nested optional structures. Flatten where you can; the model handles shallow, explicit schemas better than deeply recursive ones.
- Use enums for constrained fields instead of free-text strings you'll regex-match later.
- Mark fields
requiredexplicitly rather than relying on descriptions alone. - Keep descriptions short and concrete — "ISO 4217 currency code" beats a paragraph of context.
- Set
max_tokensgenerously for schemas with arrays — truncated tool calls produce invalid JSON that no validator can fix.
Where this fits with SubToAPI
If you're building on top of a Claude subscription through SubToAPI, tool use and structured output work the same way — it's a pass-through HTTPS API over your existing Claude access, so schemas, tool_choice, and validation patterns above apply unchanged. You get an sub_live_... application key, usage metadata per request, and the same /v1/messages endpoint shape documented in /docs/messages and /docs/tools. If you're new to the platform, /docs/quickstart walks through the first authenticated call, and /signup gets you a trial key to test schema-constrained extraction before committing to a plan.
questions
Does Claude have a dedicated JSON mode? No. Claude enforces structured output through tool use — define an input_schema for a tool, force it with tool_choice, and the response comes back as a structured tool_use block instead of free text.
Is tool-based output always valid against my schema? It's reliable but not guaranteed. Always re-validate the returned object with a schema library like Ajv and handle the rare malformed case with a targeted retry rather than assuming correctness.
Can I get structured output without defining a tool? You can prompt for JSON directly and parse the response, but expect occasional markdown fences, stray text, or missing fields. Tool use with a schema is the more reliable approach for production systems.