Guides

Security Guide

Rate Limiting Explained: Protect APIs Without Slowing Customers Down

Good rate limiting is a capacity and abuse control, not a blunt request counter. Set limits around identities, routes, cost, and risk, then tune them from evidence.

Rate limiting is one of the simplest ways to protect an API, login flow, or expensive web action. It is also easy to get wrong. A limit that is too loose leaves an endpoint open to brute force, scraping, denial of service, or unexpected provider costs. A limit that is too strict turns shared office networks, mobile carriers, and legitimate bursts into customer-facing errors.

The right question is not "How many requests can an IP address make?" It is which actor can perform which operation, at what cost, over what time window, and what should happen when the budget is exhausted? This guide explains how to answer that question without tying the design to one edge provider.

What rate limiting protects

The threat model should cover more than volumetric attacks. OWASP classifies unrestricted resource consumption as an API security risk because each request can consume bandwidth, CPU, memory, storage, or paid downstream services. A low-volume attack against a costly search, export, message, or password reset operation can be more damaging than a large number of cheap cache hits.

Separate the outcomes you want to prevent:

  • Authentication abuse: brute force, credential stuffing, and password reset flooding.
  • Resource exhaustion: too many concurrent or expensive calls.
  • Business abuse: inventory checks, coupon guessing, account creation, or message sending.
  • Fairness problems: one client consuming a shared quota and starving others.
  • Cost inflation: repeated calls to metered storage, email, SMS, AI, or partner APIs.

Rate limits are not authorization

A caller can stay under a request limit and still access another user's object or perform an unsafe business action. Keep authentication, authorization, input validation, and transaction controls in place alongside rate limiting.

Choose the right limit key

An IP-only rule is a useful first line, but it is not a complete policy. Shared addresses create false positives, while distributed attackers can rotate addresses. Use separate counters where the application can safely identify the actor:

  • IP or network: contains anonymous floods and protects the edge itself.
  • Authenticated user or account: protects one account from a distributed attack.
  • API key or OAuth client: gives a known integration a predictable budget.
  • Session or device signal: helps contain unauthenticated flows without relying on one address.
  • Route and method: distinguishes a cheap GET from an expensive POST, export, or search.
  • Resource or business object: protects one account, SKU, mailbox, or reservation.
  • Aggregate service budget: protects the origin and paid dependencies across all callers.

Use more than one key when the threat warrants it. For example, a login endpoint can have a source budget to stop one network from trying many accounts and an account budget to stop many networks from targeting one username. Do not expose the exact internal bucket that fired in an error response.

Pick an algorithm and a response

A fixed window is easy to understand but can allow a burst at the boundary between two windows. A sliding window measures recent history more smoothly. A token bucket allows controlled bursts while enforcing an average rate, which is often a better fit for APIs with normal short spikes. A leaky bucket drains at a steady rate and can be useful when smoothing is more important than burst capacity.

The algorithm is less important than consistent, distributed enforcement. If several edge nodes or application instances count independently, the real limit may be much higher than intended. Use a shared, low-latency counter or an enforcement layer designed for distributed state, and define what happens when that state is unavailable. Failing open may preserve availability but increase abuse; failing closed may protect the origin but interrupt customers. Make that decision per route and document it.

When a client exceeds a limit, return 429 Too Many Requests where a response is appropriate. RFC 6585 defines 429 for rate limiting and permits a Retry-After header. Use it when the client can safely retry after a known delay. Keep the response generic, avoid revealing bucket internals, and ensure the response is not stored by a cache. For severe floods, dropping connections or using an upstream mitigation can consume fewer resources than generating a response for every request.

Design limits from evidence

Start with an inventory of routes and classify each by authentication requirement, normal traffic pattern, computational cost, downstream cost, and business impact. Measure legitimate traffic over representative periods that include releases, campaigns, regional peaks, mobile clients, and partner jobs. Look at bursts and concurrency, not only averages.

Set a provisional limit with a documented reason. For public reads, a shared anonymous budget may be adequate. For authenticated APIs, per-client quotas and route-specific limits are usually more useful. For sensitive actions, combine a generous normal budget with a tighter failure budget and a step-up control. Never copy a number from another API without understanding its workload and client population.

Document the contract for developers. State whether quotas are per minute, per second, rolling, or monthly; whether failed calls count; whether retries are safe; and how clients should respond to 429. Idempotent operations should use bounded exponential backoff with jitter. Non-idempotent operations need an idempotency strategy so a retry does not duplicate a charge or order.

Roll out safely

Deploy a policy in log-only or shadow mode first. Record the proposed decision, key, route, client category, status, latency, and downstream outcome without enforcing it. Exclude secrets and avoid storing more personal data than the investigation needs.

Canary enforcement on one route or a small percentage of traffic. Watch:

  • 429 responses by route, customer, region, and client version.
  • Successful login, checkout, order, and API completion rates.
  • Retry volume and request amplification after a limit is hit.
  • Origin CPU, memory, connection count, queue depth, and dependency usage.
  • Support contacts and partner error reports.

Keep a tested, audited break-glass process for a known partner or incident responder. Do not make it a secret header that anyone can add. Revisit limits after every material change to the application or client behavior.

Mistakes that create customer pain

  • Using one global IP threshold for every route and user type.
  • Counting retries and failed requests inconsistently across edge and application layers.
  • Trusting a client-supplied identity header without validating the proxy that sets it.
  • Returning 403 for every limit event, which hides the retry semantics from clients.
  • Setting Retry-After so low that clients create a synchronized retry storm.
  • Enforcing before observing, with no baseline for legitimate bursts.
  • Forgetting WebSockets, GraphQL queries, file uploads, background jobs, or internal service-to-service calls.

Rate limiting works best as one layer of API protection, alongside bot management and a WAF. For the next step, read API Protection at the Edge and How to Hide and Protect Your Origin Server.

Authoritative references

There are only two hard things in Computer Science: cache invalidation and naming things.

Make your limits work for customers

Talk to our team about route-aware rate limiting and layered API protection at the edge.

Contact Optimi