← Blog

Claude API Structured Output Example (Tool Use Method)

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

If you need Claude to return data in a predictable shape — a JSON object with specific fields, an array of records, a nested object matching your database schema — you want structured output. Claude doesn't have a dedicated "JSON mode" toggle like some other APIs. Instead, you get structured output by defining a tool with an input schema and forcing Claude to call it. This article shows exactly how that works, with a full working example.

The core idea: you describe the shape of the data you want as a JSON Schema, attach it to a tool definition, send it with your message, and tell Claude it must use that tool. Claude's response then contains a tool_use block whose input field is already a parsed object matching your schema — no regex, no markdown-fence stripping, no "please respond only in JSON" prompt begging.

Why tool use is the structured output method

Early approaches to getting JSON out of LLMs relied on prompting: "respond only with valid JSON, no explanation." This works most of the time but fails unpredictably — the model adds a preamble, wraps the JSON in a code block, or produces invalid syntax under load. Tool use solves this at the API level because the schema is enforced as part of the tool-calling mechanism, not just a suggestion in the prompt.

The tradeoff: you're asking Claude to call a "tool" that doesn't actually do anything — it just packages your data. This is a well-established pattern and works reliably across Claude models.

A full structured output example

Suppose you're extracting structured data from a support ticket: customer name, issue category, urgency, and a short summary.

{
  "name": "extract_ticket",
  "description": "Extract structured fields from a support ticket",
  "input_schema": {
    "type": "object",
    "properties": {
      "customer_name": { "type": "string" },
      "category": {
        "type": "string",
        "enum": ["billing", "technical", "account", "other"]
      },
      "urgency": {
        "type": "string",
        "enum": ["low", "medium", "high"]
      },
      "summary": { "type": "string" }
    },
    "required": ["customer_name", "category", "urgency", "summary"]
  }
}

Here's the full request using curl against SubToAPI's Claude-compatible endpoint:

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": 500,
    "tools": [{
      "name": "extract_ticket",
      "description": "Extract structured fields from a support ticket",
      "input_schema": {
        "type": "object",
        "properties": {
          "customer_name": { "type": "string" },
          "category": { "type": "string", "enum": ["billing", "technical", "account", "other"] },
          "urgency": { "type": "string", "enum": ["low", "medium", "high"] },
          "summary": { "type": "string" }
        },
        "required": ["customer_name", "category", "urgency", "summary"]
      }
    }],
    "tool_choice": { "type": "tool", "name": "extract_ticket" },
    "messages": [{
      "role": "user",
      "content": "Ticket from Maria Gomez: I was charged twice for my subscription this month and need a refund ASAP, this is affecting my business."
    }]
  }'

The tool_choice field with "type": "tool" forces Claude to call exactly that tool, which is what guarantees structured output instead of a free-text reply.

Parsing the response

The response contains a tool_use block in the content array:

{
  "content": [
    {
      "type": "tool_use",
      "id": "toolu_01abc",
      "name": "extract_ticket",
      "input": {
        "customer_name": "Maria Gomez",
        "category": "billing",
        "urgency": "high",
        "summary": "Double-charged for subscription, requesting refund."
      }
    }
  ]
}

In JavaScript, extracting the structured data is one line:

const response = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    model: "claude-sonnet-4-5",
    max_tokens: 500,
    tools: [/* schema from above */],
    tool_choice: { type: "tool", name: "extract_ticket" },
    messages: [{ role: "user", content: ticketText }]
  })
});

const data = await response.json();
const toolUse = data.content.find(block => block.type === "tool_use");
const ticket = toolUse.input; // already a parsed object

No JSON.parse() call needed on free text, no stripping of markdown code fences. The input field is structured data by construction.

Handling nested and array outputs

The same pattern scales to more complex shapes. For an array of extracted entities:

"input_schema": {
  "type": "object",
  "properties": {
    "entities": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "type": { "type": "string", "enum": ["person", "organization", "location"] }
        },
        "required": ["name", "type"]
      }
    }
  },
  "required": ["entities"]
}

Wrap arrays in a top-level object (as shown) rather than making the schema's root type array — tool input schemas expect an object at the top level, so a wrapper key like entities or items is the standard workaround.

Common pitfalls

If you're building this against SubToAPI, the request and response formats match the examples above exactly — see /docs/tools for the full tool-use reference and /docs/messages for general request shape. Streaming structured output (partial tool-input deltas) is covered in /docs/streaming.

FAQ

Does Claude have a native "JSON mode" like some other APIs? No. Structured output is achieved through tool use with a forced tool_choice, which is functionally equivalent and often more reliable because the schema is explicit.

What happens if the input doesn't match my schema? Claude is trained to respect the schema closely, but it's not a hard guarantee — always validate the returned object against your schema in code and handle mismatches with a retry or fallback.

Can I get multiple structured objects back in one response? Yes — define a schema with an array property (wrapped in a top-level object) to return a list of structured records in a single call, as shown in the entities example above.

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 →