Bring your own API key: your translation engine, step by step

One configuration that works end to end — the exact endpoint, the exact model, and the measurements behind the choice.

Translation needs an engine, and an engine needs a key of your own. That is the whole setup: this guide walks through one configuration end to end — translation good enough to read closely, plus the listening and speaking review that needs speech and transcription. The point of bringing your own keys is that your text goes from your browser to that provider directly; there is no server of ours in between.

What this guide sets up

JobEngineModelWhy
TranslationDeepSeekdeepseek-v4-flashPaid, cheap, fast enough for whole pages
Read-aloudBuilt-in device voiceFree, offline, no key
TranscriptionOpenRouter · transcriptionopenai/gpt-4o-mini-transcribeNeeded only for speaking review

1. Install it and switch the extension on

Install from the App Store, or load the Chrome or Firefox build. Then switch the extension on once — on iPhone that happens in Settings, and belliedmonkey.cc/setup.html detects it and confirms when it is actually working.

2. Get an API key

Sign up at https://platform.deepseek.com and create an API key. Any of the supported providers works; this guide uses DeepSeek because it is inexpensive, fast enough for whole pages, and its key works from anywhere.

3. Enter the key and press Test connection

Open the extension's Settings, choose DeepSeek as the translation engine, paste the key, and press Test connection. The endpoint and model are filled in for you — https://api.deepseek.com/v1/chat/completions with model deepseek-v4-flash — and you only change them if you are pointing at a gateway or a different model.

The address is used exactly as you store it. Nothing is appended to it and no path is guessed, so a corporate gateway or a proxy on its own domain works by pasting its full URL — including the path. That is also why an address without a path fails: https://your-endpoint.example is not an endpoint, https://your-endpoint.example/v1/chat/completions is.

4. Read-aloud

The device's built-in voice is free, works offline and needs no key, which is the right default. If you want a better voice for listening practice, OpenRouter · speech takes a key of its own at https://openrouter.ai/api/v1/audio/speech with model deepgram/aura-2.

5. Transcription, for the speaking exercises

Speaking review needs to hear you, which means a transcription endpoint. OpenRouter · transcription uses https://openrouter.ai/api/v1/audio/transcriptions with model openai/gpt-4o-mini-transcribe and the same kind of key. If you would rather nothing left your machine, point it at a local server instead — the field takes any endpoint speaking the same shape.

6. Translate one page to confirm

Open a page in a language you do not read, switch translation on, and check that each paragraph keeps its original with the translation underneath. If a paragraph fails it now says so with a retry you can press, rather than sitting at "Translating…".

Turning the review cards on

Translation works without this, so it is not one of the six steps. Capture is off by default; turning it on is what makes the sentences you actually stopped and read become review cards — extension settings → Collect study material. It records only sentences you dwelled on, never everything you scrolled past, and the cards stay on the device until you choose to sign in.

That switch lives in the extension, not in the app. The two have separate storage, so a switch drawn in the app would write somewhere the extension never reads — which is why the app sends you here instead of pretending otherwise.

Why this engine, and why you do not have to tune it

Reasoning models are the trap here. Left alone, DeepSeek's deepseek-v4-flash spends tokens thinking about every single paragraph: measured on 2026-08-20, one paragraph took 3549 ms and burned 278 thinking tokens. With thinking switched off the same paragraph came back in 1126 ms — 3.2× faster for text that reads the same.

The extension sends that parameter for you, per host and per model, from a table built out of measurements rather than documentation. The reason it has to be measured is that documentation does not predict it: on another provider the identical situation cost 24833 ms before and 1410 ms after — 17.6× — and on a third, sending the documented parameter returns HTTP 200 with an empty body, which looks like a bug in the extension and is not.

A host the table has never seen gets the protocol minimum and nothing else, which is why a gateway inside your own company works on the first try instead of failing on a parameter it does not implement.

Every engine, with its exact address

These are read from the same registry the software ships with, so this table cannot drift away from what the app actually does.

EngineEndpointDefault modelWhere to get a key
ChatGPT (OpenAI)https://api.openai.com/v1/chat/completionsgpt-4o-miniplatform.openai.com/api-keys
Claude (Anthropic)https://api.anthropic.com/v1/messagesclaude-haiku-4-5-20251001console.anthropic.com
DeepSeekhttps://api.deepseek.com/v1/chat/completionsdeepseek-v4-flashplatform.deepseek.com
GLM (Z.ai)https://api.z.ai/api/paas/v4/chat/completionsglm-4-flashz.ai
Qwenhttps://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completionsqwen-plusmodelstudio.console.alibabacloud.com
Qwen MThttps://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completionsqwen-mt-turbomodelstudio.console.alibabacloud.com
Kimihttps://api.moonshot.ai/v1/chat/completionskimi-k2.6platform.moonshot.ai
OpenRouterhttps://openrouter.ai/api/v1/chat/completionsgoogle/gemini-3.7-flashopenrouter.ai/keys
BelliedMonkey free credithttps://cavezcufztzqsohpjmup.supabase.co/functions/v1/bt-relay/chat/completionsdeepseek/deepseek-v4-flash

Anything else that speaks one of the two request shapes works too — you supply the whole address yourself:

EntryAddress shape
Custom (OpenAI-compatible)https://your-endpoint.example/v1/chat/completions
Custom (Anthropic-compatible)https://your-endpoint.example/v1/messages

When something does not work

The key is rejected but you are sure it is right

Several providers issue region-bound keys and run separate hosts for them, so a key from one console will not authenticate against the other's address. Check that the endpoint's domain matches the console you created the key in.

Nothing happens and there is no error

A request that returns HTTP 200 with an empty body is almost always a reasoning model that spent its whole output budget thinking. The extension avoids this for the models it knows; if you switched to a model by hand, switch back or reduce it to a non-reasoning one.

It works in one browser but not another

Settings live on the device and do not travel to your other ones — enter the key again there. Sync carries the learning corpus and nothing else: the cards, the reviews, and the pages they came from. Your keys never leave the device, which is the entire point of bringing your own.

Not switched on yet? Start here →
Three steps, about a minute. That page detects the extension and confirms when it is actually working.

See also: frequently asked questions · YouTube dual subtitles · Safari on iPhone