← Blog

Claude API Output Format JSON Enforcement Guide

2026-11-08 · 5 min read · SubToAPI Team

Getting Claude to return valid, predictable JSON every single time is one of the most common integration headaches developers run into. Unlike some APIs, Claude does not have a dedicated "JSON mode" toggle — there is no response_format: "json" parameter you can pass. Instead, JSON enforcement with the Claude API is achieved through a combination of tool use (function calling), careful prompting, and server-side validation.

The short answer: use tool use with a strict input schema if you need guaranteed structure, and use prompt-based constraints plus a parser fallback if you just need "mostly JSON" for simple cases. Below is how each approach works, when to use it, and how to avoid the most common failure modes.

Why Claude Doesn't Have a Native JSON Mode

Some LLM APIs expose a strict JSON-only response mode that constrains token sampling to valid JSON grammar. Claude's API does not currently offer this as a standalone flag. What it does offer — and what works reliably in production — is tool use. When you define a tool with an input_schema, Claude is constrained to produce arguments matching that schema when it calls the tool. This is the closest thing to guaranteed JSON structure the API provides, and it's the method most developers should default to.

Method 1: Tool Use for Guaranteed Structure

This is the most robust approach because the model is explicitly instructed to populate fields matching a JSON Schema, and the API returns the result as a structured tool_use block rather than free text you have to parse yourself.

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 1024,
    "tools": [{
      "name": "extract_order",
      "description": "Extract structured order data",
      "input_schema": {
        "type": "object",
        "properties": {
          "order_id": {"type": "string"},
          "total": {"type": "number"},
          "items": {
            "type": "array",
            "items": {"type": "string"}
          }
        },
        "required": ["order_id", "total", "items"]
      }
    }],
    "tool_choice": {"type": "tool", "name": "extract_order"},
    "messages": [{"role": "user", "content": "Order #4471 for $129.90: wireless mouse, USB hub"}]
  }'

Setting tool_choice to force the specific tool means Claude won't respond with plain text at all — it must call the tool with arguments conforming to your schema. You then read tool_use.input directly as a JSON object. No regex stripping of markdown fences, no "oops the model added a sentence before the JSON."

If you're building this on top of SubToAPI, the request shape is identical — point it at https://api.subtoapi.app/v1/messages with your sub_live_... key, and the same tool-forcing pattern applies. See /docs/tools for schema examples and /docs/messages for the full request reference.

Method 2: Prompt-Based JSON with a Validation Fallback

For simpler use cases where tool use feels like overkill, you can ask Claude directly for JSON output. This works most of the time but is not guaranteed — the model can occasionally add explanatory text, wrap the JSON in markdown code fences, or produce slightly malformed output under edge-case inputs.

Best practices for this approach:

const response = await fetch("https://api.anthropic.com/v1/messages", {
  method: "POST",
  headers: {
    "x-api-key": process.env.ANTHROPIC_API_KEY,
    "anthropic-version": "2023-06-01",
    "content-type": "application/json",
  },
  body: JSON.stringify({
    model: "claude-sonnet-4-5",
    max_tokens: 500,
    temperature: 0,
    messages: [{
      role: "user",
      content: `Return ONLY valid JSON matching this shape, no other text:
{"sentiment": "positive"|"neutral"|"negative", "confidence": number}

Text: "The delivery was late but support fixed it quickly."`
    }]
  })
});

Even with careful prompting, always wrap JSON.parse() in a try/catch and have a retry or fallback path. Treat prompt-based JSON as "best effort," not a contract.

Method 3: Combine Both for Maximum Reliability

In production systems that need both flexibility and guarantees, a common pattern is:

  1. Send the request with a forced tool call as the primary mechanism.
  2. Validate the returned object against your schema (e.g., with Zod, Pydantic, or a JSON Schema validator) before using it downstream.
  3. On validation failure, retry once with an error message appended ("the previous output was missing field X, please retry") rather than failing the whole request.

This gives you structural guarantees from tool use plus a safety net for the rare cases where required fields are empty strings or numbers come back as strings.

import { z } from "zod";

const OrderSchema = z.object({
  order_id: z.string(),
  total: z.number(),
  items: z.array(z.string()),
});

const result = OrderSchema.safeParse(toolUseInput);
if (!result.success) {
  // retry or log for manual review
}

Streaming and JSON

If you're streaming responses, tool use arguments arrive as incremental input_json_delta events that you need to concatenate before parsing — don't attempt to parse partial JSON chunks as they arrive. If your use case doesn't need token-by-token UI updates, it's simpler to disable streaming for JSON-extraction endpoints entirely. SubToAPI supports both streaming and non-streaming modes against the same endpoint — details at /docs/streaming.

Getting Started

If you're already building against the Claude API and want API keys, usage metering, and team billing without managing Anthropic console access per developer, SubToAPI wraps the same Messages and tool-use behavior behind sub_live_... keys with per-key usage tracking. Plans start at Solo (€9), with Team (€19/seat) and Scale (€49/seat) tiers for larger teams, and a free trial at /signup. Full endpoint docs are at /docs/quickstart.

questions

Does Claude have a built-in JSON-only response mode like some other APIs? No. Claude does not expose a response_format parameter. The reliable way to constrain output to JSON is tool use with a forced tool_choice and an input_schema.

Why does Claude sometimes wrap JSON in markdown code fences even when I ask for plain JSON? Prompt-based JSON requests are not strictly enforced by the model's decoding process. Explicitly stating "no markdown fences, no commentary" in the prompt reduces this, but tool use avoids the problem entirely since arguments are returned as structured data, not raw text.

What should I do if the JSON Claude returns fails schema validation? Validate with a library like Zod or Pydantic, then retry the request with the validation error appended to the prompt rather than failing outright. This recovers most edge cases without needing manual intervention.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →