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

# Initialize a media upload for voice samples

> Start an owned multipart upload. Upload each part to its signed URL, then complete it before calling a generation action.

Start an owned multipart upload. Upload each part to its signed URL, then complete it before calling a generation action.

```http theme={null}
POST https://api.voicecheap.ai/v1/actions/voices_upload_initialize
```

## Authentication and permissions

<ParamField header="x-api-key" type="string" required>
  Send a VoiceCheap API key in this header. Authorization: Bearer with the same API key is also supported. OAuth access tokens are accepted only by
  MCP, not by this REST endpoint.
</ParamField>

Required permissions: `voices:write`.

An active paid subscription is required. Permissions do not change team roles, resource ownership, media credits, or plan limits.

## Parameters

<ParamField body="filename" type="string" required>
  Text value.

  Minimum length: 1. Maximum length: 255. Pattern: `^[^/\\\r\n\0]+$`.
</ParamField>

<ParamField body="contentType" type="string" required>
  Text value.

  Allowed values: "audio/mpeg", "audio/mp3", "audio/wav", "audio/x-wav", "audio/mp4", "audio/x-m4a", "audio/ogg", "audio/opus", "audio/x-opus".
</ParamField>

<ParamField body="fileSize" type="integer" required>
  Integer value.

  Maximum: 20971520.
</ParamField>

<ParamField body="kind" type="string">
  Text value.

  Required value: "voice\_sample". Default: "voice\_sample".
</ParamField>

<ParamField body="requestId" type="string">
  Optional at the transport boundary: the server injects an identifier before validation when omitted. Supply your own stable UUID v4 to recover safely from a lost response.

  Format: "uuid". Pattern: `^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$`.
</ParamField>

