Claude API Structured Output Extraction Guide
Extracting structured output from Claude means getting consistent, machine-readable data — JSON objects, arrays, typed fields — back from a model that natively generates free-form text. The reliable way to do this is Claude's tool use feature combined with a well-defined JSON schema, not regex parsing of prose responses.
This guide covers the two practical approaches (tool-based extraction and prompt-based JSON generation), when to use each, how to handle edge cases like missing fields and malformed output, and how to validate what comes back before it hits your application logic.
Why structured output is harder than it looks
Claude is a language model, not a database. Left to its own devices it will happily wrap a JSON object in explanatory sentences, use inconsistent key casing, or add a trailing comment after the closing brace. If your downstream code does JSON.parse() on the raw response, you will eventually get an exception in production — usually from a response that's 99% correct but has one stray sentence before the {.
There are two reliable fixes:
- Tool use (recommended) — define a schema and let Claude call it as a function. The model returns a structured
tool_useblock instead of free text. - Constrained prompting — ask for JSON only, with strict formatting instructions and a response prefill, when tool use isn't available or isn't a fit.
Method 1: Tool use for guaranteed structure
This is the most reliable extraction pattern because the schema is part of the request, not a hope embedded in your prompt.
{
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"tools": [
{
"name": "extract_invoice",
"description": "Extract structured invoice data from text",
"input_schema": {
"type": "object",
"properties": {
"vendor": { "type": "string" },
"invoice_number": { "type": "string" },
"total_amount": { "type": "number" },
"currency": { "type": "string" },
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": { "type": "string" },
"quantity": { "type": "number" },
"unit_price": { "type": "number" }
},
"required": ["description", "quantity", "unit_price"]
}
}
},
"required": ["vendor", "invoice_number", "total_amount", "currency"]
}
}
],
"tool_choice": { "type": "tool", "name": "extract_invoice" },
"messages": [
{ "role": "user", "content": "Invoice #4471 from Acme Supplies, total $1,240.50 USD, two line items: 10 widgets at $100 each, one assembly fee at $240.50." }
]
}
Forcing tool_choice to the specific tool name guarantees Claude won't respond with plain text — it must populate the schema. The response comes back as a tool_use content block with an input object matching your schema, ready to parse without stripping surrounding text.
const toolUse = response.content.find(block => block.type === "tool_use");
const invoiceData = toolUse.input; // already a JS object
No string parsing, no regex, no "find the first { and last }" hacks.
Method 2: JSON-only prompting
For cases where you don't need function-calling semantics — just clean JSON back — you can skip tools and instruct the model directly, using a prefill to anchor the output format:
{
"model": "claude-sonnet-4-20250514",
"max_tokens": 512,
"messages": [
{ "role": "user", "content": "Extract the person's name, age, and city from this text as JSON: 'Maria, 34, lives in Lisbon.' Return only JSON, no explanation." },
{ "role": "assistant", "content": "{" }
]
}
Prefilling the assistant turn with { strongly biases Claude toward continuing valid JSON rather than adding preamble. This works well for simple extraction but is less robust than tool use for nested or variable-length structures — there's no schema enforcement, so you still need to validate the result.
Validating what comes back
Even with tool use, treat extracted data as untrusted input until validated:
- Check required fields are present and non-null. A schema marks fields as required, but Claude can still omit them if the source text lacks the information — handle that as a legitimate "not found" case, not a crash.
- Type-check before use. JSON numbers can arrive as strings in edge cases with prompt-based extraction; tool use handles this more reliably since the schema types are enforced at generation time.
- Validate arrays aren't empty when they shouldn't be, and cap array length expectations if you're extracting from long documents where Claude might truncate due to
max_tokens. - Run a JSON Schema validator (ajv for Node, pydantic for Python) on the output even after tool use, especially if the schema has conditional logic that
input_schemaalone can't express.
Handling multi-document or batch extraction
If you're extracting structured data from many documents, send one document per request rather than concatenating them into a single prompt. This keeps tool_use outputs cleanly scoped per document, avoids field-mixing across documents, and makes retries cheap — a failed parse only costs you one document, not an entire batch.
Running this through SubToAPI
If your extraction pipeline is already built against Claude's Messages API, SubToAPI lets you route the same requests through a stable HTTPS endpoint with your own sub_live_ application keys, without changing your tool schemas or message structure. This is useful when you're shipping extraction as a feature to end users and need per-key usage tracking, team seats, or a billing layer in front of your own Claude access. See /docs/tools for tool use request formats and /docs/messages for the full Messages API reference. Get started with a free trial at /signup.
questions
Does Claude API support native JSON mode like some other providers? Not as a dedicated mode flag. Instead, use tool use with a forced tool_choice for schema-guaranteed output, or prompt-based JSON generation with an assistant prefill for simpler cases.
What happens if Claude can't find a required field in the source text? With tool use, Claude may omit the field or return an empty string/null depending on your schema's flexibility. Design your schema to make genuinely optional fields non-required, and handle missing-required-field responses explicitly in your code rather than assuming extraction always succeeds.
Is tool-based extraction slower or more expensive than plain text prompting? Token usage is similar — the schema adds some input tokens, but output tokens are typically lower since there's no explanatory text. Latency difference is negligible for most extraction workloads.