Yappy API errors
Every failure — REST, auth, and batch alike — comes back as a JSON document. Nothing in this API ever answers an agent with an HTML error page.
The shape
RFC 9457 problem details, served as application/problem+json:
{
"type": "https://yappy.biz/developers/errors#not_found",
"title": "Resource not found",
"status": 404,
"detail": "No documentation page with slug 'agent-mod'. Did you mean 'agent-mode'?",
"instance": "/api/v1/docs/agent-mod",
"code": "not_found",
"request_id": "01J9X4Q0F7",
"docs_url": "https://yappy.biz/developers/errors"
}
code is the field to branch on: it is stable, lowercase snake_case, and never changes meaning. title and detail are for humans and for an agent explaining itself to a user. detail names the fix wherever a fix exists.
Validation failures add an errors array, one entry per offending parameter:
{
"code": "invalid_request",
"status": 400,
"errors": [
{ "field": "limit", "code": "out_of_range", "detail": "limit must be between 1 and 100, got 500" }
]
}
Codes
| HTTP | code | Means | What to do |
|---|---|---|---|
| 400 | invalid_request | A parameter is missing, malformed, or out of range | Read errors[]; fix and retry |
| 400 | invalid_cursor | The cursor value was not one we issued | Restart the listing without a cursor |
| 401 | invalid_token | Token expired, malformed, or revoked | Get a new token; do not retry with the old one |
| 403 | insufficient_scope | The token is valid but lacks the scope | Re-issue with the scope named in the header |
| 404 | not_found | No such release, page, or job | Check detail — it usually suggests the near miss |
| 405 | method_not_allowed | Wrong verb for that path | See Allow on the response |
| 406 | not_acceptable | No representation matches your Accept | Ask for application/json or text/markdown |
| 409 | idempotency_conflict | Same Idempotency-Key, different body | Use a new key, or send the original body |
| 413 | payload_too_large | Batch over 20 operations, or a body over 64 KB | Split the request |
| 415 | unsupported_media_type | POST without application/json | Set the content type |
| 422 | unprocessable | Syntactically fine, semantically impossible | detail explains why |
| 429 | rate_limited | Too many requests | Wait Retry-After seconds; see rate limits |
| 500 | internal_error | Our fault | Retry once with backoff, then report request_id |
| 503 | upstream_unavailable | A dependency (the release feed) is down | Retry with backoff; cached data may still answer |
Retrying safely
429, 500, 502, 503, and 504 are worth retrying with exponential backoff and jitter. 4xx codes other than 429 are not — the same request will fail the same way, and hammering it only spends your rate budget.
Every GET in this API is safe to repeat. Every POST accepts an Idempotency-Key, so a retry after a network timeout returns the original result rather than starting a second job.
Batch semantics
A POST /api/v1/batch returns 200 even when individual operations fail. Each entry carries its own status and, on failure, a full problem document in body. Check per-operation status; do not infer success from the envelope.
Request ids
Every response carries X-Request-Id, echoed into the problem document as request_id. Quote it when you report something — it is how we find the one request among many.
MCP errors
The MCP server speaks JSON-RPC, so its errors use that shape instead: -32700 parse error, -32600 invalid request, -32601 method not found, -32602 invalid params, -32603 internal error. A tool that ran but could not answer returns a normal result with isError: true and text explaining what to try — that distinction matters, because the first kind means your call was wrong and the second means the world was.