# 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, provider, or cost attribution tag, 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 the AISIX Cloud Admin API or the open-source AISIX gateway, maintain classic policies, and schedule policy suspensions.

## Prerequisites[​](#prerequisites "Direct link to 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](https://docs.api7.ai/ai-gateway/traffic-controls/rate-limits.md).

* 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](https://docs.api7.ai/ai-gateway/cloud/teams.md) and a member.

* One of these configuration paths:

  <!-- -->

  * AISIX Enterprise or AISIX Cloud with an environment, an attached gateway, and a write-scoped [admin token](https://docs.api7.ai/ai-gateway/cloud/admin-tokens.md).
  * An open-source AISIX gateway with a declarative resources file that you can [validate and reload](https://docs.api7.ai/ai-gateway/deployment/configuration-propagation.md#reload-a-resources-file).

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 Enterprise or AISIX Cloud, also export the control-plane values and resource IDs used by the examples. Copy `TEAM_ID` from the [Teams](https://docs.api7.ai/ai-gateway/cloud/teams.md#create-a-team) page.

```
# AISIX_CP includes /api and has no trailing slash.

# The local AISIX Enterprise quickstart uses http://localhost:8080/api.

export AISIX_CP="YOUR_AISIX_CONTROL_PLANE_API_URL"

export AISIX_TOKEN="YOUR_ADMIN_TOKEN"

export ENV_ID="YOUR_ENVIRONMENT_ID"

export TEAM_ID="YOUR_TEAM_ID"
```

## Choose a Policy Form[​](#choose-a-policy-form "Direct link to Choose a Policy Form")

A policy uses one of two forms. In AISIX Enterprise and 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-field `limits` shape 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 `scope` and `scope_ref` and uses `window` with `max_requests` or `max_tokens`. Existing classic policies remain supported by the Admin API and resources file.

Use a conditional policy for new configurations. It can match the traffic selected by every classic scope and supports several policies for the same request.

## Configure a Conditional Policy[​](#configure-a-conditional-policy "Direct link to 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[​](#match-requests-with-conditions "Direct link to 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                                             | Matches the dispatched model, and — on a request routed through a model group, semantic router, or ensemble — also the group/router the caller addressed. So the group's own ID selects every request routed through it, and a member's ID selects that member whether it is called directly or reached through a group. `negate` excludes both. |
| `model_name` | Model alias                                          | Same pairing as `model`, on the display names; supports regular-expression matching across aliases.                                                                                                                                                                                                                                              |
| `provider`   | Provider catalog ID, such as `openai` or `anthropic` | Matched against the dispatched model's provider                                                                                                                                                                                                                                                                                                  |
| `tag:<key>`  | Tag value, such as `rnd` for `tag:department`        | Matched against the value the request resolves for that [cost attribution tag](https://docs.api7.ai/ai-gateway/cloud/cost-attribution-tags.md#how-a-request-resolves-its-tags). In AISIX Enterprise and AISIX Cloud, the key must be declared in the organization.                                                                               |

In AISIX Enterprise and 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](https://github.com/api7/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`, `provider`, and `tag:<key>` |
| `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[​](#split-counters-into-buckets "Direct link to Split Counters into Buckets")

`group_by` accepts `team`, `member`, `api_key`, `model`, `provider`, and `tag:<key>`. 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:

1. **Team pool**: match one team and leave `group_by` empty so the whole team shares the quota.
2. **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.
3. **Fair-share a model**: match one model and set `group_by: [team]` so each team receives an independent quota.
4. **Per-department quota**: set `group_by: ["tag:department"]` so every department value receives an independent quota, whichever JWT claims, caller API keys, teams, or members supply it. A request without a `department` value is not subject to the policy. See [Rate-Limit by Tag](https://docs.api7.ai/ai-gateway/cloud/cost-attribution-tags.md#rate-limit-by-tag).

### Set the Limits[​](#set-the-limits "Direct link to Set the Limits")

`limits` accepts `rps`, `rpm`, `rph`, `rpd`, `tpm`, `tpd`, and `concurrency`. At least one field is required. Each windowed limit counts in a fixed window aligned to the UTC clock, so a daily limit resets at 00:00 UTC. Token limits settle after the response or stream reports usage. Both behaviors are described in [Rate Limit Fields and Counter Behavior](https://docs.api7.ai/ai-gateway/traffic-controls/rate-limits.md#rate-limit-fields-and-counter-behavior).

Request-window accounting depends on the dimensions a policy references:

* A policy whose conditions and `group_by` do not use `model`, `model_name`, or `provider` reserves once at the request gate. A policy matching only `api_key`, `team`, `member`, or `tag:<key>` therefore consumes one request slot even when the request retries or fails over.
* A policy whose conditions or `group_by` use a model property reserves where the dispatched model is known. A direct-model request reserves once at the request gate. For a routing or semantic parent, AISIX evaluates the policy for each target attempt, including same-target retries and failovers. For an ensemble parent, it evaluates the policy for each panel or judge call. Each reservation consumes a request slot that stays consumed if the upstream call fails. With `group_by: [model]`, each dispatched direct model uses its own bucket.

`action` is reserved for future behaviors. `reject`, which returns HTTP `429`, is the only supported value and the default.

See the [Resources File Reference](https://docs.api7.ai/ai-gateway/reference/resources-file.md#rate-limit-policies) for the complete policy schema and field validation rules.

### AISIX Enterprise or AISIX Cloud[​](#aisix-enterprise-or-aisix-cloud "Direct link to AISIX Enterprise or AISIX Cloud")

To create a conditional policy in the dashboard:

1. Open the environment and select **Rate limits**.
2. Select **New policy** and enter a **Name**.
3. 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.
4. Under **Quota buckets**, keep **Shared quota** or choose **Split by dimension** and select the dimensions.
5. 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 Enterprise / Cloud Admin API](https://docs.api7.ai/ai-gateway/reference/cloud-admin-api/.md) for list, get, update, and delete operations.

### AISIX OSS[​](#aisix-oss "Direct link to AISIX OSS")

Replace `chat-app` in `api_keys`, then add `team-member-default` to `rate_limit_policies`. Create the policy collection if needed, and keep the other resources unchanged:

resources.yaml (conditional rate limit)

```
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
```

Conditions on `api_key` and `model` use the target entry's `display_name`. An unknown name is a load error.

❶ `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[​](#verify-the-policy "Direct link to 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](https://docs.api7.ai/ai-gateway/traffic-controls/rate-limits.md#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[​](#manage-classic-single-scope-policies "Direct link to Manage Classic Single-Scope Policies")

A classic policy targets one subject. Each classic scope has a conditional equivalent that matches the same traffic:

| Classic Scope         | Matching Traffic                                                  | Conditional Matching 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]` |

Matching equivalence does not always mean accounting equivalence. A classic model policy whose subject is a routing, semantic, or ensemble parent reserves once at the request gate. The conditional `model in {M}` form is evaluated for each target attempt, so one client request through that parent can consume more than one slot.

Classic policies use `scope` and `scope_ref` with a `window` of `second`, `minute`, `hour`, or `day`. They require `max_requests`, `max_tokens`, or both. Each window is aligned to the UTC clock and resets as a whole at its boundary; a `day` window resets at 00:00 UTC. Token caps apply to minute and day windows. A classic day-window token cap is the team daily quota that a conditional policy expresses with `tpd`. The AISIX Cloud Admin API rejects a token cap on a second or hour window. For the open-source AISIX gateway, the resources schema accepts that combination, but the runtime does not enforce the token cap.

In AISIX Enterprise and 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 matching equivalents. Its one-click **convert** action recreates a classic policy in conditional form. Conversion restarts the counters and cannot be undone. For a model-scoped routing, semantic, or ensemble parent, it can also change quota consumption as described above. Without converting, you can edit a classic policy's name, window, caps, and suspension schedules.

## Suspend a Policy on a Schedule[​](#suspend-a-policy-on-a-schedule "Direct link to 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_week` for weekly recurrence or `dates` for explicit calendar dates. These fields are mutually exclusive.
* `start_time` and `end_time` use `HH:MM` in the window's IANA `timezone`. `24:00` is valid only for `end_time` and means the end of the day.
* An `end_time` before `start_time` crosses midnight and belongs to its start day. For example, Friday `22:00` to `09:00` covers 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 Enterprise or AISIX Cloud[​](#aisix-enterprise-or-aisix-cloud-1 "Direct link to AISIX Enterprise or 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
```

note

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.

### AISIX OSS[​](#aisix-oss-1 "Direct link to AISIX OSS")

Add the same windows to a policy in `resources.yaml`. The following example adds the weekend window to the conditional policy configured earlier:

resources.yaml (scheduled rate limit)

```
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](https://docs.api7.ai/ai-gateway/reference/resources-file.md#rate-limit-policies) for every schedule field and validation rule.

## Troubleshooting[​](#troubleshooting "Direct link to 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.                                                                                                                     |
| A tag policy does not apply.                                             | Check the tags the request resolves from its JWT claims, caller API key, team, and member. A request without a value for the tag matches neither the condition nor its negation, and a policy grouped by the tag skips it. A gateway at 1.6.x or earlier drops a policy that uses `tag:`. |
| An API key or model condition does not match.                            | In AISIX Enterprise and 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 the AISIX Cloud Admin API does not take effect. | Confirm that the policy change reached the gateway and that every attached gateway supports schedules.                                                                                                                                                                                    |

## Next Steps[​](#next-steps "Direct link to Next Steps")

Review [API Key and Model Rate Limits](https://docs.api7.ai/ai-gateway/traffic-controls/rate-limits.md) for resource-specific limits, counter storage, and shared enforcement behavior. Use [Metrics](https://docs.api7.ai/ai-gateway/reference/metrics.md) to monitor rate-limit rejections.
