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

# Create a PVC voice

> Creates a new Professional Voice Clone in `PVC_VOICE_STATE_DRAFT`. Upload audio samples, then start training. See the steps below.

<Note>
**Language.** Support for multiple languages is experimental. `languageCode` defaults to `en-US` and cannot change after creation.
</Note>

A draft voice has no audio yet. Follow these steps to add samples and train it:

<Steps titleSize="h3">
  <Step title="Create the draft voice">
    This endpoint returns a `pvcVoiceId` derived from `displayName`. Use it as `{pvcVoiceId}` on the other PVC endpoints.
  </Step>
  <Step title="Upload audio samples">
    Use [Upload PVC voice samples](https://docs.inworld.ai/api-reference/pvcAPI/pvcvoiceservice/upload-pvc-voice-samples.md) until you have at least 10 minutes of audio in total. To trim individual samples, use [Trim a PVC voice sample](https://docs.inworld.ai/api-reference/pvcAPI/pvcvoiceservice/trim-pvc-voice-sample.md).
  </Step>
  <Step title="Train">
    [Train a PVC voice](https://docs.inworld.ai/api-reference/pvcAPI/pvcvoiceservice/train-pvc-voice.md) starts training and moves the voice to `PVC_VOICE_STATE_QUEUED`.
  </Step>
  <Step title="Poll until ready">
    Poll [Get a PVC voice](https://docs.inworld.ai/api-reference/pvcAPI/pvcvoiceservice/get-pvc-voice.md) until `state` reaches `PVC_VOICE_STATE_READY` or `PVC_VOICE_STATE_FAILED`. Once ready, `customVoice` names the trained voice. Find its `voiceId` with [List voices](https://docs.inworld.ai/api-reference/voiceAPI/voiceservice/list-voices.md), and use it anywhere you'd use a regular voice, such as [Synthesize speech](https://docs.inworld.ai/api-reference/ttsAPI/texttospeech/synthesize-speech.md).
  </Step>
</Steps>

Each voice uses one of your plan's **PVC voice slots** for as long as it exists. Check `usedPvcVoiceSlots` and `maxPvcVoiceSlots` with [Resolve upload limits](https://docs.inworld.ai/api-reference/pvcAPI/pvcvoiceservice/resolve-pvc-upload-limits.md). If all slots are taken, you must free a slot or upgrade your plan before creating another voice. On-Demand accounts also need a payment method on file. See [Plan limits](https://docs.inworld.ai/tts/professional-voice-cloning.md#plan-limits) for each plan's slot and training allowances.

For recording and preparation tips, see [Voice Cloning best practices](https://docs.inworld.ai/tts/best-practices/voice-cloning.md#best-practices-for-professional-voice-cloning).

## API reference

**Endpoint:** `POST 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`.

### Request body

Content type: `application/json`

- `displayName` (string; required) — The human-readable name shown anywhere the voice is listed or selected. The voice's `pvcVoiceId` is derived from this value; renaming the voice later does not change its `pvcVoiceId`.
- `languageCode` (string) — The voice's language as a BCP-47-style locale string, such as `en-US`. Send this or `langCode`; if you send both, they must name the same language. Multi-language support is experimental; defaults to `en-US` if both are omitted. Immutable after creation.
- `langCode` (enum<string>; options: "EN_US", "ZH_CN", "KO_KR", "JA_JP", "RU_RU", "AUTO", "IT_IT", "ES_ES", "PT_BR", "DE_DE", "FR_FR", "AR_SA", "PL_PL", "NL_NL", "HI_IN", "HE_IL") — Legacy enum form of the language, such as `EN_US`. Send this or `languageCode`, not a different language in each.
- `description` (string) — Description of the voice.
- `tags` (string[]) — User-defined tags.

#### Request example

```json
{
  "displayName": "my-professional-voice",
  "languageCode": "en-US"
}
```

### Response

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

- `name` (string) — Resource name. Format: `workspaces/{workspace}/pvcVoices/{voice}`.
- `pvcVoiceId` (string) — PVC voice ID, derived from `displayName` at creation time. Use this value as `{pvcVoiceId}` on every other PVC endpoint. It is not the voice ID used for synthesis; see `customVoice`.
- `displayName` (string) — The human-readable name shown anywhere the voice is listed or selected.
- `description` (string) — Description of the voice.
- `tags` (string[]) — User-defined tags.
- `langCode` (enum<string>; options: "EN_US", "ZH_CN", "KO_KR", "JA_JP", "RU_RU", "AUTO", "IT_IT", "ES_ES", "PT_BR", "DE_DE", "FR_FR", "AR_SA", "PL_PL", "NL_NL", "HI_IN", "HE_IL") — Legacy enum form of the voice's language, such as `EN_US`. Returned alongside `languageCode`. Immutable after creation.
- `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, such as `PVC_VOICE_FAILURE_REASON_INVALID_SAMPLES`. 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.
- `customVoice` (string) — Resource name of the trained voice, in the format `workspaces/{workspace}/voices/{voice}`. Set once the voice is `PVC_VOICE_STATE_READY`. Manage and use the trained voice through the Voices API.
- `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}`.
  - `fileName` (string) — Original file name of the uploaded audio.
  - `sizeBytes` (string) — Size of the uploaded file, in bytes. Encoded as a string, like other 64-bit integers in this API.
  - `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.
  - `removeBackgroundNoise` (boolean) — Whether background-noise removal is applied to this sample during training, as set by `remove_background_noise` on upload.
  - `trimStartMs` (integer) — Trim start offset in milliseconds, if set.
  - `trimEndMs` (integer) — Trim end offset in milliseconds, if set.
  - `role` (enum<string>; options: "PVC_SAMPLE_ROLE_UNSPECIFIED", "PVC_SAMPLE_ROLE_TRAINING", "PVC_SAMPLE_ROLE_GOLDEN", "PVC_SAMPLE_ROLE_AVOID") — How the sample is used in training. `PVC_SAMPLE_ROLE_UNSPECIFIED` is treated as `PVC_SAMPLE_ROLE_TRAINING` (ordinary training material). `PVC_SAMPLE_ROLE_GOLDEN` marks a positive reference whose character training should prefer; `PVC_SAMPLE_ROLE_AVOID` marks an advisory negative reference. New values may be added over time.
- `createTime` (string)
- `updateTime` (string)

### Response examples

#### 200: draft_voice

```json
{
  "name": "workspaces/your_workspace_id/pvcVoices/my-professional-voice",
  "pvcVoiceId": "my-professional-voice",
  "displayName": "my-professional-voice",
  "langCode": "EN_US",
  "languageCode": "en-US",
  "state": "PVC_VOICE_STATE_DRAFT",
  "samples": [],
  "createTime": "2026-08-31T12:00:00Z",
  "updateTime": "2026-08-31T12:00:00Z"
}
```

#### default: default_response

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

### Code examples

#### cURL

```bash
curl --location 'https://api.inworld.ai/voices/v1/pvcVoices' \
--header "Authorization: Basic $INWORLD_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
  "displayName": "my-professional-voice",
  "languageCode": "en-US"
}'
```

#### Python

```python
import requests

url = "https://api.inworld.ai/voices/v1/pvcVoices"
headers = {
    "Authorization": "Basic <api-key>",
    "Content-Type": "application/json"
}
payload = {
    "displayName": "my-professional-voice",
    "languageCode": "en-US"
}

response = requests.post(url, json=payload, headers=headers)
print(response.json())
```

#### JavaScript

```javascript
const url = 'https://api.inworld.ai/voices/v1/pvcVoices';

const response = await fetch(url, {
  method: 'POST',
  headers: {
    'Authorization': 'Basic <api-key>',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    displayName: 'my-professional-voice',
    languageCode: 'en-US',
  }),
});

const data = await response.json();
console.log(data);
```
