Your AI Feature's Bill Is a Bug Waiting to Happen: 6 Guardrails Before You Ship
You ship an AI feature on Friday. It works. People like it. On Monday you open the billing page and the number is ten times what you expected. Nobody attacked you. No server was hacked. One user pasted a book into the i
You ship an AI feature on Friday. It works. People like it.
On Monday you open the billing page and the number is ten times what you expected. Nobody attacked you. No server was hacked. One user pasted a book into the input box, another found a button that could be clicked forty times a minute, and a retry loop in your own code asked the same expensive question all weekend.
AI features are different from normal features in one way that matters here: every request has a variable, usage-based cost, and the user controls part of it.
How the Cost Actually Works
Most AI APIs charge by tokens, small chunks of text (roughly parts of words). You pay for tokens going in and tokens coming out, and the exact prices depend on your provider and model, so check their current pricing page.
Three things surprise people:
- Input is under the user's control. A longer message costs more.
- Chat history is sent again on every turn. Message 20 of a conversation carries messages 1 to 19 with it, so costs grow faster than the conversation does.
- Output can be long. Unless you limit it, the model decides how much to write, and you pay for it.
None of this is a problem at ten users. It is a problem when someone, deliberately or not, pushes on it.
The Starting Point
// app/api/chat/route.ts
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await model.generate({ messages });
return Response.json({ text: result.text });
}
(model is a placeholder for your provider's SDK. The guardrails are the same for all of them.)
This has no limit on input size, history length, output length, request rate or total spend. Every one of those is a way to lose money.
Guardrail 1: Cap the Input
Reject oversized input before it reaches the model:
const MAX_CHARS = 4000;
const last = messages.at(-1)?.content ?? '';
if (typeof last !== 'string' || last.length > MAX_CHARS) {
return Response.json({ error: 'Message too long' }, { status: 413 });
}
Choose a limit that fits your real use case, not the biggest the model accepts.
Guardrail 2: Cap the Output
Almost every API lets you set a maximum for generated tokens. Set it:
const result = await model.generate({
messages: trimmed,
maxOutputTokens: 500, // parameter name varies by provider
});
A feature that summarizes a ticket does not need 4,000 tokens of reply. This is the cheapest guardrail there is.
Guardrail 3: Limit the History You Resend
Do not send the whole conversation forever. Keep the last few turns, or summarize the older ones:
const trimmed = messages.slice(-10); // last 10 messages only
For longer chats, replace old turns with a short summary. Either way, the cost of turn 50 stops being fifty times the cost of turn 1.
Guardrail 4: Rate-Limit Per User
Cost control starts with knowing who is asking. Require login for anything that spends money, then limit by user, not just by IP:
const key = `ai:${session.userId}`;
const used = await redis.incr(key);
if (used === 1) await redis.expire(key, 60);
if (used > 10) {
return Response.json({ error: 'Too many requests' }, { status: 429 });
}
This allows 10 requests a minute per user. The numbers are examples. Any shared store with atomic counters works, and you should pick limits that match real usage.
Guardrail 5: Set a Daily Budget Per User
Rate limits stop bursts. A budget stops slow drains. Track how many tokens each user has consumed today, and stop when they pass a limit:
const usage = await getTodayUsage(session.userId);
if (usage.tokens > DAILY_TOKEN_LIMIT) {
return Response.json({ error: 'Daily limit reached' }, { status: 429 });
}
const result = await model.generate({ messages: trimmed, maxOutputTokens: 500 });
await addUsage(session.userId, result.usage.totalTokens);
Most providers return usage numbers with each response, so you do not have to estimate. Also set an overall spending limit in your provider's dashboard if it offers one, as a last line of defense.
Guardrail 6: Do Not Pay Twice for the Same Answer
If many users ask the same question, such as "summarize this public page", cache the result:
const cacheKey = `summary:${hash(pageText)}`;
const cached = await redis.get(cacheKey);
if (cached) return Response.json({ text: cached });
const result = await model.generate({ /* ... */ });
await redis.set(cacheKey, result.text, { ex: 86400 });
Only cache answers that are the same for everyone. Never cache a response that includes one user's private data and then serve it to another.
Two More Habits That Save Money
Use the smallest model that does the job. Classification, extraction and short rewrites often work on a smaller, cheaper model. Save the largest one for tasks that need it, and test to be sure.
Be careful with retries. A retry loop that fires on every error multiplies your cost, and an error caused by a bad prompt will fail the same way every time. Limit retries to two or three, with a delay between them, and do not retry errors that are your fault.
Watch It Like Any Other Cost
Log, per request: user ID, model, input tokens, output tokens and feature name. A small table or a dashboard is enough. You are looking for two things: which feature costs the most, and which users cost the most. The first tells you where to optimize. The second tells you where someone is pushing on your limits.
Set an alert for daily spend, so a surprise reaches you as a message on Tuesday morning, not as an invoice at the end of the month.
The Rule
Treat every AI call as spending money that a user can influence. Cap the input, cap the output, trim the history, limit each user, set a budget and cache what is safe to cache. Do it before launch, not after the first bill.
Quick audit: open your AI endpoint and check six things: input limit, output limit, history trimming, per-user rate limit, daily budget and whether it needs login at all. Every "no" is a way to lose money.
I build SaaS products with Next.js, and limits like these sit in the route handler, next to authentication. If you want a typed starting point to build on, you can browse my templates on pixelanas.com.
Get the templates: https://pixelanas.gumroad.com
Have you ever been surprised by an AI bill? Tell me in the comments what caused it.
Anas, full-stack Next.js developer building SaaS products and premium templates. X: @ASheikh69751
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.