Originally published at claudeguide.io/claude-api-json-mode
Claude doesn't have a dedicated "JSON mode" endpoint like OpenAI, but you can achieve near-100% reliable JSON output using 3 techniques: system prompt instruction (97% reliability), assistant prefilling (99.5%), and tool_choice force (100%). For claude-sonnet-4-5, the system prompt approach alone achieves 97%+ JSON compliance — add prefilling and you're at 99.5%.
Technique 1: System Prompt Instruction (Simplest)
The fastest path to JSON output:
import anthropic
import json
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system="""You are a data extraction API. Always respond with valid JSON only.
No explanatory text, no markdown code fences, no prefixes.
Your entire response must be parseable by json.loads().""",
messages=[{
"role": "user",
"content": "Extract: name, email, company from: 'Hi, I'm Sarah Chen, sarah@acme.io, Acme Corp'"
}]
)
data = json.loads(response.content[0].text)
# {"name": "Sarah Chen", "email": "sarah@acme.io", "company": "Acme Corp"}
Works 97%+ of the time with claude-sonnet-4-5. The remaining 3% happens when Claude adds a brief explanation before the JSON. Fix that with prefilling.
Technique 2: Response Prefilling (Most Reliable)
Force JSON by starting the assistant's response with {:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system="Extract structured data as JSON. Include all fields you find.",
messages=[
{"role": "user", "content": "Parse: Alice Johnson, Head of Engineering, alice@startup.io"},
{"role": "assistant", "content": "{"} # Prefill — Claude continues from here
]
)
# Response will be the JSON body (without the opening {)
raw = "{" + response.content[0].text
data = json.loads(raw)
When you prefill with {, Claude must continue with valid JSON. It cannot prepend explanation text because the response already started. This brings compliance to 99.5%+.
Technique 3: Tool Use Enforcement (Best for Schemas)
Use tool use to enforce a specific JSON schema. Claude cannot return non-JSON when a tool call is required:
tools = [{
"name": "extract_contact",
"description": "Extract contact information from text",
"input_schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string", "format": "email"},
"company": {"type": "string"},
"role": {"type": "string"}
},
"required": ["name"]
}
}]
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "extract_contact"}, # Force this tool
messages=[{"role": "user", "content": "Alice Johnson, Head of Engineering at Startup Inc"}]
)
# Get structured data from tool call
tool_block = next(b for b in response.content if b.type == "tool_use")
data = tool_block.input # Already a dict, no json.loads() needed
# {"name": "Alice Johnson", "role": "Head of Engineering", "company": "Startup Inc"}
Tool use with tool_choice: {type: "tool", name: "..."} achieves 100% schema compliance — Claude literally cannot return anything except valid tool arguments.
Choosing the Right Technique
| Technique | Compliance | Schema Control | Complexity | Cost |
|---|---|---|---|---|
| System prompt | 97% | Loose | Low | Base |
| + Prefilling | 99.5% | Loose | Low | Base |
| Tool use | 100% | Strict | Medium | +~15% tokens |
Use system prompt + prefill when you need quick JSON without caring about exact schema enforcement.
Use tool use when you need strict schema validation, required fields, and type checking.
Production-Grade JSON Extraction
Combining all techniques with error handling:
python
import anthropic
import json
import re
from typing import TypeVar, Type
from pydantic import BaseModel
client = anthropic.Anthropic()
T = TypeVar('T', bound=BaseModel)
def extract_json(
prompt: str,
schema: Type[T],
*,
model: str = "claude-sonnet-4-5"
) -
Top comments (0)