> ## Documentation Index
> Fetch the complete documentation index at: https://dev.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`.

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](/api-reference/pvcAPI/pvcvoiceservice/train-pvc-voice) it.

<Warning>
  **Uploads are all-or-nothing.** If any file in a request is rejected (unsupported format, too large, etc.), none of the files in that request are saved — including the valid ones. Split a batch 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 | 5 GB           |
| 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](/tts/best-practices/voice-cloning#best-practices-for-professional-voice-cloning).


## OpenAPI

````yaml post /voices/v1/pvcVoices/{voiceId}/samples
openapi: 3.0.0
info:
  title: Inworld Professional Voice Cloning API
  version: v1
  contact:
    name: Inworld AI
    url: https://inworld.ai
    email: support@inworld.ai
servers:
  - url: https://api.inworld.ai
security:
  - inworld_basic: []
tags:
  - name: PvcVoiceService
paths:
  /voices/v1/pvcVoices/{voiceId}/samples:
    post:
      tags:
        - PvcVoiceService
      summary: Upload PVC voice samples
      description: >-
        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`.
      operationId: PvcVoiceService_UploadPvcVoiceSamples
      parameters:
        - name: voiceId
          description: Voice ID of the draft PVC voice to add samples to.
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/PvcVoiceServiceUploadPvcVoiceSamplesBody'
      responses:
        '200':
          description: A successful response.
          content:
            application/json:
              schema:
                $ref: >-
                  #/components/schemas/inworldvoicev1UploadPvcVoiceSamplesResponse
              examples:
                uploaded:
                  summary: Two samples accepted
                  value:
                    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':
          description: >-
            Zero files, more than 10 files in one request, an unsupported audio
            format, or a file that exceeds the per-file or per-voice size limit.
            On any rejection, the entire batch is rejected — nothing is saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/rpcStatus'
              examples:
                unsupported_format:
                  summary: Unsupported audio format
                  value:
                    code: 3
                    message: >-
                      invalid request: unsupported audio format for file
                      'sample-03.m4a'
                    details: []
        '409':
          description: >-
            The voice is queued, training, or ready — samples can only be added
            to a draft voice.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/rpcStatus'
        '429':
          description: Too many requests, or too many concurrent uploads for the account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/rpcStatus'
        default:
          description: An unexpected error response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/rpcStatus'
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            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"'
        - lang: python
          label: Python
          source: >-
            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())
        - lang: javascript
          label: JavaScript
          source: >-
            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);
components:
  schemas:
    PvcVoiceServiceUploadPvcVoiceSamplesBody:
      type: object
      properties:
        files[]:
          type: array
          items:
            type: string
            format: binary
          description: >-
            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 GB; the voice's combined sample
            storage is capped at 5 GB and 50 samples total.
        remove_background_noise:
          type: boolean
          description: >-
            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`.
      description: Multipart form for UploadPvcVoiceSamples.
      required:
        - files[]
    inworldvoicev1UploadPvcVoiceSamplesResponse:
      type: object
      properties:
        samples:
          type: array
          items:
            $ref: '#/components/schemas/inworldvoicev1PvcVoiceSample'
          description: >-
            The samples accepted by this request, in the order they were
            uploaded.
      description: Response message for UploadPvcVoiceSamples.
    rpcStatus:
      type: object
      properties:
        code:
          type: integer
          format: int32
          description: >-
            The status code, which should be an enum value of
            [google.rpc.Code][google.rpc.Code].
        message:
          type: string
          description: >-
            A developer-facing error message, which should be in English. Any
            user-facing error message should be localized and sent in the
            [google.rpc.Status.details][google.rpc.Status.details] field, or
            localized by the client.
        details:
          type: array
          items:
            $ref: '#/components/schemas/protobufAny'
          description: >-
            A list of messages that carry the error details. There is a common
            set of message types for APIs to use.
      description: >-
        The `Status` type defines a logical error model that is suitable for
        different programming environments, including REST APIs and RPC APIs.
    inworldvoicev1PvcVoiceSample:
      type: object
      properties:
        sampleId:
          type: string
          description: >-
            Sample ID. Use this value as `{sampleId}` when trimming or deleting
            the sample.
          readOnly: true
        name:
          type: string
          description: >-
            Resource name. Format:
            `workspaces/{workspace}/pvcVoices/{voice}/samples/{sample}`.
          readOnly: true
        sizeBytes:
          type: integer
          format: int64
          description: Size of the uploaded file, in bytes.
          readOnly: true
        durationSecs:
          type: number
          format: float
          description: >-
            Analyzed duration of the sample, in seconds, before any trim is
            applied.
          readOnly: true
        mimeType:
          type: string
          enum:
            - audio/wav
            - audio/webm
            - audio/mpeg
          description: Detected audio format, sniffed from the file's byte content.
          readOnly: true
        hash:
          type: string
          description: >-
            Base64-encoded MD5 of the stored object, for verifying upload
            integrity against the source file.
          readOnly: true
        trimStartMs:
          type: integer
          format: int32
          nullable: true
          description: Trim start offset in milliseconds, if set.
        trimEndMs:
          type: integer
          format: int32
          nullable: true
          description: Trim end offset in milliseconds, if set.
      description: A single uploaded audio sample belonging to a PVC voice.
    protobufAny:
      type: object
      properties:
        '@type':
          type: string
          description: >-
            A URL/resource name that uniquely identifies the type of the
            serialized protocol buffer message.
      additionalProperties: {}
      description: >-
        `Any` contains an arbitrary serialized protocol buffer message along
        with a URL that describes the type of the serialized message.
  securitySchemes:
    inworld_basic:
      type: apiKey
      in: header
      name: Authorization
      description: >-
        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](../../../tts/resources/inworld-cli): `inworld workspace add-key`.

````