← Blog

Claude API Structured Output: Validation & Schema Guide

2026-10-20 · 5 min read · SubToAPI Team

Getting the Claude API to return clean, predictable JSON — and validating that output against a schema — comes down to two things: how you define the shape you want, and how you enforce it on the way out. Claude doesn't have a native "JSON mode" toggle like some APIs, so structured output is achieved through tool definitions (function calling) or carefully constrained prompting, then checked with a schema validator on your side.

This guide covers both halves of the problem: getting Claude to produce structured data reliably, and validating that data so malformed responses never reach your application logic.

Why structured output needs validation, not just prompting

Even with a well-written prompt asking for JSON, large language models occasionally add trailing commentary, use inconsistent key casing, omit optional fields, or return a number as a string. None of this is a "bug" in Claude — it's the nature of generative text. If your downstream code expects price: number and gets price: "9.99", things break silently or loudly depending on your language.

The fix is a two-layer approach:

  1. Constrain generation using tool/function definitions so Claude is guided toward a specific JSON shape.
  2. Validate the result against a formal schema (JSON Schema, Zod, Pydantic, etc.) before you trust it.

Skipping either layer is where most structured-output bugs come from.

Layer 1: Use tool definitions to shape output

Anthropic's tool-use feature is the most reliable way to get structured output from Claude. Instead of asking the model to "please return JSON," you define a tool with an explicit input schema, and Claude populates arguments matching that schema.

{
  "name": "extract_invoice",
  "description": "Extract structured invoice data from text",
  "input_schema": {
    "type": "object",
    "properties": {
      "invoice_number": { "type": "string" },
      "total_amount": { "type": "number" },
      "currency": { "type": "string" },
      "line_items": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "description": { "type": "string" },
            "quantity": { "type": "integer" },
            "unit_price": { "type": "number" }
          },
          "required": ["description", "quantity", "unit_price"]
        }
      }
    },
    "required": ["invoice_number", "total_amount", "currency"]
  }
}

When Claude calls this tool, the response includes a tool_use block with input that matches the schema far more consistently than free-text JSON generation. This is the closest thing to "structured output mode" that the Claude API offers today.

If you're routing requests through SubToAPI, tool calls work the same way — you send the tool definition in your request body and get the structured tool_use block back in the response. Check /docs/tools for request/response examples with real payloads.

Layer 2: Validate the output against your schema

Tool definitions make Claude's output more consistent, but they don't guarantee it. The model can still omit a required field, misjudge a type at the edges, or hallucinate an extra property. Always validate before using the data.

Node.js with Zod:

import { z } from "zod";

const InvoiceSchema = z.object({
  invoice_number: z.string(),
  total_amount: z.number(),
  currency: z.string(),
  line_items: z.array(
    z.object({
      description: z.string(),
      quantity: z.number().int(),
      unit_price: z.number(),
    })
  ),
});

function parseClaudeOutput(toolInput) {
  const result = InvoiceSchema.safeParse(toolInput);
  if (!result.success) {
    console.error(result.error.format());
    throw new Error("Invalid structured output from Claude");
  }
  return result.data;
}

Python with Pydantic:

from pydantic import BaseModel, ValidationError

class LineItem(BaseModel):
    description: str
    quantity: int
    unit_price: float

class Invoice(BaseModel):
    invoice_number: str
    total_amount: float
    currency: str
    line_items: list[LineItem]

def parse_claude_output(tool_input: dict) -> Invoice:
    try:
        return Invoice(**tool_input)
    except ValidationError as e:
        print(e.json())
        raise

Both approaches catch type mismatches, missing fields, and unexpected nesting before the data reaches your business logic — exactly the failures that are easy to miss in manual JSON parsing.

Handling validation failures gracefully

Don't just throw an exception and give up. A practical retry pattern:

  1. Validate the tool output.
  2. If invalid, send the validation error back to Claude in a follow-up message: "Your previous response failed validation with this error: <error>. Please correct it."
  3. Re-request the tool call with the error as context.
  4. Cap retries at 2–3 attempts, then fall back to a default or flag for human review.

This self-correction loop works well because Claude can usually fix a schema violation once it sees the specific error, rather than regenerating blind.

Streaming and structured output

If you're streaming responses, structured output requires extra care — partial JSON isn't valid JSON, and you shouldn't validate until the full tool-use block is complete. Buffer the input_json delta events until the stop reason confirms the tool call is finished, then run your schema validation once on the complete object. See /docs/streaming for details on how streamed tool-use events are structured.

Keeping this consistent across an API layer

If multiple services or team members are calling Claude with different prompt styles, schema drift becomes a real problem — one integration expects total_amount, another expects amount_total. Centralizing Claude access behind a single API layer, with one place to define tool schemas and API keys per application, reduces this drift significantly. That's one of the practical reasons teams put SubToAPI in front of Claude: one dashboard for sub_live_ keys per app, consistent request/response shapes, and usage visibility per key so you can see which integration is generating malformed output before it becomes a production incident. Start with /docs/quickstart if you want to see the request format end to end, or check /pricing for plan details.

Questions

Does the Claude API have a built-in JSON mode? No. Anthropic doesn't offer a dedicated JSON-only output mode. The reliable approach is defining a tool with an input_schema and letting Claude populate structured arguments via tool use, then validating the result.

What's the best way to validate Claude's output in production? Use a schema library native to your language — Zod in Node.js, Pydantic in Python — and validate every tool-use response before passing it downstream. Treat validation failures as retryable errors, not crashes.

Can I force Claude to retry if its output fails validation? Yes. Send the validation error message back to Claude as additional context and ask it to correct the specific field. This usually resolves the issue in one or two follow-up turns.

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 →