Skip to content
Console

Speech

Text to speech

Copy page

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
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.wav

That 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.

JavaScript
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()));
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.

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
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.

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.

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.

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
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.wav

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
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.pcm

Only 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.

JavaScript
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-Rate and X-Stream-Chunking: none.

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 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.