Skip to content
Coritan Docs

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.

View as Markdown

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.

Every limit on this page that counts an IP address counts an IPv4 address on its own, and an IPv6 address by its /64 network: the block a provider gives one subscriber. All the addresses in one IPv6 /64 share a single budget, so moving to another address in that block does not reset a limit. An IPv4 address written in IPv6 form, such as ::ffff:203.0.113.10, counts as the IPv4 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.

The API also limits requests by the work it takes to answer them. Each API server applies these limits on its own:

Limit What it counts Answer
A minute of database work The database time your address's requests used in the current minute 429 with {"detail": "Too many costly requests from your address. Try again in a minute."}. Retry-After is the number of seconds until the minute ends.
Requests to one route at once Requests to the same route that are still being answered, with a lower limit when the request has no Authorization header 503 with {"detail": "The service is busy. Try again in a moment."} and Retry-After: 2.

While the database is under strain, a request with no Authorization header to a route that reads the database can also answer that 503. Requests from signed-in accounts are answered first. Wait as long as Retry-After says, then repeat the request; sending more at once only extends the wait.

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. On an organization's routes the body also has "code": "rate_limited".

An organization's sign-in, POST /orgs/{org_slug}/auth/login, has one more 429. When the organization has linked an old site and that site asks the person to wait, the answer is {"detail": "Too many sign-in attempts. Wait a few minutes and try again.", "code": "rate_limited"} with Retry-After: 300, and it counts as a failed attempt from that IP address.

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. An organization's POST /orgs/{org_slug}/auth/forgot-password has two as well, in the next table. Checking a two-factor code has one too.

These are the main actions with a budget of their own, on top of the limits above. Most start work that takes time or resources on Coritan's side, and the rest guard a request that writes a record or sends a message. 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
container.database_query POST /client/servers/{uuid}/databases/{database_id}/query 60 a minute
container.database_browse The GET routes that list a database's tables and columns and read its rows, under /client/servers/{uuid}/databases/{database_id}, together 240 a minute
container.database_edit The routes that add, change or delete rows, empty a table or drop it, under /client/servers/{uuid}/databases/{database_id}/tables/{table}, together 120 a minute
container.database_export GET /client/servers/{uuid}/databases/{database_id}/tables/{table}/export.csv 12 in 5 minutes
container.sleep_setting PUT /client/servers/{uuid}/sleep-setting 20 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
vps.ipv6 POST /client/vps/{uuid}/ipv6 6 an hour
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
billing.charge_query POST /billing/charge-queries 10 an hour, for your account
billing.refund_request POST /billing/refund-requests 10 an hour, for your account
storefront.free_order POST /services/order for a plan that costs nothing 12 in 10 minutes, for your account
services.addon POST /services/{service_ref}/addons and DELETE /services/{service_ref}/addons/{addon_ref} together 20 an hour, for your account
service.keep POST /services/{service_ref}/keep 20 an hour, for each service
account_plan.trial POST /account/plan/trial 5 an hour, for your account
mail.signup_mailbox POST /auth/register with mailbox_local_part, and POST /client/webmail/account, together 3 an hour and 5 a day, from one IP address
webmail.claim_mailbox POST /client/webmail/account 5 an hour, 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, releases and webhook secret 120 an hour, for each person or API key
apps.deploy A deploy (POST .../{app_uuid}/deployments), a rebuild or a redeploy (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
org_customer.reset_resend POST /orgs/{org_slug}/auth/reset-password/resend 3 an hour, for one customer
org_customer.reset_resend_ip The same route 20 an hour, from one IP address, for each organization
org_customer.forgot_ip POST /orgs/{org_slug}/auth/forgot-password 10 an hour, from one IP address, for each organization
org_customer.forgot The same route 3 an hour, for one email address in each organization. Over it, the route answers its usual 200 and sends no email
org_customer.reset_scan_ip POST /orgs/{org_slug}/auth/reset-password when the request has a token and no id 10 an hour, from one IP address, for each organization
— PUT /billing/object-storage/{service_id}/budget and DELETE /billing/object-storage/{service_id}/budget together 30 an hour, for the account

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. Try again later.",
    "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.

org_customer.forgot is the one budget that does not answer 429. A forgot-password request that is over it gets the same 200 as every other, so an address that has no account looks the same as one that does. Both forgot-password budgets count addresses that have no account.

App Deployment and the Apps API answer the same way without action, and the message names what ran out, such as Too many releases of this app. Wait a few minutes. An Object Storage budget answers the same way without action.

Organization API keys and the Store API

Section titled 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 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 describes.

  • 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).
  • 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).