Claude API Function Calling Error Handling Guide
Function calling (what Anthropic calls "tool use") lets Claude decide when to invoke a function you've defined, fill in its arguments, and wait for a result before continuing. The tricky part isn't the happy path — it's everything that can go wrong: malformed arguments, tools that don't exist, timeouts in your own backend, rate limits mid-conversation, or Claude asking for a tool call that fails validation. Handling these cases well is what separates a demo integration from a production one.
This guide covers the specific error types you'll encounter when using Claude's tool use feature, how to detect them, and the retry/recovery patterns that keep a multi-turn tool conversation from breaking.
Where Errors Actually Happen in Tool Use
A typical tool-use loop has three places where things fail:
- The API call itself — network errors, rate limits (429), server errors (500/529), or invalid request errors (400) from a malformed tool schema.
- Argument parsing — Claude returns a
tool_useblock withinputthat doesn't match your schema, is missing required fields, or contains a type mismatch. - Tool execution — your own function throws, times out, or returns data Claude can't use (too large, wrong format).
Each of these needs a different handling strategy. Treating them all as "catch and retry" leads to either silent failures or infinite loops.
1. API-Level Errors
These are standard HTTP-style errors and should be handled with status-code checks and exponential backoff:
async function callClaudeWithRetry(payload, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": process.env.ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
body: JSON.stringify(payload)
});
if (res.status === 429 || res.status >= 500) {
const wait = 2 ** attempt * 500;
await new Promise(r => setTimeout(r, wait));
continue;
}
if (!res.ok) {
const err = await res.json();
throw new Error(`Claude API error: ${err.error?.message}`);
}
return res.json();
}
throw new Error("Max retries exceeded");
}
Only retry on 429 and 5xx. A 400 means your request is malformed (bad tool schema, invalid JSON) and retrying won't fix it — fix the payload instead.
2. Malformed or Missing Tool Arguments
Claude is good at following JSON schemas, but it isn't a validator. Always run tool_use.input through a schema check before executing anything:
import Ajv from "ajv";
const ajv = new Ajv();
function validateToolInput(toolBlock, schema) {
const validate = ajv.compile(schema);
const valid = validate(toolBlock.input);
if (!valid) {
return { ok: false, errors: validate.errors };
}
return { ok: true };
}
If validation fails, don't crash the conversation — send the error back to Claude as a tool_result with is_error: true. This lets Claude see what went wrong and retry with corrected arguments:
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Missing required field: 'currency'. Expected one of: USD, EUR, GBP.",
"is_error": true
}
This single pattern eliminates most of the brittleness in tool-use pipelines: instead of your code deciding whether to retry, Claude self-corrects within the same turn.
3. Tool Execution Failures
Your own function can fail for reasons unrelated to Claude — a downstream API timeout, a database error, a permissions check. Wrap execution and feed structured errors back the same way:
async function executeTool(name, input) {
try {
switch (name) {
case "get_weather":
return await getWeather(input.city);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (err) {
return { is_error: true, content: err.message };
}
}
Never let an unhandled exception bubble up and kill the request. A failed tool call is normal conversational data for Claude, not a reason to abort.
4. Unknown or Unexpected Tool Names
If Claude requests a tool you haven't defined — which can happen after a prompt or schema change — check the name before dispatching and return a clear tool_result error rather than throwing. Log these events; a spike usually means your tool definitions and system prompt have drifted out of sync.
5. Streaming-Specific Errors
When streaming tool use, partial JSON arrives across multiple content_block_delta events. If the stream disconnects mid-argument, you'll have an incomplete JSON string that fails to parse. Buffer deltas by index, and only attempt JSON.parse once you receive content_block_stop for that block. If the stream ends unexpectedly, treat it as a retryable error and resend the request — don't try to parse partial JSON.
A Simplified Error-Handling Checklist
- Retry only on 429/5xx, with exponential backoff and a max attempt count
- Validate
tool_use.inputagainst your JSON schema before execution - Return validation and execution failures as
tool_resultblocks withis_error: true - Never let a tool exception propagate unhandled — catch and convert to a result
- Log unknown tool names separately from execution errors
- Buffer streamed tool arguments until the block fully closes before parsing
Reducing the Error Surface
A lot of tool-use error handling is really just infrastructure work: retries, timeouts, stream reassembly, usage tracking. If you're building this on raw API access, that infrastructure is on you. SubToAPI wraps the same Messages and tool-use interface behind a standard HTTPS API with built-in retry-friendly error responses and streaming support, so you spend less time rebuilding plumbing. See the tool use docs, streaming docs, and messages reference for the exact request/response shapes, or start a free trial at /signup.
questions
What's the difference between an API error and a tool-use error in Claude? An API error (4xx/5xx) means the request to Claude's Messages endpoint itself failed — bad auth, rate limit, malformed payload. A tool-use error happens after a successful API call, when Claude's requested tool arguments are invalid or your function fails to execute them.
Should I retry when Claude sends malformed tool arguments? Don't retry the API call. Instead, send a tool_result with is_error: true describing what's wrong, in the same conversation. Claude will usually correct its next tool call based on that feedback.
How do I handle a tool call for a function that no longer exists? Return a tool_result error noting the tool is unavailable, and log it. This usually indicates your tool definitions and system prompt have drifted apart after a deployment — treat repeated occurrences as a sign to audit your tool schema.