This section as Markdown: View Download

Realtime Text to Speech

Build streaming text-to-speech apps with realtime AI models.

Overview

Realtime TTS models convert text into streaming audio. Unlike standard TTS that processes a full prompt and returns an audio file, realtime TTS streams AI-generated speech as a continuous PCM audio stream over a WebSocket connection — in real time. The text prompt is submitted via POST /Run, not over the WebSocket. The WebSocket carries only task events (task_info, task_stream_ready, etc.), binary audio frames, and control messages (task_session_end).

No microphone is required. The flow is one-directional: text goes in, audio comes out.

The flow is:

  1. Run the realtime TTS model via POST /Run with your text prompt in the parameters
  2. Connect to the WebSocket and send task_info with your socketaccesstoken
  3. Wait for task_stream_ready — the model has loaded and is generating audio
  4. Receive AI audio as binary frames and play them
  5. End the session with task_session_end or wait for the stream to finish naturally

How It Differs from Realtime Voice Conversation

Realtime Voice Conversation Realtime Text to Speech
Input Microphone audio (streamed) Text (sent with the run request)
Output AI audio + transcripts AI audio only
Direction Bidirectional (client ↔ server) Server → client only
Microphone Required Not required
Transcripts TRANSCRIPT_USER: / TRANSCRIPT_AI: via task_output None
Use case Interactive voice chat Narration, voiceover, assistants

Run Parameters

Each realtime TTS model has its own set of parameters. Use POST /Tool/Detail to discover the exact parameters for a specific model. See Model Parameters for parameter types.

Browse available realtime TTS models on the models page (filter by Realtime TTS category). Common parameters typically include the input text and an optional reference audio clip for voice cloning. Example run for MOSS-TTS-Realtime:

curl -X POST "https://api.wiro.ai/v1/Run/openmoss/moss-tts-realtime" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "text": "Hello, this is a realtime text-to-speech demo."
  }'

Important: Parameters vary per model. Always check the model's detail page or use /Tool/Detail to get the exact parameter list before integrating. The run response returns a socketaccesstoken used to subscribe to the WebSocket.

Connection & Registration

After running the task, connect to the WebSocket and register with task_info:

var ws = new WebSocket("wss://socket.wiro.ai/v1");

ws.onopen = function() {
  ws.send(JSON.stringify({
    type: "task_info",
    tasktoken: "YOUR_SOCKET_ACCESS_TOKEN"
  }));
};

Note: Both standard and realtime models use type: "task_info" with tasktoken to register on the WebSocket. The registration flow is identical to Realtime Voice Conversation.

Realtime Events

During a realtime TTS session, you'll receive these WebSocket events:

Event Description
task_stream_ready Session is ready — the model is generating audio and will begin sending chunks
task_stream_end The model finished generating audio for the current segment
task_cost Cost update — includes turnCost, cumulativeCost, and usage (raw cost breakdown from the model provider)
task_end The model process has exited. Post-processing follows — wait for task_postprocess_end to close the connection.
task_postprocess_end Post-processing is complete. Safe to close the WebSocket connection.

No task_output events. Unlike voice conversation, TTS sessions do not produce transcript events. The input text is already known (you provided it), and the AI output is audio, not text.

Event Sequence

A typical TTS session produces events in this order:

task_stream_ready     ← model is ready, audio chunks start arriving
[binary frames]       ← PCM audio data (many frames)
task_stream_end       ← audio generation complete for this segment
task_cost             ← cost for this segment
task_end              ← model process exiting
task_postprocess_end  ← safe to close WebSocket

Audio Format

Audio flows in one direction only: server → client. The client does not send any audio.

Property Value
Format PCM (raw, uncompressed)
Bit depth 16-bit signed integer (Int16)
Sample rate 24,000 Hz (24 kHz)
Channels Mono (1 channel)
Byte order Little-endian
Chunk size Variable (typically 200 ms = 4,800 samples = 9,600 bytes)

Binary Frame Format

Every binary WebSocket frame from the server is structured as:

[tasktoken]|[PCM audio data]

The pipe character | (0x7C) separates the token from the raw audio bytes. To extract the audio:

  1. Find the first | byte in the binary frame
  2. Everything after it is raw PCM Int16 audio data
  3. Convert Int16 samples to your playback format (e.g., Float32 for Web Audio API)

Client → server: In TTS mode, you do not send binary audio frames. The only messages you send are task_info (to register) and task_session_end (to end the session).

Receiving AI Audio

AI speech arrives as binary WebSocket frames in PCM Int16 24 kHz format. To play them:

  1. Check if the incoming message is binary (a Blob in JavaScript, bytes in Python) before attempting JSON parse
  2. Find the pipe | separator and extract audio data after it
  3. Convert Int16 → Float32 and create an AudioBuffer
  4. Schedule gapless playback using AudioBufferSourceNode to avoid clicks between chunks

Gapless Playback

Audio arrives in many small chunks. To play them seamlessly:

  • Track a nextPlayTime variable initialized to 0
  • For each chunk, schedule it at max(audioContext.currentTime, nextPlayTime)
  • Advance nextPlayTime by the chunk's duration
  • This ensures chunks play back-to-back with no gaps or overlaps

Ending a Session

To gracefully end a realtime TTS session, send task_session_end:

{
  "type": "task_session_end",
  "tasktoken": "YOUR_SOCKET_ACCESS_TOKEN"
}

After sending this, the server will finish any in-progress generation, send final cost events, and then emit task_postprocess_end. Wait for task_postprocess_end before closing the WebSocket.

For TTS sessions, the stream often ends naturally when the model finishes generating audio for the provided text. In this case, you'll receive task_stream_end followed by task_end without needing to send task_session_end. However, it's good practice to send it explicitly for a clean shutdown, especially if you want to stop playback early.

Safety: If the client disconnects without sending task_session_end, the server automatically terminates the session to prevent the pipeline from running indefinitely (and the provider from continuing to charge). Always send task_session_end explicitly for a clean shutdown.

Insufficient balance: The session is charged turn by turn as it runs. When a charged turn uses up your available balance (the team wallet's balance when you use a team project's API key), the server stops the session right after that turn. That turn is charged in full, and so is any final usage reported while the session closes, so your balance can end slightly below zero. You still receive the final task_cost and task_end events.