Methodology
AI chat: keys, what’s sent, and the limits
This is the trust contract behind AI chat. There is no AI built into this site — the chat page is a bring-your-own-key (BYOK) client: it calls the AI provider you choose with your key, directly from your browser. There is no server in the middle; there isn’t one to put there.
Your key: where it lives
- In memory, by default. Paste a key and it lives only in the page’s memory. Close the tab and it’s gone — it never touches disk.
- Optional encrypted storage. If you choose to save a key, it is encrypted with a key derived from your passphrase — PBKDF2 with 210,000 iterations and SHA-256, a random 16-byte salt, AES-GCM 256-bit with a random 12-byte IV — and only the encrypted blob is stored. The passphrase is never stored; a wrong passphrase fails closed with an error, and plaintext keys are never written to disk, logs, or URLs.
- Deletion purges both. Deleting a key removes it from memory and deletes any encrypted copy, with a confirmation step.
- Keys travel only in request headers (
Authorization: Bearer,x-api-key, orx-goog-api-keydepending on the provider) — never in URLs, never in logs.
What is sent when you press Send — and what is never sent
- On Send, the provider receives: your messages (the conversation you see on screen), the model id, and the generation parameters (maximum output tokens, streaming on/off). That’s the entire payload.
- Never sent: nothing leaves your browser until you press Send — typing, drafting, and switching models transmit nothing. Your API key is sent as a header to the provider only; it is not embedded in any URL. Nothing is sent to because there is nowhere to send it to.
- Model listing: the “refresh models” button makes one GET to the provider’s model-list endpoint (with your key as a header) so you can pick from real model ids.
- Responses stream in as server-sent events, chunk by chunk, so you see the answer as it’s generated. Cancel stops the stream; a hung request can’t wedge the page because cancellation doesn’t rely on the network layer honoring the abort.
Provider access notes — honest, not promotional
Your browser can only call providers that allow it (CORS). The page states each provider’s status plainly:
| Provider | Direct browser access |
|---|---|
| OpenRouter | Supported. Explicitly allows direct browser requests. |
| Anthropic Claude | Supported via header. The app sends Anthropic’s required browser-access header automatically. |
| DeepSeek | Unverified. Press Test connection first — if the browser blocks it, use a relay. |
| Google Gemini | Unverified. Key-header requests are reported to work; verify with Test connection. The key goes in a header, never the URL. |
| OpenAI | Blocked. OpenAI’s API sends no CORS headers, so browsers refuse the call. Use a custom endpoint pointed at your own local relay, or another provider. |
| Custom endpoint | Unverified. Your own OpenAI-compatible relay. KNOWN LIMITATION The page’s Content Security Policy currently allowlists only the built-in provider domains, so a custom domain is blocked by the browser until a future update. |
The CSP itself is a deliberate allowlist: connect-src permits 'self' plus the five built-in provider origins, and nothing else. No wildcards.
Cost estimates
- Before sending, the page shows an estimate of the request’s cost: prompt tokens ≈ 4 characters per token, multiplied by the provider’s published per-token price, plus the full output-token budget at the output price. It assumes you use the whole output budget — the worst case, not a promise.
- Prices are approximate public list prices (USD per million tokens) captured 2026-11-04; providers change them. Every figure is labeled an estimate and the page links the provider’s pricing page.
- Unknown models return “unknown” instead of an invented number — the page never guesses a price.
Timeouts, retries, errors
- Timeout: 60 seconds. A slower provider aborts the request with a plain-language message instead of hanging.
- Retries: up to 2 automatic retries, only on rate-limit (HTTP 429), honoring the provider’s
Retry-Afterheader (capped at 120 s; otherwise an increasing backoff). Other errors are shown immediately, never retried. - Error codes: invalid key (401), rate limited (429), model not found (404), rejected request (400), CORS/network blocked (plain guidance, key not exposed), timeout, and cancelled — each with a plain-language explanation and a next step.
Where conversations are kept
Chat history is saved in your browser’s IndexedDB (conversations store), inside Study Space storage — local to your device, included in backups, and never sent anywhere except to the provider when you press Send. Deleting a conversation removes it from your device.