TL;DR
Nari Qwen3-TTS now supports input streaming over WebSocket. Send text as your LLM generates it and receive audio before the full response is ready.
LLMs already stream their responses. Now Qwen3-TTS can speak them the same way. There is no need to wait for the final text or stitch together a series of TTS requests.
Start streaming
Connect from your backend:
wss://api.narilabs.com/v1/text-to-speech/{voice_id}/stream-input?model_id=qwen3-tts
Replace {voice_id} with a voice from the voice library. Use qwen3-tts for Standard or qwen3-tts-fast for Fast, and pass Authorization: Bearer YOUR_API_KEY in the handshake. Browser WebSocket clients cannot set this header, so open the connection from your backend.
Then send each piece of text as its own message:
{ "text": " " }
{ "text": "Hello from " }
{ "text": "Nari Labs.", "flush": true }
{ "text": "This is a second segment.", "flush": true }
{ "text": "" }
- Start with exactly one space. It is not billed.
- Include spaces between fragments yourself.
- Use
flush: trueto finish a segment without closing the connection. - Send an empty string when there is no more text.
Audio generation is automatic. It begins once there is enough text, or after you flush a shorter segment. No separate streaming option or manual trigger is required.
Receive audio
Audio comes back on the same connection:
{
"audio": "AAAAAA==",
"isFinal": false
}
Decode and append the chunks in order. The result is 24 kHz, 16-bit little-endian mono PCM with no WAV header. Keep receiving until { "isFinal": true }.
Python example
This example requires websockets>=14 and writes the result to speech.pcm.
import asyncio
import base64
import json
import os
import websockets
VOICE_ID = "diana"
MODEL_ID = "qwen3-tts-fast"
URL = (
f"wss://api.narilabs.com/v1/text-to-speech/{VOICE_ID}"
f"/stream-input?model_id={MODEL_ID}"
)
async def main():
async with websockets.connect(
URL,
additional_headers={
"Authorization": f"Bearer {os.environ['NARI_API_KEY']}"
},
) as websocket:
async def send():
messages = [
{"text": " "},
{"text": "Hello from "},
{"text": "Nari Labs.", "flush": True},
{"text": ""},
]
for message in messages:
await websocket.send(json.dumps(message))
async def receive():
with open("speech.pcm", "wb") as output:
async for raw_message in websocket:
event = json.loads(raw_message)
if audio := event.get("audio"):
output.write(base64.b64decode(audio))
if event.get("isFinal"):
break
await asyncio.gather(send(), receive())
asyncio.run(main())
Play the raw PCM file with FFmpeg’s ffplay:
ffplay -nodisp -autoexit -f s16le -ar 24000 -ac 1 speech.pcm
Input streaming is available now on both Nari Qwen3-TTS endpoints. Read the input streaming guide or the full API reference. If you are building a realtime voice product and want help taking it to production, talk to our engineers.