How to cap API key spend so an agent doesn't drain your balance
An agent stuck in a loop can burn through a balance overnight. A separate key, limits, a model list and proper error handling put a ceiling on spend.
6 min · · API keys · агенты
An agent stuck in a loop or handed too big a task can burn through your entire balance overnight. You can cap API key spend in several layers: a separate key per agent, a daily and monthly ceiling, an allowed-models list, and code that reacts correctly to limit errors. Let's go through each layer and where to see actual spend.
A separate key for each agent
Limits in Zukko are set per key, not per account. So the first step is a separate key for each agent or pipeline, with a clear name: agent-review, nightly-labeling, support-bot.
What you get:
- each agent has its own ceiling, and an agent that hits its limit doesn't stop the others — other keys keep working;
- if a key leaks or an agent misbehaves, you can revoke it without touching other integrations;
- the settings show at a glance which agent can access which models.
The full key is shown once, at creation — store it in the agent's environment variables or secrets. Keys are created under API keys.
Daily and monthly limits
Each key can have a spending ceiling in dollars per day and per month. How it works, per the key limits documentation:
- The check happens before the request reaches the model, so a rejected request costs nothing.
- Daily spend resets at 00:00 UTC, monthly spend at the start of the month (UTC).
- An empty limit or 0 means no limit.
- Web search requests count toward the limits too.
The limits are soft
This is an important detail. Before a request, the gateway compares what has already been charged with the ceiling, and the cost of the request itself isn't known in advance. So the last request let through can exceed the ceiling, and several parallel requests started before the limit was reached will all go through. With five parallel agents on one key, the overrun can be noticeable — leave a margin or split them across keys.
How to pick the number
Estimate the cost of one agent run and multiply by runs per day. Example: an agent on claude-sonnet-5 uses 3M input tokens per run (2.7M cache reads, 0.15M cache writes, 0.15M fresh input) and 0.1M output tokens. At per-1M prices (input 0.2, output 1.013, cache read 0.025, cache write 0.25):
0.15 × 0.2 + 2.7 × 0.025 + 0.15 × 0.25 + 0.1 × 1.013 = 0.03 + 0.0675 + 0.0375 + 0.1013 ≈ $0.24 per run.
Five runs a day — 5 × 0.2363 ≈ $1.18. A reasonable daily limit is $2: a normal day fits, and a looping agent stops after about eight runs (2 / 0.2363 ≈ 8.5). Set the monthly limit below daily × 30 — it catches slow spend growth that the daily limit doesn't notice.
Caching matters in this estimate: for Claude, the cache discount comes with the Anthropic format (/v1/messages); in the OpenAI format all Claude input is billed at the input price, and the same run would cost 3 × 0.2 + 0.1 × 1.013 ≈ $0.70. Take your real numbers from the usage history after the first day. Prices in the estimate are as of October 6, 2026; current models and prices: GET https://api.zukko.pro/v1/models or the Models page in the dashboard.
allowed_models: only the models you need
The second ceiling is the list of models allowed for the key (allowed_models). If a classification agent only needs deepseek-v4.1-flash and glm-5.3-flash, allow just those. Then a config mistake that makes the agent call claude-fable-5-1 ($3.48 / $17.4 per 1M versus $0.013 / $0.038 for DeepSeek V4.1 Flash) ends in a 403 error, not a bill.
For a coding agent the list might be claude-sonnet-5 and claude-opus-5-5. More on setup is in the article on allowed_models.
Which errors an agent will get and what to do
| Status | Code | What happened | Retry? |
|---|---|---|---|
| 402 | insufficient_balance | balance below $0.01 | only after a top-up |
| 403 | model_not_allowed_for_key | model not in the key's list | no, fix the config |
| 429 | key_daily_limit_reached | key's daily limit used up | after 00:00 UTC or raise the limit |
| 429 | key_monthly_limit_reached | key's monthly limit used up | next month (UTC) or raise the limit |
| 429 | provider code | limit on the model provider's side | yes, with a pause |
Zukko has no rate limit of its own: a 429 from the gateway means only the key limits you set yourself. Any other 429 came from the model provider. So parse the error by the error.code field, not just the status.
This matters especially for agents: many clients retry any 429 by default. The official OpenAI Python SDK, for example, retries 429 and 5xx errors automatically. Retries against an exhausted limit are free but pointless: the agent will spin until midnight UTC instead of stopping and reporting the problem.
Handling it in code
A Python example: the SDK's automatic retries are off, decisions are made by error code, and each request's cost is read from the X-Zukko-Cost-Micros header (in microdollars, present on regular, non-streaming responses).
import os
import time
import openai
from openai import OpenAI
client = OpenAI(
base_url="https://api.zukko.pro/v1",
api_key=os.environ["ZUKKO_AGENT_KEY"],
max_retries=0,
)
STOP_CODES = {
"insufficient_balance",agent_is_done and next_messages are your agent's own logic: checking whether the task is finished and building the next messages (the model's reply, tool results). The task budget and MAX_STEPS are a third layer on top of key limits: they cap a single task, not the whole day. The full list of codes and retry rules is in the errors section of the documentation and in the article on error handling and a fallback model.
Where to see spend
- Dashboard, Usage section. Per-request spending history: time, model, tokens, cost. The first place to look after an agent's first day.
GET /v1/balance. The current account balance in cents. The balance is shared across all keys. An expensive request started with a small remainder can push the balance negative — then subsequent requests get 402 until you top up. Details in the balance docs.GET /v1/usage. Spending history via API, with pagination. Note: it includes all account charges, not just this key's.
curl -G https://api.zukko.pro/v1/usage \
-H "Authorization: Bearer $ZUKKO_AGENT_KEY" \
--data-urlencode "limit=50"Both requests are free. If you need spend for a specific agent, sum X-Zukko-Cost-Micros in its code, as in the example above.
FAQ
Am I charged for a request rejected by a limit?
No. Limits and the model list are checked before the request reaches the model, so a rejected request costs nothing.
Why did the agent spend slightly more than the limit?
The limits are soft: the last request let through can cross the ceiling, and parallel requests started before the limit was reached all go through. Leave a margin or spread parallel agents across keys.
When does the daily limit reset?
At 00:00 UTC, which is 03:00 Moscow time. The monthly one resets at the start of the calendar month, UTC.
What happens to other keys when one hits its limit?
They keep working: the limit applies to the key, not the balance. But the balance is shared by all keys, and if it runs out, all of them get 402.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- OpenCode Zen API key or your own provider: what to pick and how to payZen is a ready-made model catalog from the opencode team. A custom provider is your own key and your own endpoint. When to use which, and how to pay from Russia.
- opencode agents: setting up roles, models and toolsA custom agent in opencode is a role, a model and a set of permissions. A working config where a cheap model handles routine work and a stronger one edits code.
- OpenAI API Keys in the Open: Why You Should Not Use ThemWhere free keys on GitHub come from, why using them is dangerous for you and for the owner, and how to protect your own key from the same leak.
- GLM API Errors: Why the Request Fails and How to Fix ItA wrong model id, an overflowing 128K context, the key, the balance, limits: the typical causes of GLM API errors and a quick way to find yours.