Rate Limit Policies
A rate limit policy is a quota rule managed separately from caller API keys and models. It can select traffic by team, member, API key, model, model name, or provider, then divide matching requests into independent buckets. A policy can also suspend its own enforcement during recurring windows.
Conditional policies use request conditions and optional bucket dimensions. They are the recommended form for new configurations. Classic policies remain available for existing configurations that target one API key, model, team, or member.
Policies do not replace limits configured on individual API keys or models. AISIX evaluates every matching policy together with the limits on those resources, and the request continues only when every matching bucket has capacity.
This guide shows how to configure and verify conditional policies through AISIX Cloud or the open-source AISIX gateway, maintain classic policies, and schedule policy suspensions.
Prerequisites
Before starting, prepare the following:
- Familiarity with the limit fields, counter behavior, storage options, and resource-specific limits described in API Key and Model Rate Limits.
- A working model alias and caller API key that can send proxy requests.
- For the per-member examples, a caller API key bound to the selected team and a member.
- One of these configuration paths:
- AISIX Cloud with an environment, an attached gateway, and a write-scoped admin token.
- An open-source AISIX gateway with a declarative resources file that you can validate and reload.
Export the gateway request values used to verify the examples:
# AISIX_PROXY has no trailing slash or endpoint path.
# The local quickstarts use http://127.0.0.1:3000.
export AISIX_PROXY="YOUR_AISIX_GATEWAY_URL"
export AISIX_MODEL="gpt-4o-prod"
export AISIX_API_KEY="YOUR_CALLER_API_KEY"
For AISIX Cloud, also export the control-plane values and resource IDs used by the examples:
# AISIX_CP includes /api and has no trailing slash.
# The local On-Premises quickstart uses http://localhost:8080/api.
export AISIX_CP="YOUR_AISIX_CLOUD_ADMIN_API_BASE_URL"
export AISIX_TOKEN="YOUR_ADMIN_TOKEN"
export ENV_ID="YOUR_ENVIRONMENT_ID"
export TEAM_ID="YOUR_TEAM_ID"
Choose a Policy Form
A policy uses one of two forms. In AISIX Cloud, the form is fixed at creation. For the open-source AISIX gateway, an entry in the declarative resources file must use exactly one form; mixing fields from both forms is a load error.
- Conditional policy (recommended): matches requests with a condition tree, splits counters by dimensions in
group_by, and caps traffic with the same seven-fieldlimitsshape used by caller API keys and models. This is the only form created by the dashboard's New policy flow. - Classic policy (legacy): targets one subject through
scopeandscope_refand useswindowwithmax_requestsormax_tokens. Existing classic policies remain supported by the Admin API and resources file.
Use a conditional policy for new configurations. It can reproduce every classic scope and supports several policies for the same request.
Configure a Conditional Policy
A conditional policy makes three decisions: which requests match, how their counters split, and how much traffic each bucket can use.
{
"name": "algo-team-premium-models",
"conditions": [
{ "dimension": "team", "operator": "in", "value": ["<team-id>"] },
{
"logic": "or",
"children": [
{ "dimension": "model_name", "operator": "~~", "value": "^gpt-4" },
{ "dimension": "provider", "operator": "==", "value": "anthropic" }
]
}
],
"group_by": ["member"],
"limits": { "rpm": 20 }
}
Match Requests with Conditions
Top-level nodes in conditions all apply, which creates an AND relationship. A node can be a single condition or a group that combines nested children with logic: and or logic: or. An empty or omitted list matches every request in the environment.
| Dimension | Value | Notes |
|---|---|---|
team | Team ID | Matched against the caller API key's team binding |
member | Member or user ID | Matched against the caller API key's member binding |
api_key | Caller API key ID | |
model | Model ID | Matched per dispatched model; inside a model group, this is the selected target |
model_name | Model alias | Supports regular-expression matching across aliases |
provider | Provider catalog ID, such as openai or anthropic | Matched against the dispatched model's provider |
In AISIX Cloud, identity dimensions use resource IDs; model_name and provider use strings. For the open-source AISIX gateway, api_key and model values use the matching entries' display_name in the declarative resources file. The team and member values match the caller API key's team_id and user_id verbatim.
Operators follow lua-resty-expr, the same expression vocabulary used by APISIX route vars:
| Operator | Meaning | Applies To |
|---|---|---|
== / ~= | Equal / not equal | All dimensions |
in | Value in a list of 1–64 entries | All dimensions |
~~ / ~* | Case-sensitive / case-insensitive regular expression, up to 256 characters | model_name and provider |
negate: true | Invert a condition or group | Any node |
The team, member, api_key, and model dimensions carry IDs, so they use equality or list operators. A request that does not carry a dimension matches neither a condition on that dimension nor its negation, although another branch of an or group can still match. A policy can contain up to 16 conditions with up to 3 levels of nesting.
Split Counters into Buckets
group_by accepts team, member, api_key, model, and provider. It gives every distinct combination of the selected dimension values an independent bucket with the same limits. An empty or omitted group_by creates one shared bucket. A matched request that lacks a selected bucket dimension is not subject to the policy.
Common patterns include:
- Team pool: match one team and leave
group_byempty so the whole team shares the quota. - Per-member default: match one team and set
group_by: [member]so every member receives an independent quota. Stack this with a team-pool policy to enforce both a team total and a per-member maximum. - Fair-share a model: match one model and set
group_by: [team]so each team receives an independent quota.
Set the Limits
limits accepts rps, rpm, rph, rpd, tpm, tpd, and concurrency. At least one field is required. Token limits settle after the response or stream reports usage, as described in Rate Limit Fields and Counter Behavior.
action is reserved for future behaviors. reject, which returns HTTP 429, is the only supported value and the default.
See the Resources File Reference for the complete policy schema and field validation rules.
AISIX Cloud
To create a conditional policy in the dashboard:
- Open the environment and select Rate limits.
- Select New policy and enter a Name.
- Under Match conditions, add condition rows. Add condition groups when the policy needs any-of logic or nested combinations. No conditions means the policy matches every request.
- Under Quota buckets, keep Shared quota or choose Split by dimension and select the dimensions.
- Fill at least one field in the Limits grid and select Create policy.
The policy list renders conditions as chips, with groups in parentheses and explicit AND/OR relationships. Editing a policy can change its conditions, buckets, and limits. Changing the set of bucket dimensions changes the counter keys; editing only the conditions or limits does not reset existing counters.
For automation, create a policy through the Admin API. The following example gives every member of one team an independent one-request-per-minute bucket:
curl -sS -X POST "${AISIX_CP}/environments/${ENV_ID}/rate_limits" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
--data-binary @- <<EOF
{
"name": "team-member-default",
"conditions": [
{ "dimension": "team", "operator": "in", "value": ["${TEAM_ID}"] }
],
"group_by": ["member"],
"limits": { "rpm": 1 }
}
EOF
A successful request returns 201 Created, and the policy projects to attached gateways. Conditional policies have no one-per-subject constraint, so you can stack as many as the scenario requires. See the AISIX Cloud Admin API Reference for list, get, update, and delete operations.
Open-Source AISIX Gateway
Declare conditional policies in the rate_limit_policies collection. Condition values on api_key and model take the target entry's display_name. AISIX resolves those names when it loads the resources file, and an unknown name is a load error.
api_keys:
- display_name: chat-app
key_env: CALLER_API_KEY
allowed_models:
- gpt-4o-prod
team_id: team-blue
user_id: user-alice
rate_limit_policies:
- name: team-member-default
conditions:
- dimension: team
operator: in
value: [team-blue]
group_by: [member]
limits:
rpm: 1
❶ team_id and user_id bind the caller API key to a team and member. The team condition matches the key's team_id; the member bucket keys on its user_id.
❷ Conditions on team and member match these bound values verbatim.
❸ group_by: [member] gives every bound member an independent one-request-per-minute bucket.
Make the caller credential available to the validation command:
export CALLER_API_KEY="${AISIX_API_KEY}"
Validate the complete resources file and reload the gateway.
Verify the Policy
Configure and test one policy at a time. The runnable policy examples use one request per minute, so you can use the request sequence in Verify Rate Limiting with a caller and model that match the policy.
When a conditional policy rejects a request on an OpenAI-compatible endpoint, the 429 body identifies the policy:
{
"error": {
"message": "request limit exceeded (requests) (policy 'team-member-default')",
"type": "rate_limit_exceeded",
"policy": {
"id": "<policy-id>",
"name": "team-member-default"
}
}
}
If a request matches several policies or direct limits, every matching layer must have capacity. The policy named in an OpenAI-compatible response is the layer that rejected that request. Anthropic-compatible errors retain the strict Anthropic envelope and do not include the policy object.
Manage Classic Single-Scope Policies
A classic policy targets one subject. Each classic scope has a direct conditional equivalent:
| Classic Scope | Matching Traffic | Conditional Equivalent |
|---|---|---|
| API key | Requests authenticated with the selected caller API key | conditions: api_key in {K} |
| Model | Requests resolved to the selected model | conditions: model in {M} |
| Team | Requests whose caller API key carries the selected team binding | conditions: team in {T} |
| Member | Requests whose caller API key carries the selected member binding | conditions: member in {U} |
| Each member in a team | Requests carrying both the selected team and a member binding | conditions: team in {T} with group_by: [member] |
Classic policies use scope and scope_ref with a window of second, minute, or hour. They require max_requests, max_tokens, or both. Token caps apply only to minute windows. AISIX Cloud rejects a token cap on another window. For the open-source AISIX gateway, the resources schema accepts it, but the runtime does not enforce the cap.
In AISIX Cloud, an environment can contain only one classic policy for each scope and target. The scope and target cannot change after creation.
Export the ID of the caller API key that the classic policy should limit:
export API_KEY_ID="YOUR_CALLER_API_KEY_ID"
Then create a one-request-per-minute policy for that caller API key:
curl -sS -X POST "${AISIX_CP}/environments/${ENV_ID}/rate_limits" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
--data-binary @- <<EOF
{
"name": "rate-limited-app-policy",
"scope": "api_key",
"scope_ref": "${API_KEY_ID}",
"window": "minute",
"max_requests": 1
}
EOF
The dashboard marks classic policies with a Legacy badge and displays their conditional equivalents. Its one-click convert action recreates a classic policy in conditional form. Conversion restarts the counters and cannot be undone. Without converting, you can edit a classic policy's name, window, caps, and suspension schedules.
Suspend a Policy on a Schedule
A policy of either form can carry recurring suspension windows in schedules. AISIX skips the policy while any window matches and resumes enforcing it when the window closes.
Scheduling has the following behavior:
- A window uses
days_of_weekfor weekly recurrence ordatesfor explicit calendar dates. These fields are mutually exclusive. start_timeandend_timeuseHH:MMin the window's IANAtimezone.24:00is valid only forend_timeand means the end of the day.- An
end_timebeforestart_timecrosses midnight and belongs to its start day. For example, Friday22:00to09:00covers Friday night through Saturday morning. - Multiple windows form a union; the policy is suspended while any window matches.
- Suspension does not reset counters. A bucket retains its counts when a window opens and closes.
- Budgets, authentication, and guardrails remain active because only the rate-limit policy is suspended.
The following schedule suspends a policy on weekends and holidays and on weeknights from 22:00 to 09:00. Sunday is included in the overnight window so Monday morning before 09:00 is covered:
{
"schedules": [
{
"timezone": "Asia/Shanghai",
"days_of_week": ["mon", "tue", "wed", "thu", "fri", "sun"],
"start_time": "22:00",
"end_time": "09:00"
},
{
"timezone": "Asia/Shanghai",
"days_of_week": ["sat", "sun"],
"start_time": "00:00",
"end_time": "24:00"
},
{
"timezone": "Asia/Shanghai",
"dates": ["2026-10-01", "2026-10-02", "2026-10-03"],
"start_time": "00:00",
"end_time": "24:00"
}
]
}
AISIX Cloud
In the dashboard, open the policy's create or edit form. Under Suspension windows, select the timezone, choose weekly days or explicit dates, and set the time range or All day. The policy list summarizes the windows and shows a Suspended now badge while one is active.
For automation, export the ID of the policy you want to update:
export POLICY_ID="YOUR_RATE_LIMIT_POLICY_ID"
Then PATCH the policy with the complete replacement list. Send null or an empty list to remove every window:
curl -sS -X PATCH "${AISIX_CP}/environments/${ENV_ID}/rate_limits/${POLICY_ID}" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
--data-binary @- <<EOF
{
"schedules": [
{
"timezone": "Asia/Shanghai",
"days_of_week": ["sat", "sun"],
"start_time": "00:00",
"end_time": "24:00"
}
]
}
EOF
Gateway versions released before schedules existed reject a policy row that carries the field and stop enforcing that policy. Upgrade every attached gateway before adding schedules. Policies without schedules are unaffected.
Open-Source AISIX Gateway
Add the same windows to a policy in resources.yaml. The following example adds the weekend window to the conditional policy configured earlier:
rate_limit_policies:
- name: team-member-default
conditions:
- dimension: team
operator: in
value: [team-blue]
group_by: [member]
limits:
rpm: 1
schedules:
- timezone: Asia/Shanghai
days_of_week: [sat, sun]
start_time: "00:00"
end_time: "24:00"
Validate the complete resources file and reload the gateway. See the Resources File Reference for every schedule field and validation rule.
Troubleshooting
When a policy does not behave as expected, first confirm which request attributes it can evaluate and which other limit layers still apply.
| Symptom | What to Check |
|---|---|
| A team or member policy does not apply. | Confirm that the caller API key carries the matching team or member binding. A policy grouped by member skips a request whose caller API key has no member binding. |
| An API key or model condition does not match. | In AISIX Cloud, use the resource ID. In the open-source resources file, use the entry's display_name, then validate and reload the complete file. |
Requests still return 429 while a policy is suspended. | Check for direct caller or model limits and other matching policies. A schedule suspends only the policy that carries it. |
| A schedule added through AISIX Cloud does not take effect. | Confirm that the policy change reached the gateway and that every attached gateway supports schedules. |
Next Steps
Review API Key and Model Rate Limits for resource-specific limits, counter storage, and shared enforcement behavior. Use Metrics to monitor rate-limit rejections.