Other clients

ElevenLabs API drop-in

ElevenLabs' own WebSockets, REST and voices APIs through the cache: keep the ElevenLabs SDK, change the URL and key.

ElevenLabs' Python SDK#

ElevenLabs’ API, mirrored under https://api.ttscacherouter.dev/elevenlabs. Keep the ElevenLabs SDK: pass the router as its base_url and your router API key as its api_key.

speak.py
import osfrom elevenlabs.client import ElevenLabsclient = ElevenLabs(    base_url="https://api.ttscacherouter.dev/elevenlabs",    api_key=os.environ["TTS_CACHE_ROUTER_API_KEY"],)audio = client.text_to_speech.convert(    "JBFqnCBsd6RMkjVDRZzb",    text="Your order has shipped. It arrives on Friday.",    model_id="eleven_flash_v2_5",    output_format="mp3_22050_32",)with open("speech.mp3", "wb") as f:    f.writelines(audio)

convert, stream, convert_with_timestamps, stream_with_timestamps and convert_realtime work through the router. Requests, responses, messages, status codes and error bodies are ElevenLabs’ own; your query parameters, settings and body are forwarded as you sent them, with the ElevenLabs key stored under Provider keys. For a LiveKit agent, see ElevenLabs with LiveKit.

WebSockets#

  • /elevenlabs/v1/text-to-speech/{voice_id}/multi-stream-input: contexts by id, as LiveKit’s plugin uses it.
  • /elevenlabs/v1/text-to-speech/{voice_id}/stream-input: one stream per connection, as the SDK’s convert_realtime uses it.
  • /elevenlabs/v1/text-to-dialogue/multi-stream-input: eleven_v3 and eleven_v4 models.
wss://api.ttscacherouter.dev/elevenlabs/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb/multi-stream-input?model_id=eleven_flash_v2_5&output_format=pcm_24000&auto_mode=true&sync_alignment=true

Audio messages carry the same timings (alignment, normalizedAlignment) a direct call gets, from the cache too, and each context ends with its isFinal (is_final on dialogue) once you close it. Every output format works, opus included. In MP3, a context’s later sentences carry no ID3 tag, as one direct stream carries only one.

REST and voices#

  • POST /elevenlabs/v1/text-to-speech/{voice_id} and …/stream: the audio in the requested output_format.
  • POST /elevenlabs/v1/text-to-speech/{voice_id}/with-timestamps and …/stream/with-timestamps: audio with character timings, as JSON.
  • POST /elevenlabs/v1/text-to-dialogue/stream: dialogue models.
  • GET /elevenlabs/v1/voices: your voices, never cached.

Any other path under /elevenlabs answers 404.

curl -N "https://api.ttscacherouter.dev/elevenlabs/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb/stream?output_format=mp3_22050_32" \  -H "xi-api-key: $TTS_CACHE_ROUTER_API_KEY" \  -H "Content-Type: application/json" \  -d '{"text": "Your order has shipped. It arrives on Friday.", "model_id": "eleven_flash_v2_5"}' \  --output speech.mp3

What is cached#

  • WebSockets: each sentence. With auto_mode=true (LiveKit’s default) each text message you send is synthesized on its own, exactly as ElevenLabs does. Without it, ElevenLabs groups buffered text into chunks of its own choosing; the router instead splits it into sentences with LiveKit’s BlingFire tokenizer and synthesizes each once it is complete, so the audio can be grouped slightly differently from a direct call. flush ends the sentence in progress.
  • REST: each request as a whole. The same query and body again replays the same response.
  • A voice’s stored settings (used when voice_settings is left out or partial) and a pronunciation dictionary named without a version_id can change in ElevenLabs, so the router re-reads them about once a minute: after a change, new sentences are synthesized and cached afresh within about a minute.

Errors#

A wrong router key gets ElevenLabs’ own refusal: HTTP 400 invalid_api_key (401 when no key is sent), and on a WebSocket the same error message followed by a close with code 1008. So does an org with no ElevenLabs key stored, with a message that links to the dashboard. Errors from ElevenLabs, such as an unknown voice, reach you as ElevenLabs sent them.

HTTP/1.1 400 Bad Request{"detail": {"type": "authentication_error", "code": "invalid_api_key", "message": "API key is invalid.",            "status": "invalid_api_key", "request_id": null, "param": "api_key"}}