---
title: "Voices and uploads API"
description: "Discover accessible voices, inspect current engine eligibility, and upload a private voice through the ordinary asynchronous upload flow. Retain original voice, resource and operation identifiers when recovering an interrupted upload."
canonical_url: https://lyricwinter.com/docs/api/voices
markdown_url: https://lyricwinter.com/docs/api/voices.md
last_updated: 2026-11-03
status: beta
---
# LyricWinter API reference: Voices and uploads

> Discover accessible voices, inspect current engine eligibility, and upload a private voice through the ordinary asynchronous upload flow. Retain original voice, resource and operation identifiers when recovering an interrupted upload.

The LyricWinter API is in beta. Base URL: `https://lyricwinter.com/api/v1`. Authenticate every request with `Authorization: Bearer lw_...`. Successful responses are wrapped as `{ "data": ..., "request_id": "req_..." }`. Machine-readable contract: https://lyricwinter.com/api/v1/openapi.json.

## Endpoints

- [Discover accessible voices](https://lyricwinter.com/docs/api/voices#list-voices): `GET /voices`
- [Initiate a voice upload](https://lyricwinter.com/docs/api/voices#initiate-voice-upload): `POST /voices`
- [Read accessible voices by exact IDs](https://lyricwinter.com/docs/api/voices#get-voices-by-ids): `POST /voices/by-ids`
- [Read voice processing status](https://lyricwinter.com/docs/api/voices#get-managed-voice): `GET /voices/{voiceId}`
- [Read current voice engine options](https://lyricwinter.com/docs/api/voices#list-voice-engine-samples): `GET /voices/{voiceId}/engine-samples`
- [Refresh original upload targets](https://lyricwinter.com/docs/api/voices#refresh-voice-upload): `POST /voices/{voiceId}/uploads/refresh`
- [Complete the original voice upload](https://lyricwinter.com/docs/api/voices#complete-voice-upload): `POST /voices/{voiceId}/uploads/complete`

### Discover accessible voices

`GET /voices`

Scope: `voices:read` · Operation ID: `listVoices`

Browse or search completed selectable voices in scope=mine, shared, or library. Library discovery works anonymously; private collections require voices:read. To receive pagination, send include_pagination=true with an explicit scope, without mine=true or sort=popular. Follow pagination.next_offset with the same scope, query and limit even when voices is empty: access or selectability filtering can remove rows after the page boundary. next_offset=null ends the result. Without this option, the existing voices-only payload is unchanged. context_project_id applies current Studio project authority and relevance. Results reflect current access and may change between reads; engine eligibility is checked separately.

#### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `q` | string, default "" | No | Search text matched against voice name, description, tags, and the hybrid voice search RPC. Empty or omitted returns browse results. |
| `limit` | integer, 1–50, default 20 | No | Maximum number of public/search results. Omitted means 20. |
| `offset` | integer, ≥ 0, default 0 | No | Non-negative integer offset. Omitted means 0. |
| `mine` | boolean, default false | No | When true, returns only the authenticated caller's voices. Anonymous callers receive an empty list. |
| `include_mine` | boolean, default true | No | When false, authenticated non-mine browse/search results omit the caller's owned voices. Only exact true/false strings are accepted. |
| `scope` | string, one of `mine`, `shared`, `library` | No | Limits results to the caller's own voices, voices shared with the caller, or the public voice library. |
| `include_pagination` | boolean, default false | No | Opt in to raw-page continuation. Requires explicit scope=mine, shared or library; cannot combine with mine=true or sort=popular. Follow pagination.next_offset even on an empty filtered page. Omitted or false preserves the legacy voices-only payload. |
| `context_project_id` | string, uuid | No | Authenticated Studio project UUID. When present, owned AI-designed voices are limited to voices created for or actively used by this standalone or named project; omit it to request the full account catalog. |
| `sort` | string | No | Only the exact value popular changes behavior. Other values currently fall through to the normal list/search path. |

#### Response 200

Processed voice picker results.

Fields inside `data`:

- `voices` (array of object; required)
  - `id` (string, uuid; required)
  - `acoustic_revision` (integer, ≥ 1; optional): Stable logical voice acoustic revision. It increments when a reroll publishes new voice acoustics.
  - `name` (string; required)
  - `description` (string | null; required)
  - `tags` (array of string; required)
  - `status` (string; required)
  - `available_models` (array of string; required)
  - `default_model` (string | null; required)
  - `is_selectable` (boolean; required): True when the voice is completed and has at least one available generation model.
  - `is_user_created` (boolean; required): True when the resolved authenticated user owns the voice.
  - `is_public` (boolean; required)
  - `is_shared_with_user` (boolean; required): True when an authenticated caller can access the voice through a share and does not own it.
  - `voice_origin` (string | null; required)
  - `design_provider` (string | null; required)
  - `sample_resource_version` (string | null; required): Current sample resource version token, when a sample resource is available.
  - `creator_username` (string | null; required): Creator username for public or accepted-shared voices; null for private owned voices or profiles without a username.
  - `creator_display_name` (string | null; required): Creator profile display name for public or accepted-shared voices, used when no username exists.
- `pagination` (object; optional)
  - `next_offset` (integer | null, ≥ 0; required)
  - `has_more` (boolean; required)

Errors: `400`, `401`, `500` return the standard error envelope.

#### Example request

```bash
curl "https://lyricwinter.com/api/v1/voices" \
  -H "Authorization: Bearer $LYRICWINTER_API_KEY"
```

### Initiate a voice upload

`POST /voices`

Scope: `voices:write` · Retries: send an `Idempotency-Key` header · Operation ID: `initiateVoiceUpload`

Create a private voice upload with permission to use the recording. Send exact file metadata and an Idempotency-Key; retain that key, metadata, voiceId and resource identifiers. After a lost response, send the identical key/body with Idempotency-Recovery: only; this never creates a replacement. Allocation is saved atomically before signing. Recovery returns current status and fresh targets only while uploading; later statuses return an empty signedUrls array. Upload original bytes with the returned Content-Type to the issued targets, then complete that same voice. Changed input conflicts. Missing/expired keys fail closed; older started keys remain in-progress. Valid older completed receipts recover their original IDs and current status. Key retention is 24 hours; a known voice ID remains readable with GET /voices/{voiceId}. Unknown after expiry never proves that creation did nothing. Initiation is not a completed clone. Requires current voices:write.

#### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string, 8–128 chars, pattern ^[\x21-\x7E]+$ | No | Opaque client-generated key for retrying one intended mutation. Accepted values are 8-128 visible ASCII characters. Reusing the same key with the same authenticated principal, method, route, scope, and normalized body replays the stored terminal response; reusing it for a different request returns a 409 conflict. |
| `Idempotency-Recovery` | string, one of `only` | No | Recovery-only lookup: requires the original Idempotency-Key and identical metadata. Never allocates a voice or resources; unavailable after the original 24-hour retention. |

#### Request body (application/json, required)

- `name` (string, 1–80 chars; required)
- `description` (string, 1–255 chars; required)
- `tags` (string | array of string; required): String inputs are comma-split by the service. Parsed tags must contain 1-20 entries, each 100 characters or fewer.
- `isPublic` (boolean; required): Currently must be false; public voice publishing is disabled during initiation.
- `uploadType` (string, min 1 chars; required)
- `files` (array of object, min 1 items; required)
  - `filename` (string, min 1 chars; required)
  - `size` (number, ≥ 0; required): Client-reported file size in bytes. AUDIO_SAMPLE_* files are rejected above 25 MiB by the service.
  - `contentType` (string; optional): Content type used when signing the GCS write URL.
  - `file_type` (string, min 1 chars; required): Current callers use values such as AUDIO_SAMPLE_WAV, AUDIO_SAMPLE_MP3, RVC_SAFETENSORS, and RVC_INDEX.

#### Response 201

Voice upload records and signed direct-upload URLs were created.

Fields inside `data`:

- `success` (true; required)
- `voiceId` (string, uuid; required)
- `signedUrls` (array of object; required)
  - `resourceId` (string, uuid; required)
  - `signedUrl` (string, uri; required): Short-lived v4 signed GCS write URL. Current implementation signs for 15 minutes.
  - `gcsPath` (string; required): Quarantine bucket object path created for this resource.
  - `contentType` (string; optional): Echoed content type when one was provided for signing.
- `message` (string; required)
- `status` (string, one of `uploading`, `pending`, `processing`, `completed`, `failed`; optional): Present on recovery-only responses and advanced replays. Upload targets are empty unless status is uploading; acceptance is not processing completion.

Errors: `400`, `401`, `403`, `409`, `500`, `503` return the standard error envelope.

#### Example request

```bash
curl -X POST "https://lyricwinter.com/api/v1/voices" \
  -H "Authorization: Bearer $LYRICWINTER_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "My integration",
  "description": "string",
  "tags": "string",
  "isPublic": true,
  "uploadType": "string",
  "files": [
    {
      "filename": "string",
      "size": 0,
      "file_type": "string"
    }
  ]
}'
```

### Read accessible voices by exact IDs

`POST /voices/by-ids`

Scope: `voices:read` · Operation ID: `getVoicesByIds`

Hydrate exact voice identifiers through current owned, shared and public access checks. Missing or inaccessible voices are not evidence of a different replacement voice. Use the returned IDs and current status rather than matching display names.

#### Request body (application/json, required)

- `ids` (array of string, 1–100 items; required)

#### Response 200

Processed voice picker results.

Fields inside `data`:

- `voices` (array of object; required)
  - `id` (string, uuid; required)
  - `acoustic_revision` (integer, ≥ 1; optional): Stable logical voice acoustic revision. It increments when a reroll publishes new voice acoustics.
  - `name` (string; required)
  - `description` (string | null; required)
  - `tags` (array of string; required)
  - `status` (string; required)
  - `available_models` (array of string; required)
  - `default_model` (string | null; required)
  - `is_selectable` (boolean; required): True when the voice is completed and has at least one available generation model.
  - `is_user_created` (boolean; required): True when the resolved authenticated user owns the voice.
  - `is_public` (boolean; required)
  - `is_shared_with_user` (boolean; required): True when an authenticated caller can access the voice through a share and does not own it.
  - `voice_origin` (string | null; required)
  - `design_provider` (string | null; required)
  - `sample_resource_version` (string | null; required): Current sample resource version token, when a sample resource is available.
  - `creator_username` (string | null; required): Creator username for public or accepted-shared voices; null for private owned voices or profiles without a username.
  - `creator_display_name` (string | null; required): Creator profile display name for public or accepted-shared voices, used when no username exists.
- `pagination` (object; optional)
  - `next_offset` (integer | null, ≥ 0; required)
  - `has_more` (boolean; required)

Errors: `400`, `401`, `500` return the standard error envelope.

#### Example request

```bash
curl -X POST "https://lyricwinter.com/api/v1/voices/by-ids" \
  -H "Authorization: Bearer $LYRICWINTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "ids": [
    "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10"
  ]
}'
```

### Read voice processing status

`GET /voices/{voiceId}`

Scope: `voices:read` · Operation ID: `getManagedVoice`

Read the accessible original voice's current status and acoustic revision. Upload completion can return while processing is pending; poll this read rather than initiating another upload. Completed processing and current engine eligibility are separate checks.

#### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `voiceId` | string, uuid | Yes |  |

#### Response 200

Managed voice details and caller admin status.

Fields inside `data`:

- `voice` (object; required)
  - `id` (string, uuid; required)
  - `acoustic_revision` (integer, ≥ 1; required)
  - `name` (string; required)
  - `description` (string | null; required)
  - `tags` (array of string; required)
  - `is_public` (boolean; required)
  - `created_at` (string, date-time; required)
  - `updated_at` (string, date-time; required)
  - `status` (string; required)
  - `error_message` (string | null; optional)
  - `voice_origin` (string | null; optional)
  - `design_provider` (string | null; optional)
  - `public_visibility_locked` (boolean | null; optional)
  - `public_visibility_locked_at` (string | null, date-time; optional)
  - `public_visibility_lock_reason` (string | null; optional)
  - `is_shared_with_user` (boolean; optional)
  - `creator_username` (string | null; required)
  - `creator_display_name` (string | null; required)
  - `can_update_metadata` (boolean; required)
  - `can_delete` (boolean; required)
  - `can_reroll` (boolean; required)
  - `can_replace_sample` (false; required): Reserved for a future same-ID sample replacement contract; v1 does not support that operation.
  - `resources` (array of object | null; optional)
    - `id` (string | null, uuid; optional)
    - `acoustic_revision` (integer | null, ≥ 1; optional)
    - `resource_type` (string | null; optional)
    - `gcs_path` (string | null; optional)
    - `metadata` (object | null; optional)
    - `created_at` (string | null, date-time; optional)
    - `status` (string | null; optional)
    - `error_message` (string | null; optional)
- `isAdmin` (boolean; required)

Errors: `400`, `401`, `403`, `404`, `500` return the standard error envelope.

#### Example request

```bash
curl "https://lyricwinter.com/api/v1/voices/$VOICE_ID" \
  -H "Authorization: Bearer $LYRICWINTER_API_KEY"
```

### Read current voice engine options

`GET /voices/{voiceId}/engine-samples`

Scope: `voices:read` · Operation ID: `listVoiceEngineSamples`

Read current-account engine availability, preparation requirements, reasons, acoustic revision and existing samples for this exact voice. This read neither selects an engine nor generates a sample. Check the returned status and profile before casting or requesting generation; do not silently substitute another engine.

#### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `voiceId` | string, uuid | Yes |  |

#### Response 200

Current voice engine sample catalog.

Fields inside `data`:

- `schema_version` (1; required)
- `voice_id` (string, uuid; required)
- `acoustic_revision` (integer, ≥ 1; required)
- `canonical_text` (object; required)
  - `version` ("voice-engine-sample-en-v1"; required)
  - `text` ("At first light, Rowan whispered, "We made it," then laughed softly into the rain."; required)
- `default_model` (string | null; required)
- `model_options` (array of object, max 50 items; required)
  - `provider` (string; required)
  - `model` (string; required)
  - `label` (string; required)
  - `availability` (string, one of `ready`, `preparation_required`, `unavailable`; required)
  - `reason` (string | null; required)
  - `compiler_version` (string; required)
  - `estimated_snowcoins` (integer, ≥ 0; required)
  - `sample` (object | null; required)
    - `schema_version` (1; required)
    - `sample_id` (string, uuid; required)
    - `voice_id` (string, uuid; required)
    - `acoustic_revision` (integer, ≥ 1; required)
    - `provider` (string, 1–100 chars; required)
    - `model` (string, 1–100 chars; required)
    - `canonical_text_version` (string, one of `voice-engine-sample-en-v1`, `voice-preview-line-v1`; required)
    - `compiler_version` (string, 1–200 chars; required)
    - `status` (string, one of `preparing_voice`, `queued`, `running`, `completed`, `failed`, `expired`; required)
    - `cache_status` (string | null, one of `miss`, `coalesced`, `hit`; required)
    - `estimated_snowcoins` (integer, ≥ 0; required)
    - `charged_snowcoins` (integer, ≥ 0; required)
    - `audio_url` (string | null, uri; required)
    - `audio_url_expires_at` (string | null, date-time; required)
    - `error` (object | null; required)
      - `code` (string, 1–120 chars; required)
      - `message` (string, 1–500 chars; required)
      - `retryable` (boolean; required)
    - `created_at` (string, date-time; required)
    - `updated_at` (string, date-time; required)
    - `expires_at` (string, date-time; required)

Errors: `400`, `401`, `403`, `404`, `429`, `500` return the standard error envelope.

#### Example request

```bash
curl "https://lyricwinter.com/api/v1/voices/$VOICE_ID/engine-samples" \
  -H "Authorization: Bearer $LYRICWINTER_API_KEY"
```

### Refresh original upload targets

`POST /voices/{voiceId}/uploads/refresh`

Scope: `voices:write` · Operation ID: `refreshVoiceUpload`

Renew expiring upload targets for the same uploading voice and original resources. This does not create a replacement voice. Keep the original identifiers and upload only to the newly returned targets. Requires voices:write and authority over the upload.

#### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `voiceId` | string, uuid | Yes |  |

#### Response 200

Voice upload records and signed direct-upload URLs were created.

Fields inside `data`:

- `success` (true; required)
- `voiceId` (string, uuid; required)
- `signedUrls` (array of object; required)
  - `resourceId` (string, uuid; required)
  - `signedUrl` (string, uri; required): Short-lived v4 signed GCS write URL. Current implementation signs for 15 minutes.
  - `gcsPath` (string; required): Quarantine bucket object path created for this resource.
  - `contentType` (string; optional): Echoed content type when one was provided for signing.
- `message` (string; required)
- `status` (string, one of `uploading`, `pending`, `processing`, `completed`, `failed`; optional): Present on recovery-only responses and advanced replays. Upload targets are empty unless status is uploading; acceptance is not processing completion.

Errors: `400`, `401`, `404`, `409`, `503` return the standard error envelope.

#### Example request

```bash
curl -X POST "https://lyricwinter.com/api/v1/voices/$VOICE_ID/uploads/refresh" \
  -H "Authorization: Bearer $LYRICWINTER_API_KEY"
```

### Complete the original voice upload

`POST /voices/{voiceId}/uploads/complete`

Scope: `voices:write` · Retries: send an `Idempotency-Key` header · Operation ID: `completeVoiceUpload`

Finalize the existing voice after its files have been uploaded. Use an Idempotency-Key and retain it together with the original voice ID on uncertainty; retry only that same completion. Acceptance starts or recovers asynchronous processing and does not mean the clone is ready. Read the voice's current status and engine options before use. Requires voices:write.

#### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `voiceId` | string, uuid | Yes |  |

#### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string, 8–128 chars, pattern ^[\x21-\x7E]+$ | No | Opaque client-generated key for retrying one intended mutation. Accepted values are 8-128 visible ASCII characters. Reusing the same key with the same authenticated principal, method, route, scope, and normalized body replays the stored terminal response; reusing it for a different request returns a 409 conflict. |

#### Response 202

Voice upload finalization was accepted and background processing was scheduled.

Fields inside `data`:

- `success` (true; required)
- `message` (string; required)

Errors: `400`, `401`, `403`, `404`, `409`, `500`, `503` return the standard error envelope.

#### Example request

```bash
curl -X POST "https://lyricwinter.com/api/v1/voices/$VOICE_ID/uploads/complete" \
  -H "Authorization: Bearer $LYRICWINTER_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

