Shipping an LLM provider key to a browser or mobile client feels like a shortcut until the key shows up in a public GitHub repo or a user intercepts the traffic. The tradeoff between client-side vs server-side api key handling determines who controls the credential, who pays for tokens, and how resilient your app is when a model provider degrades. This article breaks down both patterns across the dimensions that actually matter in production.
Defining the two patterns
Client-side key handling
In this model, the API key is embedded in code that runs on the user’s device. A React app might call OpenAI directly:
// danger: VITE_OPENAI_KEY is bundled into client JS
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${import.meta.env.VITE_OPENAI_KEY}`,
},
body: JSON.stringify({ model: "gpt-4o", messages: [{ role: "user", content: "hi" }] }),
});
The key is recoverable from the network tab or the minified bundle. Providers explicitly forbid this in their terms for production.
Server-side key handling
The key lives in an environment variable on a backend you control. The frontend talks to your endpoint; your endpoint attaches the key.
# FastAPI proxy snippet
import os
from fastapi import FastAPI, Request
import httpx
app = FastAPI()
PROVIDER_KEY = os.environ["PROVIDER_KEY"]
@app.post("/chat")
async def chat(req: Request):
payload = await req.json()
async with httpx.AsyncClient() as client:
r = await client.post(
"https://api.openai.com/v1/chat/completions",
headers={"Authorization": f"Bearer {PROVIDER_KEY}"},
json=payload,
)
return r.json()
You can swap the provider URL for an OpenAI-compatible gateway without touching the client.
Dimensions of comparison
Capabilities
Client-side gives you direct access to provider features with zero middleware. You can stream responses straight to the UI. But you cannot enforce server-side rate limits, audit logs, or per-user quotas. Server-side lets you inject routing logic, cache responses, and apply fallback when a provider is throttled.
Price and cost model
With client-side handling, the bill goes straight to the provider from the key owner’s account. There is no intermediary margin, but abuse is unbounded—any leaked key runs up your tab. Server-side centralizes spend. You can meter per tenant and absorb infrastructure cost. A gateway such as n4n.ai provides per-token usage metering across 240+ models behind one OpenAI-compatible endpoint, which simplifies reconciliation if you already route through it.
Latency and throughput
Client-side saves one network hop: the device talks directly to the provider. On a good connection this trims a single network round-trip, typically tens of milliseconds. Server-side adds a proxy hop but enables connection pooling and regional colocation. More importantly, server-side can honor fallback directives—if the primary provider is rate-limited, the gateway can retry another without the client noticing.
Ergonomics
For a weekend prototype, client-side is unbeatable: set an env var, ship. Rotation means rebuilding the client. Server-side requires deploying a service, managing secrets, and handling CORS. However, once you have a backend, adding new models or providers is a config change, not a client release.
Ecosystem
Most provider SDKs are designed for Node or Python servers. Browser builds often need shims and expose fewer features (no async batch, limited retry). Server-side uses the full SDK surface. Gateways that are OpenAI-compatible let you keep the same client code while swapping the base URL.
Limits
Client-side is constrained by browser CORS policies and provider terms that prohibit key exposure. You also can’t set IP allowlists. Server-side is limited by your backend’s scalability—if your proxy is a single Lambda, you’ll hit concurrency caps.
Head-to-head table
| Dimension | Client-side | Server-side |
|---|---|---|
| Capabilities | Direct provider calls, no server logic | Proxy, rate limits, logging, fallback |
| Cost model | Direct provider billing, abuse risk | Centralized metering, infra overhead |
| Latency | One less hop, lower baseline | Extra hop, but connection reuse |
| Ergonomics | Zero backend, painful rotation | More setup, flexible iteration |
| Ecosystem | Partial SDK support, CORS issues | Full SDK, gateway compatible |
| Limits | Provider ToS, no IP lock | Backend scaling ceiling |
Security reality
The core issue with client-side vs server-side api key handling is trust boundary. A key in the client is public. Anyone with the bundle can extract it and run up spend or pull data accessible to that key. Server-side keeps the secret in a process boundary you control. Even if you trust users, a compromised CDN or browser extension can exfiltrate it.
Rotate keys by revoking at the provider; with client-side you must ship an update and hope users refresh. Server-side rotation is instantaneous.
When client-side is acceptable
Only for local-only tools or personal scripts where the key owner is the sole user. Never in a distributed binary or web app.
Server-side proxy pattern with a gateway
A practical server-side setup points the SDK at a gateway that fronts multiple models:
from openai import OpenAI
import os
client = OpenAI(
base_url="https://api.n4n.ai/v1", # OpenAI-compatible, 240+ models
api_key=os.environ["N4N_API_KEY"],
)
# Automatic fallback and cache-control forwarding handled by gateway
resp = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[{"role": "user", "content": "Summarize this."}],
)
The key stays server-side; the client calls your /chat endpoint. You get provider abstraction without leaking credentials.
Which to choose
Public web or mobile app
Use server-side. Exposing keys in a shipped artifact is a breach waiting to happen. Proxy through a backend that injects the key.
Internal admin tool behind SSO
Server-side still preferred, but you can relax if the network is isolated. Keep the key in a secret manager.
Local CLI or personal notebook
Client-side is fine. The key never leaves your machine. Use an env file with 0600 perms.
Rapid prototype for a demo
Client-side gets you moving, but set a low usage limit on the provider key and rotate immediately after.
Multi-tenant SaaS
Server-side mandatory. Centralize billing, per-tenant metering, and route via a gateway that supports fallback so one provider outage doesn’t page you.
The decision around client-side vs server-side api key handling is not about convenience; it’s about who holds the blast radius. If a leaked key can cost you real money or data, it belongs on a server you control.