To cap OpenAI API costs and avoid a runaway AI bill, you need a number on four separate things: how long a single answer can be, how many calls one person can make, how many times your code retries a call that failed, and a hard monthly spend limit set in your provider’s billing dashboard. The first three live in your code. The fourth is the backstop that catches the mistakes in the first three. A four-figure invoice is almost never one expensive call. It is a small cost, repeated more times than anyone counted, with nothing in the loop that says stop.
The one setting most apps leave out
Every major provider’s API has a setting that means “stop after this much output”. In OpenAI’s API the name depends on which endpoint you call: max_tokens on Chat Completions, max_output_tokens on the Responses API. On the reasoning models (the o-series and the GPT-5 family) Chat Completions does not just rename it — it rejects max_tokens outright and requires max_completion_tokens instead. A token is roughly three quarters of a word, so 1,000 tokens is about a page and a half.
If you leave the setting out, you have not asked for a short answer. You have said: write until you decide you are finished, or until you hit the model’s own ceiling. That ceiling is large. OpenAI’s current flagship models top out at 128,000 output tokens in a single reply. On the reasoning models the same setting also covers the hidden “thinking” the model does before it answers — text you never see and still pay for.
Here is the arithmetic, and treat these figures as illustrative rather than a quote for your app. At a flagship list price of about $30 per million output tokens, one answer that runs all the way to a 128,000-token ceiling costs roughly $3.84 in output alone. Most answers stop long before that. But a thousand of them is around $3,800, and a thousand requests is a quiet evening for anything that gets shared in a group chat.
So: put an explicit number on every call, and make it the smallest number the feature can live with. A chat reply rarely needs more than 1,000; a summary needs a few hundred. On a reasoning model, size it with the thinking included — a ceiling that only fits the visible answer can be spent entirely on reasoning and hand you back nothing. If you are using Anthropic’s Messages API, max_tokens is a required parameter, so you already have a ceiling — but check the number, because generated code often copies a large placeholder from an example.
Then a retry loop multiplies it
OpenAI’s official Python and Node libraries retry twice on their own before your code ever sees an error — not on every failure, but on the ones they treat as transient: connection errors, 409, 429 and 5xx. That is three attempts. If your code then wraps its own retry around that, you get three of three: nine calls for one user action. Add a background job that retries the whole job, and you have a number nobody in the building can name.
A call rejected on a rate limit generally costs nothing, because nothing was generated. The dangerous one is a timeout. Your side gave up waiting, but a client-side timeout does not necessarily stop the work on the provider’s side: the model may have finished generating and billed you anyway. Whether abandoning a request actually stops the meter varies by provider, so do not count on it. Then you retry and pay twice for one answer.
Set the retry count yourself instead of accepting the default. Two or three attempts total is plenty. Retry the errors that are worth retrying (a rate limit, a network blip) and give up immediately on the ones that will fail identically the second time.
A limit per person, and per person per day
Count calls in your own database, per account, per day. Before calling the model, read the count. If one account has already made 50 requests today, return a message saying they have hit the daily limit instead of calling the model. It is a small amount of code for what it buys, because it turns “infinity” into a number you can multiply.
The trap: per-person caps can still add up to no cap at all
This is the part that catches people who did everything else right. A per-user cap bounds each user. It does not bound your bill, because the number of users is not bounded either. Sign-up is free and takes seconds. Fifty calls per user per day might be a $2 ceiling per person, and it stays a $2 ceiling right up until your link gets posted somewhere and four thousand accounts appear overnight. Then the same cap you carefully calculated produces an $8,000 day.
It is worse for any endpoint that works without logging in, where “per user” means per browser and a script can be a million users.
The fix is a second counter that is not per user: a total for the whole app, per day, checked in the same place. When the day’s total is hit, the feature returns a polite message for everyone until midnight. You also want an off switch you can flip without deploying — an environment variable your code reads — so that when something is wrong at 2am you can stop the spending in thirty seconds rather than waiting on a build.
The backstop your code cannot break
Everything above is code, and code has bugs. So set a limit outside your code as well.
OpenAI added hard spend limits in July 2026. In your organization or project settings, enter a monthly amount and turn on “Enforce a hard limit”. Once tracked spend reaches it, affected requests fail with a 429 carrying organization_spend_limit_exceeded or project_spend_limit_exceeded, depending on which limit was hit. The important detail: spend alerts are a separate thing and they enforce nothing, they only notify you. Plenty of people believe they have a cap when what they have is a notification. Note also that enforcement is not instantaneous, so recorded spend can slightly exceed the number you set — set it below the amount that would actually hurt.
Anthropic has the equivalent: monthly spend limits per workspace, set in the Console. Either way, give each app its own project or workspace with its own key. Then the limit is scoped to the thing that might misbehave, and your usage dashboard tells you which project is burning money instead of showing you one undifferentiated line.
Before you launch
Check four things. Every model call has an explicit output ceiling. Retries are capped at a number you chose. There is a per-user daily count and a whole-app daily count. And there is a hard spend limit in the dashboard, not just an alert. The rest of the pre-launch list, including a common cause of these incidents — a key that ended up somewhere public — is in the launch checklist.
npx launchprep checks the code-side items on your machine, free and unlimited: it flags model calls that ship without an output ceiling, and calls with no retry or backoff. The two spend ceilings are harder to see from source alone — the free scan goes only as deep as a pattern can there. And nothing that reads your code can read your billing dashboard, so the hard limit is one you have to go and check yourself.
Questions people ask
If I set a hard spend limit, will my app just break? Yes, and that is the point. Calls start failing once you hit the number. So catch that error and show a real message (“this feature is briefly unavailable”) instead of a stack trace, and set an alert at a lower amount so you hear about it before the cap bites.
Doesn’t using a cheaper model solve this? It changes the multiplier, not the limit. A model twenty-five times cheaper turns a $4,000 night into a $160 night, which is better and still not a cap. Unbounded times a small number is still unbounded.
How would I know if this is already happening to me? Open your usage dashboard and look at daily spend for the last two weeks, broken down by project — and by key, if your provider breaks it out that far. You are looking for a day that does not match your traffic, or spend on a key you thought was unused. Do it once a day for your first week live; it is a few seconds of looking, and it is the cheapest way to catch this early.