How to · Building

Don't Call OpenAI From Your Frontend: the Proxy Pattern Every AI App Needs

By Ghiles Asmani, founder of Weckr · Published August 15, 2026

Short version: an API key that reaches a browser or mobile app is public the moment it ships, visible in the network tab, extractable from the bundle, hunted by bots. The fix is a thin backend proxy: frontend calls your endpoint, your endpoint authenticates the user and calls the provider with the server side key. Twenty lines of code. And that proxy is more than a security fix: it is the one place every AI request passes with the user attached, which makes it exactly where limits, abuse control, and per user cost tracking belong.

Why the frontend key always leaks

There is no way to hide a secret in code you send to the client. Environment variables prefixed for the browser are bundled in. Obfuscation delays extraction by minutes. Once out, a key is used from anywhere by anyone: scrapers watch public sites and app stores for exposed keys and monetize them fast, and the spend lands on your card. Same story for OpenAI, Anthropic, Gemini, and every metered API. If a key has ever shipped to a client, or been committed to a repository, treat it as leaked: revoke, reissue, and move the call server side.

The proxy, minimally

// app/api/ai/summarize/route.ts (Next.js)
import OpenAI from 'openai';
import { Weckr } from '@/lib/weckr';           // module level singletons

const openai = new OpenAI();                    // key from server env only
const wk = new Weckr({ apiKey: process.env.WECKR_API_KEY!, plans: { free: 0, pro: 29 } });

export async function POST(req: Request) {
  const session = await auth(req);              // 1. who is calling
  if (!session) return new Response('unauthorized', { status: 401 });

  const { text } = await req.json();            // 2. validate input, never raw passthrough

  const result = await wk.chat(openai, {        // 3. the provider call, tracked
    model: 'gpt-5.4-mini',
    messages: [{ role: 'user', content: prompt(text) }],
    userId: session.user.id,
    feature: 'summarize',
    plan: session.user.plan,
  });

  return Response.json({ summary: result.choices[0].message.content });
}

Three jobs in one endpoint: authentication, input validation (accept your feature’s parameters, never a raw passthrough of arbitrary prompts, or you have built a free LLM proxy for the internet), and the tracked provider call. Latency cost of the hop: tens of milliseconds against responses measured in seconds.

The proxy is your control point, use it

  • Per user limits: this is the only place a request and a user identity coexist before money is spent, which is where per user rate limits on your AI endpoints and per user spending caps have to live.
  • Cost attribution: tagging the call with user and feature here is what makes AI cost per user exist at all. In the snippet above, the wk.chatwrapper does it as a side effect: cost recomputed server side, margin against the user’s plan, runaway detection included.
  • Abuse shape control: capping input length and max_tokens here bounds the worst case cost of any single request.

The architecture questions that follow, one key for thousands of users, which limits at which grain, are the subject of multi user AI app architecture.

FAQ

Can I call the OpenAI API directly from my frontend?

No. Any key shipped to a browser or mobile app is public: it is visible in the network tab, extractable from the bundle, and will be scraped and abused, often within hours for popular apps. Bots actively hunt exposed keys. Every provider key belongs on a server you control, with the frontend calling your backend and your backend calling the provider.

What is the API key proxy pattern?

A thin backend endpoint that receives the frontend’s request, checks that the user is authenticated and allowed, makes the provider call with your server side key, and returns the result. Ten to thirty lines in most frameworks. It is not just security theater: the proxy is also the only place where per user limits, abuse control, and cost attribution can exist.

My key leaked. What do I do right now?

Revoke it in the provider dashboard immediately and issue a new one, then check the usage dashboard for the damage window. Set a spend limit on the account if the provider offers one. Then fix the root cause: rotate to a backend proxy so no future key can ship to a client, and scan your repository history, since keys committed to git stay exposed even after deletion.

Does a proxy add meaningful latency to LLM calls?

Not meaningfully. One extra hop through your backend adds tens of milliseconds against model responses measured in seconds, and streaming passes through cleanly. The latency argument for calling providers from the client does not survive contact with the numbers, and the security argument against it is decisive.

What should the proxy do besides hide the key?

Everything per user, because it is the one chokepoint every AI request passes through with the user identity attached. Authenticate the user, apply your rate and spending limits, tag the call with the user id and feature for cost attribution, and log the usage tokens from the response. Weckr wraps this whole layer in two lines: wrap the client, pass the user id, get cost, margin, and caps per user.

Keep reading

You built the chokepoint. Get the visibility free.

Since every AI call now passes through your server with a user attached, per user cost tracking is two lines away instead of a project. Weckr wraps the client you already have: cost, margin, and caps per user, free for 50,000 requests a month. See it working on the live demo, or follow the integration docs.

See the dashboard with real data, no signup needed.

Try the demo →