The voices a key may speak with, and — on voices:write — adding, designing, editing and deleting your own.
When and why to call these: the voices guide.
List available voices
Section titled “List available voices”GET /v1/voices
Every voice this API key may use: the built-in voice, any voices the platform provides, and any your organisation has uploaded. id is what POST /v1/tts takes; name is a label and is not unique. Only a ready voice speaks. One page: next_cursor is always null.
curl "https://api.lumavox.ai/v1/voices" \ -H "Authorization: Bearer $API_KEY"Responses
| Status | Description |
|---|---|
200 |
application/json — Every voice available to this key, built-in first |
401 |
Missing or invalid credentials |
403 |
The key is valid but its scopes do not cover this capability — a key limited to tts gets this from /v1/images rather than a 401 |
422 |
The request failed validation |
429 |
Rate limit exceeded |
503 |
The voices could not be looked up; the message names the built-in voice, which needs no lookup |
200 response body
| Field | Type | Description |
|---|---|---|
data |
array of VoiceInfo | |
data[].id |
string | |
data[].name |
string | |
data[].description |
string | |
data[].kind |
"builtin", "platform", "org" |
builtin: ships with the platform. platform: added by the platform operator, available to everyone. org: uploaded or designed by your organisation and visible only to it. |
data[].status |
"pending", "ready", "failed" |
pending: being cloned, and not yet usable for speech. ready: usable. failed: the clone was refused; error says why, and a retry runs it again. |
data[].duration_seconds |
number or null | Length of the recording it was cloned from, in seconds; null for a voice not cloned from one. |
data[].provider |
"elevenlabs" |
Who generates the voice: elevenlabs, whom we speak to on your behalf, billed at that provider’s own rate. |
data[].models |
array of string | Which models this voice may be spoken with. Send one of these as model; anything else is refused. |
data[].samples |
array of VoiceSample | A spoken sample per model. Always one entry per model, whether or not it has been generated yet. Empty for the built-in voice. |
data[].samples[].model |
string | |
data[].samples[].ready |
boolean | |
data[].samples[].error |
string or null | Why the sample could not be made; null otherwise. |
data[].samples[].url |
string or null | Temporary link to the audio, or null when it is not ready. Expires — read it from a fresh response rather than storing it. |
data[].created_at |
string (date-time) or null | When it was added; null for the built-in voice. |
next_cursor |
string or null | Pass as cursor for the next page; null when this is the last. |
Upload a voice
Section titled “Upload a voice”POST /v1/voices
Needs voices:write. Counts against your rate limit like a generation.
Creates the voice from a recording uploaded to the URL from POST /v1/voices/upload-url: its upload_id, a name, an optional description, and consent=true confirming you have the right to clone this voice — the upload is refused without it.
The voice comes back with status: pending and cannot be used for speech yet — poll GET /v1/voices/{voice_id} until it is ready, or failed with the reason in error. The recording is cloned by ElevenLabs (instant voice cloning) after this answers, so a recording they refuse arrives as a failed status rather than as a 422.
curl -X POST "https://api.lumavox.ai/v1/voices" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"upload_id":"...","name":"..."}'Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
upload_id | string | Required | From the ticket, after the recording was PUT there. |
name | string | Required | At most 120 characters. |
description | string | At most 500 characters. Default "". | |
consent | boolean | true confirms you have the right to clone this voice; the upload is refused without it. Default false. |
Responses
| Status | Description |
|---|---|
202 |
application/json — Accepted. The voice is pending until it is cloned |
400 |
The request could not be read: malformed JSON |
401 |
Missing or invalid credentials |
402 |
Out of credits, or the organisation is suspended — nothing was charged |
403 |
The key is valid but its scopes do not cover this capability — a key limited to tts gets this from /v1/images rather than a 401 |
409 |
No room for another voice (as for the upload URL), the upload never arrived, or it is a voice already |
422 |
The request failed validation |
429 |
Rate limit exceeded |
503 |
Object storage, or the worker, is unavailable |
202 response body
| Field | Type | Description |
|---|---|---|
id |
string | |
name |
string | |
description |
string | |
kind |
"builtin", "platform", "org" |
builtin: ships with the platform. platform: added by the platform operator, available to everyone. org: uploaded or designed by your organisation and visible only to it. |
status |
"pending", "ready", "failed" |
pending: being cloned, and not yet usable for speech. ready: usable. failed: the clone was refused; error says why, and a retry runs it again. |
error |
string or null | Why a failed voice failed; null otherwise. |
source |
"upload", "design" |
upload: cloned from a recording. design: kept from the voice designer. |
design_prompt |
string or null | What a designed voice was described as; null otherwise. |
duration_seconds |
number or null | Length of the recording it was cloned from, in seconds; null for a voice not cloned from one. |
samples |
array of VoiceSample | |
samples[].model |
string | |
samples[].ready |
boolean | |
samples[].error |
string or null | Why the sample could not be made; null otherwise. |
samples[].url |
string or null | Temporary link to the audio, or null when it is not ready. Expires — read it from a fresh response rather than storing it. |
provider |
"elevenlabs" |
Who generates the voice: elevenlabs, whom we speak to on your behalf, billed at that provider’s own rate. |
external_voice_id |
string or null | The provider’s id for the voice; null until it is cloned. |
models |
array of string | |
provider_defaults |
ProviderVoiceSettingsInfo | |
provider_defaults.stability |
number or null | Low is more expressive and less consistent between generations; high is flatter and more repeatable. |
provider_defaults.similarity_boost |
number or null | How closely to cling to the original voice. High can reproduce artefacts in the source. |
provider_defaults.style |
number or null | Style exaggeration. Costs latency; leave unset on anything conversational. |
provider_defaults.use_speaker_boost |
boolean or null | Similarity at the cost of latency. |
provider_defaults.speed |
number or null | 1.0 is the voice’s own pace. |
created_at |
string (date-time) | |
created_by_clerk_user_id |
string or null | The person who added it, from a screen; null otherwise. |
created_by_api_key_id |
string or null | The API key that added it; null otherwise. |
Ask where to upload a voice’s recording
Section titled “Ask where to upload a voice’s recording”POST /v1/voices/upload-url
Needs voices:write. Counts against your rate limit.
A short-lived URL to PUT the recording to, with its type signed in — send exactly that Content-Type and no authentication headers — and the upload_id to create the voice with afterwards (POST /v1/voices). At most the platform’s limits.uploads.voice_bytes (GET /v1/platform/limits). Nothing is created until then.
curl -X POST "https://api.lumavox.ai/v1/voices/upload-url" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"content_type":"audio/mpeg"}'Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
content_type | string | Required | The recording’s type — audio/wav, audio/mpeg, audio/mp4, audio/ogg, audio/webm… — signed into the URL: the PUT must send exactly this Content-Type. |
Responses
| Status | Description |
|---|---|
200 |
application/json — Where to PUT the recording, and the upload to create the voice from |
400 |
The request could not be read: malformed JSON |
401 |
Missing or invalid credentials |
402 |
Out of credits, or the organisation is suspended — nothing was charged |
403 |
The key is valid but its scopes do not cover this capability — a key limited to tts gets this from /v1/images rather than a 401 |
409 |
The organisation already has as many voices as its plan allows — never more than the platform’s limits.org.voices (GET /v1/platform/limits) — or the provider’s account has no room for another. Delete one, or ask for a higher limit. |
422 |
The request failed validation |
429 |
Rate limit exceeded |
503 |
Object storage is unavailable |
200 response body
| Field | Type | Description |
|---|---|---|
upload_id |
string | The voice’s id to be: pass it to the create once the PUT is done. |
url |
string | PUT the recording here with the Content-Type below and no authentication headers, before expires_at. |
content_type |
string | |
expires_at |
string (date-time) | When url stops accepting the recording. |
Design a voice from a description
Section titled “Design a voice from a description”POST /v1/voices/design
Needs voices:write.
Describes a voice in words and gets back several candidates to listen to, as base64 audio. Nothing is saved — keep one with POST /v1/voices/design/save.
Billed per design, whether or not a candidate is kept.
curl -X POST "https://api.lumavox.ai/v1/voices/design" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"...","prompt":"..."}'Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Required | 1–120 characters. |
prompt | string | Required | 10–1000 characters. |
Responses
| Status | Description |
|---|---|
200 |
application/json — Candidates to listen to, and the brief they were designed from |
400 |
The request could not be read: malformed JSON |
401 |
Missing or invalid credentials |
402 |
Out of credits, or the organisation is suspended — nothing was charged |
403 |
The key is valid but its scopes do not cover this capability — a key limited to tts gets this from /v1/images rather than a 401 |
422 |
The request failed validation |
429 |
Rate limit exceeded |
502 |
The designer failed; the message says why |
503 |
Voice design or object storage not configured |
200 response body
| Field | Type | Description |
|---|---|---|
previews |
array of VoiceDesignPreview | |
previews[].id |
string | |
previews[].audio_base64 |
string | |
previews[].media_type |
string | |
previews[].duration_seconds |
number | |
previews[].long_enough |
boolean | Whether the preview is long enough to be kept as a voice. |
description |
string | The brief the previews were designed from. |
script |
string | What the previews say. |
improved |
boolean | Whether the description was rewritten into a brief; false when it was used as it came. |
Keep a designed voice
Section titled “Keep a designed voice”POST /v1/voices/design/save
Needs voices:write.
Turns one of the candidates from POST /v1/voices/design into a voice: send back its id as preview_id, a name, and the prompt. Not billed — the design was. The voice is ready at once; its spoken samples follow.
curl -X POST "https://api.lumavox.ai/v1/voices/design/save" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"...","preview_id":"..."}'Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Required | 1–120 characters. |
preview_id | string | Required | The chosen preview’s id, from the design’s answer. 1–64 characters. |
description | string | At most 500 characters. Default "". | |
prompt | string | At most 1000 characters. Default "". |
Responses
| Status | Description |
|---|---|
202 |
application/json — Accepted. The voice is ready; its samples follow |
400 |
The request could not be read: malformed JSON |
401 |
Missing or invalid credentials |
402 |
Out of credits, or the organisation is suspended — nothing was charged |
403 |
The key is valid but its scopes do not cover this capability — a key limited to tts gets this from /v1/images rather than a 401 |
409 |
The organisation already has as many voices as its plan allows — never more than the platform’s limits.org.voices (GET /v1/platform/limits) — or the provider’s account has no room for another. Delete one, or ask for a higher limit. |
422 |
The request failed validation |
429 |
Rate limit exceeded |
502 |
The provider could not keep the preview |
503 |
Object storage, or the worker, is unavailable |
202 response body
| Field | Type | Description |
|---|---|---|
id |
string | |
name |
string | |
description |
string | |
kind |
"builtin", "platform", "org" |
builtin: ships with the platform. platform: added by the platform operator, available to everyone. org: uploaded or designed by your organisation and visible only to it. |
status |
"pending", "ready", "failed" |
pending: being cloned, and not yet usable for speech. ready: usable. failed: the clone was refused; error says why, and a retry runs it again. |
error |
string or null | Why a failed voice failed; null otherwise. |
source |
"upload", "design" |
upload: cloned from a recording. design: kept from the voice designer. |
design_prompt |
string or null | What a designed voice was described as; null otherwise. |
duration_seconds |
number or null | Length of the recording it was cloned from, in seconds; null for a voice not cloned from one. |
samples |
array of VoiceSample | |
samples[].model |
string | |
samples[].ready |
boolean | |
samples[].error |
string or null | Why the sample could not be made; null otherwise. |
samples[].url |
string or null | Temporary link to the audio, or null when it is not ready. Expires — read it from a fresh response rather than storing it. |
provider |
"elevenlabs" |
Who generates the voice: elevenlabs, whom we speak to on your behalf, billed at that provider’s own rate. |
external_voice_id |
string or null | The provider’s id for the voice; null until it is cloned. |
models |
array of string | |
provider_defaults |
ProviderVoiceSettingsInfo | |
provider_defaults.stability |
number or null | Low is more expressive and less consistent between generations; high is flatter and more repeatable. |
provider_defaults.similarity_boost |
number or null | How closely to cling to the original voice. High can reproduce artefacts in the source. |
provider_defaults.style |
number or null | Style exaggeration. Costs latency; leave unset on anything conversational. |
provider_defaults.use_speaker_boost |
boolean or null | Similarity at the cost of latency. |
provider_defaults.speed |
number or null | 1.0 is the voice’s own pace. |
created_at |
string (date-time) | |
created_by_clerk_user_id |
string or null | The person who added it, from a screen; null otherwise. |
created_by_api_key_id |
string or null | The API key that added it; null otherwise. |
Read a voice
Section titled “Read a voice”GET /v1/voices/{voice_id}
One of your organisation’s own voices, with its status — pending while the upload is being cloned, failed with the reason in error, ready otherwise. Only voices your organisation owns.
curl "https://api.lumavox.ai/v1/voices/$VOICE_ID" \ -H "Authorization: Bearer $API_KEY"Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
voice_id | string | Required |
Responses
| Status | Description |
|---|---|
200 |
application/json — The voice |
400 |
The request could not be read: malformed JSON |
401 |
Missing or invalid credentials |
403 |
The key is valid but its scopes do not cover this capability — a key limited to tts gets this from /v1/images rather than a 401 |
404 |
Unknown voice |
422 |
The request failed validation |
429 |
Rate limit exceeded |
200 response body
| Field | Type | Description |
|---|---|---|
id |
string | |
name |
string | |
description |
string | |
kind |
"builtin", "platform", "org" |
builtin: ships with the platform. platform: added by the platform operator, available to everyone. org: uploaded or designed by your organisation and visible only to it. |
status |
"pending", "ready", "failed" |
pending: being cloned, and not yet usable for speech. ready: usable. failed: the clone was refused; error says why, and a retry runs it again. |
error |
string or null | Why a failed voice failed; null otherwise. |
source |
"upload", "design" |
upload: cloned from a recording. design: kept from the voice designer. |
design_prompt |
string or null | What a designed voice was described as; null otherwise. |
duration_seconds |
number or null | Length of the recording it was cloned from, in seconds; null for a voice not cloned from one. |
samples |
array of VoiceSample | |
samples[].model |
string | |
samples[].ready |
boolean | |
samples[].error |
string or null | Why the sample could not be made; null otherwise. |
samples[].url |
string or null | Temporary link to the audio, or null when it is not ready. Expires — read it from a fresh response rather than storing it. |
provider |
"elevenlabs" |
Who generates the voice: elevenlabs, whom we speak to on your behalf, billed at that provider’s own rate. |
external_voice_id |
string or null | The provider’s id for the voice; null until it is cloned. |
models |
array of string | |
provider_defaults |
ProviderVoiceSettingsInfo | |
provider_defaults.stability |
number or null | Low is more expressive and less consistent between generations; high is flatter and more repeatable. |
provider_defaults.similarity_boost |
number or null | How closely to cling to the original voice. High can reproduce artefacts in the source. |
provider_defaults.style |
number or null | Style exaggeration. Costs latency; leave unset on anything conversational. |
provider_defaults.use_speaker_boost |
boolean or null | Similarity at the cost of latency. |
provider_defaults.speed |
number or null | 1.0 is the voice’s own pace. |
created_at |
string (date-time) | |
created_by_clerk_user_id |
string or null | The person who added it, from a screen; null otherwise. |
created_by_api_key_id |
string or null | The API key that added it; null otherwise. |
Rename or re-describe a voice
Section titled “Rename or re-describe a voice”PATCH /v1/voices/{voice_id}
Needs voices:write.
A partial update: fields you omit are left alone. There is no way to replace the recording — upload a new voice and delete the old one.
curl -X PATCH "https://api.lumavox.ai/v1/voices/$VOICE_ID" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{}'Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
voice_id | string | Required |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name | string | 1–120 characters. | |
description | string or null | At most 500 characters. | |
models | array of string | At least 1 items. | |
provider_defaults | ProviderVoiceSettings or null | ||
provider_defaults.stability | number or null | Low is more expressive and less consistent between generations; high is flatter and more repeatable. 0–1. | |
provider_defaults.similarity_boost | number or null | How closely to cling to the original voice. High can reproduce artefacts in the source. 0–1. | |
provider_defaults.style | number or null | Style exaggeration. Costs latency; leave unset on anything conversational. 0–1. | |
provider_defaults.use_speaker_boost | boolean or null | Similarity at the cost of latency. | |
provider_defaults.speed | number or null | 1.0 is the voice’s own pace. 0.7–1.2. |
Responses
| Status | Description |
|---|---|
200 |
application/json — The voice as it now stands |
400 |
The request could not be read: malformed JSON |
401 |
Missing or invalid credentials |
403 |
The key is valid but its scopes do not cover this capability — a key limited to tts gets this from /v1/images rather than a 401 |
404 |
Unknown voice |
422 |
The request failed validation |
429 |
Rate limit exceeded |
200 response body
| Field | Type | Description |
|---|---|---|
id |
string | |
name |
string | |
description |
string | |
kind |
"builtin", "platform", "org" |
builtin: ships with the platform. platform: added by the platform operator, available to everyone. org: uploaded or designed by your organisation and visible only to it. |
status |
"pending", "ready", "failed" |
pending: being cloned, and not yet usable for speech. ready: usable. failed: the clone was refused; error says why, and a retry runs it again. |
error |
string or null | Why a failed voice failed; null otherwise. |
source |
"upload", "design" |
upload: cloned from a recording. design: kept from the voice designer. |
design_prompt |
string or null | What a designed voice was described as; null otherwise. |
duration_seconds |
number or null | Length of the recording it was cloned from, in seconds; null for a voice not cloned from one. |
samples |
array of VoiceSample | |
samples[].model |
string | |
samples[].ready |
boolean | |
samples[].error |
string or null | Why the sample could not be made; null otherwise. |
samples[].url |
string or null | Temporary link to the audio, or null when it is not ready. Expires — read it from a fresh response rather than storing it. |
provider |
"elevenlabs" |
Who generates the voice: elevenlabs, whom we speak to on your behalf, billed at that provider’s own rate. |
external_voice_id |
string or null | The provider’s id for the voice; null until it is cloned. |
models |
array of string | |
provider_defaults |
ProviderVoiceSettingsInfo | |
provider_defaults.stability |
number or null | Low is more expressive and less consistent between generations; high is flatter and more repeatable. |
provider_defaults.similarity_boost |
number or null | How closely to cling to the original voice. High can reproduce artefacts in the source. |
provider_defaults.style |
number or null | Style exaggeration. Costs latency; leave unset on anything conversational. |
provider_defaults.use_speaker_boost |
boolean or null | Similarity at the cost of latency. |
provider_defaults.speed |
number or null | 1.0 is the voice’s own pace. |
created_at |
string (date-time) | |
created_by_clerk_user_id |
string or null | The person who added it, from a screen; null otherwise. |
created_by_api_key_id |
string or null | The API key that added it; null otherwise. |
Delete a voice
Section titled “Delete a voice”DELETE /v1/voices/{voice_id}
Needs voices:write.
Removes the voice, its samples, and the voice we made for it at the provider. A POST /v1/tts naming the id afterwards is a 404.
curl -X DELETE "https://api.lumavox.ai/v1/voices/$VOICE_ID" \ -H "Authorization: Bearer $API_KEY"Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
voice_id | string | Required |
Responses
| Status | Description |
|---|---|
204 |
Deleted |
400 |
The request could not be read: malformed JSON |
401 |
Missing or invalid credentials |
403 |
The key is valid but its scopes do not cover this capability — a key limited to tts gets this from /v1/images rather than a 401 |
404 |
Unknown voice |
422 |
The request failed validation |
429 |
Rate limit exceeded |
Retry a voice, or regenerate its samples
Section titled “Retry a voice, or regenerate its samples”POST /v1/voices/{voice_id}/sample
Needs voices:write. Counts against your rate limit like a generation.
A failed voice has its clone run again; a ready one has its spoken samples regenerated; a pending one is already queued and answers 409.
curl -X POST "https://api.lumavox.ai/v1/voices/$VOICE_ID/sample" \ -H "Authorization: Bearer $API_KEY"Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
voice_id | string | Required |
Responses
| Status | Description |
|---|---|
200 |
application/json — The voice, pending again where a clone was requeued |
400 |
The request could not be read: malformed JSON |
401 |
Missing or invalid credentials |
402 |
Out of credits, or the organisation is suspended — nothing was charged |
403 |
The key is valid but its scopes do not cover this capability — a key limited to tts gets this from /v1/images rather than a 401 |
404 |
Unknown voice |
409 |
Still being processed — nothing to retry yet |
422 |
The request failed validation |
429 |
Rate limit exceeded |
503 |
Object storage, or the worker, is unavailable |
200 response body
| Field | Type | Description |
|---|---|---|
id |
string | |
name |
string | |
description |
string | |
kind |
"builtin", "platform", "org" |
builtin: ships with the platform. platform: added by the platform operator, available to everyone. org: uploaded or designed by your organisation and visible only to it. |
status |
"pending", "ready", "failed" |
pending: being cloned, and not yet usable for speech. ready: usable. failed: the clone was refused; error says why, and a retry runs it again. |
error |
string or null | Why a failed voice failed; null otherwise. |
source |
"upload", "design" |
upload: cloned from a recording. design: kept from the voice designer. |
design_prompt |
string or null | What a designed voice was described as; null otherwise. |
duration_seconds |
number or null | Length of the recording it was cloned from, in seconds; null for a voice not cloned from one. |
samples |
array of VoiceSample | |
samples[].model |
string | |
samples[].ready |
boolean | |
samples[].error |
string or null | Why the sample could not be made; null otherwise. |
samples[].url |
string or null | Temporary link to the audio, or null when it is not ready. Expires — read it from a fresh response rather than storing it. |
provider |
"elevenlabs" |
Who generates the voice: elevenlabs, whom we speak to on your behalf, billed at that provider’s own rate. |
external_voice_id |
string or null | The provider’s id for the voice; null until it is cloned. |
models |
array of string | |
provider_defaults |
ProviderVoiceSettingsInfo | |
provider_defaults.stability |
number or null | Low is more expressive and less consistent between generations; high is flatter and more repeatable. |
provider_defaults.similarity_boost |
number or null | How closely to cling to the original voice. High can reproduce artefacts in the source. |
provider_defaults.style |
number or null | Style exaggeration. Costs latency; leave unset on anything conversational. |
provider_defaults.use_speaker_boost |
boolean or null | Similarity at the cost of latency. |
provider_defaults.speed |
number or null | 1.0 is the voice’s own pace. |
created_at |
string (date-time) | |
created_by_clerk_user_id |
string or null | The person who added it, from a screen; null otherwise. |
created_by_api_key_id |
string or null | The API key that added it; null otherwise. |