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

Call this endpoint as many times as you need. Samples accumulate on the voice until you [train](https://docs.inworld.ai/api-reference/pvcAPI/pvcvoiceservice/train-pvc-voice.md) it.

<Warning>
A rejection (unsupported format, too large, etc.) drops the entire batch, including the valid files. Split it and retry with just the accepted files.
</Warning>

<Note>
Format is detected from the file's byte content, 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 |

Uploading beyond these limits returns HTTP error code `400` (per-request/per-file/per-voice limits) or `429` (too many concurrent uploads).

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/{voiceId}/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

- `voiceId` (string; required) — Voice ID 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}`.
  - `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.

### Response examples

#### 200: uploaded

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

#### 400: unsupported_format

```json
{
  "code": 3,
  "message": "invalid request: unsupported audio format for file 'sample-03.m4a'",
  "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/<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

voice_id = "<voice-id>"
url = f"https://api.inworld.ai/voices/v1/pvcVoices/{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 voiceId = '<voice-id>';
const url = `https://api.inworld.ai/voices/v1/pvcVoices/${voiceId}/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);
```
