An error is your agent’s next prompt
Your API was designed for a caller who could go and look things up. A growing share of your callers cannot.
Here is a trace from this morning. An agent called a platform tool with the tenant slug wwww, one keystroke off from www. Every API I have worked with answers that with some version of 404 Not Found, and those two words are the entire budget a model has to work with.
It cannot open the docs or check the dashboard. Everything available to it on the next step is already in the transcript, and the transcript now says a thing it believed was true is false, with no indication of which part. So it does one of two things. It guesses again, burning a round trip and possibly wandering somewhere worse. Or it stops and reports failure to a person who has to work out what happened from a stack of tool calls.
Both outcomes are avoidable, and the fix lives entirely on your side of the wire.
The consumer changed and the errors did not
Error design has always assumed a lookup step outside the response. A human reads unknown domain, opens a tab, finds the right slug, gets on with it. That lookup is real work, but it happens somewhere you never see, so it never showed up as a cost of your API.
A model has no somewhere else. Whatever the error does not say, it has to invent. That is the whole change, and it moves error design from a diagnostic concern to a correctness one.
Four things are worth putting in the payload. All four examples below are real responses from the OBTO control plane, captured while writing this.
1. Echo what you actually looked for
The agent sent one composite identifier. The response breaks it into the four fields the server resolved it to. That difference is the useful part: it separates “the name is wrong” from “the name is right and I am pointed at the wrong app.” Without the echo those two failures are the same string, and the agent has no way to tell which correction to attempt.
This costs nothing. You already have the resolved values in scope at the point you raise the error.
2. Return the near miss
This is the field that pays for itself. An open-ended re-guess becomes a closed set of one, and the correction arrives on the next call rather than after a run of blind attempts.
It is also the field a layer above your API cannot produce. Computing didYouMean means running a similarity pass over the tenant registry at the moment of failure. A framework wrapping someone else’s service receives the 404 after that registry is out of reach, so the most it can do is rewrite the wording. Wording was never the missing part.
3. Say what did not happen
Look again at the clause in the middle of that message: Nothing was read or written.
That clause is a control-flow instruction. An agent holding an ambiguous failure on a write has to decide whether to retry, and deciding wrong is how a single logical operation lands twice. We wrote about that failure at length after it happened to us. Five words in the error settle the question, and they settle it for the caller least equipped to work it out from context.
State it explicitly on every refusal, including the ones where it seems obvious. Obvious to you is not in the transcript.
4. Name the legal moves
Three things arrive together: the exact JSON path that offended, the reason, and the shape of what would have been accepted. A bare refusal teaches the agent nothing, so it tries a neighbouring operator and gets refused again. Drawing the boundary converts one failure into a rule the agent applies for the rest of the session.
The same principle covers argument validation generally. If you build an MCP tool that rejects a field, the rejection should carry the accepted set, not the observation that the value was invalid.
The part that is easy to get wrong
Suggestions are a disclosure decision. didYouMean over a tenant registry tells the caller which tenants exist, and a helpful error handler is a fine way to build a directory-enumeration endpoint by accident.
The rule that keeps it safe is narrow: suggest only from the set the caller was already entitled to read. On OBTO that falls out of the same tenancy scoping that rewrites every query before it runs, so the suggestion list can never be wider than the caller’s own view. If you bolt near-miss hints onto an API whose reads are permission-checked somewhere else, the hint path is a second read that has to clear the same gate, and it is worth confirming rather than assuming.
Why this matters more than it used to
Under a stateless contract, every call carries its own context rather than inheriting it from a session. That removes a large class of drift bugs, which is why we moved to one. It also means every call is a fresh opportunity to get that context wrong, and the error response is the only channel through which a wrong guess becomes a right one.
None of these four fields require new infrastructure. The resolved identifiers, the registry, the write status and the allowlist are all in scope at the moment you construct the error. Adding them is an afternoon of work in the error handler, and they get read on every single failure for as long as the API exists.