Skip to main content

POST /tts/bytes

Generates audio from the input transcript. The response body is the raw audio in the requested format, streamed as it’s generated.

Request Body

voice is required — there is no default voice. The only accepted form is the nested object { "mode": "id", "id": "<voice-id>" }; a top-level voice_id is not supported, and mode must be "id". A request that doesn’t resolve to a voice is rejected rather than silently synthesized: missing_voice / missing_voice_id / unknown_voice_mode (400) for a bad specifier, voice_not_found (404) for an unknown id, and missing_transcript (400) for an empty transcript. See Errors for the full envelope.

Length limit

A single request should stay under ~46.5 seconds of speech — the generator’s hard ceiling. The limit is on predicted audio duration, not character count: the estimate is digit-aware and per-language (a digit like 4 is spoken as a whole word, “four”, so it costs far more than a letter), so there’s no single character cap. As a rough en-US guide:
  • ~900 characters of normal prose, but
  • only ~130 digits — phone numbers, amounts, codes, and dates hit the limit with much less text.
The exact threshold shifts slightly by voice language. The check runs before synthesis, so a rejection is never billed. Over the limit returns 400 with error_code: "input_too_long" (message like "transcript predicts ~54.1s of audio, over the 46.5s limit; send shorter text").
For content longer than one request, split it into multiple requests — or, over WebSocket, stream it across turns with continue: true, keeping each turn under the limit.
The request body itself is capped at 64 KB — a body over 65536 bytes returns 413 with error_code: "payload_too_large". Normal requests are nowhere near this.

Output Format

output_format has three fields, each validated against a fixed set. Anything outside these values returns 400 — the API does not silently fall back.
  • The model is natively 22050 Hz — use it to skip resampling; 8000/16000 are resampled server-side.
  • Container by endpoint: wav is available only on /tts/bytes. Streaming endpoints (/tts/sse, /tts/websocket) are raw-only — there’s no place to put a RIFF/WAV header in a chunked stream.
  • pcm_mulaw / pcm_alaw are 8-bit G.711 telephony codecs. Browsers can’t play them directly; decode to linear PCM first (or hand them straight to your telephony stack).

Telephony (G.711)

For Twilio / SIP media streams, request 8 kHz μ-law or A-law raw:
Removed formats. mp3 (container) and pcm_f32le (encoding), plus sample rates 24000 / 44100 / 48000, are not supported and now return 400. Request wav + pcm_s16le and transcode client-side if you need another format.

Models

Billing

1 credit = 1 character of transcript (Unicode code points). The charge is taken before generation and refunded automatically on failure.

Examples

POST /tts/sse

Streams audio over Server-Sent Events for low time-to-first-audio without opening a WebSocket. The request body is identical to /tts/bytes, except the container must be raw (SSE can’t wrap a WAV/RIFF header):
Each event’s data is a JSON object. chunk events carry a base64-encoded slice of raw PCM; a final done event closes the stream:
Billing is identical to /tts/bytes — one pre-charge on the full transcript, confirmed when the stream completes. The same length limit and 64 KB body cap apply. Because they’re checked before synthesis, input_too_long (400) and payload_too_large (413) are returned as a normal HTTP error response before the event stream opens — not as an in-stream event.

WebSocket Streaming

For real-time, low-latency generation over a persistent connection — wss://api.nineninesix.ai/tts/websocket — see the WebSocket Streaming page, which includes the send/receive message schemas and an interactive Connect playground.