Streaming
Real-time ASR over WebSocket for live transcription, dictation, and voice agents. The endpoint emits partial transcripts as audio streams in, and a final transcript at end-of-utterance.
Streaming language coverage varies. For the current list of supported locales and access details, email [email protected].
TTS streaming
Synthesis is batch-only today via POST /v1/tts; there is no /v1/tts/stream equivalent yet. For voice agent use cases that need low latency, the Fast quality tier (options.qualityTier: "fast") is the lowest- latency path the batch endpoint offers.
Endpoint
WebSocket
wss://api.phonetico.ai/v1/asr/streamProtocol
- Open the WebSocket.
- Send an
initJSON message with your API key andlocaleCode. You have 5 seconds (streamInitTimeoutSecondsinGET /v1/asr/limits) to send it, or the connection closes with code4001. - Wait for
ready. Audio sent before it is held and forwarded in order once the stream opens; if the stream is refused, held audio is discarded and never billed. - Stream raw PCM Int16 binary frames (16 kHz, mono, little-endian).
- Receive
partialmessages as the model produces them.stablePrefixLentells you how many characters of the partial are stable and won't change. - Send
{ "type": "end" }when done, or close the socket. Server emits a finalfinalmessage and closes with1000.
Frames
| Type | Direction | When | Body |
|---|---|---|---|
| init | client → server | First message. Add "retainTranscript": false to keep nothing of this stream | { "type": "init", "apiKey": "pk_live_...", "localeCode": "amh" } |
| ready | server → client | Auth + upstream open succeed | { "type": "ready" } |
| queued | server → client | Upstream is queueing (busy GPU); arrives between init and ready | { "type": "queued" } |
| audio | client → server | After init; frames sent before ready are held and forwarded in order | Binary frames: raw PCM Int16, 16 kHz mono, little-endian |
| partial | server → client | During streaming | { "type": "partial", "text": "...", "stablePrefixLen": 12 } |
| final | server → client | Once, after you send end; transcriptRetainedUntil is null when nothing is kept | { "type": "final", "text": "...", "audioDurationMs": 4200, "confidence": 0.93, "transcriptRetainedUntil": "2026-10-29T14:03:11.000Z" } |
| error | server → client | Credit runs out mid-stream (insufficient_balance), or the key reaches its monthly spend cap (key_spend_cap): later audio is dropped, the audio before it is transcribed and billed, then the stream closes 1000 | { "type": "error", "code": "insufficient_balance", "message": "Stream ended at 60.0 s: ..." } |
| end | client → server | No more audio coming | { "type": "end" } |
| cancel | client → server | Abort the stream | { "type": "cancel" } |
Example
import WebSocket from "ws";
import { readFileSync } from "node:fs";
const ws = new WebSocket("wss://api.phonetico.ai/v1/asr/stream");
ws.on("open", () => {
ws.send(JSON.stringify({
type: "init",
apiKey: "pk_live_your_key_here",
localeCode: "en-US",
}));
});
ws.on("message", (raw) => {
const msg = JSON.parse(raw.toString());
if (msg.type === "ready") {
// Stream raw PCM Int16 16kHz mono in 320-sample (20ms) chunks
const pcm = readFileSync("speech.pcm");
const chunkSize = 640; // 320 samples * 2 bytes
for (let i = 0; i < pcm.length; i += chunkSize) {
ws.send(pcm.subarray(i, i + chunkSize));
}
ws.send(JSON.stringify({ type: "end" }));
} else if (msg.type === "partial") {
process.stdout.write("\r" + msg.text);
} else if (msg.type === "final") {
console.log("\nFinal:", msg.text);
}
});Close codes
| Code | Meaning |
|---|---|
| 1000 | Normal close. Stream completed. |
| 4001 | Auth error. Missing/invalid init message or API key. |
| 4002 | Not enough credit to open. A stream opens holding the cost of its first stretch of audio; an error frame with code insufficient_balance, naming the amount needed and available, arrives first, or key_spend_cap when the hold would take the key past its monthly spend cap. Device keys: monthly minutes used. |
| 4003 | Audio too long. Exceeded the per-stream duration cap. |
| 4004 | Transcription for this language is temporarily unavailable. When found at init, an error frame with code capability_unavailable arrives first. |
| 4005 | Upstream ASR stream error during transcription. |
| 4029 | Request limit reached for this key. The close reason names the window. |
| 4400 | Language or streaming not offered. An error frame with code capability_unavailable, naming both, arrives first. |
| 4403 | The key is not allowed here: not allowed to use transcription (its product scopes; error frame code key_scope) or not from one of its allowed networks (code key_network_not_allowed), or the account has not accepted the current developer terms (code terms_not_accepted). The error frame arrives first. |
Tips
- Send 20-40 ms frames (320-640 samples). Smaller frames waste overhead, larger ones add latency.
stablePrefixLenonpartialmessages lets you commit a prefix to the UI without flicker. The suffix may still change.- A stream carries up to 300 seconds of audio (
maxAudioDurationSeconds). For a longer recording, use a transcription job. - For browsers, capture with
AudioWorkletNode, downsample to 16 kHz, and pack to Int16. TheMediaRecorderWebM/Opus output is not accepted on this endpoint.