Claude API Structured Output & JSON Mode

Force Claude to return valid JSON using tool use or system prompts. Python and Node.js examples for structured output, schemas, and parsing. Works with all Claude 4 models.

🔥 Launch tonight — Power Prompts PDF 50p (just 50p tonight)30 battle-tested Claude Code prompts · 8 pages · paste into CLAUDE.md · price reverts to £5

Claude does not have a built-in "JSON mode" like OpenAI, but you can reliably get structured JSON output using two approaches: tool use (recommended) or a well-crafted system prompt. Tool use is more reliable because Claude is specifically trained to populate tool arguments as valid JSON.

Method 1: Tool use (recommended)

Define a tool whose input_schema matches the JSON structure you want. Force Claude to call it with tool_choice.

import anthropic, json

client = anthropic.Anthropic()

tool = {
    "name": "extract_info",
    "description": "Extract structured information from text.",
    "input_schema": {
        "type": "object",
        "properties": {
            "name": {"type": "string", "description": "Person's full name"},
            "email": {"type": "string", "description": "Email address"},
            "company": {"type": "string", "description": "Company name"},
            "role": {"type": "string", "description": "Job title"}
        },
        "required": ["name", "email"]
    }
}

message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=512,
    tools=[tool],
    tool_choice={"type": "tool", "name": "extract_info"},  # force this tool
    messages=[
        {"role": "user", "content": "From this email signature: John Doe, CTO at Acme Corp, john@acme.com — extract the contact info."}
    ]
)

# Tool use blocks are in message.content
tool_block = next(b for b in message.content if b.type == "tool_use")
data = tool_block.input  # already a dict — no json.loads needed
print(data)
# {'name': 'John Doe', 'email': 'john@acme.com', 'company': 'Acme Corp', 'role': 'CTO'}

Method 2: System prompt with JSON instruction

Works well for simple schemas. Less reliable for complex nested structures — use tool use for those.

message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=256,
    system='Respond ONLY with valid JSON. No explanation, no markdown, no code fences. Use the schema: {"sentiment": "positive|negative|neutral", "confidence": 0.0-1.0, "topics": [string]}',
    messages=[
        {"role": "user", "content": "The new Claude 4 models are incredibly fast and cheap. Highly recommend!"}
    ]
)

import json
result = json.loads(message.content[0].text)
print(result)
# {'sentiment': 'positive', 'confidence': 0.95, 'topics': ['Claude 4', 'speed', 'pricing']}

Node.js: structured output with tool use

import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 512,
  tools: [{
    name: "record_product",
    description: "Record product details as structured data.",
    input_schema: {
      type: "object",
      properties: {
        name: { type: "string" },
        price: { type: "number" },
        category: { type: "string" },
        in_stock: { type: "boolean" }
      },
      required: ["name", "price", "category", "in_stock"]
    }
  }],
  tool_choice: { type: "tool", name: "record_product" },
  messages: [{ role: "user", content: "Apple AirPods Pro 2 cost $249, they're in the audio category, and yes they're available." }]
});

const toolBlock = response.content.find(b => b.type === "tool_use");
console.log(toolBlock.input);
// { name: 'Apple AirPods Pro 2', price: 249, category: 'audio', in_stock: true }

Batch structured extraction

# Use the Message Batches API to extract JSON from 100s of documents cheaply
# (50% cost reduction vs standard API)
batch = client.messages.batches.create(
    requests=[
        {
            "custom_id": f"doc-{i}",
            "params": {
                "model": "claude-haiku-4-5",
                "max_tokens": 256,
                "tools": [tool],
                "tool_choice": {"type": "tool", "name": "extract_info"},
                "messages": [{"role": "user", "content": doc_text}]
            }
        }
        for i, doc_text in enumerate(documents)
    ]
)
print(f"Batch ID: {batch.id}")  # poll until complete

See Claude Cost Calculator to estimate batch extraction costs. Use Prompt-Pricing Recommender to pick the right model for your schema complexity.

Frequently asked questions

Does Claude have a JSON mode like GPT-4?
Not as a single flag, but tool use achieves the same result more reliably. Set tool_choice to {type: 'tool', name: 'your_tool'} to force Claude to always return valid JSON matching your schema. The tool input is a Python dict (or JS object) already parsed — no json.loads needed.
How do I make Claude return a list of objects?
Wrap the list in an object: {type: 'object', properties: {items: {type: 'array', items: {/* your schema */}}}, required: ['items']}. Claude populates the items array and you access tool_block.input['items'].
Can Claude validate the JSON against my schema?
Claude generates JSON that conforms to the input_schema you define in the tool, but it does not run a JSON Schema validator internally. For critical pipelines, validate the output yourself using jsonschema (Python) or ajv (Node.js) after receiving the response.
What happens if Claude can't fill a required field?
Claude will still populate all required fields, but may use null or a placeholder value if the information is absent from the input. For strict extraction, add instructions like 'if a field is not present in the text, use null' in the tool description or system prompt.

Free tools

Cost Calculator → Prompt-Pricing Recommender → Diff Summarizer → Skills Browser →

Related

Claude Opus 4.7 vs Sonnet 4.6 Pricing (2026 Comparison)How Much Does Claude Cost? (2026 API Pricing Guide)Claude Prompt Caching: 90% Cost Savings Explained (2026)Claude API Cost Calculator: Estimate Your Anthropic BillClaude vs GPT-4 Pricing: 2026 API Cost Comparison