<Accordion title="Complete input schema">
  The JSON Schema below preserves the exact field names, nested types, defaults, bounds, and accepted values exposed by discovery. Additional cross-field rules are listed in the operation notes.

  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "filename": {
        "type": "string",
        "minLength": 1,
        "maxLength": 255,
        "pattern": "^[^/\\\\\\r\\n\\0]+$"
      },
      "contentType": {
        "type": "string",
        "enum": ["audio/mpeg", "audio/mp3", "audio/wav", "audio/x-wav", "audio/mp4", "audio/x-m4a", "audio/ogg", "audio/opus", "audio/x-opus"]
      },
      "fileSize": {
        "type": "integer",
        "exclusiveMinimum": 0,
        "maximum": 20971520
      },
      "kind": {
        "default": "voice_sample",
        "type": "string",
        "const": "voice_sample"
      },
      "requestId": {
        "description": "UUID v4 for this action. Reuse the same ID with identical arguments on retries.",
        "type": "string",
        "format": "uuid",
        "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
      }
    },
    "required": ["filename", "contentType", "fileSize"],
    "additionalProperties": false
  }
  ```
</Accordion>

## Idempotency

Use a UUID v4 in requestId or the Idempotency-Key HTTP header for each mutation. Reuse the same identifier and identical arguments after a network failure. If both are supplied, they must match. Replaying a saved request does not repeat the operation or charge another monthly call. Failed or interrupted requests are not restarted automatically.

Every mutation can return HTTP 202 when a retry finds the same request still running, even when its first execution is normally synchronous. If execution completed but resultStored is false, a retry returns HTTP 200 with saved request state instead of the original result. Retrieve the output through its domain history, result, or download action.

## Request examples

Replace example resource identifiers and media keys with values returned for your account. Keep the request identifier stable when retrying the same mutation.

<RequestExample>
  ```bash cURL theme={null}
  curl --request POST 'https://api.voicecheap.ai/v1/actions/voices_upload_initialize' \
    --header "x-api-key: $VOICECHEAP_API_KEY" \
    --header 'Content-Type: application/json' \
    --data '{"filename":"voice-sample.wav","contentType":"audio/wav","fileSize":1048576,"kind":"voice_sample","requestId":"13579bdf-2468-4ace-8bdf-0123456789ab"}'
  ```

  ```javascript JavaScript theme={null}
  const input = {
    filename: 'voice-sample.wav',
    contentType: 'audio/wav',
    fileSize: 1048576,
    kind: 'voice_sample',
    requestId: '13579bdf-2468-4ace-8bdf-0123456789ab',
  };
  const response = await fetch('https://api.voicecheap.ai/v1/actions/voices_upload_initialize', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.VOICECHEAP_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(input),
  });
  const body = await response.json();
  if (!response.ok) throw new Error(body.code + ': ' + body.message);
  console.log(body);
  ```

  ```python Python theme={null}
  import os
  import requests

  response = requests.post(
      "https://api.voicecheap.ai/v1/actions/voices_upload_initialize",
      headers={"x-api-key": os.environ["VOICECHEAP_API_KEY"]},
      json={"filename": "voice-sample.wav", "contentType": "audio/wav", "fileSize": 1048576, "kind": "voice_sample", "requestId": "13579bdf-2468-4ace-8bdf-0123456789ab"},
      timeout=120,
  )
  body = response.json()
  if not response.ok:
      raise RuntimeError(f"{body.get('code')}: {body.get('message')}")
  print(body)
  ```
</RequestExample>

## Response

A successful first execution returns HTTP 200 with the operation result under data. A replay may return saved request state instead. The example shows one supported response, not every optional field.

An owned multipart upload session. key is a storage reference; complete all parts before using it as a generation fileKey.

<ResponseExample>
  ```json HTTP 200 theme={null}
  {
    "data": {
      "uploadId": "a1b2c3d4-e5f6-4789-8abc-def012345678",
      "key": "uploads/account-user/a1b2c3d4-e5f6-4789-8abc-def012345678-voice-sample.wav",
      "partSize": 5242880,
      "maxConcurrency": 4
    },
    "requestId": "13579bdf-2468-4ace-8bdf-0123456789ab"
  }
  ```

  ```json HTTP 202 replay_running theme={null}
  {
    "data": {
      "requestId": "13579bdf-2468-4ace-8bdf-0123456789ab",
      "operation": "voices_upload_initialize",
      "createdAt": 1792144800000,
      "updatedAt": 1792144820000,
      "status": "running",
      "pollAfterSeconds": 5
    },
    "requestId": "13579bdf-2468-4ace-8bdf-0123456789ab"
  }
  ```

  ```json HTTP 200 replay_completed_without_result theme={null}
  {
    "data": {
      "requestId": "13579bdf-2468-4ace-8bdf-0123456789ab",
      "operation": "voices_upload_initialize",
      "createdAt": 1792144800000,
      "updatedAt": 1792144820000,
      "status": "completed",
      "resultStored": false,
      "message": "The action completed. Retrieve its output from the corresponding history or resource action."
    },
    "requestId": "13579bdf-2468-4ace-8bdf-0123456789ab"
  }
  ```
</ResponseExample>

## Result fields

| Field | Description |
| - | - |
| `data.uploadId` | Opaque UUID for the active upload session; use it verbatim for subsequent multipart actions. |
| `data.key` | Actor-owned storage key. A generation must wait for upload\_complete. |
| `data.partSize` | Size in bytes of every nonfinal part. |
| `data.maxConcurrency` | Recommended maximum number of concurrently uploaded parts. |

## Recover and poll this request

Use the same identifier even after a lost connection. If resultStored is false, retrieve the output through the corresponding history, result, or download action. Never start a second generation merely to check progress.

```bash theme={null}
curl --request POST 'https://api.voicecheap.ai/v1/actions/action_request_get' \
  --header "x-api-key: $VOICECHEAP_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{"requestId":"13579bdf-2468-4ace-8bdf-0123456789ab","operation":"voices_upload_initialize"}'
