> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Bring your own speech credentials (STT/TTS)

> Register your own Deepgram, Cartesia, or ElevenLabs API key under Settings → Voice so transcription and voicemail-greeting synthesis run against your provider account. Console and API walkthroughs for register, enforce, rotate, and revoke, plus live-call and fallback behavior.

# Bring your own speech credentials (STT/TTS)

By default, speech-to-text (STT) and text-to-speech (TTS) on Devotel Orbit run against platform-owned provider keys. If your organization wants that usage on its own provider account and cost centre, register your own key under **Settings → Voice**, in the **Bring your own voice-model key** section (the voice inference provider console). This guide walks the happy path end to end: registering a correctly scoped key, enforcing it, rotating it, and revoking it, from the dashboard and from the API.

If a register or rotate request fails with `422 VOICE_CREDENTIAL_PROVIDER_REJECTED`, see [Troubleshooting: voice inference credential rejected](https://docs.orbit.devotel.io/troubleshooting/voice-inference-credential-rejected.md).

## What BYO speech credentials are (and are not)

The console states the contract in one paragraph:

> Register your own Cartesia (TTS) or Deepgram (STT) API key so voicemail-greeting synthesis and on-demand call transcription run against a credential and cost centre your organization owns. Until a key is active and enforced, the platform-owned key stays authoritative; revoking falls back to it immediately for new turns.

Unpacked, that is four commitments:

* **Your account, your cost centre.** Usage under your key bills to your provider account, not to Orbit. The key is stored encrypted at rest and is never echoed back; every read returns only a fingerprint.
* **Your route stays inside your credential's boundary.** Traffic served by your key never hops into the platform failover chain mid-turn, and your key never joins the platform chain for any other organization.
* **The platform key stays authoritative until your key is active *and* enforced.** Registering alone changes nothing about live traffic. A key that is registered but not enforced is advisory.
* **Revoke falls back immediately.** Revoking clears enforcement and new turns use the platform-owned key right away. Turns already in flight finish on your credential.

A BYO speech credential is not a carrier or dial-plan change. It covers voice inference only (transcription and synthesis); how your calls are placed, routed, and terminated is untouched.

## Providers and scopes per kind

The console holds one panel per kind, and each kind has its own provider catalog:

| Kind | Provider | Required key access | Key source |
| - | - | - | - |
| `stt` | Deepgram | Transcription or speech-to-text API access | Deepgram dashboard |
| `tts` | Cartesia | Text-to-speech or voice-synthesis API access | Cartesia dashboard |
| `tts` | ElevenLabs | Text-to-speech or voice-synthesis API access | ElevenLabs dashboard |

Create the key in the provider dashboard with the scope above. A key restricted to billing, account administration, or another unrelated API is rejected at registration even when it is active in the provider console, because Orbit probes every key against the provider before saving it.

## Dashboard walkthrough

Open **Settings → Voice** and find **Bring your own voice-model key**. The section shows two independent panels: **Text-to-speech (TTS)**, used for voicemail-greeting synthesis, and **Speech-to-text (STT)**, used for on-demand call transcription. You can register one kind, both, or neither.

### Register a key

1. In the panel for the kind you want, select the **Provider**. Optionally set an **Alias** (for example, "ACME prod voice key") and a **Justification** for your audit trail.
2. Paste the API key. It is never echoed back; only its fingerprint is stored.
3. Submit **Register key**. Orbit probes the key against the provider first. A key the provider refuses is rejected with the provider's status, and nothing is saved.
4. A successful registration lands in **Pending activation**. Press **Activate**. With **Enforce immediately** checked (the default), the key becomes active and enforced in one step; uncheck it to activate first and enforce later while you verify output.

### Read the state

Each panel shows a state badge next to its title and an enforcement row in the details:

| What the panel shows | Enforcement row | Live traffic runs on |
| - | - | - |
| "No credential registered — using the platform-owned key." | (none) | Platform key |
| **Pending activation** badge | Registered (advisory — platform key active) | Platform key |
| **Active** badge | Registered (advisory — platform key active) | Platform key |
| **Active** badge | Routing this org's traffic | Your key |
| **Revoked** badge | (none) | Platform key |

The panel also shows the provider, the key fingerprint, and the rotation count. Until the enforcement row reads "Routing this org's traffic", the platform-owned key remains authoritative for live turns.

### Rotate

**Rotate** opens the same provider/key form inside the panel. The replacement key is probed against the provider before it is saved, and a successful rotation preserves the credential's lifecycle state: an active, enforced key stays active and enforced on the new secret, with no fallback window. The rotation count increments on every rotation. A revoked credential cannot be rotated; register afresh instead.

### Revoke

**Revoke** opens a confirmation dialog because it is an irreversible fallback-to-platform transition: enforcement is cleared and new turns fall back to the platform-owned key immediately. Re-enabling BYO afterwards requires registering a credential afresh. You can attach an optional justification, stored with the action.

Register, activate, rotate, and revoke are all written to the audit log (**Settings → Audit log**), so every lifecycle change is attributable.

## API walkthrough

The same lifecycle is available under `/api/v1/voice/voice-provider-credential`, with `:kind` = `tts` or `stt`:

* `GET /api/v1/voice/voice-provider-credential` — both kinds' current configs (`null` when unset), the provider catalog per kind, and the resolved posture. Read-only.
* `PUT /api/v1/voice/voice-provider-credential/:kind` — register. New registrations start `pending`.
* `POST /api/v1/voice/voice-provider-credential/:kind/activate` — mark a pending credential active; `enforce: true` begins routing through it.
* `POST /api/v1/voice/voice-provider-credential/:kind/rotate` — replace the secret, preserving lifecycle state.
* `POST /api/v1/voice/voice-provider-credential/:kind/revoke` — clear enforcement; new turns fall back to the platform key.

Writes require an owner or admin role (or an API key scoped for voice administration); reads use the voice read scope. Writes run under a tighter rate limit than reads.

### Register and enforce

```bash cURL theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/voice/voice-provider-credential/stt \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "deepgram",
    "credential": "<your Deepgram API key>",
    "key_alias": "ACME prod voice key",
    "justification": "Move STT usage to our cost centre"
  }'
```

```typescript Node.js theme={null}
import { Devotel } from '@devotel-orbit/node';
const orbit = new Devotel({ apiKey: process.env.ORBIT_API_KEY! });

await orbit.request('PUT', '/voice/voice-provider-credential/stt', {
  provider: 'deepgram',
  credential: process.env.DEEPGRAM_API_KEY!,
  key_alias: 'ACME prod voice key',
});
```

A successful register returns `201` with the stored config: `state: "pending"`, the fingerprint, the alias, and no secret. Then activate:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/voice/voice-provider-credential/stt/activate \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "enforce": true }'
```

`enforce` defaults to `false`, so an activate without it leaves the platform key authoritative while you verify. Only a `pending` credential can be activated; activating an active or revoked one returns `409 VOICE_CREDENTIAL_NOT_PENDING`.

### Probe-first semantics

Register and rotate both probe the credential against the provider before persisting. If the provider refuses (HTTP 401 or 403), the request fails and nothing is saved:

```json theme={null}
{
  "error": {
    "code": "VOICE_CREDENTIAL_PROVIDER_REJECTED",
    "message": "Deepgram rejected this credential (HTTP 401). Check the key in your provider dashboard and try again.",
    "status": 422,
    "details": {
      "kind": "stt",
      "provider": "deepgram",
      "provider_status": 401
    }
  }
}
```

`error.details.provider_status` carries the provider's HTTP status, and `details.kind` / `details.provider` name what was probed. This is a deterministic verdict: the same key keeps failing until you fix its scope or replace it. A transient provider outage (5xx or timeout) does not block registration.

### Rotate

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/voice/voice-provider-credential/tts/rotate \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "provider": "cartesia", "credential": "<new Cartesia key>" }'
```

