← Blog

Claude API Structured Output Best Practices

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

If you're building anything beyond a chat interface, you need Claude to return data your code can parse without regex gymnastics or prayer. The best practice for structured output with the Claude API is to use tool use (function calling) with a strict JSON schema instead of asking the model to "respond in JSON" inside a plain text prompt. Tool use constrains the model's output shape at generation time, which is far more reliable than hoping it follows formatting instructions.

The second part of the answer is validation: even with tool use, you should validate the returned object against your schema before trusting it downstream, handle the edge cases where Claude declines to call a tool, and design your schema so there's only one obviously correct way to fill it in. The rest of this guide walks through each of these in practice.

Use Tool Use Instead of Prompt-Based JSON

Asking Claude to "only respond with valid JSON, nothing else" works most of the time, but "most of the time" is not a production guarantee. The model can wrap output in markdown fences, add a sentence before the JSON, or produce near-valid JSON with trailing commas.

Tool use solves this at the API level. You define a schema, Claude is guided to populate it, and the response comes back as a structured tool call rather than free text you have to extract from a larger message.

{
  "name": "extract_invoice",
  "description": "Extract structured invoice data from text",
  "input_schema": {
    "type": "object",
    "properties": {
      "vendor": { "type": "string" },
      "total_cents": { "type": "integer" },
      "currency": { "type": "string" },
      "line_items": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "description": { "type": "string" },
            "amount_cents": { "type": "integer" }
          },
          "required": ["description", "amount_cents"]
        }
      }
    },
    "required": ["vendor", "total_cents", "currency"]
  }
}

If you're routing API calls through SubToAPI, this works the same way — send your tool definitions in the request and parse the tool_use block in the response. See /docs/tools for the full schema reference.

Force the Tool Call When You Need Structure

By default, Claude decides whether to call a tool or respond in plain text. If structured output is mandatory — not optional — force it by setting tool choice to require that specific tool. This removes an entire class of failure where the model answers conversationally instead of returning data.

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4",
    "max_tokens": 1024,
    "tools": [{"name": "extract_invoice", "input_schema": { ... }}],
    "tool_choice": {"type": "tool", "name": "extract_invoice"},
    "messages": [{"role": "user", "content": "Invoice text here..."}]
  }'

Forcing the tool choice is the single highest-impact change you can make if your pipeline breaks whenever Claude decides to be chatty instead of structured.

Keep Schemas Narrow and Unambiguous

Structured output quality drops when schemas are vague or overloaded. A few concrete rules:

Validate Before You Trust It

Tool use dramatically reduces malformed output, but it doesn't guarantee your business logic is satisfied — a schema can be technically valid JSON while still containing a date in the wrong format or a total that doesn't match the line items. Run the output through a JSON Schema validator (Zod, Pydantic, ajv, whatever fits your stack) as a mandatory step before it touches your database or triggers downstream actions.

import { z } from "zod";

const InvoiceSchema = z.object({
  vendor: z.string().min(1),
  total_cents: z.number().int().positive(),
  currency: z.string().length(3),
  line_items: z.array(
    z.object({
      description: z.string(),
      amount_cents: z.number().int(),
    })
  ),
});

const result = InvoiceSchema.safeParse(toolCallInput);
if (!result.success) {
  // retry with a clarifying message, or fall back to manual review
}

If validation fails, the cheapest fix is often a single retry with the validation error appended to the conversation — Claude is generally good at correcting itself when told exactly what was wrong.

Handle the "No Tool Call" Case

Even with tool choice forced, build for the case where the API returns an error, a refusal, or an unexpected stop_reason. Check stop_reason before assuming you have a tool_use block, and have a defined fallback (retry, log for review, return a typed error to your caller) rather than letting a null-pointer exception surface in production.

Keep an Eye on Token Usage

Large schemas and verbose descriptions add to your input token count on every call. If you're extracting the same structure repeatedly, measure actual token usage via response metadata and trim unused fields. SubToAPI surfaces usage per request in your dashboard, which makes it easy to spot schemas that have grown bloated over time. Getting started takes a few minutes — see /docs/quickstart — and the free trial at /signup lets you test tool-based extraction against your own documents before committing to a plan at /pricing.

Streaming and Structured Output

Structured output and streaming don't mix cleanly — a tool call typically arrives as a complete block rather than token-by-token, so you can't partially parse it mid-stream the way you can with plain text. If your application needs both low-latency text and structured extraction, consider using two calls: a streamed response for the user-facing text and a separate non-streamed tool call for the data extraction. Details on streaming behavior are in /docs/streaming.

Questions

Does forcing tool_choice reduce response quality? Not meaningfully for well-scoped extraction tasks. It removes the model's option to respond conversationally, which is exactly the behavior you want when structure is mandatory.

Should I use tool use or just ask for JSON in the prompt for simple cases? For one-off scripts or prototypes, prompt-based JSON is fine. For anything running in production or processing user data at scale, tool use with a schema and validation is worth the extra setup time.

What's the most common cause of broken structured output? Overly broad schemas with too many optional fields. Narrow the schema, add enums and descriptions, and split large extractions into multiple smaller tool calls.

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 →