# Lip sync a video to a new voice track

- **Method:** `POST`
- **Path:** `/v1/lipsyncs`
- **Operation ID:** `createLipsync`
- **Tags:** Dubs

Matches the lips in a video of up to 60 seconds to a new voice track. Only the segments with a face on screen are synced. Returns a job whose result is a `LipsyncResult`.

Estimated cost: 3000 tokens plus 1250 per second of video with version 1.6, or 775 with 1.5, at 25 inference steps. The per second part grows in proportion to `inference_steps`.

## Effective servers

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

## Authentication

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

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

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

schema: `LipsyncBody`, no additional properties

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

  A file\_id from POST /v1/uploads of the new voice track as an audio file. It plays from the start of the video.
- **`duration_s` (required)**: `number`, maximum: `60`, exclusiveMinimum: `0`

  The video length in seconds.
- **`fps` (required)**: `number`, maximum: `120`, exclusiveMinimum: `0`

  The video frame rate.
- **`segments` (required)**: `array`, minItems: `1`, maxItems: `500`

  The spoken lines in order. Each one ends after it starts and no later than duration\_s. Lines with a face on screen are synced and the rest of the video stays as it is.

  **Items:**

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

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

    Seconds from the start of the video.
- **`version` (required)**: `string`, possible values: `"1.5", "1.6"`

  The lip sync model. 1.6 gives sharper mouths and costs more.
- **`video_file_id` (required)**: `string`, minLength: `1`

  A file\_id from POST /v1/uploads of an MP4, MOV, MKV or WEBM video up to 60 seconds long.
- **`guidance_scale`**: `number`, default: `1.5`, minimum: `1`, maximum: `3`

  How closely the mouth follows the voice track. Higher follows it more closely.
- **`inference_steps`**: `integer`, default: `25`, minimum: `10`, maximum: `50`

  More steps give a cleaner mouth and cost more. The cost per second of video grows in proportion to the steps.

**Example:**

```json
{
  "video_file_id": "8383fc02-625a-4b71-a226-cbb2d19aaf17",
  "audio_file_id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
  "segments": [
    {
      "start": 0.4,
      "end": 2.6
    },
    {
      "start": 3.1,
      "end": 7.8
    }
  ],
  "duration_s": 12.5,
  "fps": 25,
  "version": "1.6"
}
```

## Responses

### 202 Accepted. Returns the job.

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

schema: `Job`, no additional properties

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

  When the job was created.
- **`error` (required)**: `object | null`, schema: `ErrorDetail`, no additional properties

  Why the job failed, or null.
  - **`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.
- **`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 or lip sync runs or a video is cut, otherwise null.
- **`resource_id` (required)**: `string | null`

  The voice id for voice clones, the project id for dubs, lip syncs and cuts, and the video id for avatar videos.
- **`result` (required)**

  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, lip syncs give LipsyncResult and cuts give CutResult, all kept for 24 hours after the job was created.

  **Any of:**
  - `object`, schema: `AudioResult`, 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.
  - `object`, schema: `TranscriptionResult`, 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 | null`

      Each word with its timing, or null without timestamps.

      **Items:**

      schema: `Word`, 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`
  - `object`, schema: `VoiceCloneResult`, no additional properties

    The saved voice.
    - **`transcript` (required)**: `string | null`

      What the clip says, or null if it has no transcript.
    - **`voice` (required)**: `object`, schema: `Voice`, 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"`
  - `object`, schema: `AvatarVideoResult`, 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.
  - `object`, schema: `DubResult`, 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.
  - `object`, schema: `LipsyncResult`, no additional properties

    Link to the lip-synced video. The video is kept for 24 hours after the job was created.
    - **`download_url` (required)**: `string | null`, format: `uri`

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

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

      How many segments show a speaking face.
    - **`lipsynced` (required)**: `boolean`

      True when at least one line was synced.
    - **`skipped_lines` (required)**: `integer`, minimum: `0`

      How many of those segments could not be synced. They keep the original video.
    - **`video_url` (required)**: `string | null`, format: `uri`

      MP4 video link for playback, with the new voice track and the lips matched to it. Null when no line could be synced.
  - `object`, schema: `CutResult`, 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.
  - `null`
- **`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", "lipsync"`

**Example:**

```json
{
  "id": "04bd0571-e2c8-4d5b-a3cc-e56f85773abf",
  "type": "lipsync",
  "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 bought tokens, 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`

schema: `ErrorBody`, no additional properties

- **`error` (required)**: `object`, schema: `ErrorDetail`, no additional properties

  *Schema `ErrorDetail` is shown above.*

**Example:**

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

## Schemas

### ErrorBody

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

*Schema `ErrorBody` is shown above.*

**Example:**

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

- `ErrorDetail` — shown above.

### JobType

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

### JobStatus

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

- `Job` — shown above.

### AudioResult

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

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

*Schema `AudioResult` is shown above.*

**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"
}
```

### TranscriptionResult

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

The transcript of the audio.

*Schema `TranscriptionResult` is shown above.*

**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"
    }
  ]
}
```

### VoiceCloneResult

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

The saved voice.

*Schema `VoiceCloneResult` is shown above.*

**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."
}
```

### AvatarVideoResult

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

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

*Schema `AvatarVideoResult` is shown above.*

**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"
}
```

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

*Schema `DubResult` is shown above.*

**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"
}
```

### LipsyncResult

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

Link to the lip-synced video. The video is kept for 24 hours after the job was created.

*Schema `LipsyncResult` is shown above.*

**Example:**

```json
{
  "video_url": "https://files.example.com/lipsync.mp4",
  "download_url": "https://files.example.com/lipsync.mp4?download=lipsync.mp4",
  "lipsynced": true,
  "face_lines": 6,
  "skipped_lines": 0,
  "expires_at": "2026-10-31T00:10:42Z"
}
```

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

*Schema `CutResult` is shown above.*

**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"
}
```

- `Word` — shown above.
- `Voice` — shown above.
- `LipsyncBody` — shown above.
