Streaming
One WebSocket, wss://api.waridi.io/v1/stream, handles real-time speech recognition, real-time speech generation, or both at once. Use it for voice agents, live captions and anything where people are waiting on the other end.
Latency goal. For voice agents, our target is first reply audio in about 500 ms after the caller stops speaking, and always under 800 ms. This is a target for the preview, not a measured result or a guarantee.
Connecting
Open the socket with your API key in the Authorization header, then send a session.start message. Choose stt, tts or duplex as the mode.
import asyncio
from waridi import AsyncWaridi
client = AsyncWaridi()
async def main():
async with client.stream.connect(
mode="stt",
model="sikia-1",
language="sw-TZ",
encoding="pcm_s16le",
sample_rate=16000,
) as stream:
asyncio.create_task(stream.send_file("simu.wav"))
async for event in stream:
if event.type == "transcript.final":
print(event.text)
asyncio.run(main())
import { Waridi } from "@waridi/sdk";
const client = new Waridi();
const stream = await client.stream.connect({
mode: "stt",
model: "sikia-1",
language: "sw-TZ",
encoding: "pcm_s16le",
sampleRate: 16000,
});
stream.on("transcript.partial", (e) => process.stdout.write("\r" + e.text));
stream.on("transcript.final", (e) => console.log("\n" + e.text));
microphone.on("data", (chunk: Buffer) => stream.sendAudio(chunk));
# cURL cannot hold a WebSocket open. To try the raw protocol, use wscat:
npm install -g wscat
wscat -c "wss://api.waridi.io/v1/stream" \
-H "Authorization: Bearer $WARIDI_API_KEY"
# then paste:
{"type": "session.start", "mode": "tts", "model": "sauti-standard", "voice": "zawadi", "format": "opus"}
{"type": "text.append", "text": "Karibu tena!"}
{"type": "text.flush"}
Session options
| Field | Modes | Description |
|---|---|---|
mode |
all | stt, tts or duplex |
model |
all | sikia-1 for stt. sauti-standard or sauti-pro for tts. In duplex, pass stt_model and tts_model |
language |
all | sw-TZ (default), sw-KE or auto for stt |
encoding |
stt | Input audio: pcm_s16le, opus or mulaw |
sample_rate |
all | 8000 for mulaw. 16000 or 24000 recommended otherwise |
voice |
tts | Voice ID |
format |
tts | Output audio: pcm_s16le, opus, mp3 or mulaw |
vad |
stt | Default true. Sends speech.started and speech.ended events |
interim_results |
stt | Default true. Sends transcript.partial events |
Client messages
Audio for recognition is sent as binary frames. Everything else is a JSON text frame.
| Type | Description |
|---|---|
session.start |
First message. Carries the session options above |
| (binary frame) | Raw audio in the declared encoding, 20–100 ms per frame |
text.append |
Text to speak. Can be sent token by token from your LLM |
text.flush |
Speak everything appended so far, even mid-sentence |
response.cancel |
Stop speaking now, for example when the caller interrupts |
session.end |
Finish cleanly. The server sends remaining events, then closes |
Server events
| Type | Description |
|---|---|
session.started |
Session accepted. Includes session_id and the resolved options |
speech.started |
Voice activity detected on the input |
transcript.partial |
Interim text that may still change |
transcript.final |
Stable text for a finished utterance, with start_ms, end_ms and language |
speech.ended |
The speaker paused long enough to count as end of turn |
audio.chunk |
Generated speech. Sent as a binary frame, preceded by this JSON header with seq and duration_ms |
audio.done |
All audio for the last flush has been sent |
error |
Something went wrong. See Errors |
session.ended |
Usage summary, then the server closes the socket |
Example events:
{"type": "session.started", "session_id": "ses_01J9Z8A0TT", "mode": "stt", "model": "sikia-1"}
{"type": "speech.started", "at_ms": 140}
{"type": "transcript.partial", "text": "nataka kulipa"}
{"type": "transcript.final", "text": "Nataka kulipa bili ya maji.", "language": "sw", "start_ms": 140, "end_ms": 1980}
{"type": "speech.ended", "at_ms": 2400}
Interruptions
In duplex mode, if speech.started arrives while you are playing audio, the caller is talking over the agent. Send response.cancel, stop local playback, and wait for the next transcript.final. Waridi Agents handle this for you.
Limits and close codes
- Idle sessions close after 30 seconds without audio or text.
- One session can last up to 60 minutes. Open a new one to continue.
- Concurrent sessions are limited per plan. See Rate limits.
| Close code | Meaning |
|---|---|
1000 |
Normal close after session.end |
4001 |
Authentication failed |
4008 |
Idle timeout |
4029 |
Concurrent session limit reached |
4400 |
Invalid session.start or malformed message |
1011 |
Server error. Safe to reconnect with backoff |