API error shapes
Errors come back as {"error": {"code": …, "message": …}} with a matching HTTP status. Two codes exist today — not_found at 404 and rate_limited at 429. A parameter outside its documented bounds is rejected earlier, by request validation, and that one response uses the framework's own shape rather than this envelope.
The envelope#
{"error": {"code": "not_found",
"message": "No public page for '/docs/nope/'. Search first: /api/public/docs/search?q=…"}}| HTTP | code | Cause | What to do |
|---|---|---|---|
404 | not_found | No page matches the ref given to /docs/page | Search first and use a url or id from the result |
404 | not_found | No manifest by that name at /docs/manifest/{name} | Read the manifest list rather than guessing a name |
429 | rate_limited | More than 240 requests in the last minute from one address | Wait, then retry with a widening delay |
The message is written for a person reading a log and often names the next call to make. The code is the part to branch on: it is a short string, it does not change wording between releases, and it is what the published description documents.
The response that is not this shape#
Every parameter has bounds — q is 2 to 200 characters, limit on search is 1 to 25, limit on listing is 1 to 1,000, ref is at most 300 characters. A request that violates one is rejected by request validation *before* the handler runs, so it never reaches the code that builds the envelope. That response carries the framework's own detail array naming the offending parameter, with status 422, and the published description's 400 entry describes the malformed-request case rather than this one.
It is worth handling deliberately rather than discovering in production: a client that only knows the error envelope will fail to read the most informative failure it can get, which is the one that names the parameter you got wrong.
GET /api/public/docs/search?q=a HTTP/1.1 -> 422 (q is shorter than 2)
GET /api/public/docs/search?q=email&limit=99 -> 422 (limit exceeds 25)
GET /api/public/docs/page?ref=/docs/nope/ -> 404 {"error": …}
GET /api/public/docs/status -> 200Server-side failure#
An unhandled fault is recorded on the server and answered with a deliberately vague body: no stack trace, no internal path and no identifier that would say anything about another workspace. That is the correct behaviour and it does mean a 5xx gives you nothing to act on beyond *retry once, then stop*.
Distinguish it from the errors above by intent. A 404 and a 422 are your request being wrong and will not improve on retry. A 429 is timing and will. A 5xx is the service, and a client that retries it in a tight loop turns one fault into a rate-limit ban on top of it.
A retry policy that fits this API#
Classify on the status first, then on
error.codeif there is one.Result You get a decision without parsing prose, and one branch for the validation shape that has no code.
Retry only
429and5xx, with a widening delay and a cap.Result The rate-limit window is sixty seconds, so a few seconds of waiting usually clears it.
On
404from/docs/page, fall back to a search rather than retrying the same reference.Result The reference was wrong, and the error message says so with the search URL to use.
Log the
codeand the request path, not the whole body.Result The codes are a small closed set and count cleanly; messages are prose and do not.
Questions#
Are there more codes than these two?
These are the ones the handlers produce today. The published description also names bad-request and rate-limited responses at the schema level, all using the same envelope, so a client that branches on error.code with a default branch will not break if another is added.
Does an error response carry the cache headers?
No. The five-minute cache header is attached to successful responses only, so a 404 or a 429 is not cached on your behalf and a corrected request is answered fresh.
What do the MCP and A2A endpoints return instead?
JSON-RPC error objects, which are a different shape with numeric codes. See MCP error shapes and Interoperating over A2A.