# Errors

> TwittrAPI status codes, error bodies, which errors are charged and refunded, and which ones to retry.

HTML page: https://www.twittrapi.com/docs/helpful-infos/error-handling

Errors come back as JSON with an `error` string:

```json
{ "error": "Invalid tweet ID" }
```

Some errors add fields: `errorInfo` (X's raw reply, for example on `/username/to/id`), `errorCode`, or a stable `code` string (for example on [proxy errors](#proxy-errors-407)). Do not parse the message text; it can change. Use the status code and, where present, `code`.

## Status codes

"Charged" means the call costs one call's price. "Refunded" means it was charged and the amount was put back right away; the response then has the header `X-Refunded: true`. "Not charged" means the request was rejected before billing.

| Status | When | Billing |
| --- | --- | --- |
| 200 | Success. | Charged |
| 400 | A required parameter is missing or malformed (for example no `listId`, or more than 500 IDs), or X answered with an error the API could not classify. | Refunded |
| 401 | Missing or unknown API key. | Not charged |
| 402 | No balance left, or the trial expired. | Not charged |
| 403 | Key deactivated, or the endpoint is not in your plan. | Not charged |
| 403 | X refused the request for this region. | Charged |
| 404 | X says the user, tweet or other object does not exist, or the path does not exist. | Charged |
| 407 | Account actions only: your proxy rejected the credentials, could not be reached, timed out or refused the connection to X. See [Proxy errors](#proxy-errors-407). | Refunded |
| 408 | The request took longer than 60 seconds. | Refunded |
| 409 | The request failed with 400 on every automatic retry (see below). | Refunded |
| 422 | A parameter has an invalid value (non-numeric ID, bad username, unsupported search operator) or X rejected the input. | Charged |
| 429 | Rate limit for your key exceeded. See [Rate limits](https://www.twittrapi.com/docs/helpful-infos/rate-limits). | Not charged |
| 500, 502, 503, 504 | Something failed on our side or at X. 502 also covers a search that got only empty pages from X after several retries. 504: X did not answer in time. | Refunded |
| X's status | Account actions pass X's own HTTP status through (for example 401 or 403 when X rejects the session). | Charged unless it is 400, 407, 408 or 5xx |

Endpoint pages list the validation errors of each endpoint under "Errors".

## Proxy errors (407)

Account actions (`/client/*`, `/premium/*`) go out through the `proxy` you send. When that proxy fails, the request never reaches X and you get **407** with a `code`:

```json
{ "error": "Your proxy rejected the credentials (407)", "code": "PROXY_AUTH_FAILED" }
```

| `code` | Meaning | What to do |
| --- | --- | --- |
| `PROXY_AUTH_FAILED` | The proxy refused the username or password (it answered 407, or SOCKS5 authentication failed). | Check the credentials in the proxy URL and that this IP is allowed by your proxy provider. |
| `PROXY_UNREACHABLE` | No connection to the proxy (refused, unknown host). | Check host and port; is the proxy online? |
| `PROXY_TIMEOUT` | Connecting through the proxy timed out (message `Proxy connection timed out`). | Retry, or use a faster proxy. |
| `PROXY_TUNNEL_FAILED` | The proxy accepted the connection but would not open a tunnel to X (for example 502 or 403 from the proxy). | Check your proxy provider's target or country restrictions. |
| `PROXY_INVALID` | The `proxy` value is not a valid proxy URL (422 instead of 407). | Use the form `http://user:pass@host:port`. |

Proxy errors are refunded at once, because nothing reached X, and they do not count as API errors on the [status page](https://www.twittrapi.com/status). An invalid proxy URL (422) is charged like other input errors.

## Automatic retries

A request that fails with 400 or a connection error is retried automatically. If every attempt fails, you get **409** (after 400s) or **502** / **503** (connection errors), all refunded. A response that needed a retry has the header `X-Retried: true`. You are billed once per request. Account actions (`/client/*`, `/premium/*`) are never retried, because the action may already have happened on X.

## What to retry

| Status | Retry? |
| --- | --- |
| 408, 409, 429, 500, 502, 503, 504 | Yes, with exponential backoff (for example 0.5 s, 1 s, 2 s, up to a few tries). |
| 400 | Once at most. If it repeats, check the parameters. |
| 401, 402, 403, 404, 422 | No. Fix the key, balance or input first. |
| Account actions | Only after checking on X that the action did not happen. |

Temporary failures carry `code` and `retryable` in the body, and `Retry-After` when you can retry:

```json
{"error": "Temporarily unavailable. You were not charged, please retry in a few seconds.", "code": "service_restarting", "retryable": true}
```

| Code | Status | Meaning | What to do |
| --- | --- | --- | --- |
| `service_restarting` | 503 | Briefly unavailable on our side. | Retry after `Retry-After`. |
| `x_unavailable` | 503 | X is briefly not reachable. | Retry after `Retry-After`. |
| `upstream_timeout` | 504 | No answer in time. | Retry. |
| `result_unknown` | 502 | The connection broke during an account action. It may have gone through. | Check on X first. |

The official SDKs retry 429, 5xx and network errors with backoff by default, and raise typed errors (for example `NotFoundError`, `RateLimitError`, `InsufficientBalanceError` in Python).

```python
from twittrapi import TwittrAPI, InsufficientBalanceError, NotFoundError, TwittrAPIError

client = TwittrAPI()
try:
    user = client.users.get_by_username("someone")
except NotFoundError:
    print("No such user")
except InsufficientBalanceError:
    print("Top up your balance")
except TwittrAPIError as exc:
    print(f"API error {exc}")
```

## Empty results are not errors

X often answers with 200 and an empty timeline instead of an error: for example for likes, which X made private. `/user/by/username/{username}` answers 200 with `{"data": {}, "userExists": false}` for a handle that does not exist.
