A token bucket holds up to capacity tokens and refills at a fixed rate. A request costs one token; a request that cannot pay is rejected. Two properties fall out, and they are why this is the right shape for an API quota:
- it permits a burst of
capacity, then settles to exactly the refill rate - it never has a window boundary — a fixed window of 60/minute lets a client send 120 requests in two seconds across the seam
The implementation detail that matters: do not run a refill timer. Refill lazily on access from (now - last_seen) * rate, clamped at capacity.
let earned = (now_ms - self.last_ms) * REFILL_PER_MS;
self.tokens = (self.tokens + earned).min(CAPACITY);
One arithmetic line, no background task, two integers of state per client. Milli-tokens keep it in integers so there is no floating-point drift, and (deficit + rate - 1) / rate is the ceiling division that turns a shortfall into a retry_after the client can honour.
