Error Messages as an Agent Interface: Designing API Failures an Agent Can Recover From
An AI agent only sees what your error body contains. A field-by-field guide to API errors agents can act on — stable codes, explicit retryable flags, wait hints, fix examples — and the error shapes that trap agents in retry loops.
Your API returns 400 Bad Request with the body {"error": "invalid input"}. A human developer opens the docs in a second tab, compares the payload against the schema, and fixes it in under a minute. An AI agent has no second tab. It has the request it sent, the string you sent back, and a decision it has to make right now.
So it guesses. It reorders fields. It retries the identical call in case the failure was transient. It renames user_id to userId, then back again. Every guess is a round trip and a few thousand tokens of context, and the run ends with the agent telling your user that your API is down.
The fix is not better docs. Agents read docs once, at plan time, and then operate on whatever comes back over the wire. The error body is the interface.
What an agent sees when your API says no
For a human, an error message is one input among many — docs, source code, a Slack thread, the last five minutes of memory. For an agent, the error body is close to the entire environment for that step. Whatever bytes you return get appended to the context window, where they function as instructions.
That means every failure response has to answer three questions on its own:
- Is this my fault or yours? Decides whether the agent edits the request or waits.
- If it’s mine, what exactly do I change? Decides whether the next attempt is a targeted edit or a random walk.
- If it’s yours, when do I come back? Decides whether you get a polite backoff or a retry storm.
Status codes answer part of question one and nothing else. 400 covers malformed JSON, a missing scope, a violated business rule, and a field that’s three characters too long — four failures with four different recovery strategies, flattened into one signal. When the signal is that lossy, the model falls back on its prior, which was trained on every badly documented API on the internet.
Five fields that turn a rejection into a repair
Most APIs return a sentence. Agents do much better with a record. A workable minimum shape:
{
"error": {
"code": "date_range_too_wide",
"message": "start_date and end_date must be at most 31 days apart.",
"retryable": false,
"field": "end_date",
"constraint": "end_date - start_date <= 31 days",
"received": { "start_date": "2026-01-01", "end_date": "2026-06-01" },
"example": { "start_date": "2026-01-01", "end_date": "2026-02-01" },
"docs_url": "https://api.example.com/errors/date_range_too_wide"
}
}
What each field buys you:
codeis a stable enum, not a sentence. Agents pattern-match on it, your own client can switch on it, and your evals can assert against it. Reword amessagefreely; treat acodechange like a breaking API change, because every cached plan and prompt that referenced it breaks silently.retryableis explicit. Don’t make a model infer retryability from a status code.409vs422vs429is not consistent across APIs, and whether your500is transient depends on infrastructure the agent can’t see. One boolean deletes the guess.fieldplusconstraintbeat prose. “Invalid input” tells the agent to search.field: end_datetells it where to edit, andconstrainttells it what valid means, in a form it can check before spending another request.receivedcloses the loop. The agent’s original tool call may be dozens of turns back, or already summarized out of context. Echoing what you actually parsed lets it diff instead of re-deriving.exampleis the highest-leverage field and the one most APIs omit. A valid example payload converts a reasoning problem into a copy-edit.
For 429 and 503, put the wait hint in the body as retry_after_seconds, not only in the Retry-After header. Plenty of agent HTTP wrappers surface the response body to the model and drop headers entirely, so a header-only hint is invisible exactly where it matters.
The shapes that trap agents in loops
The catch-all 400. One code for schema errors, auth scope errors, and business-rule violations forces the agent to try all three recovery paths in sequence. Split it: one code per distinct fix.
429 with no wait hint. Without a number, the agent invents a backoff, and models tend to invent short ones. You’ve turned a rate limit into a retry storm from a client that never gets tired.
Errors that change on every call. Request IDs and timestamps inside message mean two identical failures look like two different problems, which defeats the agent’s own “I already tried that” heuristic and any caching in front of it. Keep varying data in separate fields and keep message byte-stable for a given code.
200 OK with an error in the body. The worst one. The tool wrapper reports success, the model takes the payload at face value, and a wrong value propagates through the rest of the run without ever surfacing as a failure.
“Contact support.” That’s an instruction the agent cannot execute, so it will improvise alternatives — retrying, hunting for another endpoint, or fabricating a workaround. Write terminal errors as explicit stop instructions instead: state that the condition is not recoverable programmatically and that the agent should stop and report to its user.
OpenCode
An open-source terminal coding agent. Point it at a scratch script that calls your own API and watch how it responds to each error code — the transcript shows exactly which failures it repairs in one turn and which ones send it looping.
Free and open source; bring your own model API key
Affiliate link · We earn a commission at no cost to you.
Testing errors like you test the happy path
Happy paths get integration tests. Error paths usually get a status-code assertion and nothing about whether the response is actionable. For an agent-facing API, that’s the half that decides whether a run completes.
A practical loop: build one fixture per error code, then script an agent — OpenCode, Cursor’s agent mode, whatever your team already runs — to call the endpoint in a way that triggers it, with the docs available. Measure turns to recovery. One turn is the target for anything the caller can fix. Anything above two is a bug in the error message, not in the model.
Two things that make this cheap to maintain: enumerate every code at a public endpoint or in a checked-in errors.json so you can hand the full enum to an agent up front, and generate both your docs and your test fixtures from that same file. Then a new code can’t ship undocumented, and an error string can’t drift away from the tests that assert on it.
FAQ
Doesn't detailed error output leak implementation details to attackers?
Should I return a different error shape for agents than for humans?
What should I return when an agent exhausts its quota rather than its rate limit?
Tools used in this review
Some links above are affiliate links. We may earn a commission if you sign up. See our disclosure for details.
Related reading
2026-08-13
Authenticating AI Agents: API Keys vs OAuth Device Flow vs Scoped Tokens
A practical breakdown of the three credential models for non-human callers — static API keys, the OAuth 2.0 device authorization grant, and short-lived scoped tokens — and when each one actually fits.
2026-08-12
Spec-Driven Development With AI Agents: Writing a Spec an Agent Can Actually Execute
How to structure a spec so a coding agent can run it end to end without babysitting: ground truth files, an interface contract, one acceptance command, and explicit out-of-bounds rules.
2026-05-26
Macchiato Day 2: Live Token Metrics and Parallel AI Terminals Reviewed
Macchiato's day-2 build adds a live token/cost sidebar and keyboard shortcuts for swapping between Claude Code and OpenCode in one terminal. Here's what shipped and what it means.
2026-05-21
Agnt Review: An Open-Source CLI for Running Public and MIT-Licensed AI Agents
Agnt is a free, open-source CLI for running any public or MIT-licensed AI agent from one interface. What it does, how it compares to other agent runners, and whether to install it.
2026-05-21
How to Measure AI Coding Agents Beyond Lines of Code and PR Acceptance Rates
Lines of code and PR acceptance rates look like productivity signals but reward verbosity and rubber-stamping. Here is what engineering managers should track instead when adopting Copilot, Cursor, and Claude Code.
Get the best tools, weekly
One email every Friday. No spam, unsubscribe anytime.