AI applications consume varying numbers of tokens per request, making traditional request-count rate limiting ineffective for cost and capacity control. The ai-token-ratelimit plug-in enforces rate limits based on the number of tokens consumed by large language model (LLM) requests, so you can cap token usage per API key, client IP, consumer, HTTP header, or cookie.
Enable the ai-token-ratelimit plug-in together with the AI observability plug-in. The observability plug-in counts the tokens consumed by each request, which ai-token-ratelimit relies on to enforce limits.
How it works
The plug-in evaluates incoming requests against an ordered list of rate limiting rules (rule_items). The first matching rule is applied and subsequent rules are skipped. Each rule identifies requests by a key -- such as an API key parameter, HTTP header value, or client IP -- and enforces a token quota per time window (second, minute, hour, or day).
Token counts and quotas are tracked in Redis. The Redis key is formed by concatenating the rule name, rate limiting type, key name, and actual key value.
When a request exceeds the configured token limit, the plug-in returns HTTP 429 with the body Too many requests by default. You can customize both the status code and response body.
Runtime attributes
| Attribute | Value |
|---|---|
| Execution stage | default stage |
| Execution priority | 600 |
Configuration reference
Top-level fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
rule_name | string | Yes | - | Name of the rate limiting rule. Combined with the rate limiting type, key name, and key value to form the Redis key. |
rule_items | array of object | Yes | - | Ordered list of rate limiting rules. The first match wins. |
rejected_code | int | No | 429 | HTTP status code returned when a request is throttled. |
rejected_msg | string | No | Too many requests | Response body returned when a request is throttled. |
redis | object | Yes | - | Redis connection settings. See Redis fields. |
Rule item fields
Each object in rule_items specifies a rate limiting key source and its token limits. Configure exactly one limit_by_* field per rule item.
Exact-match key sources apply a single shared quota to all requests that carry the specified key value:
| Field | Type | Description |
|---|---|---|
limit_by_header | string | Rate limit by the value of the specified HTTP request header. |
limit_by_param | string | Rate limit by the value of the specified URL parameter. |
limit_by_consumer | string | Rate limit by consumer name. Leave the value blank -- the consumer identity is resolved automatically. |
limit_by_cookie | string | Rate limit by the value of the specified cookie key. |
Per-key sources maintain a separate quota for each distinct key value. Use limit_keys to define matching patterns, which support regular expressions and wildcards (*):
| Field | Type | Description |
|---|---|---|
limit_by_per_header | string | Separate rate limit for each matching HTTP request header value. |
limit_by_per_param | string | Separate rate limit for each matching URL parameter value. |
limit_by_per_consumer | string | Separate rate limit for each matching consumer. Leave the value blank. |
limit_by_per_cookie | string | Separate rate limit for each matching cookie value. |
limit_by_per_ip | string | Separate rate limit for each client IP address. Set to from-header-<header-name> (for example, from-header-x-forwarded-for) to extract the IP from a request header, or from-remote-addr to use the peer socket IP directly. |
Each rule item also requires:
| Field | Type | Required | Description |
|---|---|---|---|
limit_keys | array of object | Yes | Token quotas for matched key values. See Limit key fields. |
Limit key fields
Each object in limit_keys defines a key pattern and its token quota.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
key | string | Yes | - | The key value to match. Supports exact values, regular expressions (prefix with regexp:), and wildcards (* matches all). For limit_by_per_ip, specify an IP address or CIDR block. |
token_per_second | int | No | - | Maximum tokens allowed per second. |
token_per_minute | int | No | - | Maximum tokens allowed per minute. |
token_per_hour | int | No | - | Maximum tokens allowed per hour. |
token_per_day | int | No | - | Maximum tokens allowed per day. |
Configure exactly one token_per_* field per limit key.
Regular expression example: regexp:^d.* matches all key values that start with d.
Redis fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
service_name | string | Yes | - | Fully qualified domain name (FQDN) of the Redis service, such as my-redis.dns or redis.my-ns.svc.cluster.local. |
service_port | int | No | 80 (static service) or 6379 (other services) | Port of the ApsaraDB for Redis instance. |
username | string | No | - | Username for the ApsaraDB for Redis instance. |
password | string | No | - | Password for the ApsaraDB for Redis instance. |
timeout | int | No | 1000 | Connection timeout in milliseconds. |
Configuration examples
Rate limit by URL parameter
Rate limit requests based on the apikey URL parameter. The first rule applies exact-match quotas to two specific API keys. The second rule uses per-key matching with regular expressions and a wildcard fallback.
rule_name: default_rule
rule_items:
- limit_by_param: apikey
limit_keys:
- key: 9a342114-ba8a-11ec-b1bf-00163e1250b5
token_per_minute: 10
- key: a6a6d7f2-ba8a-11ec-bec2-00163e1250b5
token_per_hour: 100
- limit_by_per_param: apikey
limit_keys:
# Match keys starting with "a": 10 tokens per second each
- key: "regexp:^a.*"
token_per_second: 10
# Match keys starting with "b": 100 tokens per minute each
- key: "regexp:^b.*"
token_per_minute: 100
# Fallback for all other keys: 1,000 tokens per hour each
- key: "*"
token_per_hour: 1000
redis:
service_name: redis.staticRate limit by HTTP request header
Rate limit requests based on the x-ca-key header.
rule_name: default_rule
rule_items:
- limit_by_header: x-ca-key
limit_keys:
- key: 102234
token_per_minute: 10
- key: 308239
token_per_hour: 10
- limit_by_per_header: x-ca-key
limit_keys:
# Match keys starting with "a": 10 tokens per second each
- key: "regexp:^a.*"
token_per_second: 10
# Match keys starting with "b": 100 tokens per minute each
- key: "regexp:^b.*"
token_per_minute: 100
# Fallback for all other keys: 1,000 tokens per hour each
- key: "*"
token_per_hour: 1000
redis:
service_name: redis.staticRate limit by client IP address
Extract client IP addresses from the x-forwarded-for header and apply per-IP token quotas. Supports exact IP addresses and CIDR blocks.
rule_name: default_rule
rule_items:
- limit_by_per_ip: from-header-x-forwarded-for
limit_keys:
# Exact IP: 10 tokens per day
- key: 1.1.1.1
token_per_day: 10
# CIDR block: 100 tokens per day per IP
- key: 1.1.1.0/24
token_per_day: 100
# Default for all other IPs: 1,000 tokens per day per IP
- key: 0.0.0.0/0
token_per_day: 1000
redis:
service_name: redis.staticRate limit by consumer
Rate limit requests based on consumer identity.
rule_name: default_rule
rule_items:
- limit_by_consumer: ''
limit_keys:
- key: consumer1
token_per_second: 10
- key: consumer2
token_per_hour: 100
- limit_by_per_consumer: ''
limit_keys:
# Match consumers starting with "a": 10 tokens per second each
- key: "regexp:^a.*"
token_per_second: 10
# Match consumers starting with "b": 100 tokens per minute each
- key: "regexp:^b.*"
token_per_minute: 100
# Fallback for all other consumers: 1,000 tokens per hour each
- key: "*"
token_per_hour: 1000
redis:
service_name: redis.staticRate limit by cookie value
Rate limit requests based on the value of cookie key key1. This example also customizes the throttle response to return HTTP 200 with a JSON error body.
rule_name: default_rule
rule_items:
- limit_by_cookie: key1
limit_keys:
- key: value1
token_per_minute: 10
- key: value2
token_per_hour: 100
- limit_by_per_cookie: key1
limit_keys:
# Match values starting with "a": 10 tokens per second each
- key: "regexp:^a.*"
token_per_second: 10
# Match values starting with "b": 100 tokens per minute each
- key: "regexp:^b.*"
token_per_minute: 100
# Fallback for all other values: 1,000 tokens per hour each
- key: "*"
token_per_hour: 1000
rejected_code: 200
rejected_msg: '{"code":-1,"msg":"Too many requests"}'
redis:
service_name: redis.static