A leaked or looping API key keeps billing until something actually blocks it. This month's OpenCode Console breach showed how many of us assumed a provider "limit" would do that, and found out it didn't. Here's a quick check, straight from the providers' own docs.
OpenAI
-
A limit only blocks traffic if "Enforce a hard limit" is on. Spend alerts only notify. With enforcement on, requests get a
429withorganization_spend_limit_exceededorproject_spend_limit_exceeded. Enforcement isn't immediate, so spend can go slightly past the cap. (OpenAI spend limits) - Give each project that runs agents its own hard limit, so one runaway project can't eat the whole org budget.
- Use project-scoped keys with an expiry date, and rotate them. OpenAI also says never to ship a key to a browser or mobile app. (OpenAI key safety)
Anthropic
- You can't set a spend limit on the Default Workspace. Only the organization limit applies there. Create a separate workspace, give it a monthly limit, and move production keys into it. (Anthropic workspaces)
- Expect usage data to lag a little, so don't rely on a check that runs once a day.
OpenRouter
-
Set a per-key credit limit with a reset (
daily,weeklyormonthly).GET https://openrouter.ai/api/v1/keyshowslimit_remaining. (OpenRouter keys)
Every provider
- Keep keys out of frontend code and public repos, including git history.
- Give agents a max-steps, max-tokens or timeout guard. Retries in a loop are the most common surprise bill.
- Have something other than your laptop watching spend overnight.
A shortcut
Disclosure: I built KeyBark. Its free limit audit walks through the checks above as yes/no questions (runs in your browser, no sign-up, no key) and lists what to fix. KeyBark itself is a hosted watcher that emails you when spend crosses a threshold (free for one key), but you don't need it to do anything in this post.
Top comments (1)
For an application-side cap, I'd test two requests starting together when the remaining allowance can cover only one. Checking recorded spend before each call can let both through, even without a long reporting delay. Reserving an upper bound before dispatch and settling it from actual usage gives the cap a concurrency contract. Retries need to participate in that reservation too. An alert and a refusal are easy to distinguish in a single-request demo; parallel calls are where I'd check the refusal actually holds.