# Voices

> List the voices you can speak with, clone one from a recording, design one from a description, and edit or delete your own.

A voice is what `voice_id` names on [text to speech](https://docs.lumavox.ai/speech/text-to-speech.md), [stored audio](https://docs.lumavox.ai/speech/stored-audio.md) and an agent. You can speak with three kinds:

| `kind` | What it is | Who can change it |
|---|---|---|
| `builtin` | `default`, a female English voice. Always available, even when nothing else is. | nobody |
| `platform` | Voices the platform provides to every organisation. | nobody, through the API |
| `org` | Your organisation's own: cloned from a recording, or designed from a description. Only your organisation sees them. | you, with `voices:write` |

**Reading needs `tts`; changing needs `voices:write`.** A key that may speak cannot delete the voices your organisation made. See [Authentication](https://docs.lumavox.ai/authentication.md).

## List voices

```bash title="curl"
curl https://api.lumavox.ai/v1/voices \
  -H "Authorization: Bearer $API_KEY"
```

```json title="Response"
{
  "data": [
    {
      "id": "default",
      "name": "Default",
      "description": "Female English voice (ElevenLabs' Sarah). Built into the platform; always available.",
      "kind": "builtin",
      "status": "ready",
      "duration_seconds": null,
      "provider": "elevenlabs",
      "models": ["eleven_flash_v2_5", "eleven_multilingual_v2", "eleven_v3"],
      "samples": [],
      "created_at": null
    },
    {
      "id": "voi_q1W2e3R4t5Y6u7I8",
      "name": "Narrator",
      "description": "Warm, measured, British.",
      "kind": "org",
      "status": "ready",
      "duration_seconds": 42.7,
      "provider": "elevenlabs",
      "models": ["eleven_flash_v2_5", "eleven_multilingual_v2", "eleven_v3"],
      "samples": [
        {"model": "eleven_flash_v2_5", "ready": true, "error": null, "url": "https://…"},
        {"model": "eleven_multilingual_v2", "ready": true, "error": null, "url": "https://…"},
        {"model": "eleven_v3", "ready": false, "error": null, "url": null}
      ],
      "created_at": "2026-10-20T14:02:11.000Z"
    }
  ],
  "next_cursor": null
}
```

- Every voice is in one page: `next_cursor` is always `null` ([Conventions](https://docs.lumavox.ai/conventions.md#lists-and-pages)). The built-in voice is always first.
- `id` is what you send as `voice_id`. `name` is a label and is not unique.
- `status` is `ready` for every voice that can speak; only a voice your organisation is adding can be `pending` or `failed`.
- `models` is what the voice can be spoken with — send one of them as `model`.
- `samples` holds one spoken sample per model, generated or not yet. A sample's `url` is temporary: read it from a fresh response rather than storing it.
- `duration_seconds` is the length of the recording a voice was cloned from, and `null` for one that was not.

## Read one of your voices

`GET /v1/voices/{voice_id}` returns one of **your organisation's** voices in full. A platform voice or the built-in voice is a 404 here; they are in the list.

```bash title="curl"
curl https://api.lumavox.ai/v1/voices/voi_a1B2c3D4e5F6g7H8 \
  -H "Authorization: Bearer $API_KEY"
```

| `status` | Meaning |
|---|---|
| `pending` | Being cloned. It cannot speak yet. |
| `ready` | Usable. |
| `failed` | The clone failed. `error` says why. Retry it, or delete it. |

```json title="Response"
{
  "id": "voi_a1B2c3D4e5F6g7H8",
  "name": "Narrator",
  "description": "Warm, measured, British.",
  "kind": "org",
  "status": "ready",
  "error": null,
  "source": "upload",
  "design_prompt": null,
  "duration_seconds": 42.7,
  "samples": [
    {"model": "eleven_flash_v2_5", "ready": true, "error": null, "url": "https://…"},
    {"model": "eleven_multilingual_v2", "ready": true, "error": null, "url": "https://…"},
    {"model": "eleven_v3", "ready": true, "error": null, "url": "https://…"}
  ],
  "provider": "elevenlabs",
  "external_voice_id": "pNInz6obpgDQGcFmaJgB",
  "models": ["eleven_flash_v2_5", "eleven_multilingual_v2", "eleven_v3"],
  "provider_defaults": {
    "stability": 0.6,
    "similarity_boost": null,
    "style": null,
    "use_speaker_boost": null,
    "speed": null
  },
  "created_at": "2026-10-20T14:02:11.000Z",
  "created_by_clerk_user_id": null,
  "created_by_api_key_id": "0193a8c2-51f0-7d2e-8b6a-4c1e9f3d2a70"
}
```

Beyond the list's fields, the detail has `error`, `source` (`upload` or `design`), `design_prompt` (what a designed voice was described as), `external_voice_id` (the provider's own id, `null` until it is cloned), `provider_defaults` (the settings it speaks with unless a request overrides them, each `null` where it keeps the provider's) and who added it — `created_by_clerk_user_id` for a person in the console, `created_by_api_key_id` for a key, the other `null`.

## Clone a voice from a recording

Cloning takes three calls: ask where to upload, upload the file, then create the voice. The recording goes straight to storage and never passes through the API.

**1. Ask for an upload URL**, naming the recording's type:

```bash title="curl"
curl https://api.lumavox.ai/v1/voices/upload-url \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content_type": "audio/mpeg"}'
```

```json title="Response"
{
  "upload_id": "voi_a1B2c3D4e5F6g7H8",
  "url": "https://…",
  "content_type": "audio/mpeg",
  "expires_at": "2026-10-24T09:45:00.000Z"
}
```

Accepted types: `audio/wav`, `audio/x-wav`, `audio/wave`, `audio/vnd.wave`, `audio/mpeg`, `audio/mp3`, `audio/mp4`, `audio/x-m4a`, `audio/aac`, `audio/ogg`, `audio/opus`, `audio/webm`, `audio/flac` and `audio/x-flac`. Anything else is a 422.

**2. `PUT` the recording to `url`**, with exactly the `Content-Type` you named and no `Authorization` header — the URL carries its own signature. It stops accepting the recording at `expires_at`, fifteen minutes after you asked. Its size is at most the platform's `limits.uploads.voice_bytes` ([Limits](https://docs.lumavox.ai/limits.md)).

```bash title="curl"
curl -X PUT "<url>" \
  -H "Content-Type: audio/mpeg" \
  --data-binary @narrator.mp3
```

**3. Create the voice** from the upload:

```bash title="curl"
curl https://api.lumavox.ai/v1/voices \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "upload_id": "voi_a1B2c3D4e5F6g7H8",
    "name": "Narrator",
    "description": "Warm, measured, British.",
    "consent": true
  }'
```

**`consent: true` is required.** It confirms you have the right to clone this voice; without it the create is refused with a 422.

It answers **202** with the voice in `status: pending`. **It cannot speak yet**: poll `GET /v1/voices/{voice_id}` until it is `ready`, or `failed` with the reason in `error`. The clone happens after the create answers, so a recording the provider refuses shows up as `failed`, not as an error on this call.

A cloned voice can be spoken with every model: `eleven_flash_v2_5`, `eleven_multilingual_v2` and `eleven_v3`.

## Design a voice from a description

Describe a voice in words and listen to candidates before you keep one.

**1. Design:**

```bash title="curl"
curl https://api.lumavox.ai/v1/voices/design \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ship AI",
    "prompt": "A calm, slightly synthetic female voice, mid-thirties, precise diction, like a starship computer."
  }'
```

`name` is 1–120 characters and `prompt` 10–1,000.

```json title="Response (abridged)"
{
  "previews": [
    {
      "id": "<a preview id>",
      "audio_base64": "SUQzBAAAAAAA…",
      "media_type": "audio/mpeg",
      "duration_seconds": 19.4,
      "long_enough": true
    }
  ],
  "description": "A calm, precise female voice in her thirties…",
  "script": "Systems nominal. Course plotted for the outer ring…",
  "improved": true
}
```

- `previews` are candidates, as base64 audio. How many come back can vary.
- `long_enough` is `false` for a preview under 18 seconds — too short to judge a voice by. It can still be kept.
- `description` is the brief the previews were designed from. Your prompt may have been rewritten into a fuller one; `improved` says whether it was.
- `script` is what the previews say.

**Nothing is saved by this call, and it is billed per design**, whether or not you keep a candidate.

To listen to a preview:

```bash title="curl"
curl … | jq -r '.previews[0].audio_base64' | base64 --decode > preview.mp3
```

**2. Keep one:**

```bash title="curl"
curl https://api.lumavox.ai/v1/voices/design/save \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ship AI",
    "preview_id": "<a preview id>",
    "prompt": "A calm, slightly synthetic female voice, mid-thirties, precise diction, like a starship computer."
  }'
```

It answers **202** with the voice already `ready` — you can speak with it at once. Its spoken samples follow. Keeping is not billed; the design was. An optional `description` (up to 500 characters) is stored with it.

## Edit a voice

`PATCH /v1/voices/{voice_id}` changes only the fields you send:

| Field | Effect |
|---|---|
| `name` | Rename it. 1–120 characters; cannot be `null`. |
| `description` | Up to 500 characters. `null` clears it; it reads back as `""`. |
| `models` | Which models it may be spoken with. Each must be one the provider offers, or it is a 422 at `models`. |
| `provider_defaults` | The settings it speaks with by default: `stability`, `similarity_boost`, `style`, `use_speaker_boost`, `speed` — the same fields as [`provider_options`](https://docs.lumavox.ai/speech/text-to-speech.md#tuning-the-voice). |

```bash title="curl"
curl -X PATCH https://api.lumavox.ai/v1/voices/voi_a1B2c3D4e5F6g7H8 \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Narrator (warm)", "provider_defaults": {"stability": 0.6}}'
```

**There is no way to replace the recording.** Clone a new voice and delete the old one.

## Retry a voice, or regenerate its samples

`POST /v1/voices/{voice_id}/sample`:

- on a `failed` voice, runs the clone again — the voice is `pending` until it finishes;
- on a `ready` voice, regenerates its spoken samples;
- on a `pending` voice, answers 409: it is already being processed.

## Delete a voice

```bash title="curl"
curl -X DELETE https://api.lumavox.ai/v1/voices/voi_a1B2c3D4e5F6g7H8 \
  -H "Authorization: Bearer $API_KEY"
```

It answers **204**, with no body. The voice, its samples and the voice made for it at the provider are removed. A speech request naming it afterwards is a 404.

## Limits

- **Each organisation may keep a number of voices set by its plan.** Over it, uploading, creating and keeping a design answer 409. Delete a voice, or ask for a higher limit. Your plan is at `GET /v1/plan` — see [Credits and usage](https://docs.lumavox.ai/usage.md).
- **The platform's own capacity for custom voices can fill up.** Then new voices are refused with a 409 that says so, whatever your plan allows.
- **Uploading, creating, designing, keeping and retrying count against your rate limit**, like a generation. Reading, editing and deleting do not.

## Errors

The common envelope is described in [Errors and limits](https://docs.lumavox.ai/errors.md). The ones specific to voices:

| Status | When |
|---|---|
| 400 | The body is not valid JSON. |
| 402 | The organisation is out of credits (on the calls that spend: uploading, creating, designing, keeping, retrying). |
| 404 | The voice is unknown, not your organisation's, or a platform or built-in voice (for read, edit, delete and retry). |
| 409 | The plan's voice limit is reached; the platform's voice capacity is full; the upload never arrived, or was already used; or a retry of a voice that is still `pending`. |
| 422 | A content type that is not accepted; `consent` not `true`; a stored file that is empty, over the platform's size for a recording, or not audio; a missing name; a `models` entry the provider does not offer; an unknown field. `issues` names the field. |
| 429 | Over your organisation's rate limit. |
| 502 | The designer failed, or a preview could not be kept. The message says why. |
| 503 | Voice storage, the designer or the jobs that clone voices are not available on this deployment; or, on the list, your voices could not be looked up — the list never answers the built-in voice alone. Retry shortly. |

## Related

- [Text to speech](https://docs.lumavox.ai/speech/text-to-speech.md) — speaking with a voice.
- [Agents](https://docs.lumavox.ai/agents.md) — giving an agent a voice.
- [Reference: voices](https://docs.lumavox.ai/reference/voices.md)
