Skip to content

External providers (BYOK)

BYOK (bring your own key) routes your requests using your own provider account, not Routeplane’s. You pay the provider directly at their list price — Routeplane 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. Routeplane detects them at startup; no upload or encryption step.
  • Routeplane Cloud — keys are encrypted client-side with a sealed box before submission. The node only ever sees ciphertext.

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.

Terminal window
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-...
export GOOGLE_API_KEY=AIza...
routeplane

Routeplane 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.

Provider Preferred name Passthrough fallback
OpenAI ROUTEPLANE_OPENAI_API_KEY OPENAI_API_KEY
Anthropic ROUTEPLANE_ANTHROPIC_API_KEY ANTHROPIC_API_KEY
Google ROUTEPLANE_GOOGLE_API_KEY GOOGLE_API_KEY, GEMINI_API_KEY
Custom (registry-listed) ROUTEPLANE_<PROVIDER_ID>_API_KEY

The ROUTEPLANE_* variants take precedence over the passthrough names — useful when your shell already has OPENAI_API_KEY set for a different tool and you want Routeplane to use a different account.

For a custom provider registered as id: my-provider in the registry, set ROUTEPLANE_MY_PROVIDER_API_KEY (uppercased, hyphens become underscores).

**Key rotation is live.** Routeplane watches the environment of its parent process; updating a key (re-export and `kill -HUP $(pgrep routeplane)`) takes effect on the next request without restart. **Routeplane Cloud is on the Phase D roadmap and not yet shipping.** The sealed-box upload flow below describes how Cloud will work once it's live. For now, self-host with env-var keys (the [Local mode](#local-mode--env-var-auto-detection) section above).

On cloud.routeplane.app, 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:

  1. Fetch the node’s public key. GET /v1/byok/encryption-pubkey returns the current X25519 public key and a kek_id fingerprint. Cache by fingerprint and pass it back as If-None-Match to short-circuit on 304 Not Modified.
  2. Encrypt the plaintext key. Use libsodium crypto_box_seal (or any sealed-box implementation) against the public key.
  3. 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.
**Cloud not yet shipping — these snippets will fail today.** The recipes below describe how the scriptable onboarding will work once Cloud is live. For now, use [Local mode](#local-mode--env-var-auto-detection) above.

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://cloud.routeplane.app/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://cloud.routeplane.app/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.
Terminal window
META=$(curl -s https://cloud.routeplane.app/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.

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.

  • 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_id used. Plaintext is never visible to anyone, including Routeplane 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.

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 ROUTEPLANE_<PROVIDER_ID>_API_KEY convention.

Routeplane routes requests through accounts you hold. What each account may be used for is governed by the agreement between you and that provider — not by Routeplane.

Three things to understand before routing:

  • API keys are the intended path. Standard pay-per-use API keys (OpenAI, Anthropic, Google, OpenRouter, and registry providers) exist precisely so you can call the API from software of your choosing. Routing these through a local proxy is ordinary usage.
  • Subscription products can be different. Some flat-rate subscriptions (for example, coding-assistant or chat subscriptions) carry terms that restrict access to the provider’s own clients, or prohibit automated or third-party access. Routing such a subscription through any proxy — Routeplane included — may violate those terms, and providers can and do suspend accounts for it. Read your provider’s current terms and decide for yourself; we do not make that call for you.
  • Enforcement lands on your account. If a provider objects to how its service was accessed, the consequence is applied to your account. Routeplane runs on your machine, never transmits your keys to us, and has no ability to shield an account from its provider.

If you want to stay unambiguously inside every provider’s terms: use API keys via BYOK, or run local models. Treat subscription routing as a convenience you have personally verified against your subscription’s terms.