# Voices

> GET /v1/voices: every voice your org can speak with at a provider, your account's own included, in one shape.

## Request

`GET https://api.ttscacherouter.dev/v1/voices` with your router API key as `Authorization: Bearer tcr_…`. It lists the voices your org can speak with at one provider, your provider account’s own included, read live with the provider key stored under [Provider keys](https://ttscacherouter.dev/dashboard/provider-keys).

**All voices**

```shell
curl "https://api.ttscacherouter.dev/v1/voices?provider=elevenlabs" \
  -H "Authorization: Bearer $TTS_CACHE_ROUTER_API_KEY"
```

**One model**

```shell
curl "https://api.ttscacherouter.dev/v1/voices?provider=deepgram&model=flux" \
  -H "Authorization: Bearer $TTS_CACHE_ROUTER_API_KEY"
```

- `provider` (`string`, Required): `sarvam`, `raya`, `elevenlabs`, `openai`, `deepgram`, `xai` or `soniox`.
- `model` (`string`, Optional): Only the voices this model takes, by the router’s model id without the provider (`bulbul:v4-flash`, `aura-2`, `m1`). See [Models and options](https://docs.ttscacherouter.dev/reference/models.md).

The answer is the whole list: where a provider pages its list, the router reads every page. Nothing is cached, so a voice you add at the provider is listed on your next request. Only voices the router can speak with are listed.

## Response

One shape for every provider. Each voice's id is what voice takes, with any of its models, on every surface.

```json
{
  "voices": [
    {
      "id": "JBFqnCBsd6RMkjVDRZzb",
      "name": "George - Warm, Captivating Storyteller",
      "provider": "elevenlabs",
      "models": ["eleven_flash_v2_5", "eleven_turbo_v2_5", "eleven_multilingual_v2", "…"],
      "languages": ["en", "es", "fr"],
      "gender": "male",
      "accent": "british",
      "age": "middle_aged",
      "description": "Warm resonance that instantly captivates listeners.",
      "preview_url": "https://storage.googleapis.com/eleven-public-prod/…/preview.mp3",
      "category": "builtin",
      "provider_data": {"voice_id": "JBFqnCBsd6RMkjVDRZzb", "category": "premade", "labels": {"…": "…"}, "…": "…"}
    }
  ]
}
```

- `id` (`string`): What `voice` takes on `/v1/stream`, `/v1/audio/speech` and the LiveKit plugin. A Deepgram voice is its model id without the model: `thalia-en` with `aura-2` is Deepgram’s `aura-2-thalia-en`.
- `name` (`string`): The provider's display name.
- `provider` (`string`): The provider asked for.
- `models` (`string[]`): The router's model ids the voice works with. Every ElevenLabs voice works with every ElevenLabs model.
- `languages` (`string[]`): The languages the voice is made for, in the provider’s codes. A voice that speaks every language of its model lists them all (Sarvam’s bulbul:v3, OpenAI, xAI’s built-in voices, Soniox); an ElevenLabs voice lists its own language, then those ElevenLabs verified it in; a bulbul:v4-flash persona the one it was recorded in. Empty when the provider doesn’t say.
- `gender` (`string | null`): `male`, `female` or `neutral`, where the provider says.
- `accent` (`string | null`): In the provider's own words, where it says.
- `age` (`string | null`): In the provider's own words, where it says.
- `description` (`string | null`): The provider's description of the voice, where it has one.
- `preview_url` (`string | null`): A sample of the voice at the provider's own URL, where it has one (ElevenLabs, Deepgram). Play it straight from there: it needs no key.
- `category` (`string`): Where the voice comes from: see [Categories](https://docs.ttscacherouter.dev/reference/voices.md#categories).
- `provider_data` (`object | null`): The provider’s own voice object, as it came: ElevenLabs’ `labels`, `verified_languages` and `high_quality_base_model_ids`, Deepgram’s `tags` and `image`, Soniox’s `use_case` and `style`. Null for voices from the router’s catalog (Sarvam, OpenAI), which no provider API lists.

## Categories

Where each provider's voices come from, and the categories they can have.

- `builtin`: The provider's own stock voices.
- `cloned`: Cloned in your provider account from recordings: ElevenLabs instant and professional clones, xAI custom voices, Soniox cloned voices (once ready).
- `custom`: Made in your provider account another way: ElevenLabs voice design.
- `library`: Added to your provider account from the provider's shared library: ElevenLabs.

| provider | Voices from | Categories | Previews |
| --- | --- | --- | --- |
| sarvam | The router's catalog: bulbul:v3's 37 speakers (with gender) and bulbul:v4-flash's personas | builtin | No |
| raya | Raya's voice list; each voice belongs to one model | builtin | No |
| elevenlabs | Your ElevenLabs account's voices, every page | builtin, cloned, custom, library | Yes |
| openai | The router's catalog | builtin | No |
| deepgram | Deepgram's model lists: Aura and Aura-2, and Flux | builtin | Yes |
| xai | xAI's voices and your custom voices | builtin, cloned | No |
| soniox | Soniox's voices and your project's cloned voices | builtin, cloned | No |

Sarvam’s cloned voices aren’t listed: Sarvam speaks them only through its voice-cloning API, not its Bulbul models. OpenAI lists no custom voices, and the router passes `voice` as a name.

## Speaking with a voice

Pass the voice's id as voice, with one of its models, on any surface.

**agent.py**

```python
from livekit.plugins import tts_cache_router as tcr

# A voice from GET /v1/voices?provider=deepgram&model=aura-2
tts = tcr.TTS(model="deepgram/aura-2", voice="thalia-en")
```

See [TTS(model=...)](https://docs.ttscacherouter.dev/livekit.md#from-model) for the plugin, and [/v1/audio/speech](https://docs.ttscacherouter.dev/clients/speech.md) over HTTP.

## Library voices and voice settings

ElevenLabs' library and voice settings are on the ElevenLabs drop-in, with your router API key in xi-api-key.

**Add a library voice**

```shell
# Browse ElevenLabs' voice library
curl "https://api.ttscacherouter.dev/elevenlabs/v1/shared-voices?page_size=10&language=hi" \
  -H "xi-api-key: $TTS_CACHE_ROUTER_API_KEY"

# Add one to your ElevenLabs account: its public_owner_id and voice_id
curl -X POST "https://api.ttscacherouter.dev/elevenlabs/v1/voices/add/$PUBLIC_OWNER_ID/$VOICE_ID" \
  -H "xi-api-key: $TTS_CACHE_ROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"new_name": "Support voice"}'
```

**Voice settings**

```shell
curl "https://api.ttscacherouter.dev/elevenlabs/v1/voices/JBFqnCBsd6RMkjVDRZzb/settings" \
  -H "xi-api-key: $TTS_CACHE_ROUTER_API_KEY"

{"stability": 0.5, "use_speaker_boost": true, "similarity_boost": 0.75, "style": 0.0, "speed": 1.0}
```

A voice you add appears in `GET /v1/voices?provider=elevenlabs` as `library`. A voice’s settings are what a request without `voice_settings` uses. Each provider’s own voice endpoints are on its drop-in page: [ElevenLabs](https://docs.ttscacherouter.dev/clients/elevenlabs.md#http), [Raya](https://docs.ttscacherouter.dev/clients/raya.md#endpoints), [Deepgram](https://docs.ttscacherouter.dev/clients/deepgram.md#http), [xAI](https://docs.ttscacherouter.dev/clients/xai.md#http) and [Soniox](https://docs.ttscacherouter.dev/clients/soniox.md#http).

## Errors

The router's error body, with its HTTP status. A provider's refusal passes through: its status in provider_status, its body in message.

```http
HTTP/1.1 400 Bad Request

{"error": {"code": "provider_rejected",
           "message": "xAI answered HTTP 400: {\"code\":\"Client specified an invalid argument\",\"error\":\"Incorrect API key provided. You can obtain an API key from https://console.x.ai.\"}",
           "retryable": false, "provider_status": 400}}
```

- `bad_request` (HTTP 400, not retryable): The request failed validation or a message arrived out of order. The message says which field or rule.
- `unauthorized` (HTTP 401, not retryable): The API key is missing, wrong or deleted, or its org is disabled.
- `insufficient_credits` (HTTP 402, not retryable): Your org is out of credit, so the router refuses new connections and requests until you [buy more](https://ttscacherouter.dev/dashboard/billing). Connections already open carry on.
- `provider_key_missing` (HTTP 400, not retryable): Your org has no key stored for the provider. Add one under [Provider keys](https://ttscacherouter.dev/dashboard/provider-keys).
- `provider_rejected` (HTTP 400, not retryable): The provider refused the request, e.g. a wrong stored provider key or an unknown voice. The provider’s message is passed through.
- `provider_rate_limited` (HTTP 429, retryable): Your own quota at the provider is exhausted.
- `provider_unavailable` (HTTP 502, retryable): The provider returned a server error, or the connection to it failed or closed unexpectedly.
- `provider_timeout` (HTTP 504, retryable): The provider took too long to send audio.
- `internal` (HTTP 500, retryable): The router failed.

A missing or unknown `provider` or `model`, or any other query parameter, is a `bad_request`. An org with no key stored for the provider gets `provider_key_missing`, also for Sarvam and OpenAI, whose voices come from the router’s catalog. Deepgram’s model lists are public, so a wrong Deepgram key still lists its voices; it is refused when you speak.
