Skip to content
ai engineering questions

AI Engineering · 2 of 12junior~1 min

Why should you never call an LLM API directly from the browser with an API key?

#security#api

Keep the key on the server

Anything shipped to the browser is public. An API key in client code, in an env variable exposed to the bundle or in a request header can be read from DevTools in seconds - and the key is a direct line to your bill.

What goes wrong

  • Key theft - someone copies it and runs their own workloads on your account.
  • No control over usage - you cannot rate-limit per user, block abusers or cap spending.
  • No control over the prompt - the client decides the system prompt, the model and max_tokens, so anyone can turn your "support bot" into a free general-purpose model.
  • Leaked env vars - in Next.js anything prefixed with NEXT_PUBLIC_ is inlined into the client bundle.

The right shape: a thin proxy

ts
// app/api/chat/route.ts - runs on the server, the key never leaves it export async function POST(req: Request) { const user = await getUser(req); // auth first if (!user) return new Response('Unauthorized', { status: 401 }); await rateLimit(user.id); // per-user limits const { messages } = await req.json(); const upstream = await fetch(LLM_API_URL, { method: 'POST', headers: { Authorization: `Bearer ${process.env.LLM_API_KEY}` }, body: JSON.stringify({ model: MODEL, // chosen by the server system: SYSTEM_PROMPT, // not by the client max_tokens: 1024, messages: validate(messages), stream: true, }), }); return new Response(upstream.body, { headers: { 'Content-Type': 'text/event-stream' } }); }

The browser talks only to /api/chat. The server owns the key, the model choice, the system prompt, limits and logging.

The exception is ephemeral, scoped tokens that some providers issue for realtime/voice APIs: the server mints a short-lived token and the browser uses it. Your long-lived key still never leaves the server.

updated

did you know it?

related questions