← Blog

Claude API Structured Output Validation with JSON Schema

2026-10-15 · 4 min read · SubToAPI Team

Claude does not have a native "JSON mode" flag like some other model APIs. To get structured output validation with a JSON schema, you define a tool with an input_schema and force Claude to call it, then validate the result on your side with a schema library. This gets you reliable, typed JSON output without parsing free-text responses or hoping the model remembers to wrap things in code fences.

The short answer: use tool use (function calling) as your schema enforcement mechanism, not prompt instructions like "respond only in JSON." Prompt instructions get you close most of the time, but they break under edge cases — the model adds a sentence before the JSON, wraps it in markdown, or drops a required field. Tool calling constrains the model's output shape at generation time and gives you a parseable object directly in the response, which you then re-validate with a proper JSON Schema validator for belt-and-suspenders correctness.

Why prompting alone isn't enough

Asking Claude to "return valid JSON matching this schema" works often enough in a demo and fails often enough in production to be a problem. Common failure modes:

None of these are catastrophic on their own, but at scale (thousands of requests a day) a 2-3% failure rate on parsing is a steady stream of production errors. Structured output validation needs to happen at the API boundary, not just in the prompt.

Use tool definitions as your schema

Claude's tool use feature accepts a JSON Schema-style input_schema for each tool. When you force Claude to use a specific tool via tool_choice, the model's response comes back as a structured tool_use block with arguments that match your schema far more reliably than free-text generation.

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_invoice",
      "description": "Extract structured invoice data",
      "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"]
      }
    }],
    "tool_choice": {"type": "tool", "name": "extract_invoice"},
    "messages": [{"role": "user", "content": "Invoice #4471, total $1,240.00 USD, 2x widget at $500, 1x install fee at $240."}]
  }'

The response contains a tool_use content block with input already shaped as an object matching your schema — no markdown fences, no preamble text to strip out.

Validate again on your side

Tool schemas constrain generation, but they are not a hard guarantee. The model can still occasionally omit a field it judges irrelevant, misformat an enum value, or hit an edge case your schema didn't anticipate. Treat the tool output as "likely correct" and run it through a real validator before trusting it downstream.

import Ajv from "ajv";

const ajv = new Ajv();
const validate = ajv.compile(invoiceSchema);

const toolUseBlock = response.content.find(b => b.type === "tool_use");
const valid = validate(toolUseBlock.input);

if (!valid) {
  console.error(validate.errors);
  // retry with error feedback, or fall back to a repair prompt
}

If validation fails, the cheapest fix is usually a single retry where you append the Ajv error messages to the conversation and ask Claude to correct the specific fields that failed — this is far more reliable than re-running the whole prompt from scratch.

Keep schemas simple and explicit

A few patterns that reduce validation failures:

Where this fits with SubToAPI

If you're building on top of a Claude subscription through SubToAPI, tool use and structured output work the same way — it's a pass-through HTTPS API over your existing Claude access, so schemas, tool_choice, and validation patterns above apply unchanged. You get an sub_live_... application key, usage metadata per request, and the same /v1/messages endpoint shape documented in /docs/messages and /docs/tools. If you're new to the platform, /docs/quickstart walks through the first authenticated call, and /signup gets you a trial key to test schema-constrained extraction before committing to a plan.

questions

Does Claude have a dedicated JSON mode? No. Claude enforces structured output through tool use — define an input_schema for a tool, force it with tool_choice, and the response comes back as a structured tool_use block instead of free text.

Is tool-based output always valid against my schema? It's reliable but not guaranteed. Always re-validate the returned object with a schema library like Ajv and handle the rare malformed case with a targeted retry rather than assuming correctness.

Can I get structured output without defining a tool? You can prompt for JSON directly and parse the response, but expect occasional markdown fences, stray text, or missing fields. Tool use with a schema is the more reliable approach for production systems.

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 →