Launch

Input Streaming for Nari Qwen3-TTS

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: true to 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.