nestonexStart a project
BACKEND ARCHITECTURE

Rate limiting

How do you cap request volume without breaking the legitimate traffic you wanted?

Key the limit on something an attacker cannot rotate cheaply, size it from observed legitimate usage rather than a guess, use a token bucket so bursts are tolerated, and tell the caller what the limit is and when it resets. A limit clients cannot see is a limit they cannot respect.

Swap backend architecture with per-user sequencing, HMAC-signed requests and rate limiting across sixteen chains
System architecture · Gaya Wallet

What you are actually defending against

Three different things, which want three different limits. Abuse: credential stuffing, scraping, enumeration — high volume from few sources, defeated by a strict limit on the specific endpoint being abused.

Cost: an expensive operation called more than the economics support. A model-backed endpoint, a report generation, an export. Here the limit protects a budget, and it usually belongs per user per day rather than per second.

And accidents: a client with a retry loop and no backoff, which is by far the most common cause of a traffic spike. This one is your own fault and the limit is protecting you from yourself.

Naming which of the three you are solving changes the answer. A single global limit tuned for abuse will be far too generous for cost and far too strict for a legitimate burst.

What to key it on

Authenticated user id is the best key, because it survives IP rotation and maps to something you can hold accountable. It only covers authenticated traffic, which is not where most abuse arrives.

IP address is the fallback and it is a blunt one. Behind a corporate NAT or a mobile carrier, one address is thousands of people, so a strict per-IP limit takes out an office. Attackers rotate addresses cheaply. Useful, never sufficient.

API key is the right key for machine traffic and lets you set different tiers without changing any code.

In practice you combine them: a generous per-IP limit as a floor, a tighter per-user limit for cost, and a strict per-endpoint limit on the small number of routes that are worth attacking — login, signup, password reset, anything that sends a message or spends money.

Choosing the algorithm

A fixed window — a counter that resets every minute — is the easiest to implement and has a well-known flaw: a client can send a full allowance at the end of one window and another at the start of the next, producing twice the limit in a moment.

A sliding window fixes that by counting over the trailing period rather than a calendar minute. More accurate, slightly more expensive to store.

A token bucket is usually the best fit for a product. Tokens refill at a steady rate up to a ceiling, so a client that has been quiet can burst, and a client that is constantly at the limit is throttled to the refill rate. That matches how real clients behave: quiet, then a flurry when someone opens a page.

Whichever you pick, the counter belongs in shared storage rather than in process memory, or the effective limit is multiplied by the number of servers you happen to be running.

Telling the caller, and what not to solve this way

Return 429 with a Retry-After header, and send the limit, remaining and reset values on normal responses too. A client that can see its budget can pace itself; a client that cannot will retry immediately and make the problem worse.

Fail open or closed deliberately. If the store holding the counters is unavailable, decide in advance whether requests are allowed or blocked, and make it a decision rather than an accident.

And know the boundary. Rate limiting does not stop a determined distributed attack, does not replace authentication, and does not fix an endpoint that is expensive because it is badly implemented. It buys time and caps damage. It is not a security model.

How we have used it

Gaya Wallet runs rate limiting alongside HMAC-signed requests and per-user sequencing across a swap backend spanning sixteen chains. The sequencing removes the race between one user's concurrent swaps; the rate limit keeps the aggregate load and the aggregator quota within bounds.

ProTrader uses it as a cost control rather than an abuse control: a daily spend cap per member on the AI coach, which is a rate limit denominated in money instead of requests. When an endpoint's cost is the risk, that is the more honest unit.

COMMON QUESTIONS /

Questions people also ask

What status code should a rate-limited request return?

429 Too Many Requests, with a Retry-After header saying when to try again. Returning 503 or 403 instead sends clients into behaviour tuned for a different problem — usually immediate retry, which is the opposite of what you want.

Should rate limits be per endpoint or global?

Both. A global per-user limit protects overall capacity, and tighter limits on the specific routes worth attacking — login, signup, password reset, anything that sends a message or spends money — do the work that matters. A single global number is either too strict everywhere or too loose where it counts.

How do you set the number?

From observed legitimate usage, not intuition. Look at the request rate distribution for real users, set the limit comfortably above the high percentile, and watch how often it triggers. A limit that fires for ordinary customers is a bug report waiting to be filed.

Does rate limiting stop scraping?

It raises the cost and slows it down. A determined scraper distributes across addresses and paces itself under the limit. Treat rate limiting as one layer alongside authentication and monitoring rather than as the defence.

THE PROOF /

Where we have actually done this.