Skip to content

Per-user quotas

Per-user quotas cap how much each user can consume — by cost, tokens, or request count — over a configured period. Enforcement happens as early as possible in the request lifecycle: an over-quota caller is rejected with 429 before any body parsing, signal evaluation, or model call.

Quotas build on persistence and require token-based identity (auth.tokens_file) to be meaningful.

Attach a quota to a token entry in your tokens file. Any dimension left unset is unlimited; a user with no quota is unlimited.

tokens:
- id: alice
secret:
env: TOKEN_ALICE
quota:
max_cost: 50.0 # dollars per period
max_tokens: 5000000 # prompt + completion tokens per period
max_requests: 10000 # requests per period
- id: bob # no quota → unlimited
secret:
env: TOKEN_BOB

Give each dimension a positive value. Unlimited is expressed by omitting the dimension, never by setting it to 0 — a max_requests, max_tokens, or max_cost of 0 (or any non-positive max_cost) is rejected at config-load time, e.g. quota.max_requests must be greater than 0 (omit the field for unlimited).

Quotas are loaded into memory at startup, so the enforcement check is a couple of atomic reads with no database round-trip.

The accounting period is set once, globally, on the persistence config:

persistence:
enabled: true
quota_period: monthly # daily | weekly | monthly | total
  • daily — resets at UTC midnight.
  • weekly — resets at UTC midnight on Monday.
  • monthly — resets at UTC midnight on the first of the calendar month (default).
  • total — never resets (a lifetime cap).

When a period rolls over, a user’s current-period consumption resets to zero while their lifetime totals are preserved. The period boundary is persisted, so both the boundary and the current-period consumption are restored after a restart — a user who was over quota before a restart stays over quota until the period actually rolls over.

When an identified user’s current-period consumption meets or exceeds any of their limits, the next request is rejected with HTTP 429 and an OpenAI-compatible error:

{
"error": {
"message": "You have exceeded your usage quota for the current period.",
"type": "insufficient_quota",
"code": "insufficient_quota"
}
}

The check runs right after the user is identified and before any expensive work, so an over-quota request never reaches a model. Each rejection increments the llmsoup_quota_exceeded_total counter (labeled by user_id).

Requests with no token identity are attributed to a single reserved anonymous bucket and are never counted against any identified user’s quota. How the anonymous bucket itself is treated is configurable:

persistence:
anonymous_quota: exempt # exempt | reject
  • exempt (default) — anonymous requests are never rejected for quota.
  • reject — anonymous requests are rejected with 429 while quotas are in use, effectively requiring token identity for access.