# Voice Creator Pro Cloud API

- **OpenAPI Version:** `3.2.0`
- **API Version:** `1.0.0`

Generate speech, clone and design voices, change voices, transcribe audio, and make avatar videos, dubs and cuts.

## Quickstart

Create a key in Settings > API at [app.voicecreator.pro](https://app.voicecreator.pro). The API needs a Starter or Premium plan.

```bash
curl -X POST https://api.vcp.name/v1/speech \
  -H "Authorization: Bearer $VCP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello from the API.", "voice": "af_alloy"}'

curl "https://api.vcp.name/v1/jobs/JOB_ID?wait=60" \
  -H "Authorization: Bearer $VCP_API_KEY"
```

When the job succeeds, `result.audio_url` links to the WAV. `GET /v1/voices` lists voice ids.

## Authentication

Send your key as a bearer token: `Authorization: Bearer vcp_live_...`. Keep it secret.

## Jobs

Endpoints that generate media return `202` with a job. `GET /v1/jobs/{id}?wait=60` returns when the job finishes, or after at most 60 seconds. Call it again while the status is queued or running, or use webhooks. Results are kept for 24 hours.

## Files

Send audio as `audio_file_id` or `audio_url`, photos as `photo_file_id` or `photo_url`, and videos as `video_file_id`. To upload, call `POST /v1/uploads`, PUT the file to `upload_url` with the same Content-Type within 2 hours, then pass `file_id`. Uploads are kept for 24 hours.

| Type  | Formats                  | Upload | Link          |
| ----- | ------------------------ | ------ | ------------- |
| Audio | WAV, FLAC, Ogg/Opus, MP3 | 200 MB | 25 MB         |
| Photo | JPEG, PNG, WEBP          | 20 MB  | 20 MB         |
| Video | MP4, MOV, MKV, WEBM      | 2 GB   | Not supported |

Avatar audio can be up to 20 seconds.

## Errors

Errors return an HTTP status and a body with a `code`, a `message` and, when one field is at fault, a `param`.

```json
{ "error": { "code": "invalid_request", "message": "text is required.", "param": "text" } }
```

Retry `429`, `500` and `503` with exponential backoff, waiting at least `Retry-After` when it is set. Send an `Idempotency-Key` so a retried POST cannot start a second job. Other errors fail again until the request changes.

Every response has a `Request-Id` header. Include it when you contact support.

| Status | Code                   | Meaning                                              |
| ------ | ---------------------- | ---------------------------------------------------- |
| 400    | `invalid_request`      | A parameter is missing or invalid. `param` names it. |
| 400    | `invalid_json`         | The body is not JSON.                                |
| 400    | `model_not_supported`  | The model cannot do this.                            |
| 400    | `url_fetch_failed`     | The audio or photo link could not be downloaded.     |
| 401    | `unauthorized`         | The API key is missing, invalid or revoked.          |
| 402    | `out_of_tokens`        | The account is out of tokens for this period.        |
| 403    | `paid_plan_required`   | The API needs a Starter or Premium plan.             |
| 403    | `preset_read_only`     | Preset voices cannot be renamed or deleted.          |
| 404    | `not_found`            | No such resource.                                    |
| 404    | `voice_not_found`      | No such voice.                                       |
| 404    | `file_not_found`       | No such upload. Uploads expire after 24 hours.       |
| 404    | `speech_job_not_found` | No such speech job, or its audio expired.            |
| 405    | `method_not_allowed`   | The path does not support this method.               |
| 409    | `idempotency_conflict` | The Idempotency-Key was used with a different body.  |
| 409    | `job_not_ready`        | The job has not succeeded yet.                       |
| 409    | `design_expired`       | The voice design is older than 24 hours.             |
| 409    | `voice_unavailable`    | The voice has no reference audio.                    |
| 409    | `voice_save_failed`    | The voice could not be saved.                        |
| 413    | `too_large`            | The file or text is too large.                       |
| 413    | `storage_full`         | The account storage is full.                         |
| 422    | `photo_refused`        | The photo cannot be used.                            |
| 429    | `rate_limited`         | Too many requests for this key.                      |
| 429    | `concurrency_limit`    | Too many jobs in progress.                           |
| 500    | `internal_error`       | Something went wrong on our side.                    |
| 503    | `unavailable`          | Temporarily unavailable.                             |

A failed job has status `failed` and an `error` with one of these codes:

| Code                   | Meaning                                                             |
| ---------------------- | ------------------------------------------------------------------- |
| `out_of_tokens`        | The account ran out of tokens.                                      |
| `no_speech`            | No speech was found, or none was produced.                          |
| `no_face`              | No face was found in the photo.                                     |
| `many_faces`           | The photo shows more than one face.                                 |
| `too_long`             | The video is longer than 30 minutes for a dub or 3 hours for a cut. |
| `unsupported_language` | The model cannot speak the target language.                         |
| `bad_video`            | The video has no audio track or cannot be read.                     |
| `generation_failed`    | The job failed. Try again.                                          |

## Idempotency

POST requests that start a job accept an `Idempotency-Key` header. Retrying with the same key and body returns the original job. The same key with a different body returns `409 idempotency_conflict`. Keys are kept for 90 days.

## Rate limits

| Limit                        | Starter | Premium |
| ---------------------------- | ------- | ------- |
| Requests per minute, per key | 60      | 60      |
| Jobs in progress             | 3       | 10      |

Over a limit, requests get `429` with a `Retry-After` header. Responses to a valid key have a `RateLimit-Policy` header.

## Webhooks

Add up to 5 https endpoints in Settings > API. When a job made with an API key finishes, every enabled endpoint gets a POST with `job.succeeded`, `job.failed` or `job.cancelled`. `data` is the job.

Requests are signed with HMAC-SHA256 in the `webhook-id`, `webhook-timestamp` and `webhook-signature` headers of [Standard Webhooks](https://www.standardwebhooks.com). Verify them with your `whsec_` secret and the raw request body:

```js
import { Webhook } from 'standardwebhooks'

const event = new Webhook(process.env.VCP_WEBHOOK_SECRET).verify(rawBody, headers)
```

```python
import os
from standardwebhooks.webhooks import Webhook

event = Webhook(os.environ["VCP_WEBHOOK_SECRET"]).verify(raw_body, headers)
```

`verify` fails when the signature is wrong or the timestamp is more than 5 minutes off.

Respond with a 2xx within 10 seconds. Redirects are not followed. Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 8 hours and 12 hours, up to 7 attempts over about 23 hours. An endpoint that fails for 3 days is turned off. Use `webhook-id` to skip duplicates. Events can arrive out of order.

## Usage

Jobs use tokens from your plan. `tokens_charged` on the job shows the cost. API requests stay out of History. List them in Settings > API or with `GET /v1/requests`.

## Contents

**Operations**

- [GET /v1/models](#scalar-operation-get-v1models)
- [GET /v1/usage](#scalar-operation-get-v1usage)
- [GET /v1/requests](#scalar-operation-get-v1requests)
- [POST /v1/avatar-videos](#scalar-operation-post-v1avatar-videos)
- [POST /v1/cuts](#scalar-operation-post-v1cuts)
- [POST /v1/voices/design](#scalar-operation-post-v1voicesdesign)
- [POST /v1/voices/design/{id}/save](#scalar-operation-post-v1voicesdesignidsave)
- [POST /v1/dubs](#scalar-operation-post-v1dubs)
- [GET /v1/dubs/{id}/subtitles](#scalar-operation-get-v1dubsidsubtitles)
- [GET /v1/jobs/{id}](#scalar-operation-get-v1jobsid)
- [POST /v1/jobs/{id}/cancel](#scalar-operation-post-v1jobsidcancel)
- [POST /v1/speech](#scalar-operation-post-v1speech)
- [POST /v1/transcriptions](#scalar-operation-post-v1transcriptions)
- [POST /v1/uploads](#scalar-operation-post-v1uploads)
- [POST /v1/voice-changer](#scalar-operation-post-v1voice-changer)
- [POST /v1/voices](#scalar-operation-post-v1voices)
- [GET /v1/voices](#scalar-operation-get-v1voices)
- [GET /v1/voices/{id}](#scalar-operation-get-v1voicesid)
- [PATCH /v1/voices/{id}](#scalar-operation-patch-v1voicesid)
- [DELETE /v1/voices/{id}](#scalar-operation-delete-v1voicesid)

**Webhooks**

- [POST job](#scalar-webhook-post-job)

**Schemas**

- [ErrorBody](#scalar-schema-errorbody)
- [ErrorDetail](#scalar-schema-errordetail)
- [JobType](#scalar-schema-jobtype)
- [JobStatus](#scalar-schema-jobstatus)
- [Job](#scalar-schema-job)
- [AudioResult](#scalar-schema-audioresult)
- [TranscriptionResult](#scalar-schema-transcriptionresult)
- [VoiceCloneResult](#scalar-schema-voicecloneresult)
- [AvatarVideoResult](#scalar-schema-avatarvideoresult)
- [DubResult](#scalar-schema-dubresult)
- [CutResult](#scalar-schema-cutresult)
- [SubtitleLine](#scalar-schema-subtitleline)
- [SubtitleList](#scalar-schema-subtitlelist)
- [Word](#scalar-schema-word)
- [Voice](#scalar-schema-voice)
- [VoiceList](#scalar-schema-voicelist)
- [Model](#scalar-schema-model)
- [Preset](#scalar-schema-preset)
- [ModelList](#scalar-schema-modellist)
- [Usage](#scalar-schema-usage)
- [RequestSummary](#scalar-schema-requestsummary)
- [RequestLogEntry](#scalar-schema-requestlogentry)
- [RequestLog](#scalar-schema-requestlog)
- [SpeechBody](#scalar-schema-speechbody)
- [VoiceDesignBody](#scalar-schema-voicedesignbody)
- [TranscriptionBody](#scalar-schema-transcriptionbody)
- [VoiceChangeBody](#scalar-schema-voicechangebody)
- [VoiceCloneBody](#scalar-schema-voiceclonebody)
- [AvatarVideoBody](#scalar-schema-avatarvideobody)
- [DubBody](#scalar-schema-dubbody)
- [CutBody](#scalar-schema-cutbody)
- [VoiceNameBody](#scalar-schema-voicenamebody)
- [UploadBody](#scalar-schema-uploadbody)
- [Upload](#scalar-schema-upload)
- [WebhookEvent](#scalar-schema-webhookevent)

<a id="scalar-context-global-servers"></a>

## Servers

- **URL:** `https://api.vcp.name`

<a id="scalar-context-global-authentication"></a>

## Authentication

- **bearer**: HTTP bearer (`API key`)

  An API key from Settings, starting vcp\_live\_.

## Tags

### Speech

### Voices

### Transcriptions

### Voice changer

### Avatar videos

### Dubs

### Cuts

### Jobs

### Uploads

### Account

### Webhooks

## Operations

<a id="scalar-operation-get-v1models"></a>

### List models

- **Method:** `GET`
- **Path:** `/v1/models`
- **Operation ID:** `listModels`
- **Tags:** Account

Lists the models you can generate with, with their languages and preset voices.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Responses

##### 200 OK.

**Content type:** `application/json`

[ModelList](#scalar-schema-modellist)

**Example:**

```json
{
  "models": [
    {
      "id": "qwen3-tts",
      "name": "Qwen3",
      "clone": true,
      "design": true,
      "design_takes_text": true,
      "languages": [
        "English",
        "Spanish",
        "French"
      ],
      "presets": [
        {
          "id": "Ryan",
          "name": "Ryan",
          "description": "",
          "language": "English"
        }
      ]
    }
  ]
}
```

##### 401, 403, 429, 500, 503

- `401` The API key is missing, invalid or revoked.
- `403` The account needs a paid plan, or the action is not allowed.
- `429` Too many requests. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-get-v1usage"></a>

### Get usage

- **Method:** `GET`
- **Path:** `/v1/usage`
- **Operation ID:** `getUsage`
- **Tags:** Account

Returns your plan and the tokens used this period.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Responses

##### 200 OK.

**Content type:** `application/json`

[Usage](#scalar-schema-usage)

**Example:**

```json
{
  "tier": "premium",
  "tokens_used": 188586,
  "tokens_limit": 1500000,
  "period_start": "2026-10-01T00:00:00.000Z",
  "period_end": "2026-11-01T00:00:00.000Z"
}
```

##### 401, 403, 429, 500, 503

- `401` The API key is missing, invalid or revoked.
- `403` The account needs a paid plan, or the action is not allowed.
- `429` Too many requests. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-get-v1requests"></a>

### List requests

- **Method:** `GET`
- **Path:** `/v1/requests`
- **Operation ID:** `listRequests`
- **Tags:** Account

Lists your API requests, newest first. Pass `next_before` as `before` to get the next page, until it is null. A page can be empty while `next_before` is set, or hold more than `limit` requests made at the same moment.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Query parameters

- **`limit`**: `integer`, default: `50`, minimum: `1`, maximum: `100`

  Requests per page.
- **`before`**: `string`, format: `date-time`

  Only requests made before this time.
- **`type`**: `string`, possible values: `"speech", "voice_design", "voice_clone", "voice_change", "transcription", "dub", "avatar_video", "cut"`
- **`status`**: `string`, possible values: `"queued", "running", "succeeded", "failed", "cancelled"`
- **`key_id`**: `string`, format: `uuid`

  Only requests made with this API key.

#### Responses

##### 200 OK.

**Content type:** `application/json`

[RequestLog](#scalar-schema-requestlog)

**Example:**

```json
{
  "requests": [
    {
      "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
      "type": "speech",
      "status": "succeeded",
      "error": null,
      "tokens_charged": 42,
      "api_key_id": "5f0c2a8e-3b1d-4c7e-9a62-1d8f4e7b2c90",
      "summary": {
        "text": "Hello from the API.",
        "voice": "Alloy",
        "model": "kokoro"
      },
      "created_at": "2026-10-30T00:10:42Z",
      "finished_at": "2026-10-30T00:18:01Z"
    }
  ],
  "next_before": null
}
```

##### 400, 401, 403, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `403` The account needs a paid plan, or the action is not allowed.
- `429` Too many requests. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-post-v1avatar-videos"></a>

### Make an avatar video

- **Method:** `POST`
- **Path:** `/v1/avatar-videos`
- **Operation ID:** `createAvatarVideo`
- **Tags:** Avatar videos

Makes a video of the person in a photo speaking the audio. Send the audio as `speech_job_id` from POST /v1/speech, or as a WAV of up to 20 seconds. The photo must show one face. Returns a job whose result is an `AvatarVideoResult`.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Header parameters

- **`Idempotency-Key`**: `string`, maxLength: `255`

  Optional. Up to 255 characters, scoped to your account and kept for 90 days. Sending the same key with the same body again returns the original job. The same key with a different body is rejected with 409.

#### Request body

**Required:** `true`

**Content type:** `application/json`

[AvatarVideoBody](#scalar-schema-avatarvideobody)

**Example:**

```json
{
  "photo_file_id": "8383fc02-625a-4b71-a226-cbb2d19aaf17",
  "speech_job_id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
  "consent": true
}
```

#### Responses

##### 202 Accepted. Returns the job.

**Content type:** `application/json`

[Job](#scalar-schema-job)

**Example:**

```json
{
  "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
  "type": "avatar_video",
  "status": "queued",
  "progress": null,
  "result": null,
  "error": null,
  "tokens_charged": null,
  "resource_id": "857fcb81-9073-4f26-97c7-a388979fcdca",
  "created_at": "2026-10-30T00:10:42Z",
  "finished_at": null
}
```

##### 400, 401, 402, 403, 404, 409, 413, 422, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `402` The account is out of tokens for this period.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `409` Conflicts with the current state, or an Idempotency-Key was reused with a different body.
- `413` The request is too large.
- `422` The photo cannot be used.
- `429` Too many requests, or this account already has too many jobs in progress. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-post-v1cuts"></a>

### Cut a video

- **Method:** `POST`
- **Path:** `/v1/cuts`
- **Operation ID:** `createCut`
- **Tags:** Cuts

Cuts filler words, retakes and long pauses out of a video of up to 3 hours. Returns a job whose result is a `CutResult`. The job stays `running` until the cut video is ready.

Estimated cost: 7000 tokens plus 20 per second of video, plus 4 per second of the cut video.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Header parameters

- **`Idempotency-Key`**: `string`, maxLength: `255`

  Optional. Up to 255 characters, scoped to your account and kept for 90 days. Sending the same key with the same body again returns the original job. The same key with a different body is rejected with 409.

#### Request body

**Required:** `true`

**Content type:** `application/json`

[CutBody](#scalar-schema-cutbody)

**Example:**

```json
{
  "video_file_id": "8383fc02-625a-4b71-a226-cbb2d19aaf17"
}
```

#### Responses

##### 202 Accepted. Returns the job.

**Content type:** `application/json`

[Job](#scalar-schema-job)

**Example:**

```json
{
  "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
  "type": "cut",
  "status": "queued",
  "progress": null,
  "result": null,
  "error": null,
  "tokens_charged": null,
  "resource_id": "857fcb81-9073-4f26-97c7-a388979fcdca",
  "created_at": "2026-10-30T00:10:42Z",
  "finished_at": null
}
```

##### 400, 401, 402, 403, 404, 409, 413, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `402` The account is out of tokens for this period.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `409` Conflicts with the current state, or an Idempotency-Key was reused with a different body.
- `413` The request is too large.
- `429` Too many requests, or this account already has too many jobs in progress. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-post-v1voicesdesign"></a>

### Design a voice

- **Method:** `POST`
- **Path:** `/v1/voices/design`
- **Operation ID:** `designVoice`
- **Tags:** Voices

Designs a voice from a description and speaks a sample. Returns a job whose result is an `AudioResult`. Save the voice with POST /v1/voices/design/{id}/save within 24 hours.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Header parameters

- **`Idempotency-Key`**: `string`, maxLength: `255`

  Optional. Up to 255 characters, scoped to your account and kept for 90 days. Sending the same key with the same body again returns the original job. The same key with a different body is rejected with 409.

#### Request body

**Required:** `true`

**Content type:** `application/json`

[VoiceDesignBody](#scalar-schema-voicedesignbody)

**Example:**

```json
{
  "description": "A calm, low narrator voice.",
  "text": "Welcome back to the show."
}
```

#### Responses

##### 202 Accepted. Returns the job.

**Content type:** `application/json`

[Job](#scalar-schema-job)

**Example:**

```json
{
  "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
  "type": "voice_design",
  "status": "queued",
  "progress": null,
  "result": null,
  "error": null,
  "tokens_charged": null,
  "resource_id": null,
  "created_at": "2026-10-30T00:10:42Z",
  "finished_at": null
}
```

##### 400, 401, 402, 403, 409, 413, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `402` The account is out of tokens for this period.
- `403` The account needs a paid plan, or the action is not allowed.
- `409` Conflicts with the current state, or an Idempotency-Key was reused with a different body.
- `413` The request is too large.
- `429` Too many requests, or this account already has too many jobs in progress. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-post-v1voicesdesignidsave"></a>

### Save a designed voice

- **Method:** `POST`
- **Path:** `/v1/voices/design/{id}/save`
- **Operation ID:** `saveDesignedVoice`
- **Tags:** Voices

Saves the voice from a succeeded design job. Returns `409 design_expired` after 24 hours.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Path parameters

- **`id` (required)**: `string`

#### Request body

**Required:** `true`

**Content type:** `application/json`

[VoiceNameBody](#scalar-schema-voicenamebody)

**Example:**

```json
{
  "name": "Narrator"
}
```

#### Responses

##### 201 Created.

**Content type:** `application/json`

[Voice](#scalar-schema-voice)

**Example:**

```json
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "name": "Narrator",
  "type": "design",
  "model": null,
  "duration_seconds": null,
  "created_at": "2026-10-30T00:10:42Z"
}
```

##### 400, 401, 403, 404, 409, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `409` Conflicts with the current state, or an Idempotency-Key was reused with a different body.
- `429` Too many requests. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-post-v1dubs"></a>

### Dub a video

- **Method:** `POST`
- **Path:** `/v1/dubs`
- **Operation ID:** `createDub`
- **Tags:** Dubs

Dubs a video of up to 30 minutes into another language. Each speaker gets a voice cloned from their own lines unless `clone_voices` is false. Returns a job whose result is a `DubResult`.

Estimated cost: 225 tokens per second of video, plus lip sync.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Header parameters

- **`Idempotency-Key`**: `string`, maxLength: `255`

  Optional. Up to 255 characters, scoped to your account and kept for 90 days. Sending the same key with the same body again returns the original job. The same key with a different body is rejected with 409.

#### Request body

**Required:** `true`

**Content type:** `application/json`

[DubBody](#scalar-schema-dubbody)

**Example:**

```json
{
  "video_file_id": "8383fc02-625a-4b71-a226-cbb2d19aaf17",
  "target_language": "es"
}
```

#### Responses

##### 202 Accepted. Returns the job.

**Content type:** `application/json`

[Job](#scalar-schema-job)

**Example:**

```json
{
  "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
  "type": "dub",
  "status": "queued",
  "progress": null,
  "result": null,
  "error": null,
  "tokens_charged": null,
  "resource_id": "857fcb81-9073-4f26-97c7-a388979fcdca",
  "created_at": "2026-10-30T00:10:42Z",
  "finished_at": null
}
```

##### 400, 401, 402, 403, 404, 409, 413, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `402` The account is out of tokens for this period.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `409` Conflicts with the current state, or an Idempotency-Key was reused with a different body.
- `413` The request is too large.
- `429` Too many requests, or this account already has too many jobs in progress. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-get-v1dubsidsubtitles"></a>

### Get the subtitles of a dub

- **Method:** `GET`
- **Path:** `/v1/dubs/{id}/subtitles`
- **Operation ID:** `getDubSubtitles`
- **Tags:** Dubs

Returns the lines of a dub job as SRT, VTT or JSON.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Path parameters

- **`id` (required)**: `string`

#### Query parameters

- **`format`**: `string`, possible values: `"srt", "vtt", "json"`, default: `"srt"`

  srt for SubRip, vtt for WebVTT, or json for each line with its speaker and the original text.

#### Responses

##### 200 OK.

**Content type:** `application/x-subrip`

`string`

SubRip subtitles.

<a id="scalar-example-1"></a>

**Generated example:**

```json
""
```

**Content type:** `text/vtt`

`string`

WebVTT subtitles.

<a id="scalar-example-2"></a>

**Generated example:**

```json
""
```

**Content type:** `application/json`

[SubtitleList](#scalar-schema-subtitlelist)

Each subtitle line in order.

**Example:**

```json
[
  {
    "start": 0.4,
    "end": 2.6,
    "text": "Bienvenidos de nuevo.",
    "speaker": "SPEAKER_00",
    "source_text": "Welcome back."
  }
]
```

##### 400, 401, 403, 404, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `429` Too many requests. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-get-v1jobsid"></a>

### Get a job

- **Method:** `GET`
- **Path:** `/v1/jobs/{id}`
- **Operation ID:** `getJob`
- **Tags:** Jobs

Returns a job. With `wait`, holds the request until the job finishes or `wait` seconds pass.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Path parameters

- **`id` (required)**: `string`

#### Query parameters

- **`wait`**: `number`, minimum: `0`, maximum: `60`

  Seconds to wait for the job to finish, up to 60.

#### Responses

##### 200 OK.

**Content type:** `application/json`

[Job](#scalar-schema-job)

**Example:**

```json
{
  "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
  "type": "speech",
  "status": "succeeded",
  "progress": null,
  "result": {
    "audio_url": "https://files.example.com/speech.wav",
    "download_url": "https://files.example.com/speech.wav?download=speech.wav",
    "expires_at": "2026-10-31T00:10:42Z"
  },
  "error": null,
  "tokens_charged": 42,
  "resource_id": null,
  "created_at": "2026-10-30T00:10:42Z",
  "finished_at": "2026-10-30T00:18:01Z"
}
```

##### 400, 401, 403, 404, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `429` Too many requests. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-post-v1jobsidcancel"></a>

### Cancel a job

- **Method:** `POST`
- **Path:** `/v1/jobs/{id}/cancel`
- **Operation ID:** `cancelJob`
- **Tags:** Jobs

Cancels a queued or running job. A finished job is returned unchanged.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Path parameters

- **`id` (required)**: `string`

#### Responses

##### 200 OK.

**Content type:** `application/json`

[Job](#scalar-schema-job)

**Example:**

```json
{
  "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
  "type": "dub",
  "status": "cancelled",
  "progress": null,
  "result": null,
  "error": null,
  "tokens_charged": null,
  "resource_id": "857fcb81-9073-4f26-97c7-a388979fcdca",
  "created_at": "2026-10-30T00:10:42Z",
  "finished_at": "2026-10-30T00:18:01Z"
}
```

##### 401, 403, 404, 429, 500, 503

- `401` The API key is missing, invalid or revoked.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `429` Too many requests. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-post-v1speech"></a>

### Generate speech

- **Method:** `POST`
- **Path:** `/v1/speech`
- **Operation ID:** `createSpeech`
- **Tags:** Speech

Speaks text in a saved or preset voice. Returns a job whose result is an `AudioResult`.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Header parameters

- **`Idempotency-Key`**: `string`, maxLength: `255`

  Optional. Up to 255 characters, scoped to your account and kept for 90 days. Sending the same key with the same body again returns the original job. The same key with a different body is rejected with 409.

#### Request body

**Required:** `true`

**Content type:** `application/json`

[SpeechBody](#scalar-schema-speechbody)

**Example:**

```json
{
  "text": "Hello from the API.",
  "voice": "af_alloy"
}
```

#### Responses

##### 202 Accepted. Returns the job.

**Content type:** `application/json`

[Job](#scalar-schema-job)

**Example:**

```json
{
  "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
  "type": "speech",
  "status": "queued",
  "progress": null,
  "result": null,
  "error": null,
  "tokens_charged": null,
  "resource_id": null,
  "created_at": "2026-10-30T00:10:42Z",
  "finished_at": null
}
```

##### 400, 401, 402, 403, 404, 409, 413, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `402` The account is out of tokens for this period.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `409` Conflicts with the current state, or an Idempotency-Key was reused with a different body.
- `413` The request is too large.
- `429` Too many requests, or this account already has too many jobs in progress. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-post-v1transcriptions"></a>

### Transcribe audio

- **Method:** `POST`
- **Path:** `/v1/transcriptions`
- **Operation ID:** `createTranscription`
- **Tags:** Transcriptions

Transcribes audio. Returns a job whose result is a `TranscriptionResult`.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Header parameters

- **`Idempotency-Key`**: `string`, maxLength: `255`

  Optional. Up to 255 characters, scoped to your account and kept for 90 days. Sending the same key with the same body again returns the original job. The same key with a different body is rejected with 409.

#### Request body

**Required:** `true`

**Content type:** `application/json`

[TranscriptionBody](#scalar-schema-transcriptionbody)

**Example:**

```json
{
  "audio_url": "https://example.com/interview.mp3"
}
```

#### Responses

##### 202 Accepted. Returns the job.

**Content type:** `application/json`

[Job](#scalar-schema-job)

**Example:**

```json
{
  "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
  "type": "transcription",
  "status": "queued",
  "progress": null,
  "result": null,
  "error": null,
  "tokens_charged": null,
  "resource_id": null,
  "created_at": "2026-10-30T00:10:42Z",
  "finished_at": null
}
```

##### 400, 401, 402, 403, 404, 409, 413, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `402` The account is out of tokens for this period.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `409` Conflicts with the current state, or an Idempotency-Key was reused with a different body.
- `413` The request is too large.
- `429` Too many requests, or this account already has too many jobs in progress. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-post-v1uploads"></a>

### Upload a file

- **Method:** `POST`
- **Path:** `/v1/uploads`
- **Operation ID:** `createUpload`
- **Tags:** Uploads

Creates an upload link. PUT the file to `upload_url` with the same Content-Type within 2 hours, then pass `file_id` to another endpoint.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Request body

**Required:** `true`

**Content type:** `application/json`

[UploadBody](#scalar-schema-uploadbody)

**Example:**

```json
{
  "content_type": "video/mp4",
  "size": 284023112
}
```

#### Responses

##### 201 Created.

**Content type:** `application/json`

[Upload](#scalar-schema-upload)

**Example:**

```json
{
  "file_id": "8383fc02-625a-4b71-a226-cbb2d19aaf17",
  "upload_url": "https://files.example.com/upload/8383fc02-625a-4b71-a226-cbb2d19aaf17?token=abc",
  "expires_at": "2026-10-31T00:10:42Z"
}
```

##### 400, 401, 403, 413, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `403` The account needs a paid plan, or the action is not allowed.
- `413` The request is too large.
- `429` Too many requests. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-post-v1voice-changer"></a>

### Change a voice

- **Method:** `POST`
- **Path:** `/v1/voice-changer`
- **Operation ID:** `createVoiceChange`
- **Tags:** Voice changer

Makes speech sound like one of your saved voices. The audio can be up to 60 minutes long. Returns a job whose result is an `AudioResult`.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Header parameters

- **`Idempotency-Key`**: `string`, maxLength: `255`

  Optional. Up to 255 characters, scoped to your account and kept for 90 days. Sending the same key with the same body again returns the original job. The same key with a different body is rejected with 409.

#### Request body

**Required:** `true`

**Content type:** `application/json`

[VoiceChangeBody](#scalar-schema-voicechangebody)

**Example:**

```json
{
  "audio_file_id": "8383fc02-625a-4b71-a226-cbb2d19aaf17",
  "voice": "Narrator"
}
```

#### Responses

##### 202 Accepted. Returns the job.

**Content type:** `application/json`

[Job](#scalar-schema-job)

**Example:**

```json
{
  "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
  "type": "voice_change",
  "status": "queued",
  "progress": null,
  "result": null,
  "error": null,
  "tokens_charged": null,
  "resource_id": null,
  "created_at": "2026-10-30T00:10:42Z",
  "finished_at": null
}
```

##### 400, 401, 402, 403, 404, 409, 413, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `402` The account is out of tokens for this period.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `409` Conflicts with the current state, or an Idempotency-Key was reused with a different body.
- `413` The request is too large.
- `429` Too many requests, or this account already has too many jobs in progress. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-post-v1voices"></a>

### Clone a voice

- **Method:** `POST`
- **Path:** `/v1/voices`
- **Operation ID:** `createVoice`
- **Tags:** Voices

Clones a voice from a WAV clip of 5 to 30 seconds. Returns a job whose result is a `VoiceCloneResult`, and `resource_id` is the new voice. A clip with no speech fails with `no_speech` but the voice is saved.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Header parameters

- **`Idempotency-Key`**: `string`, maxLength: `255`

  Optional. Up to 255 characters, scoped to your account and kept for 90 days. Sending the same key with the same body again returns the original job. The same key with a different body is rejected with 409.

#### Request body

**Required:** `true`

**Content type:** `application/json`

[VoiceCloneBody](#scalar-schema-voiceclonebody)

**Example:**

```json
{
  "name": "Narrator",
  "audio_file_id": "8383fc02-625a-4b71-a226-cbb2d19aaf17"
}
```

#### Responses

##### 202 Accepted. Returns the job.

**Content type:** `application/json`

[Job](#scalar-schema-job)

**Example:**

```json
{
  "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
  "type": "voice_clone",
  "status": "queued",
  "progress": null,
  "result": null,
  "error": null,
  "tokens_charged": null,
  "resource_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "created_at": "2026-10-30T00:10:42Z",
  "finished_at": null
}
```

##### 400, 401, 402, 403, 404, 409, 413, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `402` The account is out of tokens for this period.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `409` Conflicts with the current state, or an Idempotency-Key was reused with a different body.
- `413` The request is too large.
- `429` Too many requests, or this account already has too many jobs in progress. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-get-v1voices"></a>

### List voices

- **Method:** `GET`
- **Path:** `/v1/voices`
- **Operation ID:** `listVoices`
- **Tags:** Voices

Lists your saved voices, newest first, then the preset voices.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Query parameters

- **`type`**: `string`, possible values: `"saved", "preset"`

  Only saved voices or only presets.

#### Responses

##### 200 OK.

**Content type:** `application/json`

[VoiceList](#scalar-schema-voicelist)

**Example:**

```json
{
  "voices": [
    {
      "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "name": "Narrator",
      "type": "clone",
      "model": null,
      "duration_seconds": 12.4,
      "created_at": "2026-10-30T00:10:42Z"
    },
    {
      "id": "af_alloy",
      "name": "Alloy",
      "type": "preset",
      "model": "kokoro",
      "duration_seconds": null,
      "created_at": null
    }
  ]
}
```

##### 400, 401, 403, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `403` The account needs a paid plan, or the action is not allowed.
- `429` Too many requests. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-get-v1voicesid"></a>

### Get a voice

- **Method:** `GET`
- **Path:** `/v1/voices/{id}`
- **Operation ID:** `getVoice`
- **Tags:** Voices

Returns a saved or preset voice.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Path parameters

- **`id` (required)**: `string`

#### Responses

##### 200 OK.

**Content type:** `application/json`

[Voice](#scalar-schema-voice)

**Example:**

```json
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "name": "Narrator",
  "type": "clone",
  "model": null,
  "duration_seconds": 12.4,
  "created_at": "2026-10-30T00:10:42Z"
}
```

##### 401, 403, 404, 429, 500, 503

- `401` The API key is missing, invalid or revoked.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `429` Too many requests. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-patch-v1voicesid"></a>

### Rename a voice

- **Method:** `PATCH`
- **Path:** `/v1/voices/{id}`
- **Operation ID:** `renameVoice`
- **Tags:** Voices

Renames a saved voice.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Path parameters

- **`id` (required)**: `string`

#### Request body

**Required:** `true`

**Content type:** `application/json`

[VoiceNameBody](#scalar-schema-voicenamebody)

**Example:**

```json
{
  "name": "Narrator"
}
```

#### Responses

##### 200 OK.

**Content type:** `application/json`

[Voice](#scalar-schema-voice)

**Example:**

```json
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "name": "Narrator",
  "type": "clone",
  "model": null,
  "duration_seconds": 12.4,
  "created_at": "2026-10-30T00:10:42Z"
}
```

##### 400, 401, 403, 404, 429, 500, 503

- `400` The request is invalid. The message names the field.
- `401` The API key is missing, invalid or revoked.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `429` Too many requests. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-operation-delete-v1voicesid"></a>

### Delete a voice

- **Method:** `DELETE`
- **Path:** `/v1/voices/{id}`
- **Operation ID:** `deleteVoice`
- **Tags:** Voices

Deletes a saved voice.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Path parameters

- **`id` (required)**: `string`

#### Responses

##### 204 Done. No body.

##### 401, 403, 404, 429, 500, 503

- `401` The API key is missing, invalid or revoked.
- `403` The account needs a paid plan, or the action is not allowed.
- `404` Not found.
- `429` Too many requests. Retry after the Retry-After header.
- `500` Something went wrong on our side.
- `503` Temporarily unavailable. Retry after the Retry-After header.

**Content type:** `application/json`

[ErrorBody](#scalar-schema-errorbody)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

## Webhooks

<a id="scalar-webhook-post-job"></a>

### Job finished

- **Method:** `POST`
- **Webhook:** `job`
- **Operation ID:** `jobWebhook`
- **Tags:** Webhooks

Sent to every enabled endpoint when a job made with an API key succeeds, fails or is cancelled. Answer with a 2xx status within 10 seconds.

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

No authentication required.

#### Header parameters

- **`webhook-id` (required)**: `string`

  The delivery id. It stays the same on every attempt, so use it to skip events you already handled.
- **`webhook-timestamp` (required)**: `string`

  When this attempt was sent, in Unix seconds.
- **`webhook-signature` (required)**: `string`

  v1, a comma, then the base64 HMAC-SHA256 of webhook-id, webhook-timestamp and the body joined with dots. The key is the part of your secret after whsec\_, decoded from base64.

#### Request body

**Required:** `true`

**Content type:** `application/json`

[WebhookEvent](#scalar-schema-webhookevent)

The JSON body of every webhook request.

**Example:**

```json
{
  "type": "job.succeeded",
  "timestamp": "2026-10-30T00:18:01Z",
  "api_key_id": "5f0c2a8e-3b1d-4c7e-9a62-1d8f4e7b2c90",
  "data": {
    "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
    "type": "cut",
    "status": "succeeded",
    "progress": null,
    "result": {
      "video_url": "https://files.example.com/cut.mp4",
      "download_url": "https://files.example.com/cut.mp4?download=cut.mp4",
      "duration_s": 281.344,
      "source_duration_s": 485.87,
      "width": 1080,
      "height": 1920,
      "removed": [
        {
          "start": 0,
          "end": 107.62,
          "reasons": [
            "retake"
          ]
        },
        {
          "start": 120.786,
          "end": 121.106,
          "reasons": [
            "filler"
          ]
        },
        {
          "start": 129.3,
          "end": 129.44,
          "reasons": [
            "silence"
          ]
        }
      ],
      "language": "English",
      "incomplete": [],
      "expires_at": "2026-10-31T00:10:42Z"
    },
    "error": null,
    "tokens_charged": 16311,
    "resource_id": "857fcb81-9073-4f26-97c7-a388979fcdca",
    "created_at": "2026-10-30T00:10:42Z",
    "finished_at": "2026-10-30T00:18:01Z"
  }
}
```

#### Responses

##### 200 Any 2xx status within 10 seconds marks the event delivered. Anything else, including a redirect, is sent again later.

## Schemas

<a id="scalar-schema-errorbody"></a>

### ErrorBody

**Type:** `object`, no additional properties

- **`error` (required)**: [ErrorDetail](#scalar-schema-errordetail)

**Example:**

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text is required.",
    "param": "text"
  }
}
```

<a id="scalar-schema-errordetail"></a>

### ErrorDetail

**Type:** `object`, no additional properties

- **`code` (required)**: `string`

  A code from the error tables.
- **`message` (required)**: `string`

  What went wrong.
- **`param`**: `string`

  The request field at fault, when there is one.

<a id="scalar-example-3"></a>

**Generated example:**

```json
{
  "code": "",
  "message": "",
  "param": ""
}
```

<a id="scalar-schema-jobtype"></a>

### JobType

**Type:** `string`, possible values: `"speech", "voice_design", "voice_clone", "voice_change", "transcription", "dub", "avatar_video", "cut"`

<a id="scalar-schema-jobstatus"></a>

### JobStatus

**Type:** `string`, possible values: `"queued", "running", "succeeded", "failed", "cancelled"`

<a id="scalar-schema-job"></a>

### Job

**Type:** `object`, no additional properties

- **`created_at` (required)**: `string`, format: `date-time`

  When the job was created.
- **`error` (required)**: [ErrorDetail](#scalar-schema-errordetail) | `null`

  Why the job failed, or null.
- **`finished_at` (required)**: `string | null`, format: `date-time`

  When the job finished, or null while it runs.
- **`id` (required)**: `string`

  Job id.
- **`progress` (required)**: `number | null`, minimum: `0`, maximum: `1`

  From 0 to 1 while an avatar video renders, a dub runs or a video is cut, otherwise null.
- **`resource_id` (required)**: `string | null`

  The voice id for voice clones, the project id for dubs and cuts, and the video id for avatar videos.
- **`result` (required)**: [AudioResult](#scalar-schema-audioresult) | [TranscriptionResult](#scalar-schema-transcriptionresult) | [VoiceCloneResult](#scalar-schema-voicecloneresult) | [AvatarVideoResult](#scalar-schema-avatarvideoresult) | [DubResult](#scalar-schema-dubresult) | [CutResult](#scalar-schema-cutresult) | `null`

  The result of a succeeded job, or null. Speech, voice designs and the voice changer give AudioResult, kept for 24 hours after the job finishes, then this is null. Transcriptions give TranscriptionResult. Voice clones give VoiceCloneResult. Avatar videos give AvatarVideoResult, dubs give DubResult and cuts give CutResult, all kept for 24 hours after the job was created.
- **`status` (required)**: `string`, possible values: `"queued", "running", "succeeded", "failed", "cancelled"`
- **`tokens_charged` (required)**: `integer | null`, minimum: `0`

  Tokens billed, or null until billing has run.
- **`type` (required)**: `string`, possible values: `"speech", "voice_design", "voice_clone", "voice_change", "transcription", "dub", "avatar_video", "cut"`

<a id="scalar-example-4"></a>

**Generated example:**

```json
{
  "id": "",
  "type": "speech",
  "status": "queued",
  "progress": null,
  "result": {
    "audio_url": "https://files.example.com/speech.wav",
    "download_url": "https://files.example.com/speech.wav?download=speech.wav",
    "expires_at": "2026-10-31T00:10:42Z"
  },
  "error": {
    "code": "",
    "message": "",
    "param": ""
  },
  "tokens_charged": null,
  "resource_id": null,
  "created_at": "",
  "finished_at": null
}
```

<a id="scalar-schema-audioresult"></a>

### AudioResult

**Type:** `object`, no additional properties

Links to the audio. The audio is kept for 24 hours after the job finishes.

- **`audio_url` (required)**: `string`, format: `uri`

  WAV audio link for playback.
- **`download_url` (required)**: `string`, format: `uri`

  The same audio, served as a download.
- **`expires_at` (required)**: `string`, format: `date-time`

  When both links stop working, 24 hours after the job finished.

**Example:**

```json
{
  "audio_url": "https://files.example.com/speech.wav",
  "download_url": "https://files.example.com/speech.wav?download=speech.wav",
  "expires_at": "2026-10-31T00:10:42Z"
}
```

<a id="scalar-schema-transcriptionresult"></a>

### TranscriptionResult

**Type:** `object`, no additional properties

The transcript of the audio.

- **`language` (required)**: `string | null`

  The spoken language, or null if unknown.
- **`text` (required)**: `string`

  The full transcript.
- **`words` (required)**: array of [Word](#scalar-schema-word) | `null`

  Each word with its timing, or null without timestamps.

**Example:**

```json
{
  "text": "Welcome back to the show.",
  "language": "English",
  "words": [
    {
      "text": "Welcome",
      "start": 0.32,
      "end": 0.71,
      "speaker": "SPEAKER_00"
    },
    {
      "text": "back",
      "start": 0.71,
      "end": 0.94,
      "speaker": "SPEAKER_00"
    }
  ]
}
```

<a id="scalar-schema-voicecloneresult"></a>

### VoiceCloneResult

**Type:** `object`, no additional properties

The saved voice.

- **`transcript` (required)**: `string | null`

  What the clip says, or null if it has no transcript.
- **`voice` (required)**: [Voice](#scalar-schema-voice)

**Example:**

```json
{
  "voice": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "name": "Narrator",
    "type": "clone",
    "model": null,
    "duration_seconds": 12.4,
    "created_at": "2026-10-30T00:10:42Z"
  },
  "transcript": "Welcome back to the show."
}
```

<a id="scalar-schema-avatarvideoresult"></a>

### AvatarVideoResult

**Type:** `object`, no additional properties

Links to the video. The video is kept for 24 hours after the job was created.

- **`download_url` (required)**: `string`, format: `uri`

  The same video, served as a download.
- **`duration_s` (required)**: `number`, minimum: `0`

  The video length in seconds.
- **`expires_at` (required)**: `string`, format: `date-time`

  When the links stop working and the video is deleted, 24 hours after the job was created.
- **`height` (required)**: `integer | null`, minimum: `1`

  The video height in pixels, or null if unknown.
- **`thumbnail_url` (required)**: `string | null`, format: `uri`

  A still image of the video, or null if there is none.
- **`video_url` (required)**: `string`, format: `uri`

  MP4 video link for playback.
- **`width` (required)**: `integer | null`, minimum: `1`

  The video width in pixels, or null if unknown.

**Example:**

```json
{
  "video_url": "https://files.example.com/avatar.mp4",
  "download_url": "https://files.example.com/avatar.mp4?download=avatar.mp4",
  "thumbnail_url": "https://files.example.com/avatar.jpg",
  "duration_s": 8.2,
  "width": 1280,
  "height": 720,
  "expires_at": "2026-10-31T00:10:42Z"
}
```

<a id="scalar-schema-dubresult"></a>

### DubResult

**Type:** `object`, no additional properties

Links to the dubbed video and its parts. The dub is kept for 24 hours after the job was created.

- **`audio_url` (required)**: `string`, format: `uri`

  The new voices on their own, as a WAV file.
- **`background_url` (required)**: `string | null`, format: `uri`

  The music and sound effects without the voices, as a WAV file, or null when keep\_background was false.
- **`download_url` (required)**: `string`, format: `uri`

  The same video, served as a download.
- **`duration_s` (required)**: `number`, minimum: `0`

  The video length in seconds.
- **`expires_at` (required)**: `string`, format: `date-time`

  When the links stop working and the dub is deleted, 24 hours after the job was created.
- **`lipsynced` (required)**: `boolean`

  True when the lips were matched to the new voices.
- **`source_language` (required)**: `string | null`

  The spoken language of the original video, or null if unknown.
- **`speakers` (required)**: `integer`, minimum: `0`

  How many speakers were found.
- **`subtitles_url` (required)**: `string`, format: `uri`

  GET /v1/dubs/{id}/subtitles for this job. Send your API key with it.
- **`target_language` (required)**: `string`

  The language the video was dubbed into.
- **`video_url` (required)**: `string`, format: `uri`

  MP4 video link for playback. The lip-synced video when lip sync ran, otherwise the dubbed video.

**Example:**

```json
{
  "video_url": "https://files.example.com/dub.mp4",
  "download_url": "https://files.example.com/dub.mp4?download=dub.mp4",
  "audio_url": "https://files.example.com/dub-voices.wav",
  "background_url": "https://files.example.com/dub-background.wav",
  "subtitles_url": "https://api.example.com/v1/dubs/04bd0571-e2c8-4d5b-a3cc-e56f85773abf/subtitles",
  "duration_s": 94.5,
  "source_language": "English",
  "target_language": "es",
  "speakers": 2,
  "lipsynced": false,
  "expires_at": "2026-10-31T00:10:42Z"
}
```

<a id="scalar-schema-cutresult"></a>

### CutResult

**Type:** `object`, no additional properties

Links to the cut video and what was taken out. The cut is kept for 24 hours after the job was created.

- **`download_url` (required)**: `string`, format: `uri`

  The same video, served as a download.
- **`duration_s` (required)**: `number`, minimum: `0`

  The cut video length in seconds.
- **`expires_at` (required)**: `string`, format: `date-time`

  When the links stop working and the cut is deleted, 24 hours after the job was created.
- **`height` (required)**: `integer | null`, minimum: `1`

  The video height in pixels, or null if unknown.
- **`incomplete` (required)**: `array`

  Which of fillers, retakes or silences could not check the whole video. Empty when every step checked it all.

  **Items:**

  `string`, possible values: `"fillers", "retakes", "silences"`
- **`language` (required)**: `string | null`

  The spoken language, or null if unknown.
- **`removed` (required)**: `array`

  Each part taken out of the uploaded video, in order, with why it was taken out. Empty when nothing needed cutting.

  **Items:**

  no additional properties
  - **`end` (required)**: `number`, minimum: `0`

    Seconds from the start of the uploaded video.
  - **`reasons` (required)**: `array`

    Why the part was taken out. Lists silence only when no filler or retake was found in the part.

    **Items:**

    `string`, possible values: `"filler", "retake", "silence"`
  - **`start` (required)**: `number`, minimum: `0`

    Seconds from the start of the uploaded video.
- **`source_duration_s` (required)**: `number`, minimum: `0`

  The uploaded video length in seconds.
- **`video_url` (required)**: `string`, format: `uri`

  MP4 video link for playback.
- **`width` (required)**: `integer | null`, minimum: `1`

  The video width in pixels, or null if unknown.

**Example:**

```json
{
  "video_url": "https://files.example.com/cut.mp4",
  "download_url": "https://files.example.com/cut.mp4?download=cut.mp4",
  "duration_s": 281.344,
  "source_duration_s": 485.87,
  "width": 1080,
  "height": 1920,
  "removed": [
    {
      "start": 0,
      "end": 107.62,
      "reasons": [
        "retake"
      ]
    },
    {
      "start": 120.786,
      "end": 121.106,
      "reasons": [
        "filler"
      ]
    },
    {
      "start": 129.3,
      "end": 129.44,
      "reasons": [
        "silence"
      ]
    }
  ],
  "language": "English",
  "incomplete": [],
  "expires_at": "2026-10-31T00:10:42Z"
}
```

<a id="scalar-schema-subtitleline"></a>

### SubtitleLine

**Type:** `object`, no additional properties

- **`end` (required)**: `number`, minimum: `0`

  Seconds from the start of the video.
- **`source_text` (required)**: `string`

  What was said in the original video.
- **`speaker` (required)**: `string | null`

  Who says the line, or null if unknown.
- **`start` (required)**: `number`, minimum: `0`

  Seconds from the start of the video.
- **`text` (required)**: `string`

  The dubbed line.

<a id="scalar-example-5"></a>

**Generated example:**

```json
{
  "start": 0,
  "end": 0,
  "text": "",
  "speaker": null,
  "source_text": ""
}
```

<a id="scalar-schema-subtitlelist"></a>

### SubtitleList

**Type:** array of [SubtitleLine](#scalar-schema-subtitleline)

Each subtitle line in order.

<a id="scalar-schema-word"></a>

### Word

**Type:** `object`, no additional properties

- **`end` (required)**: `number`, minimum: `0`

  Seconds from the start of the audio.
- **`speaker` (required)**: `string`

  Who said the word, as SPEAKER\_00, SPEAKER\_01 and so on.
- **`start` (required)**: `number`, minimum: `0`

  Seconds from the start of the audio.
- **`text` (required)**: `string`

<a id="scalar-example-6"></a>

**Generated example:**

```json
{
  "text": "",
  "start": 0,
  "end": 0,
  "speaker": ""
}
```

<a id="scalar-schema-voice"></a>

### Voice

**Type:** `object`, no additional properties

- **`created_at` (required)**: `string | null`, format: `date-time`

  When the voice was saved, or null for presets.
- **`duration_seconds` (required)**: `number | null`, minimum: `0`

  Length of the reference clip, or null.
- **`id` (required)**: `string`

  A uuid for saved voices, or the preset id.
- **`model` (required)**: `string | null`

  The model a preset belongs to, or null for saved voices.
- **`name` (required)**: `string`
- **`type` (required)**: `string`, possible values: `"clone", "design", "preset"`

<a id="scalar-example-7"></a>

**Generated example:**

```json
{
  "id": "",
  "name": "",
  "type": "clone",
  "model": null,
  "duration_seconds": null,
  "created_at": null
}
```

<a id="scalar-schema-voicelist"></a>

### VoiceList

**Type:** `object`, no additional properties

- **`voices` (required)**: array of [Voice](#scalar-schema-voice)

<a id="scalar-example-8"></a>

**Generated example:**

```json
{
  "voices": [
    {
      "id": "",
      "name": "",
      "type": "clone",
      "model": null,
      "duration_seconds": null,
      "created_at": null
    }
  ]
}
```

<a id="scalar-schema-model"></a>

### Model

**Type:** `object`, no additional properties

- **`clone` (required)**: `boolean`

  Speaks in saved voices.
- **`design` (required)**: `boolean`

  Designs voices from a description.
- **`design_takes_text` (required)**: `boolean`

  Voice design speaks the given text.
- **`id` (required)**: `string`

  Pass as model.
- **`languages` (required)**: `array of string`

  Languages to pass as language.
- **`name` (required)**: `string`
- **`presets` (required)**: array of [Preset](#scalar-schema-preset)

  Preset voices of this model.

<a id="scalar-example-9"></a>

**Generated example:**

```json
{
  "id": "",
  "name": "",
  "clone": true,
  "design": true,
  "design_takes_text": true,
  "languages": [
    ""
  ],
  "presets": [
    {
      "id": "",
      "name": "",
      "description": "",
      "language": null
    }
  ]
}
```

<a id="scalar-schema-preset"></a>

### Preset

**Type:** `object`, no additional properties

- **`description` (required)**: `string`
- **`id` (required)**: `string`

  Pass as voice.
- **`language` (required)**: `string | null`

  The language the preset speaks, or null.
- **`name` (required)**: `string`

<a id="scalar-example-10"></a>

**Generated example:**

```json
{
  "id": "",
  "name": "",
  "description": "",
  "language": null
}
```

<a id="scalar-schema-modellist"></a>

### ModelList

**Type:** `object`, no additional properties

- **`models` (required)**: array of [Model](#scalar-schema-model)

<a id="scalar-example-11"></a>

**Generated example:**

```json
{
  "models": [
    {
      "id": "",
      "name": "",
      "clone": true,
      "design": true,
      "design_takes_text": true,
      "languages": [
        ""
      ],
      "presets": [
        {
          "id": "",
          "name": "",
          "description": "",
          "language": null
        }
      ]
    }
  ]
}
```

<a id="scalar-schema-usage"></a>

### Usage

**Type:** `object`, no additional properties

- **`period_end` (required)**: `string`, format: `date-time`

  When this token period ends and the next one starts.
- **`period_start` (required)**: `string`, format: `date-time`

  When this token period started.
- **`tier` (required)**: `string`, possible values: `"free", "starter", "premium"`
- **`tokens_limit` (required)**: `integer`, minimum: `0`

  Tokens included in the plan each period.
- **`tokens_used` (required)**: `integer`, minimum: `0`

  Tokens used this period.

<a id="scalar-example-12"></a>

**Generated example:**

```json
{
  "tier": "free",
  "tokens_used": 0,
  "tokens_limit": 0,
  "period_start": "",
  "period_end": ""
}
```

<a id="scalar-schema-requestsummary"></a>

### RequestSummary

**Type:** `object`, no additional properties

- **`description`**: `string`
- **`model`**: `string`
- **`text`**: `string`
- **`voice`**: `string`

<a id="scalar-example-13"></a>

**Generated example:**

```json
{
  "text": "",
  "voice": "",
  "model": "",
  "description": ""
}
```

<a id="scalar-schema-requestlogentry"></a>

### RequestLogEntry

**Type:** `object`, no additional properties

- **`api_key_id` (required)**: `string | null`

  The key that made the request, or null if the key was deleted.
- **`created_at` (required)**: `string`, format: `date-time`

  When the job was created.
- **`error` (required)**: [ErrorDetail](#scalar-schema-errordetail) | `null`

  Why the job failed, or null.
- **`finished_at` (required)**: `string | null`, format: `date-time`

  When the job finished, or null while it runs.
- **`id` (required)**: `string`

  Job id.
- **`status` (required)**: `string`, possible values: `"queued", "running", "succeeded", "failed", "cancelled"`
- **`summary` (required)**: [RequestSummary](#scalar-schema-requestsummary)
- **`tokens_charged` (required)**: `integer | null`, minimum: `0`

  Tokens billed, or null until billing has run.
- **`type` (required)**: `string`, possible values: `"speech", "voice_design", "voice_clone", "voice_change", "transcription", "dub", "avatar_video", "cut"`

<a id="scalar-example-14"></a>

**Generated example:**

```json
{
  "id": "",
  "type": "speech",
  "status": "queued",
  "error": {
    "code": "",
    "message": "",
    "param": ""
  },
  "tokens_charged": null,
  "api_key_id": null,
  "summary": {
    "text": "",
    "voice": "",
    "model": "",
    "description": ""
  },
  "created_at": "",
  "finished_at": null
}
```

<a id="scalar-schema-requestlog"></a>

### RequestLog

**Type:** `object`, no additional properties

- **`next_before` (required)**: `string | null`, format: `date-time`

  Pass as before to get the next page, or null on the last page.
- **`requests` (required)**: array of [RequestLogEntry](#scalar-schema-requestlogentry)

<a id="scalar-example-15"></a>

**Generated example:**

```json
{
  "requests": [
    {
      "id": "",
      "type": "speech",
      "status": "queued",
      "error": {
        "code": "",
        "message": "",
        "param": ""
      },
      "tokens_charged": null,
      "api_key_id": null,
      "summary": {
        "text": "",
        "voice": "",
        "model": "",
        "description": ""
      },
      "created_at": "",
      "finished_at": null
    }
  ],
  "next_before": null
}
```

<a id="scalar-schema-speechbody"></a>

### SpeechBody

**Type:** `object`, no additional properties

- **`text` (required)**: `string`, minLength: `1`

  The text to speak.
- **`voice` (required)**: `string`, minLength: `1`

  A saved voice id, a saved voice name or a preset id.
- **`language`**: `string`

  A language from the model, such as English.
- **`model`**: `string`

  A model id from GET /v1/models.

<a id="scalar-example-16"></a>

**Generated example:**

```json
{
  "text": "",
  "voice": "",
  "model": "",
  "language": ""
}
```

<a id="scalar-schema-voicedesignbody"></a>

### VoiceDesignBody

**Type:** `object`, no additional properties

- **`description` (required)**: `string`, minLength: `1`

  What the voice sounds like.
- **`language`**: `string`

  A language from the model, such as English.
- **`model`**: `string`

  A model id from GET /v1/models.
- **`text`**: `string`

  What the sample says.

<a id="scalar-example-17"></a>

**Generated example:**

```json
{
  "description": "",
  "text": "",
  "model": "",
  "language": ""
}
```

<a id="scalar-schema-transcriptionbody"></a>

### TranscriptionBody

**Type:** `object`, no additional properties

- **`audio_file_id`**: `string`

  A file\_id from POST /v1/uploads. Send this or audio\_url.
- **`audio_url`**: `string`, format: `uri`

  An https link to the audio, up to 25 MB. Send this or audio\_file\_id.
- **`language`**: `string`

  The spoken language, such as English. Leave it out to detect it.
- **`model`**: `string`, possible values: `"standard", "higher_quality", "parakeet_v3", "orukeet"`, default: `"higher_quality"`

  The speech recognition model.
- **`speakers`**: `integer`, minimum: `1`, maximum: `10`

  How many people speak. Leave it out to detect it. Only used when timestamps is true.
- **`timestamps`**: `boolean`, default: `true`

  Return each word with its timing and speaker.

<a id="scalar-example-18"></a>

**Generated example:**

```json
{
  "audio_file_id": "",
  "audio_url": "",
  "language": "",
  "timestamps": true,
  "speakers": 1,
  "model": "higher_quality"
}
```

<a id="scalar-schema-voicechangebody"></a>

### VoiceChangeBody

**Type:** `object`, no additional properties

- **`voice` (required)**: `string`, minLength: `1`

  A saved voice id or name. Presets cannot be used.
- **`audio_file_id`**: `string`

  A file\_id from POST /v1/uploads. Send this or audio\_url.
- **`audio_url`**: `string`, format: `uri`

  An https link to the audio, up to 25 MB. Send this or audio\_file\_id.

<a id="scalar-example-19"></a>

**Generated example:**

```json
{
  "audio_file_id": "",
  "audio_url": "",
  "voice": ""
}
```

<a id="scalar-schema-voiceclonebody"></a>

### VoiceCloneBody

**Type:** `object`, no additional properties

- **`name` (required)**: `string`, minLength: `1`, maxLength: `80`

  The voice name.
- **`audio_file_id`**: `string`

  A file\_id from POST /v1/uploads. Send this or audio\_url.
- **`audio_url`**: `string`, format: `uri`

  An https link to the audio, up to 25 MB. Send this or audio\_file\_id.
- **`transcript`**: `string`, maxLength: `4000`

  What the clip says. Leave it out to transcribe the clip.

<a id="scalar-example-20"></a>

**Generated example:**

```json
{
  "name": "",
  "audio_file_id": "",
  "audio_url": "",
  "transcript": ""
}
```

<a id="scalar-schema-avatarvideobody"></a>

### AvatarVideoBody

**Type:** `object`, no additional properties

- **`consent` (required)**: `boolean`, const: `true`

  Must be true. Confirms you have the right to use the face in the photo.
- **`audio_file_id`**: `string`

  A file\_id from POST /v1/uploads of a WAV file up to 10 MB. Send this, speech\_job\_id or audio\_url.
- **`audio_url`**: `string`, format: `uri`

  An https link to a WAV file, up to 10 MB. Send this, speech\_job\_id or audio\_file\_id.
- **`engine`**: `string`, possible values: `"ltx-2.3", "ltx-2.5"`, default: `"ltx-2.5"`

  The video model.
- **`fps`**: `integer`, possible values: `24, 25, 48, 50`, default: `24`

  Frames per second.
- **`photo_file_id`**: `string`

  A file\_id from POST /v1/uploads of a JPEG, PNG or WEBP photo. Send this or photo\_url.
- **`photo_url`**: `string`, format: `uri`

  An https link to the photo, up to 20 MB. Send this or photo\_file\_id.
- **`resolution`**: `string`, possible values: `"480p", "720p", "1080p", "1440p"`, default: `"720p"`

  The output resolution.
- **`speech_job_id`**: `string`

  The id of a succeeded POST /v1/speech job from the last 24 hours. Send this, audio\_file\_id or audio\_url.

<a id="scalar-example-21"></a>

**Generated example:**

```json
{
  "photo_file_id": "",
  "photo_url": "",
  "speech_job_id": "",
  "audio_file_id": "",
  "audio_url": "",
  "consent": true,
  "resolution": "720p",
  "fps": 24,
  "engine": "ltx-2.5"
}
```

<a id="scalar-schema-dubbody"></a>

### DubBody

**Type:** `object`, no additional properties

- **`target_language` (required)**: `string`, possible values: `"es", "fr", "de", "ja", "ko", "zh", "zh-Hant", "pt", "ru", "it", "ar", "hi", "th", "vi", "id", "tr", "pl", "nl", "uk", "cs", "en", "Spanish", "French", "German", "Japanese", "Korean", "Chinese", "Traditional Chinese", "Portuguese", "Russian", "Italian", "Arabic", "Hindi", "Thai", "Vietnamese", "Indonesian", "Turkish", "Polish", "Dutch", "Ukrainian", "Czech", "English", "Standard Arabic"`

  The language to dub into.
- **`video_file_id` (required)**: `string`, minLength: `1`

  A file\_id from POST /v1/uploads of an MP4, MOV, MKV or WEBM video up to 2 GB and 30 minutes long.
- **`clone_voices`**: `boolean`, default: `true`

  Give each speaker a voice cloned from their own lines.
- **`keep_background`**: `boolean`, default: `true`

  Keep the music and sound effects under the new voices.
- **`lipsync`**: `boolean`, default: `false`

  Match the lips to the new voices. Only for videos up to 60 seconds. A longer video is dubbed without it and lipsynced is false.
- **`model`**: `string`

  A model id from GET /v1/models. A preset voice uses its own model.
- **`source_language`**: `string`, default: `"auto"`

  The spoken language of the video, such as English. Leave it out to detect it.
- **`speakers`**: `integer`, minimum: `1`, maximum: `6`

  How many people speak. Leave it out to detect it.
- **`voice`**: `string`

  A saved voice id, a saved voice name or a preset id. Used for speakers that are not cloned, and for every speaker when clone\_voices is false. Required when clone\_voices is false.

<a id="scalar-example-22"></a>

**Generated example:**

```json
{
  "video_file_id": "",
  "target_language": "es",
  "source_language": "auto",
  "keep_background": true,
  "clone_voices": true,
  "voice": "",
  "model": "",
  "speakers": 1,
  "lipsync": false
}
```

<a id="scalar-schema-cutbody"></a>

### CutBody

**Type:** `object`, no additional properties

- **`video_file_id` (required)**: `string`, minLength: `1`

  A file\_id from POST /v1/uploads of an MP4, MOV, MKV or WEBM video up to 2 GB and 3 hours long.
- **`fillers`**: `boolean`, default: `true`

  Cut filler words such as um and uh.
- **`pace`**: `string`, possible values: `"natural", "tight"`, default: `"natural"`

  tight leaves shorter pauses than natural.
- **`retakes`**: `boolean`, default: `true`

  Cut lines that were said more than once, keeping one take.
- **`silences`**: `boolean`, default: `true`

  Shorten long pauses.

<a id="scalar-example-23"></a>

**Generated example:**

```json
{
  "video_file_id": "",
  "fillers": true,
  "retakes": true,
  "silences": true,
  "pace": "natural"
}
```

<a id="scalar-schema-voicenamebody"></a>

### VoiceNameBody

**Type:** `object`, no additional properties

- **`name` (required)**: `string`, minLength: `1`, maxLength: `80`

  The voice name.

<a id="scalar-example-24"></a>

**Generated example:**

```json
{
  "name": ""
}
```

<a id="scalar-schema-uploadbody"></a>

### UploadBody

**Type:** `object`, no additional properties

- **`content_type` (required)**: `string`, possible values: `"audio/wav", "audio/x-wav", "audio/wave", "audio/vnd.wave", "audio/flac", "audio/x-flac", "audio/ogg", "audio/opus", "audio/mpeg", "image/jpeg", "image/png", "image/webp", "video/mp4", "video/quicktime", "video/x-matroska", "video/webm"`

  The file format. Send the same Content-Type when you upload the file.
- **`size` (required)**: `integer`, minimum: `1`, maximum: `2147483648`

  The file size in bytes. Audio can be up to 209715200 bytes, a photo up to 20971520 bytes and a video up to 2147483648 bytes.

<a id="scalar-example-25"></a>

**Generated example:**

```json
{
  "content_type": "audio/wav",
  "size": 1
}
```

<a id="scalar-schema-upload"></a>

### Upload

**Type:** `object`, no additional properties

- **`expires_at` (required)**: `string`, format: `date-time`

  When upload\_url stops working, 2 hours from now.
- **`file_id` (required)**: `string`

  Pass as audio\_file\_id, photo\_file\_id or video\_file\_id. The file is kept for 24 hours.
- **`upload_url` (required)**: `string`, format: `uri`

  PUT the file here.

<a id="scalar-example-26"></a>

**Generated example:**

```json
{
  "file_id": "",
  "upload_url": "",
  "expires_at": ""
}
```

<a id="scalar-schema-webhookevent"></a>

### WebhookEvent

**Type:** `object`, no additional properties

The JSON body of every webhook request.

- **`api_key_id` (required)**: `string | null`

  The key that made the request, or null for webhook.test.
- **`data` (required)**

  The job, the same as GET /v1/jobs/{id} returns it. For webhook.test, the endpoint id.

  **Any of:**
  - [Job](#scalar-schema-job)
  - `object`, no additional properties
    - **`endpoint_id` (required)**: `string`

      The endpoint the test was sent to.
- **`timestamp` (required)**: `string`, format: `date-time`

  When this attempt was sent.
- **`type` (required)**: `string`, possible values: `"job.succeeded", "job.failed", "job.cancelled", "webhook.test"`

  What happened. webhook.test comes from Send test in Settings > API.

**Example:**

```json
{
  "type": "job.succeeded",
  "timestamp": "2026-10-30T00:18:01Z",
  "api_key_id": "5f0c2a8e-3b1d-4c7e-9a62-1d8f4e7b2c90",
  "data": {
    "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
    "type": "cut",
    "status": "succeeded",
    "progress": null,
    "result": {
      "video_url": "https://files.example.com/cut.mp4",
      "download_url": "https://files.example.com/cut.mp4?download=cut.mp4",
      "duration_s": 281.344,
      "source_duration_s": 485.87,
      "width": 1080,
      "height": 1920,
      "removed": [
        {
          "start": 0,
          "end": 107.62,
          "reasons": [
            "retake"
          ]
        },
        {
          "start": 120.786,
          "end": 121.106,
          "reasons": [
            "filler"
          ]
        },
        {
          "start": 129.3,
          "end": 129.44,
          "reasons": [
            "silence"
          ]
        }
      ],
      "language": "English",
      "incomplete": [],
      "expires_at": "2026-10-31T00:10:42Z"
    },
    "error": null,
    "tokens_charged": 16311,
    "resource_id": "857fcb81-9073-4f26-97c7-a388979fcdca",
    "created_at": "2026-10-30T00:10:42Z",
    "finished_at": "2026-10-30T00:18:01Z"
  }
}
```
