Skip to content
Back to blog
Lancartech Team 2 min read

API Rate Limiting: Protecting Your SaaS from Abuse

Token bucket, sliding window, or leaky bucket — when to use which, plus concrete Redis implementations for small-to-mid SaaS.

API Rate Limiting: Protecting Your SaaS from Abuse

Why rate limiting is mandatory

Without rate limits, one buggy or abusive client can:

  • Overload the database — queries at 10x normal volume.
  • Spike cloud costs — egress, compute, log storage.
  • Hurt other users — server slow for everyone.

For SaaS, rate limiting isn’t optional. Default it on every endpoint.

1. Token Bucket

  • User has a “bucket” of N tokens. Refills at rate R per second.
  • Each request consumes 1 token. Empty → 429.
  • Best for: general APIs, allow small bursts while capping the average.

2. Sliding Window

  • Count requests in a recent window (e.g. 60 seconds). If > limit → 429.
  • Best for: strict anti-abuse with accurate rate prediction.
  • Downside: per-request storage (timestamps).

3. Leaky Bucket

  • Queue requests, drain at a constant rate. Queue full → 429.
  • Best for: smoothing bursts (e.g. webhook delivery).

For standard SaaS, Token Bucket is the most flexible default.

Redis implementation (Token Bucket)

-- KEYS[1]: rate-limit key
-- ARGV[1]: capacity (max tokens)
-- ARGV[2]: refill rate per second
-- ARGV[3]: now (ms timestamp)

local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

local last = redis.call("HMGET", KEYS[1], "tokens", "ts")
local tokens = tonumber(last[1]) or capacity
local ts = tonumber(last[2]) or now

local elapsed = (now - ts) / 1000
tokens = math.min(capacity, tokens + elapsed * rate)

if tokens < 1 then
  redis.call("HMSET", KEYS[1], "tokens", tokens, "ts", now)
  return 0  -- denied
else
  tokens = tokens - 1
  redis.call("HMSET", KEYS[1], "tokens", tokens, "ts", now)
  redis.call("EXPIRE", KEYS[1], 60)
  return 1  -- allowed
end

Run with EVAL — atomic, race-free.

Tier-based limits (multi-tier SaaS)

tiers:
  free:
    requests_per_min: 15
    burst: 30
  basic:
    requests_per_min: 60
    burst: 100
  pro:
    requests_per_min: 240
    burst: 500

On each request → look up the user’s tier → load the limit → enforce via token bucket.

Good response headers

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1736812800
Retry-After: 23

Clients back off correctly without guessing.

Best practices

  1. Limit at the edge (Cloudflare Workers, API gateway) — keep noisy traffic out of app servers.
  2. Limit per IP + per API key — both. IP alone is bypassable with rotating addresses.
  3. Soft limit (warning) before hard limit (block) — give clients a chance to fix.
  4. Whitelist internal cron/batch jobs.

SaaS architecture consult for deeper rate-limiting + architecture chats.

Lancartech Team · · 2 min read

Ready to start your next project?

Initial consultation is free — let's design a digital strategy that fits your business.