Telluvian

Bring your own key

Run requests on your own OpenAI, Anthropic, Google AI Studio or OpenRouter account, and pay only our fees.

Copy this page as Markdown to paste into an AI assistant, or open it in one.

Add your own provider API keys and requests they can serve run on your provider account. You pay the provider for the tokens, at your own rates and under your own limits. We bill only our fees:

On a request served by your keyYou pay us
Model tokensNothing. The provider bills you directly.
Verification$1.00 per 1M completion tokens, when enabled.
Model Select$0.05 per 1M input tokens, when we pick the model.

A plain request on your own key, with no verification and a named model, costs nothing on Telluvian. Your balance still needs to be above zero for requests to be admitted.

Nothing changes in your application code: you keep calling Telluvian with your Telluvian API key, and requests your provider keys can serve use them automatically. Every response then says which of your keys served it.

Add keys in the dashboard under Provider keys, or through the API. Each key is checked with the provider when you add it, then stored encrypted. It is never shown again, only a masked label such as sk-pro…1a2b.

providerKey fromServes
openaiOpenAIopenai/ models
anthropicAnthropicanthropic/ (Claude) models
googleGoogle AI Studiogoogle/ Gemini models
openrouterOpenRouterAny catalogue model except Claude

Which key serves a request

  1. A key for the model's own provider first: an openai/ model uses your OpenAI key, a google/ Gemini model your Google AI Studio key.
  2. Then your OpenRouter key, which can serve any model in the catalogue. Claude models are the exception: they only ever use an Anthropic key.
  3. Several keys for one provider are tried in their sort_order, lowest first.

