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:

bash
{
  "error": {
    "message": "Daily spend limit reached for this API key",
    "code": "key_daily_limit_reached"
  }
}
bash
{
  "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

bash
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.

Get API keyDocumentation

api.zukko.pro · fetch_*

Daily and monthly spend limits per API key · Zukko