Narrate a chapter to disk without holding the whole take in memory, and without a seam where one request ends and the next begins.

Before you start

Create one under Developer → API keys in the console. A key carries full access to your organization — never ship it to a browser. See Authentication.
Any voice_id from GET /v1/voices. Built-in voices and ones you cloned both look like ev_1a2b3c4d. An OpenAI voice name such as alloy also resolves, to the model’s own default.

What streaming does and does not change

This file-download example uses complete-file synthesis: MP3 and WAV are encoded after the utterance finishes, then written in chunks without buffering the whole response in your application. For phrase-incremental playback, use nur-tts-v1 with response_format: "pcm" and stream_format: "sse". Each audio delta contains base64 signed PCM16, mono, 24 kHz. This can begin playing before all phrases finish; it is not an MP3 or WAV stream. What you get from streaming is memory: you write chunks as they arrive instead of buffering the whole file. On a long document that is the difference between a steady 64 KB and holding a hundred megabytes. What actually shortens the wait is asking for less at a time — which you have to do anyway, because a request takes at most 4,096 characters.

The recipe

Raise the read timeout, or drop it. A long request can take longer than a default client timeout, and a request cut mid-synthesis has still generated — and still billed — the audio you did not keep. The EESI SDKs read-time out at 60 seconds for the same reason; this recipe uses a raw HTTP client so the timeout is yours to set.
nur-tts-v1 returns mp3, wav or pcm. Asking for a format it does not advertise is a 400 naming the ones that would have worked — the six values in the request schema are not what any one model actually serves.

Text longer than one request

The schema caps input at 4,096 characters. Split on sentence boundaries rather than mid-word, and synthesize each part.
The limit is 4,096 and the default above is 3,500, which leaves room for a sentence longer than you expected rather than failing the whole run on one. A rejected request costs nothing — the length check runs before any synthesis.
The second loop is the one that matters on machine-generated input. Without it a 10,000-character run with no punctuation comes back as a single 10,000-character part and a 6,000-character opening sentence comes back whole, and each is a 400 partway through a long run — after the earlier parts have already been generated and billed. Prose almost never hits this; transcripts, logs and model output do.
A mid-word cut is audible. The last resort exists so a run finishes rather than dies, not because it sounds right — if your input can contain a 10,000-character token, split it upstream on something meaningful.

The voice does not drift between parts

This is the reason splitting is safe. Every voice — built-in or cloned — resolves to the same stored reference clip on every request, so part twelve sounds like part one. Without reference audio the synthesis model would sample a new voice per generation and a long piece would change speaker halfway. See Voices.
Concatenate the decoded audio, not the encoded files. Joining two MP3s byte-wise produces a stream most players will play and some will not, and the seam is audible. Decode each part, join the samples, encode once — or ask for pcm, which concatenates cleanly, and encode at the end.

Server-sent events instead

Set stream_format: "sse" to receive speech.audio.delta events and a final speech.audio.done. With response_format: "pcm", synthesis runs incrementally. With file formats such as MP3 or WAV, synthesis still completes first and SSE changes only the transport framing. For saving a completed file, raw bytes remain simpler.

What it costs, and what it leaves behind

$0.001 per minute of generated audio, metered on the audio the request produced. A request refused for length or an unsupported format costs nothing. Every generation is kept: the response carries an X-EESI-Generation-Id header, and GET /v1/generations lists past synthesis with its audio, so you can re-download a part rather than paying to regenerate it. Every response also carries X-Synthetic-Audio, and the marker is written into the container itself — a LIST/INFO chunk in wav, an ID3v2 TXXX frame in mp3. pcm has no container, so a pcm file carries no embedded marker; the header on the response is the only copy, and encoding it yourself drops it. See Synthetic audio marking.

Next

Text to speech

Every field on the endpoint, and what each costs you.

Voices

Why a voice holds across requests.

Clone a voice

Make the voice this recipe narrates in.

Latency

Why length decides the wait, and what does not.