A key is skipped for a request when:

  • it is turned off (disabled);
  • its allowed_models excludes the model. allowed_models takes catalogue ids or patterns such as anthropic/* or openai/gpt-5*, and null allows any;
  • its allowed_api_key_ids excludes the Telluvian API key making the request (see Keys for each API key).

If none of your keys can serve the model, the request runs on our key at the normal price, exactly as it would without BYOK.

Choosing the key on a request

To choose the key yourself, give it a slug and name it on the request:

{"model": "openai/gpt-4.1", "messages": [...], "byok_credential": "team-a"}

With the OpenAI Python SDK, pass it through extra_body:

client.chat.completions.create(
    model="openai/gpt-4.1",
    messages=[{"role": "user", "content": "Hello"}],
    extra_body={"byok_credential": "team-a"},
)

That key alone serves the request (a key's id works too). It works on /v1/chat/completions and /v1/responses, and is never sent to the provider. If the key is turned off, excluded by its allowed_models or allowed_api_key_ids, or its provider does not serve the model, the request fails with 400 rather than quietly using another key. If the key fails at the provider, our key takes over only if that key has allow_platform_fallback turned on.

Keys for each API key

By default every Telluvian API key on your account can use every provider key. To keep, say, production traffic on one OpenAI account and staging on another, set allowed_api_key_ids on each provider key to the ids of the Telluvian API keys that may use it:

{"allowed_api_key_ids": ["2b0c6a51-8f3e-4b0e-9d1a-7c5e4f3a2b10"]}

Copy an API key's id from its ⋯ menu on the API keys page. Only active, ordinary API keys on your own account are accepted. A management key or a revoked key is rejected with invalid_allowed_api_key_ids. null, the default, means every API key. A request whose API key may use none of your provider keys runs on our key at the normal price, as if you had none.

Which key served a request

When your account has a provider key that could serve the request, its usage gains byok_credential, naming the key that served it:

"usage": {
  "prompt_tokens": 12,
  "completion_tokens": 9,
  "total_tokens": 21,
  "byok_credential": {
    "id": "6f1c0d0e-6c1d-4a59-9a4c-1f3e2a7b9c10",
    "slug": "production",
    "provider": "openai"
  }
}
  • id is the key's id from /v1/byok. slug is null if you have not set one.
  • "byok_credential": null means our key served it, because your keys failed and had allow_platform_fallback on. That request was billed at the normal price.
  • No byok_credential field at all means none of your keys applied to the request, and it ran on our key at the normal price.

It is on Chat Completions and Responses, streamed or not: in a chat stream, on the chunk that carries usage, and in a Responses stream, on response.completed. With the Python SDK, read it as getattr(response.usage, "byok_credential", None).

Use it to attribute provider spend when you have several keys. Every usage record also names the key that served it, and records what your provider account was charged for it at our list price (byok_cost_micros). That figure is for your reference only and is never billed. In the dashboard, those requests are marked Own key.

When your key fails

If a key fails before it has produced any output, the next key is tried. That happens when:

  • the key itself fails: rejected (401), out of credit (402), forbidden from the model (403) or rate-limited (429);
  • the provider fails: a 5xx, an overload or a timeout.

A 400 is about the request rather than the key, so it is returned at once without trying other keys. A failure after output has started ends the request. It is never resent to another key halfway through.

When every applicable key has failed, the request fails, unless every one of them has allow_platform_fallback turned on, in which case it runs on our key and is billed at the normal price. Fallback is off by default, so bringing a key never costs you platform tokens you did not opt into.

The error never contains the provider's own message, which can quote part of your key:

{
  "error": {
    "message": "Your OpenAI API key was rejected as invalid (upstream 401). Check it in the dashboard, or enable platform fallback on the key.",
    "type": "upstream_error",
    "code": "byok_credential_rejected"
  }
}

A key the provider rejects three times in a row is turned off and shows as Rejected in the dashboard. Replace the key there to turn it back on. Provider outages and rate limits never turn a key off. A key's most recent failure is in its last_error (such as "upstream 429"), and its next success clears it.

Streamed verification requests

A streamed request with verification enabled starts its response before the provider is called, so a key that fails there ends the stream early rather than returning an error status. See streaming failures.

Managing keys with the API

Provider keys are managed with a management key, a separate kind of API key you create on the API keys page by choosing Management. A management key can only call /v1/byok. It cannot run requests, and an ordinary key cannot call /v1/byok. Keep management keys out of application code.

The API is modelled on OpenRouter's BYOK API, so code written for one ports to the other with few changes.

RequestDoes
GET /v1/byokList your provider keys.
POST /v1/byokAdd one.
GET /v1/byok/{id}Get one.
PATCH /v1/byok/{id}Change its settings, or replace the key itself.
DELETE /v1/byok/{id}Remove it.

The provider key object

Every endpoint returns provider keys in this shape. The key itself is never returned.

{
  "id": "6f1c0d0e-6c1d-4a59-9a4c-1f3e2a7b9c10",
  "provider": "openai",
  "slug": "production",
  "label": "sk-pro…1a2b",
  "name": "Production",
  "disabled": false,
  "allow_platform_fallback": false,
  "sort_order": 0,
  "allowed_models": null,
  "allowed_api_key_ids": null,
  "last_used_at": "2026-11-02T09:14:03Z",
  "last_error": null,
  "created_at": "2026-11-01T17:40:11Z",
  "updated_at": "2026-11-02T09:14:03Z"
}
FieldMeaning
idThe key's id. Usage records and usage.byok_credential refer to it.
provideropenai, anthropic, google or openrouter. Fixed once added.
slugOptional stable name for byok_credential on a request: 1 to 63 lowercase letters, digits, - and _, unique on your account.
labelA masked snippet of the key, to tell keys apart.
nameOptional free-text name.
disabledtrue when turned off, by you or after repeated rejections.
allow_platform_fallbackWhether our key may serve a request once yours have failed. Default false.
sort_orderOrder among keys for the same provider, lowest first. Default 0.
allowed_modelsCatalogue ids or patterns this key may serve (up to 100), or null for any.
allowed_api_key_idsIds of the Telluvian API keys that may use it (up to 100), or null for all.
last_used_atWhen it last served a request, updated at most about once a minute.
last_errorIts most recent failure, such as "upstream 401", or null.

Add a key

curl https://api.telluvian.ai/v1/byok \
  -H "Authorization: Bearer $TELLUVIAN_MANAGEMENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "openai",
    "key": "'"$OPENAI_API_KEY"'",
    "name": "Production",
    "slug": "production",
    "allowed_models": ["openai/gpt-5*", "openai/gpt-4.1"]
  }'

Returns 201 with {"data": <provider key>}. provider and key are required. name, slug, disabled, allow_platform_fallback, sort_order, allowed_models and allowed_api_key_ids are optional. The key is checked with the provider first. If the provider rejects it, nothing is saved and you get 400 invalid_provider_key.

List and inspect

curl "https://api.telluvian.ai/v1/byok?provider=openai" \
  -H "Authorization: Bearer $TELLUVIAN_MANAGEMENT_KEY"
{"data": [<provider key>, ...], "total_count": 1}

offset and limit (1 to 100, default 50) page the list, and provider filters it to one provider. GET /v1/byok/{id} returns {"data": <provider key>} for one key. Check disabled and last_error to see whether a key is healthy.

Change a key

Send only the fields to change. An explicit null clears slug, allowed_models or allowed_api_key_ids.

curl -X PATCH https://api.telluvian.ai/v1/byok/6f1c0d0e-6c1d-4a59-9a4c-1f3e2a7b9c10 \
  -H "Authorization: Bearer $TELLUVIAN_MANAGEMENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"allow_platform_fallback": true, "sort_order": 1}'

Returns {"data": <provider key>}. Send key to replace the key itself, for example after rotating it with the provider. The new key is checked with the provider again. To turn a key back on after it was turned off, send "disabled": false, usually together with the replacement key. provider cannot be changed: add a new key instead.

Delete a key

curl -X DELETE https://api.telluvian.ai/v1/byok/6f1c0d0e-6c1d-4a59-9a4c-1f3e2a7b9c10 \
  -H "Authorization: Bearer $TELLUVIAN_MANAGEMENT_KEY"

Returns {"id": "...", "deleted": true}. Requests it would have served use your other keys if any can serve them, and otherwise our key at the normal price. Past usage records keep its id.

In Python

import os
import httpx

byok = httpx.Client(
    base_url="https://api.telluvian.ai/v1/byok",
    headers={"Authorization": f"Bearer {os.environ['TELLUVIAN_MANAGEMENT_KEY']}"},
)

created = byok.post("", json={
    "provider": "anthropic",
    "key": os.environ["ANTHROPIC_API_KEY"],
    "slug": "claude-prod",
}).raise_for_status().json()["data"]

for key in byok.get("").raise_for_status().json()["data"]:
    status = "off" if key["disabled"] else key["last_error"] or "ok"
    print(key["provider"], key["slug"], key["label"], status)

Differences from OpenRouter

  • allow_platform_fallback replaces OpenRouter's is_fallback. Here, order is set with sort_order, and the flag means "our key may serve the request once mine have failed".
  • There are no workspaces, so no workspace_id. allowed_api_key_ids scopes a key to particular API keys instead.
  • A key can have a slug, and a request can name it with byok_credential to be served by that key alone.
  • Responses name the key that served them in usage.byok_credential.

Errors

On a model request:

StatuscodeCause
400byok_credential_rejectedYour key was rejected or is not permitted to use the model, and no other key could serve it.
402byok_credential_rejectedYour provider account is out of credit.
429byok_credential_rejectedThe provider rate-limited your key.
502byok_credential_rejectedThe provider failed on your key (a 5xx or overload). Retry shortly.
504byok_credential_rejectedThe provider did not respond in time on your key. Retry shortly.
400credential_not_foundA request's byok_credential names no key on your account.
400credential_not_usableThe key a request named is off, excluded by its allowed_models or allowed_api_key_ids, or its provider does not serve the model.
403management_key_not_allowedA model endpoint was called with a management key.

On /v1/byok:

StatuscodeCause
400invalid_provider_keyThe provider rejected the key when you added or replaced it. Nothing was saved.
400too_many_credentialsAn account can store up to 50 provider keys. Delete one first.
400invalid_slugA slug that is not 1 to 63 lowercase letters, digits, - and _.
400invalid_allowed_modelsAn allowed_models entry that is neither a catalogue id nor a pattern.
400invalid_allowed_api_key_idsAn allowed_api_key_ids entry that is not an active, ordinary API key on your account.
403management_key_required/v1/byok was called with an ordinary key.
404credential_not_foundNo such key on your account.
409slug_takenAnother of your keys already has that slug.
502byok_validation_unavailableThe provider could not be reached to check the key. Try again.
503byok_unavailableBringing your own key is not available on this account yet.