Claude API JSON Mode: Output Validation Guide
Claude doesn't have a dedicated "JSON mode" flag like some other model APIs. Instead, you get structured JSON output by using tool definitions with a JSON schema, or by prompting carefully and validating the result afterward. Either way, you still need a validation layer in your application because the model can produce output that is syntactically valid JSON but semantically wrong, or — much more rarely with a well-built prompt — not valid JSON at all.
This article covers the three practical approaches to structured output with Claude, how to validate what comes back, and how to handle the failure cases so your pipeline doesn't break in production.
Why Claude doesn't have a strict JSON mode
Some APIs let you set a response_format: { type: "json_object" } parameter that constrains token generation so the output is guaranteed to parse as JSON. Claude's Messages API doesn't expose that exact mechanism. What it does offer instead:
- Tool use with a JSON schema — you define a tool with an
input_schema, force Claude to call it withtool_choice, and read the structuredinputobject back from the response. - System prompt instructions — you tell Claude explicitly to respond with only JSON matching a given shape, often with an example.
- Prefill — you seed the assistant turn with
{to nudge the model away from prose and straight into a JSON object.
Tool use is the most reliable of the three because the input field returned by the API is already a parsed object, not a string you have to run through JSON.parse() yourself.
Using tool use for structured output
Define a tool whose only purpose is to carry your desired schema:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"tools": [{
"name": "extract_invoice",
"description": "Extract structured invoice fields",
"input_schema": {
"type": "object",
"properties": {
"invoice_number": { "type": "string" },
"total": { "type": "number" },
"currency": { "type": "string" },
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": { "type": "string" },
"amount": { "type": "number" }
},
"required": ["description", "amount"]
}
}
},
"required": ["invoice_number", "total", "currency"]
}
}],
"tool_choice": { "type": "tool", "name": "extract_invoice" },
"messages": [{ "role": "user", "content": "Invoice #4521, total $340.00, one line item: consulting hours, $340.00" }]
}'
Because tool_choice forces the specific tool, Claude has no path to reply with plain prose — it must produce arguments matching the schema. See /docs/tools for the full tool-use reference and /docs/messages for request/response shapes if you're calling through SubToAPI.
Prompt-based JSON with prefill
If you don't want the overhead of a tool definition — for example, a simple single-field extraction — prefilling the assistant response works well:
{
"model": "claude-sonnet-4-5",
"max_tokens": 512,
"messages": [
{ "role": "user", "content": "Return a JSON object with keys \"sentiment\" (positive/negative/neutral) and \"confidence\" (0-1) for this review: \"Shipping was slow but the product is great.\"" },
{ "role": "assistant", "content": "{" }
]
}
Because the assistant turn already starts with {, Claude continues from there instead of adding a preamble like "Here's the JSON:". You'll need to re-prepend the { when you concatenate the response before parsing.
Validating the output
Regardless of which method you use, treat the JSON as untrusted input until it's validated against a schema. Schema-matched doesn't mean business-logic-correct — a model can return "total": -50 or an empty line_items array that's technically valid JSON but wrong for your use case.
Use a runtime validator rather than trusting types:
import { z } from "zod";
const InvoiceSchema = z.object({
invoice_number: z.string(),
total: z.number().positive(),
currency: z.string().length(3),
line_items: z.array(
z.object({
description: z.string().min(1),
amount: z.number()
})
).optional()
});
function parseInvoice(toolInput) {
const result = InvoiceSchema.safeParse(toolInput);
if (!result.success) {
throw new Error(`Invalid invoice shape: ${result.error.message}`);
}
return result.data;
}
For Python, pydantic gives you the same guarantee:
from pydantic import BaseModel, PositiveFloat
class Invoice(BaseModel):
invoice_number: str
total: PositiveFloat
currency: str
line_items: list[dict] | None = None
invoice = Invoice.model_validate(tool_input)
Validating against a schema catches type mismatches, missing required fields, and out-of-range values before that data reaches your database or downstream systems.
Handling validation failures
When validation fails, don't just log and drop the request. Three practical patterns:
- Retry with the error appended. Send the validation error message back to Claude as a new user turn ("Your last response failed schema validation: total must be positive. Please retry.") and let it self-correct.
- Lower
max_tokensrisk. If output is getting truncated mid-JSON, the object will fail to parse entirely — bumpmax_tokensand checkstop_reasonin the response to confirm it wasn't cut off by length. - Log the raw response. Keep the raw text or tool input alongside the validation error so you can spot systematic prompt issues rather than one-off model variance.
If you're running structured extraction at volume — invoice parsing, log classification, support ticket tagging — routing those calls through a single API layer makes it easier to track failure rates per prompt version. SubToAPI turns your Claude access into a standard HTTPS API with usage metadata per key, which is useful for spotting a schema that's failing validation more often than expected without digging through logs by hand. Check /docs/quickstart to get a key running in a few minutes, or /pricing for plan details.
Checklist before shipping
- Use tool use with
tool_choiceforced whenever the schema matters — don't rely on prompt instructions alone. - Validate every response with a schema library (zod, pydantic, ajv) rather than trusting the model's output blindly.
- Check
stop_reasonformax_tokenstruncation before attempting to parse. - Build a retry path that feeds validation errors back to the model instead of failing silently.
- Log raw outputs for a sample of requests so you can catch drift in output shape over time.
questions
Does Claude API have a true JSON-only response mode? Not exactly. There's no single flag that guarantees JSON output the way some other APIs offer. The reliable equivalent is forcing a tool call with tool_choice, which returns a parsed object rather than free text.
Why does my Claude JSON output sometimes fail to parse? The most common cause is truncation — the response hit max_tokens mid-object. Check the stop_reason field; if it's max_tokens, increase the limit or shorten your schema.
Should I validate Claude's JSON output even when using tool use? Yes. Tool schemas constrain shape and types, but they don't enforce business rules like value ranges or cross-field consistency. Always run a schema validator like zod or pydantic before using the data downstream.