Claude API Tool Use for Reliable JSON Output
If you need Claude to return predictable, machine-readable JSON, tool use is the right mechanism — not prompt instructions asking Claude to "respond in JSON." Tool use lets you define a JSON schema that Claude fills in as structured input to a "tool call," and the API guarantees the output matches that schema's shape far more reliably than free-text prompting.
This matters because plain-text JSON requests are fragile: Claude might wrap the JSON in markdown code fences, add explanatory text before or after it, or produce slightly malformed output under edge cases. Tool use sidesteps all of that by treating the JSON as a structured function call rather than a chat message, which is exactly what the tool use API was designed for even when you never intend to actually execute a function.
Why Tool Use Beats Prompt-Based JSON
When you ask Claude to "output valid JSON" in a system prompt, you're relying on the model to follow formatting instructions consistently across every request. That works most of the time, but at scale — thousands of API calls — you'll eventually hit malformed responses, extra prose, or inconsistent key names.
Tool use solves this differently. You define an input_schema with typed fields, required properties, and enums. Claude's response includes a tool_use content block whose input field is already parsed JSON matching your schema. There's no string parsing, no regex stripping of code fences, no guessing.
Defining a Schema for Structured Output
Here's a minimal example extracting structured data from unstructured text — a common pattern for parsing resumes, support tickets, or invoices into JSON.
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"tools": [
{
"name": "extract_ticket",
"description": "Extract structured data from a support ticket",
"input_schema": {
"type": "object",
"properties": {
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "urgent"]
},
"category": { "type": "string" },
"summary": { "type": "string" },
"customer_email": { "type": "string" }
},
"required": ["priority", "category", "summary"]
}
}
],
"tool_choice": { "type": "tool", "name": "extract_ticket" },
"messages": [
{
"role": "user",
"content": "Ticket: My dashboard has been down for 3 hours and I am losing sales. Please help urgently. - jane@acme.com"
}
]
}'
The key detail here is tool_choice. Setting it to {"type": "tool", "name": "extract_ticket"} forces Claude to call that specific tool on every request, instead of deciding whether to call a tool at all. This is what makes output structurally guaranteed rather than probabilistic.
Reading the Structured Output
The response contains a tool_use block instead of plain text:
{
"content": [
{
"type": "tool_use",
"id": "toolu_01A2b3C4d5",
"name": "extract_ticket",
"input": {
"priority": "urgent",
"category": "outage",
"summary": "Dashboard down for 3 hours, customer losing sales.",
"customer_email": "jane@acme.com"
}
}
]
}
input is already a parsed object matching your schema — no post-processing needed beyond pulling it out of the response.
Handling Nested and Array Structures
Tool schemas support nested objects and arrays just like standard JSON Schema, which makes them useful for more complex extraction tasks:
{
"name": "extract_invoice",
"input_schema": {
"type": "object",
"properties": {
"vendor": { "type": "string" },
"total": { "type": "number" },
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": { "type": "string" },
"amount": { "type": "number" }
},
"required": ["description", "amount"]
}
}
},
"required": ["vendor", "total", "line_items"]
}
}
Keep schemas as flat as reasonably possible. Deeply nested structures with many optional fields increase the chance Claude fills fields with placeholder or inferred values when the source text doesn't clearly contain that information. Mark fields as required only when the data is genuinely expected to be present, and consider a confidence or nullable pattern for anything the model might have to guess at.
Validating Output in Production
Even with tool use, you should still validate the returned JSON against your schema in application code — using something like Zod, Ajv, or Pydantic depending on your stack — before writing it to a database or passing it downstream. Tool use dramatically improves reliability, but it isn't a hard runtime guarantee against every edge case, especially with very large or ambiguous schemas.
A practical pattern:
const response = await client.post('/v1/messages', payload);
const toolBlock = response.content.find(b => b.type === 'tool_use');
if (!toolBlock) {
throw new Error('Expected tool_use block, got none');
}
const result = schema.parse(toolBlock.input); // Zod validation
This gives you a clear failure point if Claude's output ever drifts from expectations, rather than silently shipping bad data.
Using Tool Use Through SubToAPI
If your team already has Claude access and wants to expose structured JSON extraction as a stable internal API — with its own API keys, usage tracking, and streaming support — SubToAPI turns that access into an HTTPS endpoint your services can call directly. Tool definitions and schemas work the same way documented in /docs/tools, and you can combine tool use with streaming responses as described in /docs/streaming. Getting started only takes a signup and an application key from the dashboard — see /docs/quickstart for the setup, or check /pricing for plan details.
FAQ
Do I need tool_choice to force JSON output? Yes, in most cases. Without setting tool_choice to a specific tool name, Claude may respond with plain text instead of calling the tool, especially if the input doesn't clearly warrant it. Forcing the tool name guarantees a tool_use block every time.
Can I request multiple JSON objects in one response? Define a single tool whose schema includes an array field, rather than expecting multiple separate tool calls. This keeps the output in one predictable tool_use block and avoids ordering or merging issues.
Is tool use slower than plain text generation? Not meaningfully. The model still generates tokens; the difference is that those tokens are constrained to match your schema and returned as structured input rather than raw text you'd have to parse yourself.