Rotation runs the same probe before saving, preserves an active/enforced state on the new secret, and increments `rotation_count`. Guard rails: registering over a live credential returns `409 VOICE_CREDENTIAL_ALREADY_REGISTERED` (rotate or revoke first), rotating a revoked one returns `409 VOICE_CREDENTIAL_REVOKED`, and submitting a provider that does not belong to the kind returns `409 VOICE_CREDENTIAL_PROVIDER_KIND_MISMATCH`.

### Read and revoke

`GET /api/v1/voice/voice-provider-credential` returns both kinds' configs, the provider catalog, and the resolved posture for each kind; it never returns a secret. `POST /api/v1/voice/voice-provider-credential/:kind/revoke` accepts an optional `justification`, clears enforcement, and takes effect for new turns immediately.

## Live-call behavior

**STT.** Your transcription runs inside your credential's route boundary. The platform-managed failover chain does not take over your route, add its providers to it, or move you onto a platform credential mid-call; failover and recovery follow your own provider's behavior. The boundary works both ways: your key never changes the platform chain for anyone else. See [Live call transcription](https://docs.orbit.devotel.io/voice/live-call-transcription.md) for the platform chain, and [Transcription operations](https://docs.orbit.devotel.io/voice/transcription-operations.md) (section 5, "Customer-managed credentials") for the boundary contract.

Beyond live calls, two STT surfaces honor your key directly:

* **On-demand transcription** of completed recordings runs against your Deepgram key while it is active and enforced, and against the platform key otherwise.
* **The [STT Playground](https://docs.orbit.devotel.io/guides/stt-playground.md)** compares providers using your active Deepgram key even before you enforce it, so you can verify transcription quality on your own account before routing production traffic.

**TTS.** The synthesis path that honors your TTS credential is voicemail-greeting synthesis: while your key is active and enforced, greetings synthesize against your provider account; otherwise the platform-owned key synthesizes them. AI voice agents are unaffected; each agent keeps the speech vendor selected in the agents console.

**Failover composition.** Your credential and the platform chain are two separate routes, not one merged chain. A deterministic refusal from your provider (401/403) surfaces as an error instead of silently hopping onto the platform key, because silently substituting would mask a broken credential and shift cost onto the wrong account. Only an explicit action on your side (rotate or revoke) moves traffic between the routes.

## Operations

* **Rotation cadence.** Rotate on whatever key-rotation schedule your provider policy sets. Because the replacement is probed before saving and rotation preserves the active/enforced state, a clean rotation has no fallback window. Track `rotation_count` in the panel or the GET response as your audit trail.
* **Provider-side 401/403 during live use.** A deterministic refusal surfaces rather than failing over. Check the key's status and scope in the provider dashboard, then rotate in a valid replacement. Do not retry the same key; the verdict does not change until the key does.
* **Incident fallback.** If your provider has an outage and you want Orbit back on the platform key now, revoke. Enforcement clears immediately and new turns use the platform-owned key. Register afresh when you want BYO back.
* **Rejected at write time.** See [Troubleshooting: voice inference credential rejected](https://docs.orbit.devotel.io/troubleshooting/voice-inference-credential-rejected.md) for the recovery checklist and what to include in a support ticket if a correctly scoped new key is still refused.

## See also

* [Troubleshooting: voice inference credential rejected](https://docs.orbit.devotel.io/troubleshooting/voice-inference-credential-rejected.md) — recover from `VOICE_CREDENTIAL_PROVIDER_REJECTED`.
* [Transcription operations](https://docs.orbit.devotel.io/voice/transcription-operations.md) — day-to-day transcription operations, including the customer-managed credential boundary.
* [Live call transcription](https://docs.orbit.devotel.io/voice/live-call-transcription.md) — the platform STT chain and its failover rules.
* [Voice Settings Consoles](https://docs.orbit.devotel.io/guides/channel-settings-consoles.md) — the remaining org-level voice settings consoles.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.