Rate limiting is the product. Not a feature bolted onto something else — a gateway built for exactly one job: answering "is this request allowed?" with a verdict and standard headers, in one HTTP call.
How it works
A rule binds a namespace to an algorithm, a limit, and a window. Evaluating a request is a single call:
import { createLimitClient } from '@brume/limit';
const limit = createLimitClient({ apiKey: process.env.BRUME_API_KEY! });
const result = await limit.limit('api', user.id);
if (!result.success) {
return new Response('Too many requests', {
status: 429,
headers: { 'Retry-After': String(result.retry_after ?? 60) },
});
}
Every evaluation runs as one atomic Lua script against a dedicated Redis. Blocklist lookup, override resolution, quota check, and the counter update happen in a single operation — no race windows, no double-counting, no app-side locking.
Four algorithms
- Token bucket — smooth traffic with optional burst capacity (
burst, refill_rate).
- Fixed window — one counter per window, predictable resets.
- Sliding window log — exact timestamps, strictest fairness.
- Sliding window counter — approximate rolling rate, constant memory.
limit consumes, check reads
limit() spends one unit of capacity. check() is read-only — it reports the current state with cost 0, so a UI can show "N requests left" without burning the user's budget. Both return the same typed result.
Fail-open, visibly
If the rate-limit Redis is unreachable, the gateway returns success: true with degraded: true and the X-Brume-RateLimit-Degraded header. Availability over correctness — and you always get to see it happened.
Standard headers
Every evaluation sets X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After on denial. Forward them to your callers unchanged.
See the rate-limiting guide for rules, multi-rule evaluation, and the status endpoint.