Architecture
BYOK API uses an iframe bridge pattern to enable secure cross-domain AI access. This page explains how the pieces fit together.
The problem
Web apps that want to use AI need API keys. Today there are three bad options:
- Backend proxy — app developer holds your keys on their server. Trust issue, cost markup, vendor lock-in.
- Client-side keys — paste your key into the app. Any JS on the page can steal it. XSS = game over.
- OAuth-style delegation — a central server issues tokens. Adds a middleman, requires infrastructure, keys still live server-side.
The solution: an AI key wallet
BYOK API works like MetaMask — your API keys live in your browser, in a wallet on a separate origin:
┌─────────────────────┐ postMessage/RPC ┌─────────────────────┐
│ Consumer App │ ◄──────────────────────► │ Bridge (iframe) │
│ (any-app.com) │ │ (byokapi.com) │
│ │ │ │
│ ByokClient │ │ API Keys (IndexedDB)│
│ TransportModel │ │ Grant Records │
│ AI SDK v6 │ │ Provider Routing │
└─────────────────────┘ └─────────┬───────────┘
│
HTTPS API calls
│
┌────────────┼────────────┐
│ │ │
OpenAI Anthropic OpenRouterKey properties
- Cross-origin isolation — the wallet (bridge) runs on a different origin, so the app cannot read its IndexedDB or localStorage. Same security model as MetaMask's extension isolation.
- postMessage RPC — communication uses kkrpc, a typed bidirectional RPC library over
postMessage— likewindow.ethereumbut for AI calls. - Consent popup — grant requests open a popup on the wallet origin where the user approves capabilities — like MetaMask's transaction approval dialog.
- No backend required — the entire flow happens in the browser; API calls go directly from the wallet to providers. No auth server, no middleman.
Communication flow
- Consumer app creates a
ByokClientand callsconnect() - Client creates a hidden
<iframe>pointing to the bridge's/bridgepage IframeParentIO(kkrpc) is initialized before settingiframe.srcto avoid missing the init signal- The bridge's iframe page creates an
IframeChildIOand exposes theBridgeAPI - Client calls
handshake()to register and check for existing grants - Client calls
requestGrant()which triggers a consent popup on the bridge origin - Once granted, the client gets
TransportLanguageModelinstances that proxy AI calls through the bridge
kkrpc details
The RPC layer has an important constraint: callbacks must be top-level function arguments, not nested in objects.
// Correct — callback is a top-level arg
api.streamLanguage(params, onChunk)
// Wrong — kkrpc can't serialize nested callbacks
api.streamLanguage({ ...params, onChunk })This is because kkrpc serializes top-level function args as __callback__<id> placeholders that get wired up on the other side.
Provider routing
The bridge supports multiple AI providers:
- OpenAI — direct API calls (language, image, TTS, STT)
- Anthropic — proxied through the bridge's Next.js API route (CORS workaround)
- OpenRouter — OpenAI-compatible API for hundreds of models
- WASM/WebLLM — local models running entirely in the browser
The bridge reads the model ID, determines the appropriate provider, and routes the request accordingly.