# Errors & request_id

Every API error uses one consistent JSON shape, so you can handle them all the
same way:

```json
{
  "error": { "code": "NOT_FOUND", "message": "App not found" },
  "request_id": "req_..."
}
```

- `error.code` — a stable, uppercase machine code. Branch on this, not on the
  HTTP status or the message text.
- `error.message` — a short human-readable explanation. Safe to log; don't parse.
- `error.details` — optional, present only when there is something machine-readable to
  add: `retry_after` (seconds) on a `429`, `required` (the call's cost in credits) on a
  `402`. New keys may appear; ignore the ones you don't use.
- `request_id` — the id of this request. Include it whenever you report a problem.

## Request ids

Every response — success or error — carries an `X-Request-Id` header with our id for
that request. Log it. You can also send your own `X-Request-Id` (up to 80 letters,
digits, `.`, `_`, `:` or `-`): we record it next to ours, so a request can be traced by
either. It has no effect on metering — every call is charged on its own.

## Status codes

| Status | `code` | Meaning |
| --- | --- | --- |
| 400 | `BAD_REQUEST` | Missing or invalid parameters. |
| 401 | `UNAUTHORIZED` | Missing, malformed, or revoked API key. |
| 402 | `INSUFFICIENT_CREDITS` | Your balance can't cover the call's cost. |
| 403 | `FORBIDDEN` | The key isn't allowed to do this. |
| 403 | `ACCOUNT_SUSPENDED` | The account is suspended, so API and MCP access are paused. Contact support. |
| 404 | `NOT_FOUND` | No such resource, or no data for these parameters. |
| 410 | `PAGE_EXPIRED` | A reviews page was requested out of order, more than about an hour after the previous page, or with a different `sort_type`. Not charged; read again from page 1. The App Store has only page 1. |
| 422 | `INVALID_PARAMETERS` | A parameter is outside what the endpoint accepts — its type, allowed values or limits in the API reference — or the data engine rejected it. The message names the parameter. |
| 429 | `RATE_LIMIT_EXCEEDED` | Too many requests — see [rate limits](https://appmanai.com/docs/guide/rate-limits.md). |
| 500 | `INTERNAL_ERROR` | Something broke on our side. Retry, then report with the `request_id`. |
| 502 | `UPSTREAM_ERROR` | The data engine returned an error. |
| 503 | `UPSTREAM_UNAVAILABLE` | The data engine is temporarily unavailable. Retry with backoff. |
| 503 | `MAINTENANCE` | Scheduled maintenance. Nothing is charged; `Retry-After` and `details.ends_at` say when it ends. |
| 504 | `UPSTREAM_TIMEOUT` | The data engine took too long. Retry with backoff. |

`400`, `404`, `410` and `422` are about your request and will fail identically on every
retry — fix the parameters instead. `502`, `503` and `504` are about us; those are
the ones worth retrying with backoff. A `503` also answers a live check
(`cache=false` / `force_update=true`) the store could not complete — retry it, or drop the
flag to get the stored answer. A `503` with `MAINTENANCE` is planned downtime: wait out
`Retry-After` instead of backing off. `401`, `402` and `403` will not change on retry either:
they need a key, credits or support.

## Repeatable parameters

Parameters documented as repeatable (`packages`, `keywords`) are lists even when you
send one value — `?packages=com.foo` and `?packages=com.foo&packages=com.bar` are both
valid, and a single value is not a special case.

## Insufficient credits

When your balance can't cover a call, the request is rejected **before** any work
is done — you're never charged for a `402`:

```json
{
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "Out of credits. Buy a credit pack at https://appmanai.com/app/billing#packs or wait for your monthly refill of 5,000 on 2026-10-19.",
    "details": { "required": 3 }
  },
  "request_id": "req_..."
}
```

The `message` names the way out, links to the page that resolves it, and says when your
credits next refresh. All three differ by plan — Free is pointed at the Base upgrade, Base
at the credit packs, because a subscriber cannot upgrade any further. There is no overdraft:
calls stop rather than running up a bill. An agent seeing a `402` should stop and surface it
rather than retrying; retries won't succeed until credits are added.
