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

# Upload PVC voice samples

> Uploads one or more audio samples to a draft PVC voice. The request is all-or-nothing: if any file is rejected, none of the files in that request are saved. Only allowed while the voice is in state `PVC_VOICE_STATE_DRAFT` or `PVC_VOICE_STATE_FAILED`.

You can upload samples in several requests. They accumulate on the voice until you [train](https://docs.inworld.ai/api-reference/pvcAPI/pvcvoiceservice/train-pvc-voice.md) it.

<Warning>
If an upload is rejected, for example because a file's format is unsupported or it is too large, the entire batch is dropped, including valid files. Split the batch and retry with only the accepted files.
</Warning>

<Note>
The API detects the format from the file's bytes, not its declared MIME type or extension. Renaming a file to `.wav` does not make it a WAV file.
</Note>

## Accepted audio

| Format | Notes |
| --- | --- |
| WAV | PCM (8/16/24/32-bit), 32-bit float, or WAVE_FORMAT_EXTENSIBLE |
| MP3 | — |
| WEBM | Must include a `Duration` element in its metadata |

Rejected: A-law WAV, 12-bit WAV, `.m4a`, zero-byte files, truncated WAV files, and WEBM files missing `Duration` metadata.

## Limits

| Limit | Value |
| --- | --- |
| Files per request | 10 |
| Size per file | 1 GB |
| Combined sample storage per voice | 1 GB (1,000,000,000 bytes) |
| Samples per voice | 50 |
| Concurrent uploads per account | plan-dependent |

Exceeding a per-request, per-file, or per-voice limit returns HTTP error code `400`. Too many concurrent uploads returns `429`.

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/{pvcVoiceId}/samples`

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

### Path parameters

- `pvcVoiceId` (string; required) — `pvcVoiceId` of the draft PVC voice to add samples to.

### Request body

Content type: `multipart/form-data`

- `files[]` (string[]; required) — Audio files to upload, up to 10 per request. Repeat the `files[]` form field once per file (e.g. `files[]=@"sample-01.wav"`, `files[]=@"sample-02.wav"`). It is not a single field containing a list. Accepted formats: WAV, WEBM, MP3. The actual byte content is sniffed to determine format, not the declared MIME type or file extension. Each file is capped at 1,000,000,000 bytes (1 GB); the voice's combined sample storage is capped at 1,000,000,000 bytes (1 GB) and 50 samples total.
- `remove_background_noise` (boolean) — Whether to run an audio isolation model over the uploaded samples to remove background noise before training. Can degrade quality on already-clean recordings. Defaults to `false`.

#### Request example

```json
{
  "files[]": [
    "string"
  ],
  "remove_background_noise": true
}
```

### Response

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

- `samples` (object[]) — The samples accepted by this request, in the order they were uploaded.
  - `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.

### Response examples

#### 200: uploaded

```json
{
  "samples": [
    {
      "sampleId": "s_9f1c2e",
      "name": "workspaces/your_workspace_id/pvcVoices/my-professional-voice/samples/s_9f1c2e",
      "fileName": "sample-01.wav",
      "sizeBytes": "24883220",
      "durationSecs": 312.4,
      "mimeType": "audio/wav",
      "hash": "<base64-gcs-md5>",
      "removeBackgroundNoise": false
    },
    {
      "sampleId": "s_4b7a01",
      "name": "workspaces/your_workspace_id/pvcVoices/my-professional-voice/samples/s_4b7a01",
      "fileName": "sample-02.wav",
      "sizeBytes": "19004112",
      "durationSecs": 238.1,
      "mimeType": "audio/wav",
      "hash": "<base64-gcs-md5>",
      "removeBackgroundNoise": false
    }
  ]
}
```

#### 400: unsupported_format

```json
{
  "code": 3,
  "message": "invalid request: Unsupported or malformed audio file",
  "details": []
}
```

#### 409: default_response

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

#### 429: default_response

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

#### default: default_response

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

### Code examples

#### cURL

```bash
curl --location 'https://api.inworld.ai/voices/v1/pvcVoices/<pvc-voice-id>/samples' \
--header "Authorization: Basic $INWORLD_API_KEY" \
--form 'files[]=@"sample-01.wav"' \
--form 'files[]=@"sample-02.wav"' \
--form 'remove_background_noise="false"'
```

#### Python

```python
import requests

pvc_voice_id = "<pvc-voice-id>"
url = f"https://api.inworld.ai/voices/v1/pvcVoices/{pvc_voice_id}/samples"
headers = {"Authorization": "Basic <api-key>"}

files = [
    ("files[]", ("sample-01.wav", open("sample-01.wav", "rb"), "audio/wav")),
    ("files[]", ("sample-02.wav", open("sample-02.wav", "rb"), "audio/wav")),
]
data = {"remove_background_noise": "false"}

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

#### JavaScript

```javascript
import { openAsBlob } from 'node:fs';

const pvcVoiceId = '<pvc-voice-id>';
const url = `https://api.inworld.ai/voices/v1/pvcVoices/${pvcVoiceId}/samples`;

const form = new FormData();
form.append('files[]', await openAsBlob('sample-01.wav'), 'sample-01.wav');
form.append('files[]', await openAsBlob('sample-02.wav'), 'sample-02.wav');
form.append('remove_background_noise', 'false');

const response = await fetch(url, {
  method: 'POST',
  headers: { 'Authorization': 'Basic <api-key>' },
  body: form,
});

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