← Blog

Claude API Response Format: Getting Clean JSON Output

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

Does Claude have a JSON mode?

Not in the way you might expect from other model APIs. There's no response_format: { type: "json_object" } parameter that guarantees valid JSON on every call. Instead, Claude produces structured JSON output through a combination of clear system prompts, tool use (function calling), and response prefilling. Once you know the pattern, it's just as reliable — arguably more so, because tool schemas give you actual validation, not just a promise of "valid JSON."

This matters because most production use cases — extracting fields from documents, generating structured records for a database, returning API responses your frontend can parse directly — need output that's guaranteed to be machine-readable. Free-text JSON in a chat-style response is fragile: models add explanations before the JSON, wrap it in markdown code fences, or occasionally produce a trailing comma. Below are the three approaches that actually work, in order of reliability.

Method 1: Tool use with a JSON schema (most reliable)

The strongest guarantee comes from defining a tool with an input schema and forcing Claude to call it. Claude fills the tool's input object according to your schema, and that object is already parsed JSON — no string parsing required.

{
  "model": "claude-opus-4",
  "max_tokens": 1024,
  "tools": [
    {
      "name": "extract_invoice",
      "description": "Extract structured invoice data",
      "input_schema": {
        "type": "object",
        "properties": {
          "vendor": { "type": "string" },
          "total": { "type": "number" },
          "due_date": { "type": "string", "format": "date" },
          "line_items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "description": { "type": "string" },
                "amount": { "type": "number" }
              },
              "required": ["description", "amount"]
            }
          }
        },
        "required": ["vendor", "total", "due_date"]
      }
    }
  ],
  "tool_choice": { "type": "tool", "name": "extract_invoice" },
  "messages": [
    { "role": "user", "content": "Invoice text: ..." }
  ]
}

Setting tool_choice to force that specific tool means Claude has no path that doesn't end in a schema-conformant object. This is the closest thing to a real "JSON mode" the API offers, and it's the approach worth reaching for whenever your output needs to feed directly into code. If you're proxying requests through SubToAPI, this works exactly the same way — send the same tools and tool_choice fields to your sub_live_... endpoint at /docs/tools and you get the parsed tool input back in the response payload.

Method 2: System prompt + prefill

If you don't need the full tool-use machinery, you can get plain-text JSON by combining a strict system prompt with response prefilling. Prefilling means you seed the assistant's turn with an opening character (like {), which stops Claude from adding preamble like "Here's the JSON you requested:".

{
  "model": "claude-sonnet-4",
  "max_tokens": 512,
  "system": "You output only valid JSON matching this shape: {\"name\": string, \"category\": string, \"confidence\": number}. No markdown, no explanation, no code fences.",
  "messages": [
    { "role": "user", "content": "Classify: 'wireless mechanical keyboard'" },
    { "role": "assistant", "content": "{" }
  ]
}

Because the assistant message already starts with {, Claude continues from there instead of restarting with its own formatting. Combine this with a strict system instruction describing the exact keys and types you expect, and you'll get clean JSON in the vast majority of responses. It's less bulletproof than tool use — Claude can still occasionally deviate on edge cases — so always validate before trusting the output downstream.

Method 3: Post-processing free text

Sometimes you're stuck with unstructured responses — for example when JSON is one part of a longer explanatory answer. In that case, ask Claude to wrap the JSON in a clear delimiter and extract it programmatically:

const match = response.match(/```json\n([\s\S]*?)\n```/);
const data = match ? JSON.parse(match[1]) : null;

This is the least reliable method and should be a fallback, not your primary strategy. If you find yourself parsing markdown fences regularly, switch to Method 1 or 2 instead.

Always validate on your side

Regardless of method, treat the model's output as untrusted input until it passes a schema check. A JSON.parse wrapped in try/catch is the minimum; a proper schema validator (Zod, ajv, pydantic) is better because it catches type mismatches, not just syntax errors. If validation fails, the cheapest fix is often a retry with the failed output and a message like "That wasn't valid — return only the corrected JSON object," rather than building elaborate parsing logic to rescue malformed responses.

Handling streaming with structured output

If you're streaming a response that should end up as JSON, don't try to parse partial JSON chunks — buffer the full response first, then parse once the stream completes. Tool-use responses in particular arrive as input_json_delta events that need to be concatenated before parsing as a whole object. This applies whether you're calling the Claude API directly or going through a wrapper — see /docs/streaming for how streamed tool-input deltas are shaped when using SubToAPI's endpoint.

Where SubToAPI fits in

If you're building an internal tool or a product feature on top of Claude, SubToAPI turns your existing Claude access into a standard HTTPS API with application keys, so every service that needs structured JSON output — an ingestion pipeline, a form-filling backend, a data extraction job — can call /docs/messages with the same request shapes described above and get usage metadata alongside the response. Team and Scale plans add per-seat keys so different services or teammates don't share credentials, and you can test the tool-use flow during the free trial at /signup before committing to a plan.

Practical checklist

FAQ

Does Claude support a response_format parameter like OpenAI's JSON mode? No. Claude has no dedicated JSON-mode flag. The equivalent behavior comes from forcing tool use with an input_schema, which returns a parsed object rather than a raw string.

What's the most reliable way to force valid JSON from Claude? Define a tool with a strict input_schema and set tool_choice to force that tool. Claude's tool input is validated against the schema and returned as structured data, not free text.

Can I get JSON output while streaming? Yes, but buffer the deltas until the stream finishes before parsing. Partial JSON chunks aren't individually valid, so parse only after the full tool input or text block has arrived — see /docs/streaming for the event shapes.

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 →