Never call an LLM from the browser. A Worker holds the key, checks the user's budget and streams tokens back. Waiting costs nothing on Workers.
The problem
If the browser calls OpenRouter directly, your API key has to be in the app. Anyone can open DevTools, copy it and run up your bill, and you can't limit what each user does.
The fix
Browser ──(user's login token)──▶ Worker ──(your OpenRouter key)──▶ OpenRouter
◀──── tokens streamed back word by word ◀────────────────
The Worker:
- checks the user is logged in (a Cognito JWT works);
- checks that user's quota or budget ("50 messages a day", "$2 a month");
- adds your key on the server side, so it never reaches the browser;
- streams the answer back as it's generated;
- records token usage for billing or analytics.
Why a Worker
The proxy does about 2 ms of work, then waits 10–60 seconds while the model writes.
| 1M chats a month × 20 s | Monthly |
|---|---|
| Classic Lambda | $33.50 (128 MB) to $167 (512 MB) |
| Lambda Web Functions | ~$3.40 (not in Sydney yet) |
| Cloudflare Workers | ~$0 extra, inside the $5 plan |
On Lambda you pay memory × wall-clock time for the waiting, and response streaming through a function URL doesn't work inside a VPC. Workers bill CPU time only.
Add Cloudflare AI Gateway in front of OpenRouter for caching, per-client spend caps and fallback when a provider is down.