# Rate limits

> How many requests the Coritan API accepts, the tighter limits on sign-in and some actions, and how to handle a 429 response.

Source: https://www.coritan.com/docs/api/rate-limits/

The Coritan API limits how many requests it takes from one address, how many failed sign-ins it accepts, and how often you can repeat an action that costs a lot to carry out. A request over any limit answers `429 Too Many Requests`, and the `Retry-After` header says how many seconds to wait.

## Requests from one address

| Limit | What it counts | Answer |
| --- | --- | --- |
| 2,400 requests a minute | Every API request from one IP address in the last 60 seconds | `429` with `{"detail": "Rate limit exceeded"}`. `Retry-After` is the number of seconds until the oldest of those requests is a minute old. |
| 200 requests a second, in bursts of up to 400 | Every request from one IP address, counted by the proxy in front of the API | `429` with `{"error":"Too many requests"}` and `Retry-After: 1`. |
| 128 connections | Open connections from one IP address to the proxy | The proxy closes a new connection without an answer. |

The proxy's limits are its defaults, and Coritan can change them. When the proxy refuses 8 requests in a row from one address, it blocks that address for 5 minutes. While the block lasts, every request answers `429` and every new connection is closed, so do not retry at once after a `429`.

## Sign-in attempts

Only failures count towards these limits: an answer of `401` or `403`. A sign-in that works costs nothing.

| Limit | Routes | Answer |
| --- | --- | --- |
| 40 failed attempts a minute, from one IP address, on each route | `POST /auth/login`, `POST /auth/register` and `POST /auth/refresh`; on an organization, `POST /orgs/{org_slug}/auth/login`, `/auth/register` and `/auth/forgot-password`, and `POST /orgs/{org_slug}/staff/auth/login` and `/staff/auth/forgot-password` | `429` with `{"detail": "Too many authentication attempts. Please try again later."}` and `Retry-After: 60`. |

`POST /auth/forgot-password` always answers `200`, so it has a budget that counts every request instead: 10 in 15 minutes from one IP address, and 3 in 15 minutes for one email address. Checking a two-factor code has one too, as the next table shows.

## Actions with their own budget

These actions start work that takes time or resources on Coritan's side, so each has a budget of its own on top of the limits above. The budget counts your requests for one server, instance or route, or for your whole account where the table says so.

| Action | Routes | Budget |
| --- | --- | --- |
| `container.power` | `POST /client/servers/{uuid}/power` | 24 a minute |
| `container.wake` | `POST /client/servers/{uuid}/wake` | 48 a minute |
| `container.backup` | `POST /client/servers/{uuid}/backups` | 8 in 5 minutes |
| `container.backup_restore` | `POST /client/servers/{uuid}/backups/{backup_uuid}/restore` | 8 in 5 minutes |
| `container.file_compress` | `POST /client/servers/{uuid}/files/compress` and `/files/decompress` | 24 a minute |
| `container.file_pull` | `POST /client/servers/{uuid}/files/pull` | 24 a minute |
| `container.import` | `POST /client/servers/{uuid}/import/start` and `/import/test-connection` | 12 in 5 minutes |
| `container.software` | The routes under `/client/servers/{uuid}/software/` that install, change or update software | 8 in 10 minutes |
| `container.database` | `POST /client/servers/{uuid}/databases` | 24 a minute |
| `vps.power` | `POST /client/vps/{uuid}/power` | 24 a minute |
| `vps.rebuild` | `POST /client/vps/{uuid}/rebuild` | 8 in 10 minutes |
| `vps.backup` | `POST /client/vps/{uuid}/backups` | 8 in 5 minutes |
| `vps.backup_restore` | `POST /client/vps/{uuid}/backups/{backup_id}/restore` | 8 in 5 minutes |
| `custom_domain.link` | `POST /client/external-servers/{service_id}/custom-domain` and `POST /gameproxy/routes/{route_id}/custom-domain` | 12 an hour |
| `custom_domain.verify` | The same two routes with `/verify` added | 12 an hour |
| `external_server.update` | `PATCH /client/external-servers/{service_id}` | 30 in 5 minutes |
| `external_server.probe` | `POST /client/external-servers/{service_id}/test` | 20 a minute, for your account |
| `platform_domains.availability` | `GET /client/platform-domains/availability` | 60 a minute, for your account |
| `platform_domains.claim` | `POST /client/platform-domains/claims` | 6 an hour, for your account |
| `crypto.deposit_check` | `POST /billing/crypto/check` | 12 a minute, for your account |
| `storefront.free_order` | `POST /services/order` for a plan that costs nothing | 12 in 10 minutes, for your account |
| `user.mfa_verify` | `POST /auth/mfa/verify`, `/auth/mfa/enable`, `/auth/mfa/disable` and `/auth/mfa/recovery-codes` together | 10 in 5 minutes, for your account |
| `apps.create` | `POST /client/apps`, and `POST /orgs/{org_slug}/apps` for an organization | 20 an hour, for the account or organization that will own the app |
| `apps.write` | Every request that changes an app under `/client/apps/` or `/orgs/{org_slug}/apps/`: creating, changing or deleting it, its variables, domains, deployments and webhook secret | 120 an hour, for each person or API key |
| `apps.deploy` | `POST .../{app_uuid}/deployments`, `POST .../{app_uuid}/redeploy` and a rollback | 30 in 10 minutes, for each app |
| `apps.domain_verify` | `POST .../{app_uuid}/domains/{hostname}/verify` | 30 in 10 minutes, for each app |

The matching routes of an organization's customer portal, under `/orgs/{org_slug}/portal/`, have the same budgets for each customer. The staff console has budgets of its own on actions such as charges, refunds, credit and exports.

Over a budget, the action answers:

```json
{
  "detail": {
    "error": "rate_limited",
    "message": "Too many requests for this action. Please wait and try again.",
    "action": "container.power",
    "retry_after_seconds": 60
  }
}
```

`retry_after_seconds` and the `Retry-After` header both give the length of the whole window. Waiting that long always frees the budget.

Apps answers the same way without `action`, and its `message` names what ran out, such as `Too many deployments of this app. Wait a few minutes.`

## Organization API keys and the Store API

Organization API key
: Each key has its own limit, set when it is created: 4,000 requests in any 60 minutes by default. Over it, the Commerce API answers `429` with `API key rate limit exceeded` and `Retry-After: 3600`. [Create organization API keys](/docs/organizations/api-keys/) sets the limit.

Store API payments
: Starting a payment and completing a cart each allow 20 tries for one cart in 10 minutes, and 60 from one IP address for one store. Over either, the Store API answers `429` with `"error": "rate_limited"`, the message `Too many payment attempts for this cart. Try again in a few minutes.` and `Retry-After: 600`.

SMTP Relay has sending limits of its own, which [SMTP Relay sending limits](/docs/mail/smtp-relay/sending-limits/) describes.

## Handle a 429

- Read `Retry-After`, a whole number of seconds, and send nothing more to that route until it has passed.
- When an answer has no `Retry-After`, wait 1 second, then double the wait after each `429` up to a minute, with a random part added so that many clients do not retry at the same moment.
- Never retry in a loop without a wait: 8 refusals in a row block your address for 5 minutes.
- Poll less often, and read a list once instead of one record at a time ([Pagination and filtering](/docs/api/pagination/)).
- When you retry a payment or an order after a `429` or a lost connection, send the same `Idempotency-Key`, so that it happens only once ([Idempotent requests](/docs/api/idempotency/)).
