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-Key
  • x-api-key
  • X_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 geo variable ($is_tor_exit) generated from Tor exit list include.
  • Optional override source: X-Tor-Exit header (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.