Guide
Protecting AI features from abuse and surprise bills
An AI feature is a meter that runs on your credit card. If strangers can reach it, they will use it, and some will use it a lot. People write scripts that look for open AI endpoints and resell or burn the access. A single leaked key or an endpoint with no limits can turn into hundreds or thousands of dollars before you notice.
The fixes below are listed in the order we'd do them. The first three stop most of the damage.
1. Call the AI provider from your server, never the browser
If your frontend talks to api.openai.com, api.anthropic.com or the Gemini API directly, your key is in the browser and anyone can copy it. No rate limit you write in the frontend will help, because an attacker will just use the key from their own script.
The browser should call your own endpoint, such as a Supabase Edge Function, a Vercel or Netlify function, a Cloudflare Worker or your API server. That endpoint holds the key as a server secret and makes the call. Our guide on checking for leaked keys shows how to confirm the key is gone from your bundle. If it was ever there, rotate it.
2. Require a logged-in user on every AI endpoint
Moving the call to a server isn't enough if the server endpoint is open to anyone. Your function should check the session token and reject requests without a valid user. With Supabase Edge Functions, the built-in JWT check (on by default unless you deployed with --no-verify-jwt) also accepts the public anon key, so it doesn't prove anyone is logged in. Call supabase.auth.getUser() with the request's token and reject the request if no user comes back.
If you want logged-out visitors to try the feature, give them a very small, separate allowance and protect it with a challenge like Cloudflare Turnstile.
3. Set spend limits at the provider
This is your safety net for when everything else fails. Do it today, it takes five minutes:
- Use a separate key per app and per environment. One key for production, one for development. If a key leaks, you rotate one thing and the damage is contained.
- Set a monthly budget or usage limit in each provider's billing settings. OpenAI and Anthropic both let you set limits and organize keys into projects or workspaces with their own limits. Read what yours does when the limit is hit. Some limits block requests, some only send an email.
- Know that Google Cloud budgets are alerts, not caps. A Cloud Billing budget for Gemini on Vertex or the Gemini API will email you, but it won't stop spending on its own. Set quota limits on the API in the Cloud console as well.
- Consider a gateway with per-key limits. Services like OpenRouter let you set a credit limit on each key. Cloudflare AI Gateway can add rate limiting and caching in front of several providers.
- Use prepaid credits without auto-recharge where the provider supports it. A hard stop at $50 is better than an open-ended bill.
- Turn on usage alerts at 50% and 90% of what you expect to spend.
4. Rate limit per user and per IP
Spend caps protect your wallet. Rate limits protect your other users, because one abuser hitting the cap would shut the feature off for everyone.
Use two layers:
- Short-term rate limit: for example, 10 requests per minute per user and a lower number per IP for anonymous traffic. Upstash Ratelimit, Cloudflare rate limiting rules and most API frameworks have this built in.
- Daily or monthly quota: store each user's usage in your database (requests or, better, tokens used) and refuse requests once they pass their plan's allowance. The provider's response includes token counts in the
usagefield, so record those after each call.
Check the quota on the server before calling the model, not after.
5. Cap the size of every request
Most AI cost comes from tokens, so limit them:
- Limit input length. Reject or trim user messages over a sensible size, like a few thousand characters for a chat box. Also limit how many past messages you resend as history.
- Set
max_tokens(or the equivalent output limit) on every call. Without it, output length is up to the model. - Pick the model on the server. If the client sends
"model": "...", an attacker can switch your cheap model for the most expensive one. Hard-code the model or map a small set of allowed options. - Don't let the client send the system prompt. If the browser builds the full prompt, your endpoint is a free general-purpose AI proxy. The server should build the prompt and only insert the user's input.
6. Put limits on agents and loops
Agents that call tools in a loop can run up costs quickly, even without an attacker. A bug or a confusing input can make the model call tools over and over.
- Set a maximum number of steps or tool calls per task, for example 10 or 20.
- Set a time limit and a token budget per task, and stop when either is reached.
- Limit image generation, web browsing and other costly tools separately, since they're often priced per call.
7. Watch for free-tier abuse
If new accounts get free credits, people will create many accounts. Helpful steps:
- Require email verification before free credits work.
- Add a challenge (Turnstile, hCaptcha) at sign-up.
- Block common disposable email domains if abuse becomes a pattern.
- Keep free allowances small and give more after a payment or a phone check.
- Grant paid credits only from a verified payment webhook, never from a client-side success page.
8. Monitor, and have a kill switch
Log each AI request with the user ID, model, token counts and time. A simple daily query for "top 10 users by tokens" will show abuse long before the invoice does.
Add a kill switch: an environment variable or database flag that makes your AI endpoint return a friendly "temporarily unavailable" message. If you see a spike at 2am, you can stop it in one click instead of rewriting code in a panic.
How to test this on your own app
- Log out and call your AI endpoint directly with curl or from the browser console. It should return 401.
- Log in as a test user and send requests in a quick loop (for example 30 in a row from the console). You should start getting 429 responses.
- Edit a request in DevTools to change the model name, add a huge message, or send your own system prompt. The server should reject or ignore each change.
- Set your test user's quota to almost zero in the database and confirm the next request is refused.
- Check the provider dashboard and confirm the test traffic shows up under the key you expect, and that your limits are set.
If your AI feature can also take actions, like sending emails or reading files, cost isn't the only risk. Read our prompt injection basics next.
FAQ
Do Google Cloud budgets stop spending on Gemini?
No. Cloud Billing budgets send alerts but do not cap spending by themselves. Set API quotas as well, and keep Gemini keys on your server.
Is a frontend rate limit enough?
No. If the key or endpoint is reachable, an attacker will call it from their own script and skip your frontend entirely. Limits must be enforced on the server.
What should I do first?
Move AI calls to your server, require a logged-in user, and set a spend limit at the provider. Those three steps stop most of the damage.