# Rate limits and errors

How the API answers when something is wrong, every error code it can return, and the rate limits that apply to your keys.

## Response basics

Every answer, success or error, carries these headers:

| Header | Value |
|---|---|
| `Content-Type` | `application/json` |
| `Cache-Control` | `no-store, private` |
| `X-Request-Id` | A 26-character id such as `01JCX7Q2M8ZK4T6V9B3N5R1W2Y`. Quote it when you contact support. |

No answer sets a cookie. `HEAD` works on every `GET` endpoint and returns the same status and headers without a body.

The cacheable reads (`/account`, `/offers`, `/offers/{id}`, `/servers`, `/servers/{id}`, `/servers/{id}/traffic`, `/orders`, `/orders/{id}`, `/invoices`, `/invoices/{id}`, `/ssh-keys`) also carry `X-Cache: hit` or `X-Cache: miss`. A hit is served from a copy at most about a minute old; the body is the same as a miss.

## The error body

```json
{
  "error": {
    "code": "price_changed",
    "message": "The total is not the expected_total that was sent. Nothing was ordered. The current total is in the details.",
    "request_id": "01JCX7Q2M8ZK4T6V9B3N5R1W2Y",
    "details": { "total": { "amount": "13900", "currency": "USD" } }
  }
}
```

- `code` is stable. Write your code against it.
- `message` is fixed text for the code. It never repeats anything you sent.
- `details` appears only when there is something you can act on. Its keys are listed below.
- `401` answers also carry the header `WWW-Authenticate: Bearer`. `405` answers carry `Allow`, the methods the path takes. `429 rate_limited` carries `Retry-After`, and so does any error whose details hold `retry_after` (`in_progress`, `cooling_off`).

## Rate limits

| Limit | Default | Counted |
|---|---|---|
| Requests per key | 120 a minute | Every request made with the key. |
| Requests per account | 300 a minute | Every request from every key of the account. |
| Failed authentication | 30 a minute | Per IP address, never against your key: a stranger guessing keys cannot lock you out. |

There is also a short burst limit per IP address in front of the API.

Successful answers carry the tighter of the key and account counters:

| Header | Meaning |
|---|---|
| `X-RateLimit-Limit` | Requests allowed in the current window. |
| `X-RateLimit-Remaining` | Requests left in it. |
| `X-RateLimit-Reset` | When the window ends, in Unix seconds. |

Over a limit the answer is `429 rate_limited` with a `Retry-After` header in seconds. Wait that long, then go on.

The window slides: what counts is roughly the last 60 seconds, so there is no burst at the turn of the minute. `X-RateLimit-Reset` is the end of the current clock minute.

Writes have their own ceilings (power actions per hour, reverse DNS requests per day, SSH key changes per day, order limits). Those answer `429 action_limit` or `403 cap_reached`, described on their pages.

## Error codes

