---
title: "User rate limit"
url: "/reference/filters/http/user-rate-limit/"
kind: "reference"
summary: "Arion HTTP filter that keeps a separate rate limit bucket per caller, identified by a header."
---

> Arion site index: [/llms.txt](/llms.txt)

# User rate limit

Arion HTTP filter that keeps a separate rate limit bucket per caller, identified by a header.

| Attachment | Type URL (`@type`) | Per-route override | Build feature |
|---|---|---|---|
| HTTP filter chain (`http_filters`) | `type.googleapis.com/arion.extensions.filters.http.user_rate_limit.v3.UserRateLimiter` | `disabled` only | None |

## What it does

The user rate limiter is an Arion extension that rate-limits each caller separately. The caller is identified by the value of one request header (`user_id_header_name`) — a session id, an API key id, or a value written from a JWT claim by [`claim_to_headers`](@/reference/filters/http/jwt-authn.md). Each distinct value gets its own token bucket; specific users can have dedicated limits, everyone else falls back to a default entry, and idle buckets are cleaned up automatically.

## Minimal configuration

From `arion-runtime-user-rate-limit.yaml`; the fragment is an entry in `http_filters`, before the router:

```yaml,arion=fragment
- name: user_rate_limit
  typed_config:
    "@type": type.googleapis.com/arion.extensions.filters.http.user_rate_limit.v3.UserRateLimiter
    user_id_header_name: x-session-id
    stat_prefix: user_rate_limiter
    user_rate_limits:
      - user_id: "premium-tenant"
        simple_rate_limit:
          max_tokens: 100
          rate: 50
      - simple_rate_limit:      # default for every other caller
          max_tokens: 10
          rate: 5
```

## Fields

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `user_id_header_name` | string | Yes | — | Header whose value identifies the caller. A request without the header is not limited and passes through. |
| `stat_prefix` | string | No | — | Prefix for this filter's statistics. |
| `status` | `{code}` | No | `429` | HTTP status returned to rate-limited requests. |
| `user_rate_limits` | list | Yes | — | Limit entries. Each names a `user_id` and one limit; an entry *without* `user_id` is the default for callers not listed. A caller with no entry and no default is not limited. |

Each entry carries exactly one of:

| Limit | Fields | Description |
|---|---|---|
| `simple_rate_limit` | `max_tokens`, `rate` | Shorthand bucket: capacity `max_tokens`, refilled at `rate` tokens per second. Both must be greater than zero. |
| `local_rate_limit` | `stat_prefix`, `status`, `token_bucket` | A full [local rate limit](@/reference/filters/http/local-rate-limit.md) config, giving explicit `tokens_per_fill` and `fill_interval`. |

## Ordering and interactions

Place the filter after whatever produces the identifying header. With JWT authentication first in the chain, a `claim_to_headers` mapping (for example `sub` to `x-user-id`) turns this filter into a per-subject limiter for verified callers — and because the header must be present for a limit to apply, put an authorization filter in front if unidentified traffic should not pass freely. Buckets live in one process-wide map shared by all workers, and a caller idle for a while has its bucket reclaimed. Per route the filter supports only the generic `FilterConfig` override with `disabled: true`; its configuration cannot be replaced per route.
