For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Per-key dollar or token budgets
Cap what one API key spends on LLM traffic in US dollars or in tokens, and limit which models it can reach.
Verified Code examples on this page have been automatically tested and verified.Cap what one API key spends on LLM traffic, in US dollars or in tokens.
About per-key budgets
A per-key budget is a budgets entry on an API key. It charges the realized cost or the token usage of each request that the key sends, and it rejects or records requests once the key passes its limit.
A USD budget is a true spend cap, which is the main reason to choose a per-key budget over a rate limit token budget. Agentgateway prices each request from a model cost catalog and charges the result, so the cap holds however the model mix changes.
A Tokens budget caps token usage instead, and needs no catalog.
For the charging model and the window alignment, see Budget and spend limits.
Before you begin
Install theagentgateway binary.Set a dollar budget
Step 1: Load a model cost catalog
A USD budget charges the realized cost of each request, and agentgateway computes that cost from a model cost catalog. Create a catalog that prices the model you route to. Rates are the price per 1M tokens, written as strings. The following example charges $10 per 1M input tokens and $30 per 1M output tokens.
Tip
To generate a catalog for real provider pricing instead of writing one by hand, use agctl catalog import. For the full catalog format and how to layer overrides, see Model costs.
cat <<'EOF' > catalog.json
{
"providers": {
"openai": {
"models": {
"gpt-5": { "rates": { "input": "10.0", "output": "30.0" } }
}
}
}
}
EOFStep 2: Configure budgets on your API keys
Create a configuration with a database, the catalog, API key authentication, and a budget on each key.
cat <<'EOF' > config.yaml
# yaml-language-server: $schema=https://agentgateway.dev/schema/config
config:
database:
url: sqlite://budgets.db
modelCatalog:
- file: ./catalog.json
llm:
policies:
apiKey:
mode: strict
keys:
- key: sk-team-a-abc123def456
metadata:
name: team-a
allowedModels:
- "gpt-5*"
budgets:
- name: daily-spend
limit:
unit: USD
amount: 0.008
window:
rolling: 24h
onBudgetExceeded: Block
- key: sk-team-b-xyz789uvw012
metadata:
name: team-b
budgets:
- name: daily-tokens
limit:
unit: Tokens
amount: 250
window:
rolling: 24h
onBudgetExceeded: Audit
models:
- name: "*"
provider: openAI
params:
apiKey: "$OPENAI_API_KEY"
EOFThe team-a key has a dollar budget that blocks requests when the budget is exhausted, and the team-b key has a token budget that only records the overage. Review the following table to understand this configuration. Both limits are small so that you can reach them in a few requests. Use realistic limits in production.
| Setting | Description |
|---|---|
config.database.url | Connection string for the database that holds budget counts. Agentgateway supports SQLite and PostgreSQL. A budget requires this exact field, and agentgateway refuses to start without it. Setting config.logging.database.url instead does not satisfy the requirement, because that field configures request logging only. For the other things that the database stores, see Configuration storage. |
config.modelCatalog | Catalog sources that price each request. A USD budget requires a catalog entry for every model that the key uses. A Tokens budget does not. |
metadata.name | Identifies the key in budget counts, logs, and the admin API. A key that has a budget requires this field. |
budgets | List of budgets that are charged independently. A key can have several budgets, such as an hourly dollar cap and a monthly dollar cap. |
budgets[].name | Names the budget within its key. The name must be unique among that key’s budgets. |
budgets[].limit.unit | USD to cap realized cost, or Tokens to cap token usage. |
budgets[].limit.amount | The maximum usage in the window. A Tokens amount must be a whole number. A USD amount takes up to nine decimal places. |
budgets[].window.rolling | Length of the fixed usage window, such as 1h, 24h, or 30d. Windows are aligned to the Unix epoch rather than to the key’s first request. |
budgets[].onBudgetExceeded | Block to reject requests with a 429 after the limit is passed, or Audit to record the overage and allow the request. |
allowedModels | Model name patterns that the key can reach. Omit the field to leave the key unconstrained. For more information, see Limit model access per key. |
Warning
A USD budget charges nothing unless the catalog prices the models that the key uses. Agentgateway does not report an error in this case. The budget stays at zero usage and never rejects a request. Before you rely on a USD budget, confirm that the access log records agw.ai.usage.cost.total for the traffic that the budget covers.
Step 3: Start agentgateway
agentgateway -f config.yamlStep 4: Verify model access
Send a request for a model that the key is allowed to use. Verify that the request succeeds. At the catalog rates from Step 1, this request costs about $0.003 of the key’s $0.008 budget.
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:4000/v1/chat/completions \ -H "Authorization: Bearer sk-team-a-abc123def456" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5", "max_tokens": 100, "messages": [{"role": "user", "content": "Hello!"}] }'Example output:
200Send a request for a model that the key is not allowed to use. Verify that agentgateway rejects the request with a
403status.curl -s http://localhost:4000/v1/chat/completions \ -H "Authorization: Bearer sk-team-a-abc123def456" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-5", "max_tokens": 100, "messages": [{"role": "user", "content": "Hello!"}] }'Example output:
{"error":{"message":"Model is not allowed for this API key","type":"invalid_request_error","code":"model_not_allowed"}}A rejected request never reaches a provider, so agentgateway does not charge it to the key’s budget.
Step 5: Verify the dollar budget
Send three more requests with the
team-akey. Each request costs about $0.003, and Step 4 already spent $0.003. The second request in this loop therefore takes the key past its $0.008 limit.for i in 1 2 3; do curl -s -o /dev/null -w "request $i: %{http_code}\n" http://localhost:4000/v1/chat/completions \ -H "Authorization: Bearer sk-team-a-abc123def456" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5", "max_tokens": 100, "messages": [{"role": "user", "content": "Hello!"}] }' doneThe second request completes and takes the total to $0.00903, so the third request is the first one that agentgateway rejects.
Example output:
request 1: 200 request 2: 200 request 3: 429Send one more request to see the error body.
curl -s http://localhost:4000/v1/chat/completions \ -H "Authorization: Bearer sk-team-a-abc123def456" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5", "max_tokens": 100, "messages": [{"role": "user", "content": "Hello!"}] }'Example output:
{"error":{"message":"Budget exceeded","type":"rate_limit_error","code":"budget_exceeded"}}Agentgateway also writes a log line for each rejected request. A
USDbudget reports its amounts in dollars.warn budget API key budget exceeded api_key="team-a" budget="daily-spend" used=0.00903 limit_unit="USD" limit_amount=0.008 exceeded=true
Step 6: Check budget usage
Query the admin API for the current usage of a key. To return every budget, omit the apiKeyName parameter.
curl -s "http://localhost:15000/api/budgets/status?apiKeyName=team-a" | jq .The used value is a dollar amount, and it is higher than the limit, because agentgateway charged the request that crossed the limit after it completed.
Example output:
{
"observedAt": 1787773624201,
"budgets": [
{
"apiKeyName": "team-a",
"name": "daily-spend",
"limit": {
"unit": "USD",
"amount": "0.008"
},
"usage": {
"used": "0.00903",
"remaining": "0",
"exceeded": true
},
"window": {
"start": 1787702400000,
"end": 1787788800000,
"durationMs": 86400000,
"expired": false
},
"onBudgetExceeded": "Block",
"updatedAt": 1787773623159
}
]
}You can also review and edit budgets in the built-in Admin UI, on the same LLM > Virtual API Keys page that lists your keys.
Step 7: Try a token budget
The team-b key that you configured in Step 2 caps tokens rather than dollars, and it uses the Audit action. An Audit budget records usage and logs the overage, but it never rejects a request. Use an Audit budget to size a limit before you enforce it, or to alert on a team that goes over its allocation without interrupting its work.
Send four requests with the
team-bkey. Each request uses 101 tokens, so four requests use more than the 250-token limit allows.for i in 1 2 3 4; do curl -s -o /dev/null -w "request $i: %{http_code}\n" http://localhost:4000/v1/chat/completions \ -H "Authorization: Bearer sk-team-b-xyz789uvw012" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5", "max_tokens": 100, "messages": [{"role": "user", "content": "Hello!"}] }' doneEvery request succeeds, even after the key passes its limit.
Example output:
request 1: 200 request 2: 200 request 3: 200 request 4: 200Check the gateway logs. Agentgateway logs each request that it receives while the budget is over its limit, so you can alert on the
budgettarget in your log pipeline. ATokensbudget reports its amounts as whole tokens.warn budget API key budget exceeded api_key="team-b" budget="daily-tokens" used=303 limit_unit="Tokens" limit_amount=250 exceeded=true
Limit model access per key
The allowedModels field limits which models an API key can reach. The field is independent of budgets, so it needs no database and no catalog, and you can use it on its own.
llm:
policies:
apiKey:
mode: strict
keys:
- key: sk-team-a-abc123def456
metadata:
name: team-a
allowedModels:
- "gpt-5*"
- claude-sonnet-5Each entry is an exact model name or a pattern with one * wildcard, such as a gpt-5* prefix or a *-mini suffix. Agentgateway rejects a request for any other model with a 403 response and the model_not_allowed code, before the request reaches a provider.
| Value | Effect |
|---|---|
| The field is omitted | The key can reach every model that the gateway serves. This is the default. |
| An empty list | The key can reach no models. Agentgateway rejects every LLM request from the key. |
["*"] | The key can reach every model. You cannot combine * with another pattern in the same list. |
The field also filters the model list that the key sees. A request to /v1/models returns only the models that the key is allowed to use. For example, a gateway names three models individually rather than with the "*" pattern that Step 2 uses.
llm:
models:
- name: gpt-5
# ...
- name: gpt-5-mini
# ...
- name: claude-sonnet-5
# ...A key with allowedModels: ["gpt-5*"] sees gpt-5 and gpt-5-mini. A key with no allowedModels sees all three. A key with an empty list sees none. Agentgateway returns a model that you configure as a pattern, such as "*", as that pattern rather than as expanded names.
Clean up
Stop agentgateway with
Ctrl+C.Remove the budget database, the catalog, and the configuration file.
rm -f budgets.db catalog.json config.yaml
What’s next
- Cap tokens with a bucket instead, with rate limit token budgets.
- Price your traffic with a model cost catalog.
- Review the API key policy fields in API key authentication.