← Blog

Claude API JSON Mode: Output Validation Guide

2026-09-29 · 5 min read · SubToAPI Team

Claude doesn't have a dedicated "JSON mode" flag like some other model APIs. Instead, you get structured JSON output by using tool definitions with a JSON schema, or by prompting carefully and validating the result afterward. Either way, you still need a validation layer in your application because the model can produce output that is syntactically valid JSON but semantically wrong, or — much more rarely with a well-built prompt — not valid JSON at all.

This article covers the three practical approaches to structured output with Claude, how to validate what comes back, and how to handle the failure cases so your pipeline doesn't break in production.

Why Claude doesn't have a strict JSON mode

Some APIs let you set a response_format: { type: "json_object" } parameter that constrains token generation so the output is guaranteed to parse as JSON. Claude's Messages API doesn't expose that exact mechanism. What it does offer instead:

  1. Tool use with a JSON schema — you define a tool with an input_schema, force Claude to call it with tool_choice, and read the structured input object back from the response.
  2. System prompt instructions — you tell Claude explicitly to respond with only JSON matching a given shape, often with an example.
  3. Prefill — you seed the assistant turn with { to nudge the model away from prose and straight into a JSON object.

Tool use is the most reliable of the three because the input field returned by the API is already a parsed object, not a string you have to run through JSON.parse() yourself.

Using tool use for structured output

Define a tool whose only purpose is to carry your desired schema:

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 1024,
    "tools": [{
      "name": "extract_invoice",
      "description": "Extract structured invoice fields",
      "input_schema": {
        "type": "object",
        "properties": {
          "invoice_number": { "type": "string" },
          "total": { "type": "number" },
          "currency": { "type": "string" },
          "line_items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "description": { "type": "string" },
                "amount": { "type": "number" }
              },
              "required": ["description", "amount"]
            }
          }
        },
        "required": ["invoice_number", "total", "currency"]
      }
    }],
    "tool_choice": { "type": "tool", "name": "extract_invoice" },
    "messages": [{ "role": "user", "content": "Invoice #4521, total $340.00, one line item: consulting hours, $340.00" }]
  }'

Because tool_choice forces the specific tool, Claude has no path to reply with plain prose — it must produce arguments matching the schema. See /docs/tools for the full tool-use reference and /docs/messages for request/response shapes if you're calling through SubToAPI.

Prompt-based JSON with prefill

If you don't want the overhead of a tool definition — for example, a simple single-field extraction — prefilling the assistant response works well:

{
  "model": "claude-sonnet-4-5",
  "max_tokens": 512,
  "messages": [
    { "role": "user", "content": "Return a JSON object with keys \"sentiment\" (positive/negative/neutral) and \"confidence\" (0-1) for this review: \"Shipping was slow but the product is great.\"" },
    { "role": "assistant", "content": "{" }
  ]
}

Because the assistant turn already starts with {, Claude continues from there instead of adding a preamble like "Here's the JSON:". You'll need to re-prepend the { when you concatenate the response before parsing.

Validating the output

Regardless of which method you use, treat the JSON as untrusted input until it's validated against a schema. Schema-matched doesn't mean business-logic-correct — a model can return "total": -50 or an empty line_items array that's technically valid JSON but wrong for your use case.

Use a runtime validator rather than trusting types:

import { z } from "zod";

const InvoiceSchema = z.object({
  invoice_number: z.string(),
  total: z.number().positive(),
  currency: z.string().length(3),
  line_items: z.array(
    z.object({
      description: z.string().min(1),
      amount: z.number()
    })
  ).optional()
});

function parseInvoice(toolInput) {
  const result = InvoiceSchema.safeParse(toolInput);
  if (!result.success) {
    throw new Error(`Invalid invoice shape: ${result.error.message}`);
  }
  return result.data;
}

For Python, pydantic gives you the same guarantee:

from pydantic import BaseModel, PositiveFloat

class Invoice(BaseModel):
    invoice_number: str
    total: PositiveFloat
    currency: str
    line_items: list[dict] | None = None

invoice = Invoice.model_validate(tool_input)

Validating against a schema catches type mismatches, missing required fields, and out-of-range values before that data reaches your database or downstream systems.

Handling validation failures

When validation fails, don't just log and drop the request. Three practical patterns:

If you're running structured extraction at volume — invoice parsing, log classification, support ticket tagging — routing those calls through a single API layer makes it easier to track failure rates per prompt version. SubToAPI turns your Claude access into a standard HTTPS API with usage metadata per key, which is useful for spotting a schema that's failing validation more often than expected without digging through logs by hand. Check /docs/quickstart to get a key running in a few minutes, or /pricing for plan details.

Checklist before shipping

questions

Does Claude API have a true JSON-only response mode? Not exactly. There's no single flag that guarantees JSON output the way some other APIs offer. The reliable equivalent is forcing a tool call with tool_choice, which returns a parsed object rather than free text.

Why does my Claude JSON output sometimes fail to parse? The most common cause is truncation — the response hit max_tokens mid-object. Check the stop_reason field; if it's max_tokens, increase the limit or shorten your schema.

Should I validate Claude's JSON output even when using tool use? Yes. Tool schemas constrain shape and types, but they don't enforce business rules like value ranges or cross-field consistency. Always run a schema validator like zod or pydantic before using the data downstream.

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 →