Claude API Structured Output Best Practices
If you're building anything beyond a chat interface, you need Claude to return data your code can parse without regex gymnastics or prayer. The best practice for structured output with the Claude API is to use tool use (function calling) with a strict JSON schema instead of asking the model to "respond in JSON" inside a plain text prompt. Tool use constrains the model's output shape at generation time, which is far more reliable than hoping it follows formatting instructions.
The second part of the answer is validation: even with tool use, you should validate the returned object against your schema before trusting it downstream, handle the edge cases where Claude declines to call a tool, and design your schema so there's only one obviously correct way to fill it in. The rest of this guide walks through each of these in practice.
Use Tool Use Instead of Prompt-Based JSON
Asking Claude to "only respond with valid JSON, nothing else" works most of the time, but "most of the time" is not a production guarantee. The model can wrap output in markdown fences, add a sentence before the JSON, or produce near-valid JSON with trailing commas.
Tool use solves this at the API level. You define a schema, Claude is guided to populate it, and the response comes back as a structured tool call rather than free text you have to extract from a larger message.
{
"name": "extract_invoice",
"description": "Extract structured invoice data from text",
"input_schema": {
"type": "object",
"properties": {
"vendor": { "type": "string" },
"total_cents": { "type": "integer" },
"currency": { "type": "string" },
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": { "type": "string" },
"amount_cents": { "type": "integer" }
},
"required": ["description", "amount_cents"]
}
}
},
"required": ["vendor", "total_cents", "currency"]
}
}
If you're routing API calls through SubToAPI, this works the same way — send your tool definitions in the request and parse the tool_use block in the response. See /docs/tools for the full schema reference.
Force the Tool Call When You Need Structure
By default, Claude decides whether to call a tool or respond in plain text. If structured output is mandatory — not optional — force it by setting tool choice to require that specific tool. This removes an entire class of failure where the model answers conversationally instead of returning data.
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 1024,
"tools": [{"name": "extract_invoice", "input_schema": { ... }}],
"tool_choice": {"type": "tool", "name": "extract_invoice"},
"messages": [{"role": "user", "content": "Invoice text here..."}]
}'
Forcing the tool choice is the single highest-impact change you can make if your pipeline breaks whenever Claude decides to be chatty instead of structured.
Keep Schemas Narrow and Unambiguous
Structured output quality drops when schemas are vague or overloaded. A few concrete rules:
- Use enums wherever possible.
"status": {"type": "string", "enum": ["pending", "approved", "rejected"]}is far more reliable than a free-text status field. - Avoid optional fields you don't actually need optional. Every nullable field is a decision point where the model can go either way inconsistently. Mark fields
requiredwhenever your downstream code needs them populated. - Split large extraction tasks into smaller tool calls. A single schema trying to extract 40 fields from a long document performs worse than two or three focused calls.
- Add field descriptions. A one-line
descriptionper property materially improves accuracy, especially for fields like dates or currency codes where format matters. - Use integers for money, not floats. Cents as integers avoid floating-point rounding issues entirely.
Validate Before You Trust It
Tool use dramatically reduces malformed output, but it doesn't guarantee your business logic is satisfied — a schema can be technically valid JSON while still containing a date in the wrong format or a total that doesn't match the line items. Run the output through a JSON Schema validator (Zod, Pydantic, ajv, whatever fits your stack) as a mandatory step before it touches your database or triggers downstream actions.
import { z } from "zod";
const InvoiceSchema = z.object({
vendor: z.string().min(1),
total_cents: z.number().int().positive(),
currency: z.string().length(3),
line_items: z.array(
z.object({
description: z.string(),
amount_cents: z.number().int(),
})
),
});
const result = InvoiceSchema.safeParse(toolCallInput);
if (!result.success) {
// retry with a clarifying message, or fall back to manual review
}
If validation fails, the cheapest fix is often a single retry with the validation error appended to the conversation — Claude is generally good at correcting itself when told exactly what was wrong.
Handle the "No Tool Call" Case
Even with tool choice forced, build for the case where the API returns an error, a refusal, or an unexpected stop_reason. Check stop_reason before assuming you have a tool_use block, and have a defined fallback (retry, log for review, return a typed error to your caller) rather than letting a null-pointer exception surface in production.
Keep an Eye on Token Usage
Large schemas and verbose descriptions add to your input token count on every call. If you're extracting the same structure repeatedly, measure actual token usage via response metadata and trim unused fields. SubToAPI surfaces usage per request in your dashboard, which makes it easy to spot schemas that have grown bloated over time. Getting started takes a few minutes — see /docs/quickstart — and the free trial at /signup lets you test tool-based extraction against your own documents before committing to a plan at /pricing.
Streaming and Structured Output
Structured output and streaming don't mix cleanly — a tool call typically arrives as a complete block rather than token-by-token, so you can't partially parse it mid-stream the way you can with plain text. If your application needs both low-latency text and structured extraction, consider using two calls: a streamed response for the user-facing text and a separate non-streamed tool call for the data extraction. Details on streaming behavior are in /docs/streaming.
Questions
Does forcing tool_choice reduce response quality? Not meaningfully for well-scoped extraction tasks. It removes the model's option to respond conversationally, which is exactly the behavior you want when structure is mandatory.
Should I use tool use or just ask for JSON in the prompt for simple cases? For one-off scripts or prototypes, prompt-based JSON is fine. For anything running in production or processing user data at scale, tool use with a schema and validation is worth the extra setup time.
What's the most common cause of broken structured output? Overly broad schemas with too many optional fields. Narrow the schema, add enums and descriptions, and split large extractions into multiple smaller tool calls.