API

Rate limits

Token buckets per policy, keyed by caller. The real numbers.

Every route takes one token from a bucket before it does anything else. A bucket refills continuously at its sustained rate and holds at most its burst, so a caller may arrive in a clump and then settles to the rate.

A token bucket rather than a fixed window, deliberately: a fixed window lets a caller send a whole window's worth at the boundary and the same again a moment later, which is exactly the burst it is meant to prevent.

The policies

Read from the live policy object rather than transcribed. Burst is the bucket's capacity; where a policy sets none it equals the sustained rate.

PolicyPer minuteBurst
quote3010
relay600120
status30060
list600120
write105
trade6020
exit12030

Which policy a route uses

PolicyRoutes
listMost reads: markets, market-rules, candles, funding, config, tokens, deposit-chains, insurance-fund, liquidations, leaderboard, whales, points, catalogue, referral verify, swap chains, swap tokens, earn meta, earn vaults, and every catalogued operation on the list policy.
statusThe two that a live terminal polls hardest: orderbook and trades, plus the swap status route and the markets.trades operation.
quoteThe swap quote route, because each call is a priced upstream request.
relayPOST /api/prep/relay, counted twice: once against your address and once against the account id in the envelope.
writeAccount creation and referral claims: register, registration-nonce, trading-key and referral/code.

The relay policy is generous on purpose. It is not there to protect the venue from you, who are spending your own quota under your own key; it is there so a runaway client cannot turn this server into an amplifier. An open terminal legitimately polls positions, orders and balances at once.

Being limited

Going over returns 429 with code rate_limited and a retry-after header of 10 seconds. It is marked retryable, so back off and try once rather than looping.

HTTP/1.1 429
retry-after: 10

{ "code": "rate_limited", "error": "Too many requests. Wait a moment and try again." }

How a caller is identified

By the first address in x-forwarded-for, falling back to x-real-ip and then cf-connecting-ip. Callers behind the same address share a bucket, which is why the relay adds a second bucket keyed on the account id.

The limiter is in memory and per instance. It is a blunt instrument that stops one client hammering one server, not a distributed limiter: behind several instances the effective ceiling multiplies by the instance count. Do not design a client that depends on the exact numbers above, and do build one that handles a 429 wherever it arrives.

Caching is cheaper than a limit

Most reads carry a cache-control header, and honouring it is the simplest way never to see a 429. Market rules are good for an hour, the catalogue for five minutes, markets for five seconds, and the order book for none at all.