| Status | Code | Message | When |
|---|---|---|---|
| 400 | `body_not_allowed` | This request must not have a body. | A `GET`, `HEAD` or `DELETE` was sent with a body. |
| 400 | `key_in_url` | API keys are accepted only in the Authorization header. A working key sent in a URL is revoked. | A key appeared in the address. Every working key there is revoked and the owner emailed. |
| 400 | `invalid_parameter` | A query parameter is not allowed here or its value is not valid. See the API documentation for this endpoint. | An unknown, repeated, empty or malformed query parameter. |
| 400 | `invalid_body` | The request body is not valid JSON of the documented shape for this endpoint. Unknown fields are refused. | Body missing, not `application/json`, over 4 KB, not one JSON object, a duplicate or unknown field, or a field of the wrong shape. |
| 400 | `idempotency_key_required` | This request needs an Idempotency-Key header of 8 to 64 letters, digits, dashes or underscores. | A write without a valid `Idempotency-Key`. |
| 401 | `invalid_key` | Missing or invalid API key. Send it as "Authorization: Bearer <key>". | No key, a malformed key, or a key we do not know. |
| 401 | `key_revoked` | This API key has been revoked. | See [When a key stops working](authentication.md). |
| 401 | `key_expired` | This API key has expired. | The key passed its expiry date. |
| 401 | `test_key` | Test keys cannot be used on the live API. | A `rsw_test_` key. |
| 402 | `insufficient_credit` | The account credit that may be spent through the API does not cover this order. Add funds in the client area. | `details.needed` is the amount missing. |
| 403 | `api_not_enabled` | API access is not enabled for this account. | The owner has not turned API access on. |
| 403 | `account_blocked` | API access for this account is blocked. Please contact support. | Blocked by RedSwitches. |
| 403 | `not_invited` | API access is by invitation only for now. | The account is not in the beta. |
| 403 | `ip_not_allowed` | This key cannot be used from your IP address. | The key's IP list does not include your address. |
| 403 | `account_inactive` | This account is not active. | The account is not active. |
| 403 | `owner_changed` | This API key was created by a user who no longer owns the account. The owner must create a new key. | The account has a new owner. |
| 403 | `email_not_verified` | The account owner must verify their email address before the API can be used. | Owner email not verified. |
| 403 | `permission_denied` | This API key does not have the permission this endpoint needs. | Missing permission, or a power request for a server not on the key's list. |
| 403 | `ordering_not_enabled` | Ordering through the API is not turned on for this account. The owner turns it on on the API page. | See the [Ordering guide](guide-ordering.md). |
| 403 | `not_eligible` | This account cannot order through the API right now. The account owner can see the reason on the API page. | The account may not order now. |
| 403 | `cooling_off` | Ordering opens 24 hours after it was turned on and after a new ordering key. | `details.retry_after` says how many seconds are left. |
| 403 | `cap_reached` | This order would go over a limit of this key or of the account. Nothing was ordered. | `details.cap` names the limit, `details.limit` its value and `details.window` its window. Account limits are set by RedSwitches: contact support to raise one. |
| 403 | `ordering_paused` | Ordering through the API is paused by RedSwitches. Nothing was ordered. Try again later. | Ordering is paused for everyone. |
| 403 | `power_paused` | Power actions through the API are paused for this account. RedSwitches staff have been told. | Too many power-offs in a short time. See [Power and actions](guide-power.md). |
| 404 | `not_found` | Nothing was found at this address: no such endpoint, or no such item on this account. | No such endpoint, a malformed id, or an item that does not exist or is not yours. |
| 405 | `method_not_allowed` | This endpoint does not take this method. The Allow header lists the methods it takes. | The method is not allowed on this path. `GET /actions/{id}/credentials` also refuses `HEAD`. |
| 413 | `body_too_large` | The request body is larger than 16 KB. | Refused before the API reads it. |
| 409 | `in_progress` | A request with this Idempotency-Key is still being processed. Ask again with the same key in a few seconds. | `details.retry_after` and the `Retry-After` header, in seconds. |
| 409 | `price_changed` | The total is not the expected_total that was sent. Nothing was ordered. The current total is in the details. | `details.total` is the current total. |
| 409 | `sold_out` | This server is not in stock any more. Nothing was ordered. | Pick another offer. |
| 409 | `order_not_placed` | The order could not be completed and nothing was charged. RedSwitches staff have been told. | Nothing was created. |
| 409 | `order_under_review` | This order needs a check by RedSwitches staff before it continues. Do not repeat it; you will hear from us. | `details.order` is the order id when there is one. |
| 409 | `server_busy` | Another action on this server is queued, running or unsettled. Wait until it has finished, then try again. | Also while the server is being delivered or installed. |
| 409 | `server_not_active` | This server is not active (pending, suspended or ended), so this action is not available. | |
| 409 | `ssh_key_conflict` | That key is already saved on this account, or the account holds the most keys it can. | |
| 422 | `not_supported` | This server does not support that through the API. | |
| 422 | `ip_not_on_server` | That IP address is not assigned to this server. | |
| 422 | `invalid_ssh_key` | That is not a valid SSH public key. | |
| 422 | `idempotency_conflict` | This Idempotency-Key was used for a different request. Use a new key for a new order. | Same key, different body or server. |
| 422 | `not_orderable` | This offer cannot be ordered with these choices through the API. | Offer, OS, SSH key or billing cycle not allowed. |
| 429 | `action_limit` | This would go over a limit of the account for this action (see the details). Nothing was changed. Try again later. | `details.limit` and `details.per`. |
| 429 | `rate_limited` | Too many requests. Wait for the number of seconds in the Retry-After header. | See Rate limits above. |
| 500 | `internal_error` | Something went wrong on our side. Quote the request id when you contact support. | |
| 503 | `unavailable` | The API is temporarily unavailable. | Maintenance, a part of the API switched off, or a dependency down. Retry later. |

A `404` looks exactly the same whether the item does not exist or belongs to another account.

## Details keys

| Key | Appears with | Shape |
|---|---|---|
| `total` | `price_changed` | `{"amount": "13900", "currency": "USD"}`: the current first-payment total in minor units. |
| `needed` | `insufficient_credit` | `{"amount": "2500", "currency": "USD"}`: credit missing, in minor units. |
| `cap` | `cap_reached` | `servers_day`, `servers_month`, `open`, `money_month`, `renewal_month` or `key_month`. |
| `order` | `order_under_review` | The order id, such as `"ord_456"`. |
| `retry_after` | `in_progress`, `cooling_off` | Seconds to wait. |
| `limit` | `action_limit`, `cap_reached` | `action_limit`: the ceiling, as a number. `cap_reached`: the limit in force: a number for `servers_day`, `servers_month` and `open`; an amount such as `{"amount": "200000", "currency": "EUR"}` (minor units, the account's currency) for `money_month`, `renewal_month` and `key_month`. |
| `window` | `cap_reached` | `day` (`servers_day`), `30_days` (`servers_month`, `money_month`, `renewal_month`, `key_month`) or `open` (paid orders not delivered yet; no time window). |
| `per` | `action_limit` | `server_hour`, `account_hour`, `server_day` or `account_day`. |
| `field` | `invalid_body` | The documented field that failed, such as `"label"` or `"hostname"`. Absent when the failure is not about one field. |
| `reason` | `invalid_body` | What is accepted, in fixed words, such as `"one of on, off, reboot"`. Your value is never echoed back. |

With `details.reason`, the `invalid_body` message names the problem too: `The request body is not valid: action: one of on, off, reboot.`

## Requests the API refuses before it looks at them

- Query parameters exist only where an endpoint lists them (`cursor` and `limit` on lists, `dry_run=true` on orders). Anything else is `400 invalid_parameter`.
- `GET`, `HEAD` and `DELETE` must not have a body: `400 body_not_allowed`.
- `POST` bodies are one JSON object, `Content-Type: application/json`, at most 4 KB. Unknown fields and names given twice are refused: `400 invalid_body`. A body over 16 KB is `413 body_too_large`.
- Ids in paths must have the right prefix for the endpoint (`srv_` on `/servers/{id}`). A wrong prefix, a leading zero or anything encoded is `404 not_found`.
