Rules & Descriptors
Rules are the heart of a policy. Each rule specifies which requests it applies to (limit_keys, match), and which algorithm enforces the limit.
Rule object
{
"name": "per-org-rps",
"limit_keys": ["jwt:org_id"],
"algorithm": "token_bucket",
"algorithm_config": {
"tokens_per_second": 100,
"burst": 200
},
"match": {
"jwt:plan": "enterprise"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Non-empty. Used in counter keys, log lines, response headers, and metrics labels. Must be unique within the policy. |
limit_keys |
array | yes | One or more descriptor keys that define the partitioning dimension. Each unique combination of values gets its own counter. |
algorithm |
string | yes | One of token_bucket, cost_based, token_bucket_llm. |
algorithm_config |
object | yes | Algorithm-specific configuration (see below). |
match |
object | no | Key/value equality filters. The rule is only evaluated if all match conditions are satisfied. |
Rule evaluation order
Rules in the rules[] array are evaluated in order, all-match: every rule
whose match conditions are satisfied is checked. Evaluation stops at the first
rule that rejects the request. A rule that matches but does not reject
continues to the next rule.
Example with three rules:
| Rule | match | result | next rule? |
|---|---|---|---|
enterprise |
jwt:plan = enterprise | allow | yes |
per-org-rps |
(no match condition) | allow | yes |
free-tier-cap |
(no match condition) | reject | — |
The fallback_limit is applied only when no rule in rules[] matches
(all match conditions failed for every rule).
Descriptor keys
Descriptor keys identify where a value is extracted from the request. They have the format source:name.
jwt:<claim>
Extract a claim from the JWT payload in the Authorization: Bearer <token> header.
"limit_keys": ["jwt:org_id"]
JWT signatures are not verified. Claims are decoded for value extraction only. Fairvisor trusts that your gateway (nginx, Envoy, Kong, etc.) has already validated the token before forwarding the request. Never rely on Fairvisor alone for JWT authentication.
- Claim names must match
[A-Za-z0-9_-]+ - If the header is absent or not a valid JWT, the descriptor value is nil and the rule is skipped
header:<name>
Extract an HTTP request header value.
"limit_keys": ["header:x-api-key"]
Header matching is case-insensitive and normalises both - and _ separators. All of these resolve x-api-key:
X-API-Keyx-api-keyX_API_KEY
query:<name>
Extract a URL query parameter.
"limit_keys": ["query:tenant_id"]
ip:address
The client IP address from ngx.var.remote_addr.
"limit_keys": ["ip:address"]
ip:country
GeoIP country code (two-letter ISO 3166-1 alpha-2). Requires a GeoIP module in OpenResty.
"limit_keys": ["ip:country"]
ip:asn
GeoIP Autonomous System Number.
ip:type
Network type descriptor derived from ASN/type mapping (isp, hosting, business, education_research, government_admin, unrouted, unknown).
See ip:type.
ip:tor
Boolean selector for Tor exit classification.
- Primary source: nginx
geovariable ($is_tor_exit) generated from Tor exit list include. - Optional override source:
X-Tor-Exitheader (1/0,true/false,yes/no). - Normalized values used by policy matching:
"true"/"false".
"limit_keys": ["ip:tor"]
ua:bot
Bot detection based on User-Agent. Returns the string "true" or "false".
"limit_keys": ["ua:bot"]
Known bot User-Agents include major search, AI crawler, assistant-user, social preview, and SEO crawler families.
Detection is case-insensitive substring matching with a generated runtime automaton.
ua:bot_category
Bot category for matching/partitioning. See ua:bot_category values for the full list of categories and examples.
Composite keys (multi-dimensional limiting)
Provide multiple descriptor keys to create a composite partition. Each unique combination of values gets its own counter bucket.
"limit_keys": ["jwt:org_id", "jwt:user_id"]
This limits each (org_id, user_id) pair independently. An org with 1000 users gets 1000 separate buckets.
The composite key is formed by joining descriptor values with |:
org-abc|user-xyz
match conditions
The match object applies additional filters before the rule’s limiter is invoked. Each key/value pair must match the corresponding descriptor exactly.
{
"name": "enterprise-limit",
"limit_keys": ["jwt:org_id"],
"algorithm": "token_bucket",
"algorithm_config": { "tokens_per_second": 1000, "burst": 2000 },
"match": {
"jwt:plan": "enterprise"
}
}
This rule only applies to requests where the JWT contains "plan": "enterprise". Other plans fall through to the next rule or the fallback_limit.
fallback_limit
The fallback_limit is applied when no rule in the policy matches a request (all match conditions failed). It has the same structure as a rule object, without a name requirement:
{
"spec": {
"selector": { "pathPrefix": "/api/" },
"rules": [
{
"name": "enterprise",
"limit_keys": ["jwt:org_id"],
"algorithm": "token_bucket",
"algorithm_config": { "tokens_per_second": 1000, "burst": 2000 },
"match": { "jwt:plan": "enterprise" }
}
],
"fallback_limit": {
"name": "free-tier",
"limit_keys": ["jwt:org_id"],
"algorithm": "token_bucket",
"algorithm_config": { "tokens_per_second": 10, "burst": 20 }
}
}
}
Missing descriptors
If a descriptor key cannot be resolved (e.g., the Authorization header is absent when using jwt:org_id), the rule is skipped with a warning log and a fairvisor_descriptor_missing_total metric increment. It is not an error.
To enforce that a value must be present, use ua:bot in combination with a separate kill switch on the bot UA, or add an ip:address rule as a universal fallback.
Algorithm configs
| Algorithm | When to use | Key fields |
|---|---|---|
token_bucket |
Request-rate limiting (RPS) | tokens_per_second, burst |
cost_based |
Period spend quotas | budget, period, staged_actions |
token_bucket_llm |
LLM token throughput | tokens_per_minute, tokens_per_day |
See the Algorithms section for full field references.