---
title: "Stream speech synthesis with low latency"
method: POST
path: "/api/v1/tts/stream"
tags: ["Public API v1"]
---

# Stream speech synthesis with low latency

`POST /api/v1/tts/stream`

Streams audio as it is generated so playback can start within ~1–2s instead of waiting for the whole text. The response is a binary stream (Content-Type: application/octet-stream) of length-prefixed frames: repeat { read a 4-byte big-endian uint32 = N, then read N bytes = one audio frame } until EOF. A COMPLETE stream ends with a zero-length frame (N = 0) — if it is missing, the stream was cut short and you should discard the audio; it is not billed. Each frame is a self-contained WAV by default; pass `outputFormat` for raw headerless `pcm`/`ulaw` to feed a telephony or voice-agent pipeline, or mp3/opus to cut bandwidth. DECODE EACH FRAME SEPARATELY — frames are encoded independently, so concatenating mp3 or opus frames produces gaps and clicks; only pcm/ulaw can simply be joined. The actual sample rate is in X-Sample-Rate, the encoding in X-Output-Format, the framing in X-Stream-Format. PRICE: the same per-character rate as POST /v1/tts, but every stream runs on the v4 engine and is therefore charged at the v4 engine multiplier — higher than the v3 one. Sizing a budget with POST /v1/tts on v3 and then streaming the same text will cost more; GET /v1/engines returns the live multipliers. (+ the AI surcharge only if you opt in with `aiRefine: true`, which also moderates and normalizes the text first.) `voiceId` accepts a preset or one of your own cloned voices (`clone_…` from POST /v1/voices) — a clone costs exactly what a preset costs on the engine you name; cloning adds no surcharge of its own. Note the engine you name is the engine that renders: a clone is NOT currently checked against the engine it was enrolled on. Clones have enrolled on v4 since 2026-08-28, so for those the two always agree; an older v3-enrolled clone named with v4 will render on v4 from a v3 reference clip and will not sound like the intended voice. Re-enrol it instead.

## Request body

- PublicStreamRequestDto
  - `text` string, required — Vietnamese text to synthesize and stream.
  - `voiceId` string — Voice ID from GET /v1/voices: a preset, or one of your own cloned voices (`clone_…`, created with POST /v1/voices — an admin-published clone works too). A voice is only usable on the engine it belongs to. Omit to use the default voice.
  - `emotion` 'natural' | 'storytelling' | 'tin_tuc' | 'tu_nhien' | 'doc_truyen' | 'emotion_0' | 'emotion_1' | 'emotion_2' | 'emotion_3' | 'emotion_4' | 'emotion_5' | 'emotion_6' | 'emotion_7' | 'emotion_8' | 'emotion_9' | 'emotion_10' | 'emotion_11' | 'emotion_12' | 'emotion_13' | 'emotion_14' | 'emotion_15' | 'emotion_16' | 'emotion_17' | 'emotion_18' | 'emotion_19'
  - `speed` number — Playback speed (0.5–2.0).
  - `engine` 'v4' — Streaming runs on v4 only — v3 is rejected here. You may omit this: v4 is the default engine. NOTE ON PRICE: because every stream runs on v4, it is charged at the v4 engine multiplier, which is higher than the v3 one. If you priced your workload with POST /v1/tts on v3, streaming the same text will cost more. GET /v1/engines returns the live multipliers.
  - `aiRefine` boolean — AI text refinement. Defaults to FALSE on the public API: the text is synthesized as submitted, with no AI moderation and no pronunciation normalization, billed at the plain per-character rate. Set true to run the same AI step the web app uses — formulas, acronyms and mixed-in English are read correctly and the content is checked — billed with the AI surcharge and one extra model round-trip of latency. Deterministic text preparation is applied either way.
  - `outputFormat` 'wav' | 'mp3' | 'opus' | 'pcm' | 'ulaw' — Encoding of the audio inside each frame. `wav` (default) keeps every frame self-contained and is what existing clients expect. `mp3` and `opus` cut bandwidth roughly six-fold. `pcm` and `ulaw` are HEADERLESS raw audio for telephony and voice-agent pipelines — read the rate from the X-Sample-Rate response header. The frame FRAMING is unchanged in every case, so the end-of-stream marker still means the same thing.
  - `sampleRate` 8000 | 16000 | 22050 | 24000 | 44100 | 48000 — Output sample rate in Hz. Omit for the engine native rate (48000). `opus` is always 48000 and `ulaw` always 8000 — passing a conflicting value is rejected rather than quietly ignored.

## Response `200`

A binary stream of length-prefixed frames: repeat { 4-byte big-endian uint32 N, then N bytes }. A COMPLETE stream ends with N = 0; without that marker the audio was cut short — discard it (it is not billed). `X-Sample-Rate`, `X-Output-Format` and `X-Stream-Format` describe the frames.

## Other responses

- `400` — Unknown voice, a `clone_…` voice that is not yours (or whose reference is gone), or a format/sample-rate pair that cannot be honoured (`opus` is always 48000, `ulaw` always 8000). Unlike /v1/audio/speech`s `stream_format`, every `outputFormat` is accepted here — the framing is what keeps mp3/opus usable, one frame at a time.
- `401` — API key missing, malformed, or revoked.
- `403` — Your plan does not include this engine or feature, or the grant is out of tokens / expired.
- `429` — Rate limit or token quota exceeded. When the limit came from the application the response carries `Retry-After` (seconds) and the `X-RateLimit-*` headers and is counted against your API key; a 429 with none of those headers came from the edge proxy and is counted against your source address, shared with every other key calling from it. Back off on either.
- `502` — Every worker for this engine failed. The charge is refunded automatically. Distinct from 503: a 503 means the fleet is alive but out of streaming capacity (see the STREAM_BUSY code) and retrying — or the queued POST /v1/tts path — will work.
- `503` — No worker available for the requested engine or format. Retry shortly.

---

[API](https://skmtc.dev/vieneu/apis/vieneu-api.md) · [All operations](https://skmtc.dev/vieneu/apis/vieneu-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/vieneu/vieneu-api/revisions/11c8166a6c4d/schema)
