browser-side-key-hygiene · EN · 2026-10-23

Calling the Aggregator From a Browser Tab Without Exposing the Key

A practical pattern for calling an OpenAI-compatible aggregator from browser code without shipping the API key to the client: keep the key on a thin relay endpoint, use the standard SDK shape against that relay, and apply it to experiments and internal tools.

The problem: browser code is public code

Anything a browser tab runs is visible to whoever opens dev tools. An aggregator key placed in front-end JavaScript, a build-time environment variable, or a public config file is effectively published. Since one key can reach many models (Claude, GPT, DeepSeek, Qwen, GLM, Kimi) and carries your USDC balance, leaking it is a billing and abuse problem, not just a cosmetic one.

The fix is not to hide the key better in the bundle. It is to make sure the key never reaches the browser at all.

The shape: browser → thin relay → aggregator

Insert one small server-side hop between your front end and the aggregator API. The relay:

  • Holds the aggregator key in server-side environment variables.
  • Exposes a narrow, OpenAI-compatible route (for example a chat completions endpoint) that your front end calls.
  • Adds authentication for your own users or tools, so the relay itself is not an open proxy.
  • Forwards only the fields you intend to allow.
  • Streams responses back if you use streaming.

Because the aggregator speaks the OpenAI-compatible SDK shape, your front end can keep using the familiar client interface — you only change the base URL and, if you want, swap the API key for your own session token.

Keeping the OpenAI-compatible client shape

On the client, initialise the SDK with a base URL pointing at your relay and a placeholder or session credential:

  • baseURL: your relay route, not the aggregator's public endpoint.
  • apiKey: a short-lived token your relay issues and validates, not the aggregator key.
  • Model name: passed through as usual, so experiments can switch between models without code changes.

On the relay, construct a separate client using the real aggregator key and the aggregator's base URL, then forward the request. Keep request and response shapes close to the standard so your front end stays portable.

A minimal relay checklist

  • Secret storage: aggregator key only in server environment variables or a secret manager. Never in client bundles or public repos.
  • Route surface: expose only the endpoints you need. Do not build a generic pass-through that forwards arbitrary paths and headers.
  • Auth: require a session token, cookie, or internal network identity. Rate-limit per caller.
  • Allowlists: restrict model names and parameter ranges if the relay serves untrusted users.
  • Timeouts and limits: cap prompt size and response length; set sensible upstream timeouts.
  • Logging: log request metadata for debugging, but avoid persisting full prompts unless you have a clear reason.
  • CORS: allow only your own origins.

Practical uses

This pattern fits front-end experiments, demos, and internal tools where you want fast iteration across several models. The relay is small enough to maintain as a single function or route, and it keeps your USDC balance tied to a credential that only your server holds.

If you later move to a backend service or a queue-driven job, the same relay can serve as the single egress point, so key rotation and model policy live in one place.

Cost and contribution notes

Billing runs through your aggregator account: you top up with USDC on Base, with no KYC, and pay official model price × 1.3. If you contribute keys, credits are official price × 1.1 (× 1.2 for premium), paid in USDC. Because the relay centralises calls, you get one place to watch spend across models rather than scattered client-side keys you cannot audit.