# 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`:

```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:

```json
{
  "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](https://yappy.biz/developers/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.

---

Canonical: https://yappy.biz/developers/errors/ · Last updated: 2026-08-25
