Rate limits
The default limit is 100 requests per minute, counted per IP address.
How limits work
Every request is counted against a bucket. When a bucket fills up within its window, further requests are rejected until the window rolls over.
- The window. Counting resets on a rolling window — the default is 100 requests per minute. Once the window passes, your allowance refills.
- The bucket key. The global default is counted per IP address: every request in that window — including session-token traffic — shares one bucket tied to the sender's IP, because the default limit has no custom key. Per-account bucketing, where a session token maps to your account, applies only to the tightened per-route limits below that opt into it — never to the 100/min default. A forged or expired token can never spend a real user's allowance — it falls back to the sender's IP, so the cost lands on whoever sent it.
- Sensitive endpoints are tighter. Account-security and money routes set their own, stricter limits. For example, requesting an SMS code is capped at 5 per 10 minutes and payment/checkout calls at 15 per minute. Destructive or expensive actions (creating servers, snapshots, keys) sit below the global ceiling too.
Exact numbers can change
The global ceiling and per-route caps are tuned per deployment and adjusted as the platform grows. Treat the values above as current defaults, not a contract — always read the response headers (below) to see the live limit for the endpoint you're calling.
When you hit a limit
Go over the limit and the request is rejected with 429 Too Many Requests. Every response — not just
the rejections — carries headers describing your current standing:
x-ratelimit-limit— the maximum requests allowed in the window.x-ratelimit-remaining— how many you have left (0when you're throttled).x-ratelimit-reset— seconds until the window resets and your allowance refills.
A rejected request adds one more:
retry-after— seconds to wait before trying again.
HTTP/1.1 429 Too Many Requests
x-ratelimit-limit: 100
x-ratelimit-remaining: 0
x-ratelimit-reset: 42
retry-after: 42
content-type: application/json
{
"error": {
"code": "internal_error",
"message": "Rate limit exceeded, retry in 1 minute"
}
}The body follows the standard error envelope, but a 429 carries no
rate-limit-specific code — it reports the generic internal_error, so don't branch on error.code
here. The reliable signals to code against are the 429 status and the retry-after header —
not the code or the message text.
Best practices
- Respect
retry-after. When you get a 429, wait the number of seconds it tells you before retrying. Don't hammer the endpoint in a tight loop — that just keeps the bucket empty. - Back off with jitter. For repeated retries, grow the delay (exponential backoff) and add a small random offset so many clients don't all retry on the same tick.
- Watch
x-ratelimit-remaining. Slow down as it approaches0instead of waiting for the 429. - Cache reads. If you poll for status, cache results and widen the interval. A dashboard refreshing every few seconds rarely needs to.
- Spread work over time. Batch or queue bulk operations rather than firing them all at once.
Let the response set your pace
Read the retry-after value the server sends instead of guessing a backoff constant — it already
accounts for the live window on that endpoint.