HomeGuidesStop strangers from running up your OpenAI bill

Guide

Stop strangers from running up your OpenAI bill

Your AI API route is a free proxy to your OpenAI or Anthropic account until you lock it down. Auth, rate limits, input caps, and spend limits.

3 min read

Independent notes from VibeLocked. Not a pentest, and not affiliated with the tools named below.

Moving the OpenAI key out of the frontend is step one. Step two is the one AI generated apps usually skip: the server route that holds the key will happily answer anyone on the internet. Bots find these routes, and you pay for every token.

First, make sure the key really is off the client. Open your live app, view its JavaScript files in the browser dev tools Sources tab, and search for sk-. If the code is in a public repo, run the VibeLocked scanner on it too. If it finds an sk- key, stop here and follow the first hour after an API key leaks.

What abuse looks like

A typical generated route:

// app/api/chat/route.ts  (do not ship this)
export async function POST(req: Request) {
  const { messages, model } = await req.json();
  const res = await openai.chat.completions.create({ model, messages });
  return Response.json(res);
}

Problems: no login check, no rate limit, the caller chooses the model, the caller sends unlimited text, and there is no cap on the output. Anyone can script it.

1. Require a signed in user

Check the session on the server before calling the model. With Supabase:

const supabase = createServerClient(/* cookies */);
const { data: { user } } = await supabase.auth.getUser();
if (!user) return new Response('Unauthorized', { status: 401 });

If you want a free demo for anonymous visitors, give it a separate, much smaller limit and add a bot check such as Cloudflare Turnstile.

2. Rate limit per user and per IP

Use a store that is shared across server instances. In serverless functions, an in memory counter resets on every cold start. Upstash Ratelimit with Redis is a common choice on Vercel:

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '1 m'),
});

const { success } = await ratelimit.limit(`chat:${user.id}`);
if (!success) return new Response('Too many requests', { status: 429 });

Add a daily quota per user too (for example in a usage table in your database) so one account cannot burn through your budget slowly.

3. Decide the request on the server

Do not let the client pick the model, the system prompt, or the token budget.

const { message } = await req.json();
if (typeof message !== 'string' || message.length > 4000) {
  return new Response('Bad request', { status: 400 });
}

const res = await openai.chat.completions.create({
  model: 'gpt-4o-mini',           // fixed on the server
  max_completion_tokens: 500,     // cap the output
  messages: [
    { role: 'system', content: SYSTEM_PROMPT },
    { role: 'user', content: message },
  ],
});

Cap how many past messages you resend in a chat, since long histories multiply token cost on every turn.

4. Set limits at the provider

  • Create a separate project and API key for each app in the OpenAI platform, so one leaked or abused key does not affect others and usage is easy to read.
  • Set a monthly spend limit with hard enforcement for that project and the organization. Alerts alone do not stop traffic.
  • Restrict the key's permissions to the endpoints you use, where the provider supports it.
  • Anthropic, Google, and other providers have similar workspace and spend settings. Check their consoles.

Prepaid credits with auto recharge turned off also limit the worst case.

5. Watch it

Log user id, token counts, and status for each call. Look at provider usage the day after launch. A sudden spike from one account or IP is the signal to tighten limits or revoke the key.

Checklist

  • No model key in the repo (scan it) or the browser bundle
  • Route requires a session, or a separate tiny limit for anonymous use
  • Shared rate limit per user and per IP, plus a daily quota
  • Model, system prompt, and max tokens fixed on the server
  • Input length and history length capped
  • One provider project and key per app, with budget and alerts
  • Usage logged and checked after launch

For server actions specifically, see Next.js server actions security. They are public endpoints too.

Sources

Questions

My OpenAI key is only on the server. Am I safe?

Not yet. If your API route forwards any prompt from any visitor, people can use your route as a free model endpoint. You need authentication, rate limits, and limits on what the route accepts.

Can I set a hard spending cap on OpenAI?

OpenAI lets you set monthly spend limits for an organization or project and turn on hard enforcement, which blocks requests once the limit is hit. Alerts alone do not stop traffic, and spend can slightly exceed the limit. Use a separate project and key per app, and treat limits as a backstop, not your only defense.

What is a reasonable rate limit for an AI feature?

Start with a few requests per minute per user and a daily cap per user that matches what a real user needs. Tighten it for anonymous visitors, or require sign in for the AI feature.