> ## Documentation Index
> Fetch the complete documentation index at: https://docs.withhopper.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Voice library

> List, filter, edit, and save voices; prepare a voice for a model with profiles

A voice is referenced in TTS requests as `{"mode": "id", "id": "<voice_id>"}`. Your organization sees two kinds of voices: platform voices from the shared library, which are read-only, and its own voices, created by [cloning or localization](/tts/cloning). Editing or deleting a platform voice returns 403 `voice_read_only`; a voice outside your visibility returns 404 `voice_not_found`.

## GET /voices

List and filter voices.

| Param      | Values                             | Default |
| ---------- | ---------------------------------- | ------- |
| `scope`    | `featured`, `all`, `mine`, `saved` | `all`   |
| `gender`   | filter on the voice's gender field | none    |
| `language` | filter on language                 | none    |
| `tag`      | filter on tags                     | none    |
| `q`        | text search                        | none    |
| `limit`    | 1–200                              | 50      |
| `cursor`   | opaque cursor from a previous page | none    |

<CodeGroup>
  ```python Python theme={null}
  import requests

  resp = requests.get(
      "https://api.withhopper.com/voices",
      headers={"Authorization": "Bearer sk_hopper_..."},
      params={"scope": "featured", "language": "en", "limit": 50},
  )
  voices = resp.json()["voices"]
  ```

  ```typescript TypeScript theme={null}
  const resp = await fetch(
    "https://api.withhopper.com/voices?scope=featured&language=en&limit=50",
    { headers: { Authorization: "Bearer sk_hopper_..." } },
  );
  const { voices, next_cursor } = await resp.json();
  ```

  ```bash cURL theme={null}
  curl "https://api.withhopper.com/voices?scope=featured&language=en&limit=50" \
    -H "Authorization: Bearer sk_hopper_..."
  ```
</CodeGroup>

The response is `{"voices": [Voice, ...], "next_cursor": "..." | null}`. Pass `next_cursor` back as `cursor` until it is `null`.

## The voice object

| Field             | Type       | Meaning                                                 |
| ----------------- | ---------- | ------------------------------------------------------- |
| `id`              | string     | Voice id — use it in the TTS `voice` object             |
| `name`            | string     | Display name                                            |
| `description`     | string     | Free text                                               |
| `language`        | string     | Voice language                                          |
| `gender`          | string     | Voice gender                                            |
| `tags`            | string\[]  | Filterable labels                                       |
| `featured`        | boolean    | In the curated platform set (`scope=featured`)          |
| `source`          | string     | `library`, `instant_clone`, `pro_clone`, or `localized` |
| `parent_voice_id` | string     | Set on localized voices — the voice it was derived from |
| `profiles`        | Profile\[] | Per-model preparations — see below                      |
| `is_owner`        | boolean    | Your organization owns this voice; mutation allowed     |
| `saved`           | boolean    | You saved this voice (`scope=saved`)                    |
| `created_at`      | string     | Creation timestamp                                      |

Each profile records that the voice is prepared for one model: `{model_id, profile_kind, profile_version, status, cached_at, expires_at}`. `status` is `pending`, `ready`, or `failed`. A TTS request only succeeds against a model with a `ready` profile.

## GET /voices/:id

Returns the Voice, or 404 `voice_not_found`.

## PATCH /voices/:id

Body: `{name?, description?, tags?}` — `name` must be non-empty, `tags` a string array. Own voices only; returns the updated Voice.

## DELETE /voices/:id

Soft-deletes an owned voice. Returns `{"id": "...", "deleted": true}`.

## Save and unsave

`POST /voices/:id/save` returns `{"id": "...", "saved": true}`; `DELETE /voices/:id/save` returns `saved: false`. Saved voices appear under `scope=saved`. Any visible voice can be saved, including platform voices.

## Voice profiles

`POST /voices/:id/profiles` prepares a voice for a model. [OmniVoice](/models#omnivoice) uses in-context-learning (ICL) profiles built from the voice's source audio.

Body: `{model_id?}` — default `omnivoice`.

* A cached profile already exists: **200** `{"voice_id": "...", "status": "ready", "profile": {...}}`. The voice is usable immediately.
* No cached profile: **202** `{"job_id": "...", "voice_id": "...", "status": "..."}`. Poll [`GET /jobs/:id`](/tts/cloning#polling-jobs) until the job succeeds.
* The voice has no canonical source audio: **409** `voice_source_missing`.

A TTS request that names a voice without a ready profile for the target model returns 409 `voice_profile_missing`.
