Rate limiting rollout plan

We're seeing bursts of 4–5k requests/min from a handful of integrations, and the p99 is paying for it. This is the plan we aligned on: token bucket at the edge, per-key limits, and a two-week grace period with warning headers before we enforce.

Why token bucket

  • Absorbs honest bursts (webhook retries, batch jobs) without punishing them
  • One counter per key — cheap to run in Redis
  • Degrades predictably: when the bucket is empty you wait, nothing breaks

Fixed windows looked simpler, but they double-charge clients whose bursts straddle the window edge. Token bucket costs one INCR more and none of the support tickets.

Proposed limits

PlanSustainedBurstOverage response
Free60 req/min120429 + Retry-After
Pro600 req/min1,200429 + Retry-After
EnterpriseCustomCustomContact us

Edge check (simplified)

export async function checkLimit(key: string, cost = 1) {
  const bucket = await redis.hgetall(`rl:${key}`)
  const tokens = refill(bucket, limits[bucket.plan])

  if (tokens < cost) {
    return { ok: false, retryAfterMs: msUntilNextToken(bucket) }
  }

  await redis.hincrbyfloat(`rl:${key}`, 'tokens', -cost)
  return { ok: true }
}

Rollout checklist

  • Shadow mode: log would-be 429s for two weeks
  • Add X-RateLimit-* headers to every response
  • Email the twelve integrations currently over the Pro limit
  • Enforce on Free plan (March 3)
  • Enforce on Pro plan (March 17)

Open questions

  1. Do webhook deliveries count against the same bucket, or get their own?
  2. Should Retry-After jitter be server-side or documented client guidance?

Drafted with Claude, cleaned up by hand. Ping #platform with objections before Friday.