```

```json HTTP 200 theme={null}
{
  "data": {
    "requestId": "13579bdf-2468-4ace-8bdf-0123456789ab",
    "operation": "voices_upload_initialize",
    "status": "completed",
    "createdAt": 1792144800000,
    "updatedAt": 1792144820000,
    "resultStored": true,
    "result": {
      "uploadId": "a1b2c3d4-e5f6-4789-8abc-def012345678",
      "key": "uploads/account-user/a1b2c3d4-e5f6-4789-8abc-def012345678-voice-sample.wav",
      "partSize": 5242880,
      "maxConcurrency": 4
    }
  }
}
```

## Operation notes

* Requires a paid subscription and the action permission. The actor's personal tool plan is rechecked at execution; saved tools and upload references belong to that actor, including when the credential is associated with a team.
* A successful action uses one shared monthly API/MCP call. Beginner allows 500, Starter 5,000 and Creator 25,000 calls/month; Pro, Scale and Enterprise have no monthly ceiling. Per-minute limits still apply: 30, 60, 120, 180, 300 and 600 respectively. Tool generation allowances are separate.
* Use a UUID v4 requestId, or an identical Idempotency-Key header, and retain the same action, ID and arguments on retries. A cached replay does not use another monthly call.
* The upload prefix selects the required permission. tools uses transcription:write, subtitles uses subtitles:write, projects uses projects:write, and voices uses voices:write.
* Voice samples support MP3, WAV, M4A, OGG or Opus audio only and are limited to 20 MiB (20,971,520 bytes). The MIME type must be one of the documented audio types. The kind defaults to voice\_sample; this family has no upload\_import action.
* Use the completed sample fileKey with the relevant voice creation action. Uploading alone does not create a voice or generate speech.
* The input filename must be a single filename, without slashes, backslashes, CR, LF or NUL. The contentType must be a supported audio/video MIME type, or application/octet-stream for nonvoice uploads.
* Initialization returns key, not fileKey. Retain uploadId separately; it is the UUID used for signing, completion and abortion.
* partSize is selected from 5 MiB to 100 MiB. Compute the number of parts as ceil(fileSize / partSize), use at most maxConcurrency concurrent PUTs, and retain each returned ETag.
* Upload sessions expire after 24 hours. Upload preparation does not consume a daily generation allowance.

## Usage

Each successful invocation consumes one monthly API/MCP call. Media credits and the operation's own generation allowances also apply.

## Errors

| HTTP status | Code | Meaning or recovery |
| - | - | - |
| 400 | `BAD_REQUEST` | The declared fileSize exceeds the selected kind or plan upload limit. |
| 400 | `CREDENTIAL_IN_QUERY` | Credentials were supplied in an access\_token or api\_key query parameter. Send credentials in headers. |
| 400 | `IDEMPOTENCY_CONFLICT` | The header and body request IDs differ. |
| 400 | `INVALID_REQUEST_ID` | requestId or Idempotency-Key is not a UUID v4. |
| 400 | `VALIDATION_ERROR` | Arguments do not satisfy the action schema, or an unknown argument was supplied. |
| 401 | `API_KEY_EXPIRED` | The API key has expired. |
| 401 | `INVALID_API_KEY` | Provide a valid, unexpired API key. |
| 401 | `INVALID_CREDENTIAL` | The Authorization header is malformed or conflicts with x-api-key. |
| 401 | `MISSING_CREDENTIAL` | No usable header credential was supplied, or it exceeded 4096 characters. |
| 401 | `USER_NOT_FOUND` | The credential's account no longer exists. |
| 403 | `ACCOUNT_UNAVAILABLE` | The acting or billing account is unavailable or being deleted. |
| 403 | `AMBIGUOUS_TEAM_API_KEY` | The credential's user belongs to more than one team, so the billing context is ambiguous. |
| 403 | `INSUFFICIENT_SCOPE` | The API key or MCP connection lacks the required action permission. |
| 403 | `INTEGRATION_PLAN_UNSUPPORTED` | The account's integration limits could not be verified. |
| 403 | `PAID_SUBSCRIPTION_REQUIRED` | The authenticated actor no longer has a paid subscription when this tool action executes. |
| 403 | `SUBSCRIPTION_REQUIRED` | Use an active paid subscription. |
| 403 | `TEAM_OWNER_NOT_FOUND` | The effective team owner account could not be found. |
| 409 | `CONCURRENT_ACTION_LIMIT_REACHED` | This account already has 10 integration actions running. Poll an existing request before starting another. |
| 409 | `IDEMPOTENCY_CONFLICT` | Reuse an identifier only with the same operation and arguments. |
| 429 | `INTEGRATION_MONTHLY_LIMIT_REACHED` | Used and pending calls have reached the shared monthly API/MCP allowance. |
| 429 | `INTEGRATION_RATE_LIMIT_REACHED` | The account has reached its per-minute integration limit; inspect retryAfterSeconds. |
| 500 | `INTERNAL_ERROR` | Check saved request state and resource history before deciding to retry a mutation. |
| 503 | `ACTION_RESULT_UNAVAILABLE` | The action ran but request-result persistence could not be confirmed. Poll the same action and requestId and inspect resource history before starting another write. |
| 503 | `INTERNAL_ERROR` | Upload initialization or durable session storage is unavailable. |
| 503 | `SERVER_RESTARTING` | The server is restarting. Retry shortly and keep the same request identifier for a mutation. |

```json theme={null}
{
  "code": "INSUFFICIENT_SCOPE",
  "message": "This credential does not permit the requested action.",
  "status": 403,
  "details": {
    "requiredScopes": ["voices:write"]
  }
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.