A client of mine once left an automated AI app running unsupervised for one day. The app generated websites end to end: every job triggered AI calls, the AI calls triggered more jobs, and nothing anywhere in the system capped how many could...
A client of mine once left an automated AI app running unsupervised for one day. The app generated websites end to end: every job triggered AI calls, the AI calls triggered more jobs, and nothing anywhere in the system capped how many could run at once, how much the app was allowed to spend, or how to make it stop.
One day later: an AI bill of over $1,000. The app worked exactly as built. Nobody had built the brakes.
I'm Alon Trifonov, founder of Zyntari, a small engineering studio in Israel. I take AI-generated prototypes and turn them into production software, and this story is the quiet failure I see most often. Not a hack, not a bug that crashes anything. Just an app doing its job with no limit on what that job costs.
The industry is waking up to the same problem. Gartner predicts over 40% of agentic AI projects will be canceled by the end of 2027, citing escalating costs and inadequate risk controls. And in September 2026 they added a newer, sharper warning: by 2030, 80% of organizations with public-facing AI will experience a "cost exhaustion attack", where someone deliberately drives up your AI usage to inflate your bill. So this is not only about your own code looping. Strangers can burn your budget on purpose, through the public features you shipped.
Here is how to build the brakes, in five layers, with code. Everything below is Python and Flask flavored, but the patterns work in any stack.
Layer 1: Set the hard limit at your provider (do this today)
This takes five minutes, requires zero code, and is the single highest-value step in this article.
OpenAI supports monthly spend limits at both the organization and project level. Go to your organization settings, open Limits, set a monthly spend limit, and this is the important part: turn on "Enforce a hard limit." Without that toggle, the limit only sends you alerts while requests keep flowing. With it, API calls start failing when you hit the number, which is exactly what you want at 3am. Anthropic offers the same idea for Claude, with organization and workspace limits in the Console.
Set the number to what you can actually afford to lose in a bad month. Not what you expect to spend. What you can afford.
One warning: provider limits are your last line of defense, not your only one. They reset monthly, they apply to everything under the project, and when they trip, your feature just dies. You want your own code to slow down and stop gracefully long before the provider pulls the plug. That is what the next layers are for.
Layer 2: Know what every call costs
You cannot cap what you do not measure. Most generated code treats an AI call as free: send prompt, get answer, move on. The fix is a small pricing table and a cost log on every single call.
MODEL_PRICES = {
"gpt-4o-mini": {"input": 15, "output": 60},
"gpt-4o": {"input": 250, "output": 1000},
"claude-sonnet-4-5": {"input": 300, "output": 1500},
}
# prices per 1M tokens, in USD cents.
# Check your provider's pricing page and keep this current.
def call_cost_cents(model: str, usage) -> int:
prices = MODEL_PRICES[model]
input_cost = (usage.prompt_tokens / 1_000_000) * prices["input"]
output_cost = (usage.completion_tokens / 1_000_000) * prices["output"]
return round(input_cost + output_cost)
Every API response already carries the token counts in response.usage. Wrap your AI calls so each one logs the model, the tokens, and the cost in cents to a table:
class AIUsage(db.Model):
id = db.Column(db.Integer, primary_key=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow, index=True)
model = db.Column(db.String(64))
feature = db.Column(db.String(64))
cost_cents = db.Column(db.Integer)
The feature column matters more than it looks. When the bill jumps, this is how you answer "which part of my app spent it" in one query instead of one week.
Layer 3: A budget check before every call
Now the actual brake: before any AI call, ask "are we allowed to spend this today?"
DAILY_BUDGET_CENTS = int(os.environ.get("AI_DAILY_BUDGET_CENTS", "2000")) # $20
class BudgetExceeded(Exception):
pass
def check_daily_budget():
today = datetime.utcnow().date()
spent = (
db.session.query(func.coalesce(func.sum(AIUsage.cost_cents), 0))
.filter(func.date(AIUsage.created_at) == today)
.scalar()
)
if spent >= DAILY_BUDGET_CENTS:
raise BudgetExceeded(f"Daily AI budget reached: {spent} cents")
Then wrap every AI call in one function that checks before and records after:
def guarded_ai_call(feature: str, model: str, messages: list):
check_daily_budget()
response = client.chat.completions.create(model=model, messages=messages)
cost = call_cost_cents(model, response.usage)
db.session.add(AIUsage(model=model, feature=feature, cost_cents=cost))
db.session.commit()
return response
In your routes, catch BudgetExceeded and return something graceful: a "this feature is paused until tomorrow" message beats a crashed page and beats a surprise invoice. Every AI call in the app goes through this one wrapper. No exceptions, no "just this once" direct calls in a controller somewhere.
Layer 4: Cap concurrency and retries
This is the layer that would have saved my client their $1,000 day. Their app had no limit on how many jobs could run at the same time, so one busy morning multiplied itself.
Two knobs handle most of it.
First, worker concurrency. If you run background jobs with Celery, the worker flag is the cap:
celery -A app.celery worker --concurrency=4
Four workers means at most four AI jobs burn money at the same moment. Set it from your budget, not from your CPU count.
Second, retry discipline. Retry logic is where quiet bills hide, because a retrying loop is an AI call inside a loop. If you use tenacity, cap it hard:
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(min=2, max=30))
def call_model(messages):
return guarded_ai_call(feature="site-builder", model="gpt-4o", messages=messages)
Three attempts with exponential backoff. Never retry_forever on anything that costs money per attempt. If it failed three times, the fourth try is not your biggest problem anymore.
Layer 5: A kill switch you can hit from your phone
Budgets slow the bleeding. Sometimes you need to stop it completely, fast, without a deploy. Add a switch that every AI feature respects:
def ai_enabled() -> bool:
return os.environ.get("AI_FEATURES_ENABLED", "true").lower() == "true"
# inside guarded_ai_call, before anything else:
if not ai_enabled():
raise RuntimeError("AI features are disabled")
Now stopping every AI feature in your product is one environment variable change on your host, which takes thirty seconds from the Render or Railway dashboard on your phone. Pair it with an alert before you need it: when daily spend crosses 80% of budget, send yourself a message. A Slack webhook or even a plain email works. The alert is what turns "one unsupervised day" into "one weird hour I caught at lunch."
The checklist
Run this against any app that calls an AI model:
Hard spend limit set at the provider, enforced, not just alerting
Every AI call logged with model, feature, and cost in cents
A daily budget check that refuses calls before they happen
Worker concurrency capped, retries capped at three
A kill switch you can flip without deploying
An alert at 80% of budget, so you hear about problems the same hour
None of this is exotic engineering. It is an afternoon of work. But it is also invisible, and invisible work is exactly what AI builders skip, because nobody types "and please add a spending limit" into a prompt box. The model builds what you ask for. The brakes are on you.
If you want the bigger picture around this, I wrote an honest guide to what building an app with AI actually involves in 2026, including where the demo ends and the real work starts. And if your app also needs its security holes closed, that checklist is in my previous article.
Alon Trifonov is the founder of Zyntari (zyntari.com), an independent engineering studio in Israel that turns prototypes and AI-generated apps into secure, production-grade software. He builds with Python, Flask, and React, with security and reliability as defaults rather than afterthoughts. LinkedIn: linkedin.com/in/alon-trifonov-546061234