Claude Tool Use for JSON Output: Complete 2026 Guide (Structured Outputs vs Tool Use)
Master Claude's tool use and native structured outputs for reliable JSON. Covers output_config.format, strict tool use, schema limitations, and repair fallback for edge cases.
Have broken JSON right now? Fix it free in under 1 second — no signup.
Fix My JSON →Getting reliable JSON from Claude has gone through three distinct phases: raw prompting (fragile), tool use (reliable), and now native structured outputs (guaranteed). With Anthropic's Structured Outputs feature now generally available on the Claude Developer Platform and Amazon Bedrock, developers have schema-enforced JSON generation baked into the API — but there are important caveats, schema subset limitations, and edge cases where you still need a repair layer.
This guide covers everything: when to use tool use vs. output_config.format, what the schema compiler can and can't do, and how to build a production-grade pipeline that never crashes on malformed JSON.
Written by The JSON Medic.
The Three Eras of Claude JSON Output
Before structured outputs existed, getting JSON from Claude meant:
- Prompt engineering (unreliable) — "Return only valid JSON, no prose" — works most of the time, breaks on complex schemas
- Tool use trick (reliable, but indirect) — Define a dummy tool, force Claude to "call" it, extract the arguments — the tool input schema enforced structure
- Native structured outputs (2026, GA) — Anthropic turns your schema into a sampling grammar that constrains generation token by token
Each method is still valid today for different use cases. Understanding when to use each one prevents unnecessary complexity in your codebase.
Method 1: Native Structured Outputs (output_config.format)
Claude's structured output feature constrains the model's response to match a JSON Schema you provide. The first request with a new schema incurs latency while Anthropic compiles the schema into a formal grammar — subsequent requests with the same schema use the cached grammar.
JSON Mode
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
output_config={
"format": {
"type": "json_schema",
"json_schema": {
"name": "repair_result",
"schema": {
"type": "object",
"properties": {
"fixed_json": {"type": "string"},
"errors_found": {
"type": "array",
"items": {"type": "string"}
},
"was_repairable": {"type": "boolean"}
},
"required": ["fixed_json", "errors_found", "was_repairable"]
}
}
}
},
messages=[{
"role": "user",
"content": "Analyze this JSON and return results: {name: 'Alice', age: 30,}"
}]
)
result = response.content[0].text # guaranteed valid JSON matching schema
TypeScript equivalent:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
output_config: {
format: {
type: "json_schema",
json_schema: {
name: "repair_result",
schema: {
type: "object",
properties: {
fixed_json: { type: "string" },
errors_found: { type: "array", items: { type: "string" } },
was_repairable: { type: "boolean" },
},
required: ["fixed_json", "errors_found", "was_repairable"],
},
},
},
},
messages: [
{
role: "user",
content: "Analyze this JSON and return results: {name: 'Alice', age: 30,}",
},
],
});
const result = JSON.parse(response.content[0].text); // always valid
The key guarantee: Claude cannot emit a character that breaks the schema. The sampling grammar enforces this at the token level, not in post-processing.
Method 2: Strict Tool Use
For agentic workflows where you want type-safe function calls, use strict: true on tool definitions. This constrains Claude's tool argument generation to match the tool's input_schema exactly.
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=[{
"name": "process_user_data",
"description": "Process and validate user data",
"input_schema": {
"type": "object",
"properties": {
"user_id": {"type": "integer"},
"email": {"type": "string", "format": "email"},
"tags": {"type": "array", "items": {"type": "string"}},
"metadata": {"type": "object"}
},
"required": ["user_id", "email"]
},
"strict": True # enforces schema at generation time
}],
tool_choice={"type": "tool", "name": "process_user_data"},
messages=[{"role": "user", "content": "Process user ID 42, email [email protected]"}]
)
# Extract tool call arguments — guaranteed to match schema
tool_use = next(b for b in response.content if b.type == "tool_use")
args = tool_use.input # already a dict, not a string
The tool_choice parameter with type: "tool" forces Claude to always call the specific tool — you'll never get a text response when you need a structured one.
Method 3: Classic Tool Use (Without strict)
Before the strict flag existed, developers used tool definitions as a structured output trick without the schema guarantee. This still works and is useful when:
- Your schema uses keywords outside the supported subset (see below)
- You're on older Claude model versions
- You need schema features like
oneOf,anyOf,if/then/else
# Classic approach — reliable but not grammar-enforced
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=[{
"name": "extract_data",
"description": "Extract structured data",
"input_schema": {
"type": "object",
"properties": {
"value": {"type": "string"},
"confidence": {"type": "number", "minimum": 0, "maximum": 1}
},
"required": ["value", "confidence"]
}
# no "strict": True — Claude targets the schema but isn't grammar-constrained
}],
tool_choice={"type": "tool", "name": "extract_data"},
messages=[{"role": "user", "content": "Rate the confidence for this extraction..."}]
)
Without strict, you get Claude's best effort at matching the schema — which is excellent for most schemas, but leaves open the 0.5-2% of cases where malformed output slips through.
Schema Subset Limitations
This is the most important section for production use. Anthropic's grammar compiler supports a subset of JSON Schema. Unsupported keywords cause a compile error or fall back silently. The unsupported set includes:
| Unsupported keyword | Workaround |
|---|---|
$ref (external URI) | Inline the referenced schema |
$dynamicRef | Flatten schema manually |
anyOf with incompatible types | Use oneOf with explicit types or post-validate |
if / then / else | Move conditional logic to post-processing code |
minimum / maximum on numbers | Post-validate; schema will accept any number |
pattern (regex) on strings | Post-validate; grammar cannot enforce regex |
minLength / maxLength | Post-validate |
"minimum": 0, "maximum": 100 on an integer field, the structured output will guarantee an integer — but not that it's within range. Add Pydantic or Zod validation after parsing:
from pydantic import BaseModel, Field, ValidationError
class ConfidenceScore(BaseModel):
score: float = Field(ge=0.0, le=1.0)
label: str
try:
validated = ConfidenceScore.model_validate(json.loads(response.content[0].text))
except ValidationError as e:
# Use AI JSON Medic as fallback repair layer
repaired = repair_via_api(response.content[0].text)
validated = ConfidenceScore.model_validate(json.loads(repaired))
Tool Use vs Structured Outputs: Decision Guide
| Scenario | Best approach |
|---|---|
| Extract structured data from text | output_config.format + json_schema |
| Agentic function calls | tools with strict: True + tool_choice |
Schema uses anyOf/if/then | Classic tool use (no strict) + post-validate |
| Legacy models (before Sonnet 4.5) | Classic tool use or prompt engineering |
| Maximum schema flexibility | Classic tool use with Pydantic/Zod validation |
| Guaranteed no-prose response | Either structured output mode |
Handling the Long Tail: When Structured Outputs Still Fail
Even with grammar-constrained generation, real-world pipelines encounter failures:
- Schema compilation errors — Your schema hits an unsupported keyword, falls back to unconstrained generation
- Token limit truncation — With large schemas and long content, the response truncates mid-structure
- Nested schema depth limits — Very deep nesting can exceed the grammar's ability to track state
- Tool call parsing failures — Network issues or API changes cause partial tool use blocks
For these cases, a repair layer is essential. Here's a production pattern:
import anthropic
import json
import httpx
client = anthropic.Anthropic()
def get_structured_output(prompt: str, schema: dict) -> dict:
"""Get structured output from Claude with repair fallback."""
try:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
output_config={
"format": {
"type": "json_schema",
"json_schema": {"name": "result", "schema": schema}
}
},
messages=[{"role": "user", "content": prompt}]
)
return json.loads(response.content[0].text)
except (json.JSONDecodeError, Exception) as e:
# Fallback: repair via AI JSON Medic API
raw = response.content[0].text if 'response' in dir() else "{}"
repair_response = httpx.post(
"https://aijsonmedic.com/api/repair",
json={"json": raw},
timeout=10.0
)
if repair_response.status_code == 200:
return json.loads(repair_response.json()["repaired"])
raise RuntimeError(f"Could not repair JSON output: {e}")
You can also validate the repaired JSON before returning it to ensure schema conformance.
Performance: Schema Caching
The first call with a new schema incurs latency while Anthropic compiles the grammar. This can be 2-5 seconds for complex schemas. After compilation, the grammar is cached — subsequent calls use the cached version.
Implications:- Cold-start schemas should be pre-warmed in production (send a dummy request at startup)
- Schema caching is per-account and per-model, not per-session
- Use the same exact schema object across calls — even whitespace changes invalidate the cache
Model Support (September 2026)
Structured outputs with output_config.format are supported on:
- Claude Sonnet 4.5 and later (Anthropic API + Amazon Bedrock)
- Claude Opus 4.5 and later
- Claude Haiku 4.5 and later
Strict tool use ("strict": True) follows the same model requirements.
For older models (Sonnet 4, Opus 4, etc.) use classic tool use or prompt engineering.
Migrating from Prefill to Structured Outputs
If you're using the deprecated prefill pattern to force JSON:
# OLD (deprecated — prefill removed in Sonnet 4.6+)
messages = [
{"role": "user", "content": "Extract user data as JSON"},
{"role": "assistant", "content": "{"} # prefill
]
Migrate to output_config.format:
# NEW (2026 standard)
response = client.messages.create(
model="claude-sonnet-4-6",
output_config={"format": {"type": "json_schema", "json_schema": {...}}},
messages=[{"role": "user", "content": "Extract user data as JSON"}]
)
The structured output approach is more reliable, doesn't require prompt hacking, and works on all current models. See Claude Prefill Deprecated: Fix Your JSON Scaffolding for the full migration guide.
Practical Example: LLM Pipeline with Full Error Handling
Here's a complete example of a production-grade JSON extraction pipeline using Claude's tool use with repair fallback:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
interface ExtractedUser {
name: string;
email: string;
role: "admin" | "user" | "viewer";
permissions: string[];
}
async function extractUserData(text: string): Promise<ExtractedUser> {
const schema = {
type: "object" as const,
properties: {
name: { type: "string" },
email: { type: "string" },
role: { type: "string", enum: ["admin", "user", "viewer"] },
permissions: { type: "array", items: { type: "string" } },
},
required: ["name", "email", "role", "permissions"],
};
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 512,
tools: [
{
name: "save_user",
description: "Save extracted user data",
input_schema: schema,
strict: true,
},
],
tool_choice: { type: "tool", name: "save_user" },
messages: [
{
role: "user",
content: `Extract user data from: ${text}`,
},
],
});
const toolUse = response.content.find((b) => b.type === "tool_use");
if (!toolUse || toolUse.type !== "tool_use") {
throw new Error("No tool use in response");
}
return toolUse.input as ExtractedUser;
}
This pattern gives you: schema enforcement (strict tool use), no-prose guarantees (tool_choice forced), and type safety (TypeScript inference from the tool input).
Common Errors and Fixes
schema_compilation_error
Your schema uses an unsupported keyword. Strip unsupported keywords and move that validation to Pydantic/Zod.
Empty content array
The model hit a token limit before completing the structured output. Increase max_tokens or simplify your schema.
tool_use block with partial_json
The response was truncated. The partial_json flag indicates incomplete tool arguments. Retry with a higher token limit.
Schema drift between calls
If you see inconsistent results, check that your schema object is identical across calls (same key order, same whitespace). Schema caching is sensitive to exact string equality.
Structured output returns null for required fields
The model genuinely couldn't infer the value from the input. Make sure your prompt includes the necessary data and that required fields are actually present in the input text.
For any output that still doesn't parse correctly, the AI JSON Medic Claude fixer handles malformed Claude output automatically — including partial tool use blocks, truncated JSON, and encoding errors.
FAQ
Does structured output work with Claude's streaming API?Yes. When using stream=True, the structured output guarantee still applies — the full response will conform to the schema. Individual streamed chunks may not be valid JSON until the stream completes.
output_config.format and tools at the same time?
No. output_config.format constrains the text response. Tools produce tool_use content blocks. Use one or the other depending on whether you need a text JSON response or a tool call response.
$ref to an external URI?
External $ref URIs are not resolved by the grammar compiler. Inline the referenced schema definition directly in your schema object before sending it to the API.
No — output_config.format is an API-only feature. The web interface does not support schema constraints.
With strict: true, Claude's token sampling is constrained by a grammar compiled from your schema. Without strict, Claude uses its training to produce tool arguments that match the schema — highly reliable, but not guaranteed at the sampling level.
Schema validity and semantic correctness are separate concerns. Use post-validation (Pydantic/Zod) to check business rules, and consider adding more specific field descriptions in your schema to guide Claude's interpretation. If the JSON is structurally malformed despite the schema, use the AI JSON Medic repair API as a fallback.
Still dealing with broken JSON?
Paste it in and get it fixed in under 1 second — free, no signup, no install. Works with ChatGPT, Claude, n8n, and any AI output.
Fix My JSON Free →Related Articles