# Voices

> The voices a key may speak with, and — on `voices:write` — adding, designing, editing and deleting your own.

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](https://docs.lumavox.ai/speech/voices.md).

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

```bash title="curl"
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

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

```bash title="curl"
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 | yes | From the ticket, after the recording was `PUT` there. |
| `name` | string | yes | 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

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

```bash title="curl"
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 | yes | 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

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

```bash title="curl"
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 | yes | 1–120 characters. |
| `prompt` | string | yes | 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

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

```bash title="curl"
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 | yes | 1–120 characters. |
| `preview_id` | string | yes | 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

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

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

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `voice_id` | string | yes |  |

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

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

```bash title="curl"
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 | yes |  |

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

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

```bash title="curl"
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 | yes |  |

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

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

```bash title="curl"
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 | yes |  |

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