> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://onepin.ai/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://onepin.ai/docs/_mcp/server.

# Bring your own key

> Connect your own TTS provider API key (BYOK) to a Onepin workspace. What it changes for routing, credits, imported voices, and what happens when a key stops working.

Onepin synthesizes on its own provider accounts by default and charges you credits for it. Connect your own key for a provider and that flips: every line routed to that provider goes through **your** account, the provider bills you directly, and Onepin charges **0 credits** for it.

## Connect a key

Provider keys live in the dashboard: [app.onepin.ai](https://app.onepin.ai) → **Settings → API → BYOK providers**.

> **Note**
>
> This one is dashboard-only. The provider-key endpoints take a dashboard session, not an API key, so there is no SDK or CLI equivalent — connecting a key is the one part of BYOK you cannot script. Everything downstream of it (voices, runs, usage) is fully programmatic.

Which providers you can connect is derived from the live catalog rather than a fixed list, so what the dashboard offers is the answer for your workspace.

Two things gate it:

* **Your plan.** BYOK is a plan feature. Read the flag instead of hardcoding tier names, which change:

  ```bash
  curl https://api.onepin.ai/api/v1/users/me/limits \
    -H "Authorization: Bearer $ONEPIN_API_KEY"
  # → {"data": {"byok_enabled": true, "auto_fix_enabled": …, …}}
  ```

* **Your role.** Adding or replacing a key needs workspace **admin**. Everyone in the workspace synthesizes through a connected key; only an admin can connect one.

The credential is verified against the provider before it is stored. A key the provider rejects never reaches "Connected" — it fails at save time rather than at run time.

## What changes once it's connected

|                       | Platform default               | Your key |
| --------------------- | ------------------------------ | -------- |
| Who pays the provider | Onepin                         | You      |
| Credits charged       | The model's per-character rate | `0`      |
| Counted in usage      | Yes                            | Yes      |

Four consequences worth knowing before you connect one:

* **Routing is per provider, not per voice.** Every line whose voice belongs to that provider uses your key — platform catalog voices included. There is no per-workflow or per-voice opt-in.
* **There is no silent fallback.** If your key fails, the line fails. Onepin will not quietly re-synthesize it on its own account and bill you for it.
* **Auto-routing keeps your providers in play.** [Auto-route](/docs/guides/auto-route) normally skips a model that carries no platform credit rate. For a provider you hold a valid key for that filter is relaxed — you're paying the vendor, so the model is usable.
* **Usage still counts the volume.** A node's `processed_units` counts BYOK characters while its `credits_fractional` stays `0`. That gap is by design, and it's one reason units × rate doesn't reproduce the credits on a run — see [Check usage](/docs/guides/check-usage).

## Imported voices

Some providers bring more than synthesis. For those, connecting the key also imports the voices **you cloned in that provider account** into your workspace catalog; the dashboard marks them with an **Imports voices** badge. The rest only unlock synthesis on your own quota.

An imported voice reads as `source: "provider_imported"` on the voice object, and unlike a platform voice it carries an import lifecycle that can go wrong:

```python
v = client.voices.get(voice_id="...").data
if v.source == "provider_imported" and v.availability == "unavailable":
    print("can't use this one:", v.unavailable_reason)
```

| `unavailable_reason`          | What happened                                                                                          |
| ----------------------------- | ------------------------------------------------------------------------------------------------------ |
| `reconciliation_pending`      | The import hasn't caught up with your provider account yet                                             |
| `provider_key_unavailable`    | The key behind the import is gone or no longer valid                                                   |
| `provider_voice_missing`      | The voice was deleted in your provider account                                                         |
| `provider_import_unavailable` | The voice can't be synthesized as imported — typically the provider never says which locales it speaks |

Unavailable voices stay listed and readable, but a workflow that references one is rejected at save, at run start, and at synthesis. See [Browse voices](/docs/guides/browse-voices).

### Pin an imported voice by its catalog id

A [Generator](/docs/workflow/generator)'s `voice_map` normally pins a voice by the provider's own voice id. That is not enough for an imported voice: a provider wire id is unique per workspace, not globally, so it cannot say *whose* import you mean. The assignment must also carry `catalog_voice_id`, the Onepin catalog UUID:

```python
node["config"] = {"voice_map": {"en-us": [
    {"voice_id": v.provider_voice_id,
     "catalog_voice_id": v.id,        # required for source: "provider_imported"
     "provider": "elevenlabs",
     "model": "eleven_multilingual_v2"}
]}}
```

Without it the definition is rejected with rule `voice_imported_needs_catalog_id`, and a `catalog_voice_id` naming a different voice than the wire id is rejected as `voice_catalog_id_mismatch`. Neither is a permission problem — the voice is present and usable, the reference just doesn't identify it.

## When a key fails mid-run

A provider answers `401`/`403` both for a dead key and for a voice your provider plan doesn't include, so Onepin re-runs its credential probe before deciding which happened:

* **The probe rejects the credential** — the key is marked invalid, you get a "Your provider API key failed" email, and imported voices behind it go `provider_key_unavailable`. Reconnect by replacing the key in **Settings → API**.
* **The probe passes** — only that line fails, and the key stays valid. Pick a voice your provider plan covers.

> **Note**
>
> Lines that were already synthesized are unaffected, and lines routed to providers you have no key for keep running on the platform default. A BYOK failure narrows to the provider it belongs to.

## Related

* [Check usage](/docs/guides/check-usage) — what BYOK volume does and doesn't charge
* [Browse voices](/docs/guides/browse-voices) — imported voices and availability
* [Auto-route](/docs/guides/auto-route) — how a connected key widens the candidate set
* [Generator](/docs/workflow/generator) — `voice_map` and its identifier traps