Structured Outputs with Claude's Strict Mode
By Learnia AI Research Team
Structured Outputs with Claude's Strict Mode
📅 Last updated: March 10, 2026 — Covers
strict: true, JSON schema validation, client/server tools, and tool search.
🔗 Related articles: Claude Tool Use Guide · Claude API Complete Guide · Reliable JSON Output from LLMs
In production, an LLM that returns free-form text is a liability. One missing field, one incorrect type, one renamed key — and your pipeline breaks. Claude's strict mode solves this: it guarantees that every output matches your JSON schema exactly.
Why Structured Outputs Matter in Production
When you integrate Claude into an application, you need predictable data:
- →APIs: endpoints expect precise formats
- →Databases: inserts require exact types
- →Pipelines: each stage consumes the output of the previous one
- →UI: front-end components expect defined structures
Without guaranteed structure, you're writing validation code, handling error cases, and hoping the model doesn't change format between calls.
For a broader perspective on why structured outputs are critical in AI systems, see our detailed article on why structured AI outputs matter.
How Claude's Tool Use Mechanism Works
Claude uses tools as its primary mechanism for producing structured outputs. Here's the flow:
When you define a tool with a JSON schema, Claude generates the parameters in the exact format you specified. This is the mechanism that strict mode reinforces.
Defining a Basic Tool
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "extract_contact",
"description": "Extract contact information from text",
"input_schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Full name of the person"
},
"email": {
"type": "string",
"description": "Email address"
},
"phone": {
"type": "string",
"description": "Phone number"
}
},
"required": ["name", "email"]
}
}
]
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=[{
"role": "user",
"content": "Contact Marie Dupont at marie@example.com or 555-0123"
}]
)
The strict: true Parameter
Strict mode is enabled by adding strict: true at the tool definition level. This forces Claude to produce JSON that passes schema validation — on every call, without exception.
tools = [
{
"name": "analyze_sentiment",
"description": "Analyze the sentiment of a text",
"strict": True, # ← Enables strict mode
"input_schema": {
"type": "object",
"properties": {
"sentiment": {
"type": "string",
"enum": ["positive", "negative", "neutral"]
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"keywords": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["sentiment", "confidence", "keywords"]
}
}
]
What strict: true Guarantees
| Aspect | Without strict | With strict: true |
|---|---|---|
| Required fields | Usually present | Always present |
| Correct types | Almost always | Always correct |
| Enum values | Sometimes off-list | Always within the list |
| Extra fields | Possible | Never added |
| Reliable parsing | ~95-99% | 100% error-free |
Advanced JSON Schema Patterns
Nested Objects
For complex structures, nest objects within your schema:
tools = [
{
"name": "extract_invoice",
"description": "Extract data from an invoice",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"date": {"type": "string", "description": "ISO 8601 format"},
"vendor": {
"type": "object",
"properties": {
"name": {"type": "string"},
"address": {"type": "string"},
"tax_id": {"type": "string"}
},
"required": ["name", "address"]
},
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": {"type": "string"},
"quantity": {"type": "integer"},
"unit_price": {"type": "number"},
"total": {"type": "number"}
},
"required": ["description", "quantity", "unit_price", "total"]
}
},
"total_amount": {"type": "number"},
"currency": {
"type": "string",
"enum": ["EUR", "USD", "GBP"]
}
},
"required": ["invoice_number", "date", "vendor", "line_items", "total_amount", "currency"]
}
}
]
Enums and Constrained Categories
Enums are especially powerful with strict mode — Claude will never produce a value outside the list:
"priority": {
"type": "string",
"enum": ["critical", "high", "medium", "low"],
"description": "Priority level for the ticket"
}
Typed Arrays
For lists of elements with guaranteed structure:
"tags": {
"type": "array",
"items": {
"type": "string",
"enum": ["bug", "feature", "improvement", "documentation"]
},
"description": "Tags applicable to the issue"
}
Client Tools vs Server Tools
Claude supports two types of tools, and both can use strict mode:
Client Tool Workflow
This is the most common pattern for structured outputs in production:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": user_input}]
)
# Extract the structured result
for block in response.content:
if block.type == "tool_use":
# block.input contains the schema-validated JSON
structured_data = block.input
print(f"Tool: {block.name}")
print(f"Data: {structured_data}")
# No need for try/except for JSON parsing
# In strict mode, the structure is guaranteed
process_data(structured_data)
Server Tool Workflow (Built-in)
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
tools=[{
"type": "web_search_20250305",
"name": "web_search",
"max_uses": 3
}],
messages=[{"role": "user", "content": "Latest news about Claude"}]
)
Programmatic Tool Calling (Forcing Usage)
By default, Claude decides whether to use a tool. For structured outputs, you often want to force the call:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "extract_contact"}, # Force the tool
messages=[{"role": "user", "content": text}]
)
tool_choice Options
| Value | Behavior |
|---|---|
{"type": "auto"} | Claude decides whether to use a tool (default) |
{"type": "any"} | Claude must use at least one tool |
{"type": "tool", "name": "X"} | Claude must use tool X specifically |
{"type": "none"} | Claude cannot use any tools |
💡 Production tip: Use
tool_choice: {"type": "tool", "name": "..."}when you're using a tool as a structured output mechanism — not as a function to execute. This is the most reliable pattern.
Tool Search: Handling Hundreds of Tools
When your system offers over 100 tools, including them all in every request is expensive in tokens and reduces selection accuracy. Tool search solves this problem.
The Concept
Instead of sending all tool definitions, you send a text description of each tool. Claude selects the relevant ones, then you resend only the full definitions for those.
# Step 1: Lightweight descriptions of all tools
tool_descriptions = """
Available tools:
- get_user_profile: Retrieve a user's profile by ID
- update_user_email: Update a user's email address
- list_invoices: List a client's invoices
- create_invoice: Create a new invoice
- send_notification: Send a push notification
- get_analytics: Retrieve usage metrics
... (200+ tools)
"""
# Step 2: Claude selects the relevant tools
selection_response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=512,
messages=[{
"role": "user",
"content": f"User request: '{user_query}'\n\n{tool_descriptions}\n\nWhich tools are needed? Reply with names only."
}]
)
# Step 3: Call with only the selected tools
selected_tools = parse_tool_names(selection_response)
full_tool_defs = [t for t in all_tools if t["name"] in selected_tools]
final_response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=2048,
tools=full_tool_defs, # Only the relevant tools
messages=[{"role": "user", "content": user_query}]
)
Tool Search Benefits
| Without Tool Search | With Tool Search |
|---|---|
| 200 definitions × ~200 tokens = 40K tokens | Lightweight description ~2K + 3 definitions = ~2.6K tokens |
| Imprecise selection (too many choices) | Targeted, accurate selection |
| High per-request cost | ~90% cost reduction |
| Increased latency | Optimized latency |
To see how this pattern fits into complex agent architectures, check our guide on Claude agent architecture patterns.
Error Handling and Best Practices
Extracting Results Cleanly
def extract_tool_result(response):
"""Extract the structured result from a Claude tool response."""
for block in response.content:
if block.type == "tool_use":
return {
"tool_name": block.name,
"tool_id": block.id,
"data": block.input # JSON guaranteed valid in strict mode
}
return None
# Usage
result = extract_tool_result(response)
if result:
# In strict mode, every field is guaranteed present and typed
contact = result["data"]
save_to_database(contact["name"], contact["email"])
Multi-turn Pattern (Conversation with Tools)
messages = [{"role": "user", "content": user_query}]
while True:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=2048,
tools=tools,
messages=messages
)
# Check if Claude wants to use a tool
if response.stop_reason == "tool_use":
# Execute each requested tool
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result)
})
# Append Claude's response and the tool results
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
# Final response (no tool use)
break
Production Checklist
- →Always use
strict: truefor structured output tools - →Always specify
requiredfor mandatory fields - →Use
enumfor constrained-choice values - →Force the tool with
tool_choicewhen structured output is the main goal - →Write clear, concise descriptions — they guide Claude as much as the schema
- →Test with edge case inputs (empty text, mixed languages, noisy data)
Pricing Considerations
Tool definitions consume input tokens. Here's how to optimize:
| Strategy | Impact |
|---|---|
| Concise descriptions | -30% tokens per tool |
| Tool search for 50+ tools | -80-90% input tokens |
Forced tool_choice | Avoids an extra turn |
| Minimal schemas | Fewer tokens, same results |
| Reuse conversations | Prompt caching (automatic reduction) |
💡 Tool definitions benefit from Anthropic's prompt caching. If you send the same tools on every request, the token cost is automatically reduced after the first call.
For more details on the Claude API and pricing options, see the Claude API Complete Guide.
Full Example: Data Extraction Pipeline
Here's a complete production example combining all the concepts:
import anthropic
import json
client = anthropic.Anthropic()
# Tool definition with strict mode
extraction_tool = {
"name": "extract_product_review",
"description": "Extract structured data from a product review",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"product_name": {"type": "string"},
"rating": {
"type": "integer",
"description": "Rating from 1 to 5"
},
"sentiment": {
"type": "string",
"enum": ["very_positive", "positive", "neutral", "negative", "very_negative"]
},
"pros": {
"type": "array",
"items": {"type": "string"},
"description": "Positive points mentioned"
},
"cons": {
"type": "array",
"items": {"type": "string"},
"description": "Negative points mentioned"
},
"recommendation": {"type": "boolean"},
"summary": {
"type": "string",
"description": "One-sentence summary of the review"
}
},
"required": [
"product_name", "rating", "sentiment",
"pros", "cons", "recommendation", "summary"
]
}
}
def analyze_review(review_text: str) -> dict:
"""Analyze a product review and return structured data."""
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=[extraction_tool],
tool_choice={"type": "tool", "name": "extract_product_review"},
messages=[{
"role": "user",
"content": f"Analyze this product review:\n\n{review_text}"
}]
)
for block in response.content:
if block.type == "tool_use":
return block.input # Structure guaranteed by strict: true
return None
# Usage
review = """
I bought the Sony WH-1000XM5 headphones 3 months ago. The noise cancellation
is exceptional, the comfort is top-notch, and the battery easily lasts
30 hours. Only downside: the price is a bit high and the carrying case
is less compact than the XM4. Despite that, I recommend them 100%.
"""
result = analyze_review(review)
print(json.dumps(result, indent=2))
# {
# "product_name": "Sony WH-1000XM5",
# "rating": 4,
# "sentiment": "very_positive",
# "pros": ["Exceptional noise cancellation", "Top-notch comfort", "30h battery life"],
# "cons": ["High price", "Less compact case than XM4"],
# "recommendation": true,
# "summary": "Excellent headphones with outstanding noise cancellation, despite a premium price."
# }
Summary
| Concept | Key Point |
|---|---|
strict: true | Guarantees JSON schema compliance — zero parsing errors |
Forced tool_choice | Forces Claude to use a specific tool |
| Client tools | You execute the tool — full control |
| Server tools | API executes (web_search, code_execution) |
| Tool search | Reduces tokens by ~90% for 100+ tools |
enum + required | Constrain values and guarantee fields |
| Prompt caching | Automatically reduces cost for repeated tool definitions |
Structured outputs with strict mode transform Claude from a text generator into a reliable data engine. Combined with agent architecture patterns and a robust JSON pipeline, you have everything you need to build solid production systems.
Continue Your Learning
- →Claude Tool Use: Complete Guide — Built-in and custom tools
- →Reliable JSON Output from LLMs — Complementary techniques
- →Why Structured AI Outputs Matter — Context and motivations
- →Claude Agent Architecture Patterns — Patterns for complex systems
- →Claude API Complete Guide — Full API reference
Weekly AI Insights
Tools, techniques & news — curated for AI practitioners. Free, no spam.
Free, no spam. Unsubscribe anytime.
→Related Articles
FAQ
What is strict mode in Claude tools?+
Strict mode (strict: true) forces Claude to produce JSON outputs that exactly match the defined tool schema. Every field, type, and constraint is guaranteed — eliminating parsing errors in production.
What's the difference between client tools and server tools?+
A client tool returns the result to your code for local execution. A server tool is executed directly by the Anthropic API (like web_search or code_execution). Both support strict mode.
How do I handle 100+ tools with Claude?+
Use tool search: send a text description of all your tools and let Claude select the most relevant ones. This reduces token consumption and improves call accuracy.
Does strict mode affect pricing?+
Strict mode itself doesn't add direct cost. However, tool definitions consume input tokens. Optimize by using concise descriptions and tool search for large tool sets.