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

# List PVC voices

> Lists Professional Voice Clones owned by the calling workspace, across every state (draft, queued, training, ready, and failed).

<Note>
There's no server-side `state` filter yet. Filter the `state` field on the client if you only want, for example, ready voices.
</Note>

Draft voices count against your PVC voice slots too. If `usedPvcVoiceSlots` from [Resolve upload limits](https://docs.inworld.ai/api-reference/pvcAPI/pvcvoiceservice/resolve-pvc-upload-limits.md) is higher than you expect, look for `PVC_VOICE_STATE_DRAFT` entries here and [delete](https://docs.inworld.ai/api-reference/pvcAPI/pvcvoiceservice/delete-pvc-voice.md) the ones you don't need.

Once a voice reaches `PVC_VOICE_STATE_READY` here, it's also returned alongside your other voices from [List voices](https://docs.inworld.ai/api-reference/voiceAPI/voiceservice/list-voices.md) (`source = "PVC"`).

## API reference

**Endpoint:** `GET https://api.inworld.ai/voices/v1/pvcVoices`

### Authorization

- `Authorization` (string; required) — Your [API key](../../../api-reference/introduction). Read permissions are required for GET endpoints. Write permissions are required for POST, PATCH, and DELETE endpoints.
  
   For Basic authentication, please populate `Basic $INWORLD_API_KEY`. You can create a key in one command with the [Inworld CLI](../../../developer-tools/inworld-cli): `inworld workspace add-key`.

### Query parameters

- `pageSize` (integer) — Maximum number of voices to return per page.
- `pageToken` (string) — Opaque pagination cursor from a previous response's `nextPageToken`. Pass it back unchanged to retrieve the next page.

### Response

Status: `200`. Content type: `application/json`.

- `pvcVoices` (object[]) — PVC voices for this page.
  - `name` (string) — Resource name. Format: `workspaces/{workspace}/pvcVoices/{voice}`.
  - `voiceId` (string) — Voice ID, derived from `displayName` at creation time. Use this value as `{voiceId}` on every other PVC endpoint, and as the `voiceId` in TTS synthesis requests once the voice is `PVC_VOICE_STATE_READY`.
  - `displayName` (string) — The human-readable name shown anywhere the voice is listed or selected.
  - `languageCode` (string) — The voice's language as a BCP-47-shaped locale string, e.g. `en-US`. Immutable after creation.
  - `state` (enum<string>; options: "PVC_VOICE_STATE_UNSPECIFIED", "PVC_VOICE_STATE_DRAFT", "PVC_VOICE_STATE_QUEUED", "PVC_VOICE_STATE_TRAINING", "PVC_VOICE_STATE_READY", "PVC_VOICE_STATE_FAILED") — Lifecycle state of a PVC voice.
    
    - `PVC_VOICE_STATE_DRAFT`: Editable. Samples can be added, trimmed, or removed, and metadata can be updated.
    - `PVC_VOICE_STATE_QUEUED`: Training requested; waiting for a training slot.
    - `PVC_VOICE_STATE_TRAINING`: Actively training.
    - `PVC_VOICE_STATE_READY`: Training succeeded. Usable for TTS synthesis. It's permanent and cannot be deleted through this API.
    - `PVC_VOICE_STATE_FAILED`: Training failed. Editing the voice (e.g. renaming it, or adding/removing a sample) returns it to `PVC_VOICE_STATE_DRAFT` with its remaining samples intact.
  - `failure` (object) — Populated on a PVC voice when its `state` is `PVC_VOICE_STATE_FAILED`.
    - `reason` (string) — Machine-readable failure code, e.g. `TRAINING_ERROR`. New values may be added over time, so don't validate against a hardcoded list. Fall back to displaying `message` for codes you don't recognize.
    - `message` (string) — Human-readable, scrubbed failure message. Never contains uploaded audio, filenames, or transcripts.
  - `incarnationId` (string) — Identifier that stays stable across edits to the same voice, and changes each time it is retrained. Use it to tell two reads of the same `voiceId` apart across a retrain.
  - `samples` (object[]) — Audio samples currently attached to the voice.
    - `sampleId` (string) — Sample ID. Use this value as `{sampleId}` when trimming or deleting the sample.
    - `name` (string) — Resource name. Format: `workspaces/{workspace}/pvcVoices/{voice}/samples/{sample}`.
    - `sizeBytes` (integer) — Size of the uploaded file, in bytes.
    - `durationSecs` (number) — Analyzed duration of the sample, in seconds, before any trim is applied.
    - `mimeType` (enum<string>; options: "audio/wav", "audio/webm", "audio/mpeg") — Detected audio format, sniffed from the file's byte content.
    - `hash` (string) — Base64-encoded MD5 of the stored object, for verifying upload integrity against the source file.
    - `trimStartMs` (integer) — Trim start offset in milliseconds, if set.
    - `trimEndMs` (integer) — Trim end offset in milliseconds, if set.
  - `createTime` (string)
  - `updateTime` (string)
- `nextPageToken` (string) — Opaque cursor to pass as `pageToken` to fetch the next page. Empty string when there are no more pages.

### Response examples

#### 200: successful_response

```json
{
  "pvcVoices": [
    {
      "name": "workspaces/your_workspace_id/pvcVoices/my-professional-voice",
      "voiceId": "my-professional-voice",
      "displayName": "my-professional-voice",
      "languageCode": "en-US",
      "state": "PVC_VOICE_STATE_READY",
      "incarnationId": "a1b2c3d4",
      "createTime": "2026-08-31T12:00:00Z",
      "updateTime": "2026-08-31T12:41:00Z"
    }
  ],
  "nextPageToken": ""
}
```

#### default: default_response

```json
{
  "code": 0,
  "message": "string",
  "details": [
    {
      "@type": "string"
    }
  ]
}
```

### Code examples

#### cURL

```bash
curl -sG 'https://api.inworld.ai/voices/v1/pvcVoices' \
  -H "Authorization: Basic $INWORLD_API_KEY" \
  --data-urlencode 'pageSize=20'
```

#### Python

```python
import requests

url = "https://api.inworld.ai/voices/v1/pvcVoices"
headers = {"Authorization": "Basic <api-key>"}

params = {"pageSize": 20}

while True:
    response = requests.get(url, headers=headers, params=params)
    data = response.json()
    for voice in data.get("pvcVoices", []):
        print(voice["displayName"], voice["state"])
    next_token = data.get("nextPageToken", "")
    if not next_token:
        break
    params["pageToken"] = next_token
```

#### JavaScript

```javascript
const base = 'https://api.inworld.ai/voices/v1/pvcVoices';
const headers = { 'Authorization': 'Basic <api-key>' };

let pageToken = '';
do {
  const params = new URLSearchParams({
    pageSize: '20',
    ...(pageToken && { pageToken }),
  });
  const res = await fetch(`${base}?${params}`, { headers });
  const data = await res.json();
  for (const voice of data.pvcVoices ?? []) {
    console.log(voice.displayName, voice.state);
  }
  pageToken = data.nextPageToken ?? '';
} while (pageToken);
```
