Guides

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: 1787888508

When 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:

  1. Bypass - a principal flagged bypass: true is unlimited.
  2. Custom endpoint limit - a per-user limit for this endpoint replaces everything else.
  3. Support tier - the named tier assigned to the user (basic, premium, enterprise, ... - deployments define their own).
  4. Default tier - users with no tier get the stored default (100 requests / 60 seconds unless an operator changed it).
  5. 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.

On this page