> ## Documentation Index
> Fetch the complete documentation index at: https://docs.server.express/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors and rate limits

> The response envelope, every status code the public API returns, the per-endpoint rate limits, and how repeated failed authentication gets an IP blocked.

## The response envelope

Every response has the same outer shape, success or failure, so you never have to guess whether a `200` actually worked.

```json theme={null}
{
  "success": true,
  "data": { }
}
```

```json theme={null}
{
  "success": false,
  "error": "action must be one of: start, stop, shutdown, reboot, reset"
}
```

Check `success` first, then read `data`. The `error` string is written for a person. Show it or log it, but don't parse it, because we reword messages when a clearer one comes along. Branch on the HTTP status instead.

A few errors also carry a machine-readable `code`. For example, a closed account answers with `"code": "ACCOUNT_BANNED"`.

Two endpoints don't return JSON when they succeed: invoice PDFs and ticket attachment downloads send the file itself. Their errors still use the envelope.

## Status codes

| Code | What it means |
| - | - |
| `200` | Fine. |
| `201` | Created: a reseller order or a ticket attachment upload. |
| `202` | Accepted, not finished. A reinstall or snapshot has started as a background job. |
| `400` | Your request is malformed: bad JSON, a missing field, an invalid enum value. |
| `401` | No credentials, bad credentials, an expired or revoked token, or a token sent to an endpoint that only takes sessions. |
| `403` | Authenticated, but not allowed. The token is missing a scope, the server is suspended for an unpaid invoice, the account is closed, or your IP is blocked. |
| `404` | No such resource, **or** it isn't yours. We don't distinguish, on purpose. |
| `409` | Conflicting state. For example, reinstalling a machine that's already reinstalling, or ordering the last unit of a plan someone else just bought. |
| `428` | Something's needed on your account first. Orders answer this when there's no billing address, and list what's `missing` in `data`. |
| `429` | Too many requests to a rate-limited endpoint. |
| `500` | Our fault. Retry, and tell us if it keeps happening. |
| `502` | An upstream system, such as a hypervisor, didn't answer properly. Usually safe to retry after a moment. |
| `503` | Not ready yet. Most often a server that's still being built. |

The `404` behaviour is worth remembering. If you ask about someone else's service, you get "not found", not "forbidden". Otherwise the API would let anyone check which service IDs exist.

> [!TIP]
>
> `403` covers more than scopes, so read the `error` message before you reach for a new token. A suspended server won't start working with a wider token. It starts working once the invoice is paid.

## Rate limits

Most token endpoints have no per-request limit today. A handful do, and they answer `429` once you go over:

| Endpoint | Limit |
| - | - |
| `POST /v1/client/ticket-attachments` (uploads) | 20 per minute, per credential |
| `POST /v1/client/invoices/{id}/bank-transfer` ("I've sent it") | 6 per minute |

```json theme={null}
{
  "success": false,
  "error": "Too many requests, please try again later."
}
```

Windows are fixed, not sliding. When you get a `429`, wait out the rest of the minute and try again. Don't hammer the endpoint.

No published limit doesn't mean unlimited. If you're planning something high-volume, like polling thousands of servers every few seconds, talk to us first rather than finding the edge by hitting it. List endpoints read from our database, not the hypervisor, so one paged `GET /v1/cloud` is far cheaper than a status call per machine.

## Failed authentication and IP blocks

Repeated bad credentials from one IP get that IP blocked automatically. **Ten failures inside 15 minutes** trigger the first block, and each repeat lasts longer:

| Block | Length |
| - | - |
| 1st | 15 minutes |
| 2nd | 1 hour |
| 3rd | 6 hours |
| 4th | 1 day |
| 5th and later | 1 week |

While blocked, every request from that IP gets a `403` with a `Retry-After` header, in seconds:

```json theme={null}
{
  "success": false,
  "error": "Access from this IP is temporarily blocked after repeated failed attempts. Try again after 2026-10-23T14:05:00.000Z."
}
```

Only failures that look like guessing count: an API token that doesn't match anything, or one that's the wrong shape. A revoked or expired token fails with its own message and **doesn't** count. That's nearly always a stale config, not an attack. A successful request with a valid token clears your count.

> [!WARNING]
>
> Never retry a `401` in a loop. The credential won't fix itself, and a tight retry loop with a mistyped token is the quickest way to get your CI runners' shared IP blocked for a week.

## Handling errors well

A good client:

* Checks `success`, then branches on the status code.
* Treats `401` as "stop and fix the credential", never "retry".
* Retries `429`, `502` and `503` with backoff, and caps the number of attempts.
* Doesn't retry `409` blindly. Re-read the resource's state first, because something else has changed it.
* Logs the `error` string, since it's the fastest way for us to help when you open a ticket.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.