# Errors

> Status codes, error codes and what to do.

Errors are JSON with a stable `code`, a human `message` and the `request_id`. No error is charged.

```json
{ "error": { "code": "no_credits", "message": "balance is zero; buy a credit pack" }, "request_id": "..." }
```

Match on `code`, never on `message`. The code is part of the contract and will
not change under you; the message is written for a person and may be reworded.

| Status | Code                                                   | Meaning                                             | What to do                                      |
| ------ | ------------------------------------------------------ | --------------------------------------------------- | ----------------------------------------------- |
| 400    | `bad_request`                                          | a parameter is missing or malformed                 | fix the request                                 |
| 401    | `missing_key`, `invalid_key`                           | no key, or a revoked key                            | check the header                                |
| 402    | `no_credits`                                           | balance is zero                                     | buy a pack                                      |
| 403    | `access_denied`                                        | X does not serve this object to the reading session | try later, or a different object                |
| 403    | `product_unavailable`                                  | search product not available, for example `Top`     | use `Latest`                                    |
| 404    | `not_found`                                            | no such user or tweet, or it is protected           | nothing                                         |
| 429    | `rate_limited`                                         | over your requests-per-second limit                 | wait `retry-after` seconds                      |
| 503    | `upstream_rate_limited`, `no_account`, `busy`          | X or our pool is saturated right now                | retry after `retry-after` seconds, with backoff |
| 502    | `upstream_error`, `query_id_stale`, `request_rejected` | X changed something on its side                     | retry once; if it persists we are already paged |
| 500    | `internal`                                             | our bug                                             | send us the `request_id`                        |

## RFC 9457 problem+json [#rfc-9457-problemjson]

Send `Accept: application/problem+json` and the same failure comes back in the
shape a generic HTTP client already understands. Nothing new is reported: `type`,
`title`, `status` and `detail` are the registered names for what `error.code` and
`error.message` already said, and the native `error` object is still there, so one
parser reads either shape.

```json
{
  "type": "https://xdataapi.io/docs/errors#no_credits",
  "title": "Balance is zero",
  "status": 402,
  "detail": "balance is zero; buy a credit pack",
  "code": "no_credits",
  "error": { "code": "no_credits", "message": "balance is zero; buy a credit pack" },
  "request_id": "..."
}
```

Without that header you get the native shape, which is what both SDKs read. Neither
is going away.

## Retry policy we recommend [#retry-policy-we-recommend]

Retry `503` and `502` up to three times with 2, 4 and 8 seconds between attempts. Do not retry `4xx`.
Because failed requests are free, retries never cost credits.

Read `retry-after` when it is present rather than guessing, and read `RateLimit`
on every response to throttle before a `429` happens at all. See
[Authentication](/docs/authentication) for the rate ladder.

## Every code [#every-code]

### missing\_key [#missing_key]

`401`. No key was sent. Put it in `x-api-key`, or send `Authorization: Bearer xd_live_…`.
Free.

### invalid\_key [#invalid_key]

`401`. The key is unknown or has been revoked. Check it has not been deleted in the
dashboard, and that you are not sending a key from another environment. Free.

### rate\_limited [#rate_limited]

`429`. Over the account's requests-per-second limit, which is shared by every key
on the account. Wait `retry-after` seconds. The limit and what is left of it are on
every response in the `RateLimit` header, so a client that reads it need never hit
this. Free.

### no\_credits [#no_credits]

`402`. The balance is zero. Buy a pack in the dashboard. There is no overage and no
invoice: the API stops rather than spending money you did not agree to. Free.

### bad\_request [#bad_request]

`400`. Something about the request itself is wrong: a missing required parameter, a
malformed cursor, a batch list over 100, an unparseable body. The message names the
problem. Retrying unchanged will fail again. Free.

### not\_found [#not_found]

`404`. There is no such user or tweet, or it is protected, suspended or deleted. Not
a fault and not retryable. Free.

### access\_denied [#access_denied]

`403`. X refused to serve this object to the session that asked (its error 37). Often
object-specific rather than account-wide, so another read may well succeed. Free.

### product\_unavailable [#product_unavailable]

`403`. The search product asked for is not available, typically `Top`. Use `Latest`.
Free.

### upstream\_rate\_limited [#upstream_rate_limited]

`503`. X is rate limiting our pool right now. Retry after `retry-after` seconds with
backoff. Free.

### no\_account [#no_account]

`503`. No healthy upstream account is available for this read at this moment. Transient;
retry with backoff. Free.

### upstream\_error [#upstream_error]

`502`. X answered with something we could not use. Retry once. If it persists, it is
already paging us. Free.

### query\_id\_stale [#query_id_stale]

`502`. X rotated the identifiers behind its own site and our client has not caught up
yet. This is the failure mode the whole repair loop exists for; it is measured on the
[status page](/status) and fixed in minutes, not days. Retry with backoff. Free.

### request\_rejected [#request_rejected]

`502`. X rejected the shape of the upstream request. Same handling as `upstream_error`.
Free.

### billing\_disabled [#billing_disabled]

`503`. Card checkout is not configured on this deployment. Nothing you can fix from the
client. Free.

### internal [#internal]

`500`. Our bug. Send the `request_id` to [hello@xdataapi.io](mailto:hello@xdataapi.io) and we will find the exact
call in the logs. Free.
