Rate Limits
Every authenticated request is metered against a token bucket per principal
(the JWT sub, or the client IP for anonymous requests). Two buckets can
apply: a global bucket from your tier, and an override bucket when a
module- or endpoint-level limit is stricter than your tier. Both are checked
in one atomic Redis operation.
Reading your budget
Rate-limited responses carry IETF-draft headers, lowercase on the wire:
ratelimit-limit: 100
ratelimit-remaining: 97
ratelimit-reset: 1787888508When you are limited, the request fails with 429 and a retry-after
header giving the seconds until reset. Back off until then.
Any principal can read its own effective budget without spending a request:
curl -H "Authorization: Bearer $SNUG_API_TOKEN" \
"http://localhost:4000/api/v1/rate-limits/me"{
"principal_id": "docs-writer",
"bypass": false,
"limit": 100,
"window_seconds": 60,
"endpoint": "/",
"endpoint_override_active": false
}Pass ?endpoint=/api/v1/... to resolve against a specific endpoint;
endpoint_override_active tells you a stricter module or endpoint tier
applies there. The CLI equivalent is snug rate-limit me [-e <endpoint>].
How your limit is resolved
Highest priority first:
- Bypass - a principal flagged
bypass: trueis unlimited. - Custom endpoint limit - a per-user limit for this endpoint replaces everything else.
- Support tier - the named tier assigned to the user (
basic,premium,enterprise, ... - deployments define their own). - Default tier - users with no tier get the stored default (100 requests / 60 seconds unless an operator changed it).
- Module / endpoint override - applied on top only when stricter than the resolved global limit.
Operating limits (admin)
Administrators manage all of this under /admin/rate-limits/*: the default
tier, named support tiers, per-module and per-endpoint tiers, per-user
assignments (tier, custom limits, bypass), and a cache-clear endpoint -
configuration is cached and can take up to five minutes to propagate
without it. Every operation is mirrored by a snug rate-limit verb
(update-default, create-module, create-endpoint, create-user,
create-tier, clear-cache, ...).
The full contracts live in the
Rate Limits API reference, and the
RATE_LIMIT_* environment variables (enablement, fail-open fallback,
tier seeding, proxy trust) are documented in the
rate limit CONFIG reference.
Two behaviors worth knowing when reasoning about production:
- The limiter fails open: if Redis is unreachable, requests are allowed and an error counter is incremented.
- An override tier only bites when it is stricter than the resolved global tier - a generous module tier does nothing to a basic user.