Speech
Text to speech
Turn text into speech in one request with POST /v1/tts — voices, models, WAV or raw PCM, sample rates and streaming.
POST /v1/tts takes text and answers with the audio in the response body. Use it when you want speech now: a reply to play, a line to cache, a stream to start playing before the sentence is finished.
curl https://api.lumavox.ai/v1/tts \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"text": "Hello! Your order is on its way."}' \ -o hello.wavThat is a complete request: the built-in voice, the default model, a 16-bit mono WAV at 24 kHz. The key needs the tts scope — see Authentication.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://api.lumavox.ai/v1/tts", { method: "POST", headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ text: "Hello! Your order is on its way.", voice_id: "default", }),});if (!response.ok) throw new Error(JSON.stringify(await response.json()));await writeFile("hello.wav", Buffer.from(await response.arrayBuffer()));The request
Section titled “The request”| Field | Default | What it does |
|---|---|---|
text |
— | What to say. 1 to 2,000 characters. |
voice_id |
default |
Who says it: an id from GET /v1/voices. |
model |
eleven_flash_v2_5 |
Which model speaks. Must be one of the voice’s models. |
format |
wav |
wav or pcm. |
sample_rate |
null (24000) |
Hz of the returned audio: 8000, 16000, 22050, 24000 or 44100. |
stream |
false |
Send the audio as it is generated. pcm only. |
provider_options |
null |
Voice settings for this request. |
Unknown fields are refused with a 422 that names them, so a typo in a field name never passes silently.
The full schema is in the reference.
Voices
Section titled “Voices”voice_id is an id from GET /v1/voices. That list holds:
- the built-in voice,
default, a female English voice that is always available; - platform voices, added by the platform and available to every organisation;
- your organisation’s own voices, cloned from a recording or designed from a description.
curl https://api.lumavox.ai/v1/voices \ -H "Authorization: Bearer $API_KEY"A voice your organisation added is usable once its status is ready. Until then, and for an id that is not yours, POST /v1/tts answers 404. See Voices to add your own.
Models
Section titled “Models”Every voice is spoken by ElevenLabs. Each voice lists the models it takes as models in GET /v1/voices; send one of them as model.
model |
Best for |
|---|---|
eleven_flash_v2_5 (default) |
Lowest latency. Anything conversational. |
eleven_multilingual_v2 |
Their long-standing high-quality model. |
eleven_v3 |
Their most expressive. Slower to start, so not for an agent’s turn. |
A model the voice does not list is a 422 whose issue is at model, and whose message names the models the voice accepts.
Formats and sample rates
Section titled “Formats and sample rates”All audio from POST /v1/tts is 16-bit mono.
format |
What you get | Content type |
|---|---|---|
wav |
A complete WAV file, header included. | audio/wav |
pcm |
Raw 16-bit signed little-endian samples, no header. | audio/pcm |
pcm carries nothing that says what rate it is. It is 24 kHz unless you set sample_rate to one of 8000, 16000, 22050, 24000 or 44100. Every rate is produced natively — nothing is resampled. The X-Sample-Rate header always says what was actually produced; read it rather than assuming.
Playing PCM at the wrong rate plays it at the wrong speed and pitch, with no error anywhere.
For mp3 or Opus, use stored audio: POST /v1/audio is queued, and asking for mp3 or opus here is a 422.
Tuning the voice
Section titled “Tuning the voice”provider_options adjusts the voice for one request, on top of the settings the voice was registered with. Leave a field unset, or null, to keep the voice’s own.
| Field | Range | Effect |
|---|---|---|
stability |
0–1 | Low is more expressive and varies more between generations; high is flatter and repeatable. |
similarity_boost |
0–1 | How closely to cling to the original voice. High can reproduce artefacts in the source. |
style |
0–1 | Style exaggeration. Adds latency; leave it unset for anything conversational. |
use_speaker_boost |
true / false |
More similarity, at the cost of latency. |
speed |
0.7–1.2 | 1.0 is the voice’s own pace. |
curl https://api.lumavox.ai/v1/tts \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Take a deep breath. There is no rush.", "model": "eleven_multilingual_v2", "provider_options": {"stability": 0.7, "speed": 0.9} }' \ -o calm.wavStreaming
Section titled “Streaming”Set "stream": true with "format": "pcm" and the audio arrives as it is generated, so playback can start after the first chunk instead of after the whole text.
curl https://api.lumavox.ai/v1/tts \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"text": "This starts playing before it has finished.", "format": "pcm", "stream": true}' \ --no-buffer -o speech.pcmOnly pcm can stream. A WAV header declares the total length of the audio, which is not known until all of it exists. "stream": true with wav is a 422.
const response = await fetch("https://api.lumavox.ai/v1/tts", { method: "POST", headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ text: "Streaming speech.", format: "pcm", stream: true }),});if (!response.ok) throw new Error(JSON.stringify(await response.json()));
let carry = new Uint8Array(0);for await (const chunk of response.body) { // A chunk can end half way through a sample: carry the odd byte over. const bytes = new Uint8Array(carry.length + chunk.length); bytes.set(carry); bytes.set(chunk, carry.length); const whole = bytes.length - (bytes.length % 2); carry = bytes.slice(whole); const samples = new Int16Array(bytes.buffer.slice(0, whole)); play(samples); // your player, at 24000 Hz unless you set sample_rate}What to know about a stream:
- The status comes after the first chunk. If speech cannot start at all you get an error status, never an empty 200.
- A failure after that cannot change the status. The body ends early. Compare what you received against what you expected if a cut-off matters to you.
- The timing headers are left out. A stream sends
Content-Type: audio/pcm,X-Model,X-Sample-RateandX-Stream-Chunking: none.
Response headers
Section titled “Response headers”A whole (non-streamed) response carries:
| Header | Meaning |
|---|---|
X-Request-Id |
This request’s id. Include it when you report a problem. |
X-Model |
The model that spoke. |
X-Sample-Rate |
Hz of the returned audio. |
X-Audio-Seconds |
Length of the audio. |
X-Generation-Seconds |
Time spent producing the speech, network included. |
X-Total-Seconds |
Time the request spent with us, end to end. |
Errors
Section titled “Errors”Errors use the common envelope described in Errors and limits. The ones specific to this endpoint:
| Status | When |
|---|---|
| 400 | The body is not valid JSON. |
| 402 | The organisation is out of credits. |
| 404 | voice_id is unknown, not yours, or not ready yet. |
| 422 | The body failed validation: text empty or over 2,000 characters, an unknown field, a format other than wav or pcm, stream with wav, or a model that is not one of the voice’s models. issues names the field. |
| 429 | Over your organisation’s rate limit. |
| 502 | The speech provider failed. Safe to retry. |
| 503 | Voice lookup is briefly unavailable — retry, or use voice_id: "default", which needs no lookup — or speech is not set up on this deployment. The message says which. |
Related
Section titled “Related”- Stored audio — mp3 and Opus, up to 5,000 characters, kept in your library.
- Voices — clone or design your own.
- Usage — what speech has cost.
- Reference: tts