External providers (BYOK)
Bring your own provider key — use your own account, pay the provider directly, and let BitRouter route requests without a rev share or per-token fee. Works self-hosted, locally, and on BitRouter Cloud.
BYOK (bring your own key) routes your requests using your own provider account, not BitRouter's. You pay the provider directly at their list price — BitRouter takes no rev share, adds no per-token fee, and never holds your keys in plaintext.
BYOK works the same way regardless of deployment:
- Self-hosted / local binary — set provider keys in the environment. BitRouter detects them at startup; no upload or encryption step.
- BitRouter Cloud — keys are encrypted client-side with a sealed box before submission. The node only ever sees ciphertext.
Local mode — env-var auto-detection
When you self-host or run the binary locally there is no upload step: set provider keys in the environment and you are done. No config file required.
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-...
export GEMINI_API_KEY=AIza...
bitrouterBitRouter detects keys at startup and exposes only the providers whose keys are present. If a provider's key is missing, attempts to route to that provider return 402 Payment Required with a structured error pointing at the missing variable.
Recognized variables
| Provider | Variable |
|---|---|
| OpenAI | OPENAI_API_KEY |
| Anthropic | ANTHROPIC_API_KEY |
GEMINI_API_KEY | |
| Custom (registry-listed) | <PROVIDER_ID>_API_KEY |
Registry-listed custom providers use the uppercase provider id with hyphens converted to underscores.
For a custom provider registered as id: my-provider in the registry, set MY_PROVIDER_API_KEY. To use a different variable name, declare the provider explicitly in bitrouter.yaml with api_key: ${MY_VAR}.
On BitRouter Cloud
On BitRouter Cloud, your provider key is encrypted client-side against the node's X25519 sealed-box public key before submission. The node never sees plaintext on the write path; ciphertext is decrypted in-memory at request time and never logged.
The flow:
- Fetch the node's public key.
GET /v1/byok/encryption-pubkeyreturns the current X25519 public key and akek_idfingerprint. Cache by fingerprint and pass it back asIf-None-Matchto short-circuit on304 Not Modified. - Encrypt the plaintext key. Use libsodium
crypto_box_seal(or any sealed-box implementation) against the public key. - Submit the ciphertext. The console does steps 1–2 in-browser when you paste a key — you never need to leave the dashboard. The same submission API is also available for scripted onboarding; see the API reference.
Encryption recipe
If you're scripting key submission, here's the minimum sealed-box step. The output ciphertext is what the submission endpoint expects.
<Tabs items={['Node.js', 'Python', 'Shell']}>
import sodium from 'libsodium-wrappers';
await sodium.ready;
const meta = await fetch(
'https://api.bitrouter.ai/v1/byok/encryption-pubkey'
).then(r => r.json());
const ciphertext = sodium.crypto_box_seal(
sodium.from_string(process.env.OPENAI_API_KEY),
sodium.from_base64(meta.public_key, sodium.base64_variants.ORIGINAL)
);
const ciphertextB64 = sodium.to_base64(ciphertext, sodium.base64_variants.ORIGINAL);
// Submit { provider_name: 'openai', kek_id: meta.kek_id, ciphertext_b64: ciphertextB64, key_prefix: 'sk-...' }import os, base64, requests
from nacl.public import PublicKey, SealedBox
meta = requests.get('https://api.bitrouter.ai/v1/byok/encryption-pubkey').json()
pubkey = PublicKey(base64.b64decode(meta['public_key']))
ciphertext = SealedBox(pubkey).encrypt(os.environ['OPENAI_API_KEY'].encode())
payload = {
'provider_name': 'openai',
'kek_id': meta['kek_id'],
'ciphertext_b64': base64.b64encode(ciphertext).decode(),
'key_prefix': 'sk-...',
}
# POST `payload` to the BYOK submission endpoint.META=$(curl -s https://api.bitrouter.ai/v1/byok/encryption-pubkey)
PUBKEY=$(echo "$META" | jq -r .public_key)
KEK_ID=$(echo "$META" | jq -r .kek_id)
CIPHERTEXT=$(printf '%s' "$OPENAI_API_KEY" \
| sodium-seal --pubkey "$PUBKEY" \
| base64)
# Submit { provider_name: "openai", kek_id: "$KEK_ID", ciphertext_b64: "$CIPHERTEXT", key_prefix: "sk-..." }The pubkey endpoint honors If-None-Match for cheap caching — pin the kek_id from a prior response and you'll get 304 Not Modified while the key is unchanged.
Key scope
Keys are scoped to your user account — every API key and OAuth token issued under your account can route requests through the keys you have stored. No one can read the ciphertext or the raw key.
Rotation, revocation, and audit
- Rotate by submitting a new ciphertext for the same provider — the previous record is overwritten atomically.
- Revoke from the dashboard. In-flight requests using the prior key complete; new requests get
402 Payment Required. - Audit — every submission is recorded with its time and the
kek_idused. Plaintext is never visible to anyone, including BitRouter operators.
If a node's kek_id rotates (we re-key every 90 days), the previous key is retained in memory so already-submitted ciphertexts remain decryptable at request time. New submissions must use the current kek_id; re-encrypt only if you explicitly want to migrate to the new key.
Custom providers
BYOK works for any provider listed in the registry that declares byok in its manifest's payment.modes. The provider field in the encryption submission must match the registry id exactly. Local-mode env-var detection follows the <PROVIDER_ID>_API_KEY convention, uppercased with hyphens converted to underscores.
How is this guide?