---
title: "Budgets and limits"
subtitle: "Cap spend, requests, and tokens per API key and per customer, before any provider is called."
---

The gateway checks every request against your limits before it calls a provider. A request over a hard cap is refused with an error you can act on, and costs nothing. Use limits as a safety net: give each app and environment its own API key with its own caps, and put a budget on each customer you serve.

| Limit | Scope | What it caps | Over it |
|---|---|---|---|
| [`spending_limit`](#key-limits) | API key | Spend per month, in USD | `402` `plan_permission_denied` |
| [`max_usage`](#key-limits) | API key | Requests over the key's lifetime | `429` `key_usage_limit_exceeded` |
| [`rate_limit`](#key-limits) | API key | Requests per minute | `429` `rate_limit_exceeded` |
| [`tpm_rate_limit`](#key-limits) | API key | Tokens per minute, input plus output | `429` `rate_limit_exceeded`, with `Retry-After` |
| [Limit policies](#limit-policies) | API key and other scopes | Cost, requests, or tokens per window, or lifetime | `429` `key_usage_limit_exceeded` at the hard cap |
| [Customer budget](#customer-budgets) | One `customer_identifier` | Spend per period | `402` |
| [Customer rate limit](#customer-budgets) | One `customer_identifier` | Chat requests per minute | `429` `rate_limit_exceeded` |
| Plan limits | Organization | Requests and tokens per minute on your plan | `429` `rate_limit_exceeded` |

Request and token rate limits clear in seconds and are covered on [Rate limits](/docs/ai-gateway/spend/rate-limits). This page covers the caps that hold until a window resets.

## Key limits

Open **Gateway** › **API keys** and click the key. Each limit shows its usage, what's left, and when it resets.

<img src={"/docs/images/limits/api-key-limits-light.png"} className="block dark:hidden" alt="An API key's limits: a $500 lifetime cost limit with an alert at $400, recurring limits for cost per day and requests per minute with usage bars and reset countdowns, expiry set to Never, and limit alerts sent by email to alex@acme.dev" />
<img src={"/docs/images/limits/api-key-limits-dark.png"} className="hidden dark:block" alt="An API key's limits: a $500 lifetime cost limit with an alert at $400, recurring limits for cost per day and requests per minute with usage bars and reset countdowns, expiry set to Never, and limit alerts sent by email to alex@acme.dev" />

The fields on the key itself:

| Field | Type | Description |
|---|---|---|
| `spending_limit` | number | Spend per month, in USD. At or over it, requests get `402` `plan_permission_denied` with the message `Organization key spending limit exceeded.` |
| `max_usage` | integer | Requests over the key's lifetime. `-1` is unlimited, `0` allows none. |
| `rate_limit` | number | Requests per minute. |
| `tpm_rate_limit` | number | Tokens per minute. Each scope (key, organization, plan) is its own bucket, so the tightest one wins. |

An API key can't raise its own caps: changes to these fields are refused when you authenticate with an API key, so make them from the dashboard. To make a key stop working on a date, set **Expiry** under **Expiry controls**.

## Limit policies

A limit policy caps a metric (`cost` in USD, `request_count`, or `token_count`) over a window (`minute`, `hour`, `day`, `week`, `month`, or lifetime). Each policy has a hard cap that blocks and optional warning thresholds that only alert. Usage counts up within the window and resets when it ends.

<Steps>
<Step title="Set a lifetime limit">
On the key, under **Lifetime limit**, turn on **Cost**. Click the amount next to **Block at** to set the cap, and click **+** next to **Alert at** to add warning thresholds. A lifetime limit never resets and starts from what the key has already spent.
</Step>
<Step title="Add recurring limits">
Next to **Recurring limits**, click **+**. Choose **Cost**, **Requests**, or **Tokens** and a period, set **Block at** and, if you want, **Alert at**, then click **Save** on the row. Stack rows for a daily and a monthly cap.

<img src={"/docs/images/limits/new-recurring-limit-light.png"} className="block dark:hidden" alt="A new recurring limit row: For Cost per day, Alert at $40, Block at $50, with a Save button" />
<img src={"/docs/images/limits/new-recurring-limit-dark.png"} className="hidden dark:block" alt="A new recurring limit row: For Cost per day, Alert at $40, Block at $50, with a Save button" />
</Step>
<Step title="Turn on alerts">
Under **Notifications**, turn on **Limit alerts**, then choose **Email** or **Slack** and pick an address or channel.
</Step>
<Step title="Save">
Click **Save** in the top bar, review the changes, and click **Confirm**.
</Step>
</Steps>

To create a key and its caps in one call, use an API key with the **Admin** permission:

```bash
curl https://api.respan.ai/api/temporary-keys/ \
  -H "Authorization: Bearer $RESPAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Contractor key",
    "limit_policies": [{
      "metric": "cost",
      "period": "day",
      "max_value": 50,
      "warning_threshold": 40
    }]
  }'
```

To change caps later, or to meter other scopes, use the [Limit policies API](/docs/apis/limit-policies/create-limit-policy).

### Soft and hard limits

- **Soft (Alert at):** the request goes through. Respan sends a limit alert by email or Slack and a `limit_policy_soft_triggered` [webhook](/docs/documentation/admin/webhooks) event. Each threshold alerts once per period.
- **Hard (Block at):** the request is refused with `429` `key_usage_limit_exceeded` before any provider is called, so retries and fallbacks don't run. A `limit_policy_hard_triggered` event is sent. Requests go through again when the window resets, or when you raise or turn off the cap.

Cost and tokens are counted when a call ends, so the call that crosses a cap completes and the ones after it are refused. A **Requests** cap refuses the request that would reach it, so **Block at** 100 allows 99 per period.

```json
{
  "error": {
    "message": "Spending limit reached",
    "type": "rate_limit_exceeded",
    "param": null,
    "code": "key_usage_limit_exceeded"
  }
}
```

`message` is the limit's description: `Spending limit reached` for a lifetime limit set in the dashboard, `limit policy hard cap` for a recurring one.

## Customer budgets

Customer limits apply to requests tagged with a `customer_identifier`. See [Customers](/docs/ai-gateway/customers/customers) to tag your requests.

- **Budget:** go to **Observability** › **Users & identifiers**, click the customer, and under **Budgets** click the pencil next to **Period budget**. Or set `period_budget` and `budget_duration` (`daily`, `weekly`, `monthly`, or `yearly`) with [Update user](/docs/apis/users/update-user). Once the customer's spend in the period passes it, their requests get `402` (`Customer budget limit exceeded. Current usage: ..., Budget: ...`) until the next period, and Respan sends a `customer_budget_limit_reached` event. Budget fields sent inside a request's `customer_params` are ignored.
- **Rate limit:** set **Customer rate limit (requests/min)** for every customer in **Gateway** › **Routing config** › **Limits**, or send `customer_params.rate_limit` on a request to override it for that customer. It applies to Chat Completions requests.

<img src={"/docs/images/limits/customer-limits-light.png"} className="block dark:hidden" alt="Customer limits: Customer monthly budget set to 25 and Customer rate limit (requests/min) set to 60" />
<img src={"/docs/images/limits/customer-limits-dark.png"} className="hidden dark:block" alt="Customer limits: Customer monthly budget set to 25 and Customer rate limit (requests/min) set to 60" />

```json
{
  "model": "gpt-5.5",
  "messages": [{"role": "user", "content": "Hello!"}],
  "customer_params": {
    "customer_identifier": "user_8842",
    "rate_limit": 30
  }
}
```

A realtime session counts against the customer's budget as its holds grow, and is closed with `1008` if it passes the budget.

## Check current usage

| What | Where |
|---|---|
| A key's limits | **Gateway** › **API keys** › your key: usage, what's left, and the reset time for each limit |
| A limit policy | [Get a limit policy](/docs/apis/limit-policies/get-limit-policy): `current_state.current_value` for the current window, with `interval_start` and `interval_end` |
| A customer's budget | [Get user](/docs/apis/users/get-user): `total_period_usage` against `period_budget`, with `period_start` and `period_end` |
| One call | [Get a span](/docs/apis/spans/get-span) by its `X-Respan-Log-Id`: `limit_info` holds the credit and budget state the call was checked against |

```bash
curl https://api.respan.ai/api/users/user_8842/ \
  -H "Authorization: Bearer $RESPAN_API_KEY"
```

## Admission order

Before any provider is called, a request passes these checks in order. The first one that fails decides the answer.

<Steps>
<Step title="Request and token rate limits">
The key's, organization's, and plan's requests and tokens per minute. Refused with `429` `rate_limit_exceeded`. No log is written.
</Step>
<Step title="Payment deadline">
An overdue bill refuses paid requests with `402` `billing_payment_required`. No log is written.
</Step>
<Step title="Limit policies and customer budget">
A hard cap refuses with `429` `key_usage_limit_exceeded`. An exhausted customer budget refuses with `402`. Both write a failed log at $0.
</Step>
<Step title="Key usage cap">
The key's lifetime `max_usage`. Refused with `429` `key_usage_limit_exceeded`.
</Step>
<Step title="Plan permission">
A key at or over its monthly `spending_limit`, or an organization without a subscription, is refused with `402` `plan_permission_denied`.
</Step>
<Step title="Customer rate limit">
Chat Completions only. Refused with `429` `rate_limit_exceeded`.
</Step>
</Steps>

After admission, a call on Respan's key is checked for credits. See [When credits run out](/docs/ai-gateway/spend/credits-vs-byok#when-credits-run-out). Images, embeddings, speech, and transcription pass the same checks except tokens per minute and the customer rate limit. Realtime sessions are checked once, at the handshake.

## Next steps

- [Rate limits](/docs/ai-gateway/spend/rate-limits): handle `429` and retry well.
- [Webhooks](/docs/documentation/admin/webhooks): get budget and limit events.
- [Usage accounting](/docs/ai-gateway/spend/usage-accounting): see what each call cost.
