Setting daily and monthly spend limits on an API key
One compromised or misconfigured key shouldn't be able to burn through your whole balance in an hour. Here is how to cap that from the dashboard.
4 min · · Updated · API keys
A single account can hold several API keys — one per project, per server, per contractor. Without a cap, any one of them can technically spend the entire account balance. A per-key limit is a way to decide in advance exactly how much a given key can spend per day and per month.
Where it's configured
The limit isn't set globally on the account — it's set on the individual key's page in the dashboard: https://zukko.pro/app/api-keys. Each key can independently have:
- a daily spend limit in dollars;
- a monthly spend limit in dollars;
- a list of models allowed for that specific key (a separate but adjacent setting on the same page).
Keys are created in the same section, and worth repeating: the key value is shown once, at creation — save it immediately, limits can be changed later, but the key itself cannot be recovered.
How this differs from other 429s
There's no general request-rate limit on the gateway. A 429 from Zukko itself comes with exactly two codes, and both mean a given key's budget ran out:
{
"error": {
"message": "Daily spend limit reached for this API key",
"code": "key_daily_limit_reached"
}
}{
"error": {
"message": "Monthly spend limit reached for this API key",
"code": "key_monthly_limit_reached"
}
}Any other 429 (without these codes) comes from the model provider — it's their rate limiting, unrelated to money, and it usually clears in seconds to minutes (slow down, and requests go through again). key_daily_limit_reached holds until 00:00 UTC, key_monthly_limit_reached until the next month starts. If the code in your logs is one of these two, retrying early is pointless — nothing speeds up the reset.
Why this matters in practice
A key for a contractor or intern. Handing access to someone whose code you don't fully trust yet, it's reasonable to set a daily cap of $5–10 up front, rather than figuring out later why the account balance hit zero overnight.
A key embedded in a frontend or mobile client. If a key could theoretically leak through client decompilation or a debug log, a limit bounds the potential damage to the time until the next reset, instead of the entire account balance.
Splitting budget across projects on one account. If three separate services run under one account, giving each its own key with its own monthly limit surfaces one service's overspend before the shared balance runs dry.
A key for CI/tests. Test runs that hit the model on every push occasionally loop unexpectedly (a bug in retry logic, say). A daily limit turns a potential "drained the whole balance overnight because of a bug" into "hit $10 and tests started failing with a clear error."
Handling it in code
import requests
def call_with_limit_check(payload, key):
resp = requests.post(
"https://api.zukko.pro/v1/chat/completions",
headers={"Authorization": f"Bearer {key}"},
json=payload,
)
if resp.status_code == 429:
data = resp.json()
code = data.get("error", {}).get("code")
if code == "key_daily_limit_reached":
raise RuntimeError("Key's daily limit reached, wait until 00:00 UTC or switch keys")
if code == "key_monthly_limit_reached":
raise RuntimeError("Key's monthly limit reached")
raise RuntimeError("429 from the model provider, worth waiting and retrying")
resp.raise_for_status()
return resp.json()Branching on the error code rather than the single 429 status matters: the right reaction to a provider-side 429 is to wait and retry, while the right reaction to a key limit is to stop and investigate, or switch to a backup key with its own budget — not to retry in a loop hoping the limit lifts early.
A backup key beats raising the limit on the fly
If a service hits its daily limit mid-day, raising the existing key's limit in the dashboard is an option, but for production it's more practical to keep a second key pre-provisioned with a separate, higher limit for exactly this case, rather than editing settings mid-incident. Keep an eye on the X-Fetch-Balance-Cents header on every regular (non-streaming) response too — it's the whole account's remaining balance after the charge (in microdollars, despite the name), not the specific key's remaining limit, but a sudden drop is still a useful signal for alerts.
Bottom line
A key limit isn't protection against draining the account balance altogether — it's a way to decide in advance how much one specific key can spend before manual intervention is needed. It's set on the key's page in the dashboard, it triggers as a 429 with code key_daily_limit_reached or key_monthly_limit_reached, and your code should handle those codes separately from a provider-side 429.
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
- 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.
- xAI Grok API: Connecting Grok 4.7, Price and the 256K ContextHow to call grok-4.7 through an OpenAI-compatible API, what to do about the 256K context limit and what typical requests cost. Plus a comparison with Grok 4.6.
- This organization has been disabled in the Claude API: What to DoAn Anthropic key suddenly answers "organization has been disabled". What the error is, what to check, and how to move the SDK and Claude Code to another provider.