Stream text to speech with the Voice API
Stream text to speech with the Voice API
Bidirectional WebSocket at wss://api.x.ai/v1/tts?language=en&voice=eve&codec=mp3&sample_rate=24000&bit_rate=128000. Send text.delta then text.done. The server returns audio.delta (base64) then audio.done. Unary POST /v1/tts is a separate how-to.
The connection stays open after audio.done, so you can send the next utterance without reconnecting. Cap is 50 concurrent sessions per team. Proxy the socket through your server. Never put the API key in the browser.
There is no total text-length cap on the socket. Each text.delta is capped at 15,000 characters. Unary POST /v1/tts is capped at 15,000 for the whole request.
Multi-turn
import asyncio
import base64
import json
import os
import websockets
async def multi_turn_tts():
uri = (
"wss://api.x.ai/v1/tts"
"?language=en&voice=eve&codec=mp3&sample_rate=24000&bit_rate=128000"
)
async with websockets.connect(
uri,
additional_headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"},
) as ws:
await ws.send(json.dumps({"type": "text.delta", "delta": "Hello from turn one."}))
await ws.send(json.dumps({"type": "text.done"}))
turn1 = bytearray()
async for msg in ws:
event = json.loads(msg)
if event["type"] == "audio.delta":
turn1.extend(base64.b64decode(event["delta"]))
elif event["type"] == "audio.done":
break
await ws.send(json.dumps({"type": "text.delta", "delta": "And hello from turn two."}))
await ws.send(json.dumps({"type": "text.done"}))
turn2 = bytearray()
async for msg in ws:
event = json.loads(msg)
if event["type"] == "audio.delta":
turn2.extend(base64.b64decode(event["delta"]))
elif event["type"] == "audio.done":
break
open("turn1.mp3", "wb").write(turn1)
open("turn2.mp3", "wb").write(turn2)
asyncio.run(multi_turn_tts())
Barge-in: send text.clear. The server replies audio.clear, then accept a new text.delta → text.done on the same socket. session.update can set a replace pronunciation map for later utterances.
Pitfalls
- 50 concurrent WebSocket sessions per team.
- Proxy from the browser. The API key must not ship in client JavaScript.
- Split long strings across
text.deltamessages; each delta maxes at 15,000 characters. - Console API credits are a separate bill from SuperGrok's weekly pool on grok.com.