Other clients
Soniox API drop-in
Soniox's own text-to-speech API, REST and real-time WebSocket, through the cache: keep Soniox's SDK, change the base URLs and key.
Soniox's Python SDK#
Soniox’s text-to-speech API, mirrored under https://api.ttscacherouter.dev/soniox. Point Soniox’s SDK at the router and use your router API key as the API key.
import osfrom soniox import SonioxClientfrom soniox.types import RealtimeTTSConfigclient = SonioxClient( api_key=os.environ["TTS_CACHE_ROUTER_API_KEY"], tts_api_base_url="https://api.ttscacherouter.dev/soniox", tts_websocket_base_url="wss://api.ttscacherouter.dev/soniox/tts-websocket",)config = RealtimeTTSConfig( stream_id="reply-1", model="tts-rt-v2", language="en", voice="Adrian", audio_format="pcm_s16le", sample_rate=24000,)with client.realtime.tts.connect(config=config) as session: session.send_text_chunk("Your order has shipped. ") session.send_text_chunk("It arrives on Friday.", text_end=True) audio = b"".join(session.receive_audio_chunks())with open("speech.pcm", "wb") as f: f.write(audio)# client: as in the real-time tabfrom soniox.types import CreateTtsConfigaudio = client.tts.generate( text="Your order has shipped.", model="tts-rt-v2", language="en", voice="Adrian", config=CreateTtsConfig(audio_format="wav"),)with open("speech.wav", "wb") as f: f.write(audio)Requests go to Soniox with the Soniox key stored under Provider keys. The router calls Soniox’s US endpoint, so store a key from a Soniox project in the US region. For a LiveKit agent, see Soniox with LiveKit.
WebSocket#
wss://api.ttscacherouter.dev/soniox/tts-websocket speaks Soniox’s real-time protocol: up to five streams on a connection, each a config message with its stream_id, text messages and text_end, answered with audio messages (the last with audio_end) and terminated.
Send your router API key in Authorization: Bearer, or, as LiveKit’s Soniox plugin 1.8.5 does, as the api_key of each config. cancel ends a stream with terminated, and {"keep_alive": true} keeps an idle connection open: one with no stream and no message for 40 seconds is closed (code 1001), and a stream with no text for 5 seconds and no text_end ends with request_timeout, as at Soniox.
The router splits each stream’s text into sentences and serves each sentence from the cache or synthesizes it at Soniox, so the messages are Soniox’s with these differences:
- Audio for a sentence starts once the sentence is complete, and the last sentence is spoken without waiting for
text_end. Soniox holds a stream’s text until more text ortext_endarrives. - Each sentence is a take of its own, so the pause between two sentences is shorter (about 0.2 s) than inside one Soniox stream (about 0.5 s).
- A config’s values (voice, language, rate) are checked by Soniox when its first sentence is synthesized, so an error for a bad one arrives then, not right after the config.
- A stream with no speakable text gets
audio_endon an emptyaudio. return_timestampsand theaac,opusandflacformats are refused with an error for that stream: use the REST endpoint for them. Withwavandmp3, a stream starts with one WAV header or ID3 tag, as from Soniox.- Each sentence is a stream of its own on the router’s connection to Soniox, one at a time per stream of yours: the same Soniox concurrency as your stream would use directly.
REST#
POST /soniox/tts: the audio in the format the body asks for, with Soniox’s own Content-Type. Any other path under /soniox answers 404.
curl "https://api.ttscacherouter.dev/soniox/tts" \ -H "Authorization: Bearer $TTS_CACHE_ROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "tts-rt-v2", "language": "en", "voice": "Adrian", "audio_format": "wav", "text": "Your order has shipped."}' \ --output speech.wavWhat is cached#
- REST: each request as a whole. The same body again replays the same audio.
- WebSocket: each sentence, so a sentence you have sent before with the same config comes from the cache whatever came around it. Sentences share the cache with the router’s own /v1/stream when the settings are the same.
Errors#
A missing or wrong router key gets Soniox’s own refusal: HTTP 401 unauthenticated over REST; on the WebSocket, that error message right after the connection opens, then a close (with the key in a config, an error for that stream). An org with no Soniox key stored gets the same, with a message that links to the dashboard. Errors from Soniox, such as an unknown voice or your Soniox limits, reach you as Soniox sent them; on the WebSocket they end only their stream.
{"stream_id": "", "error_code": 401, "error_type": "unauthenticated", "error_message": "Incorrect API key provided.", "more_info": null, "request_id": null}