# Render a Studio shot

- **Method:** `POST`
- **Path:** `/v1/video-renders`
- **Operation ID:** `createVideoRender`
- **Tags:** Video renders

Renders one Studio video shot. Upload every picture and sound the input names with POST /v1/uploads first, and map each name to its file\_id in `files`. Returns a job whose result is a `VideoRenderResult`. The files are kept for 24 hours.

## 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: `VideoRenderBody`, no additional properties

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

  Must be true. Confirms you have the right to use every face in the pictures.
- **`files` (required)**: `object`

  Maps each file name the input uses to a file\_id from POST /v1/uploads.

  **Additional properties:**

  `string`
- **`input` (required)**: `object`

  A Studio video\_render input. Every file it names must appear in files.

**Example:**

```json
{
  "input": {
    "model_id": "ltx-2.5",
    "seconds": 4
  },
  "files": {},
  "consent": true
}
```

## 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, cuts give CutResult and video renders give VideoRenderResult, 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.
  - `object`, schema: `VideoRenderResult`, no additional properties

    Links to the rendered shot. The files are kept for 24 hours after the job was created.
    - **`duration_s` (required)**: `number`, minimum: `0`

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

      When the links stop working and the files are deleted, 24 hours after the job was created.
    - **`fps` (required)**: `integer`, minimum: `1`

      Frames per second.
    - **`height` (required)**: `integer`, minimum: `1`

      The video height in pixels.
    - **`lead_s` (required)**: `number`, minimum: `0`

      Seconds at the start carried over from the shot before it.
    - **`model` (required)**: `string`

      The video model that made the shot.
    - **`prompt` (required)**: `string | null`

      The prompt the model was given, or null.
    - **`speech_s` (required)**: `number | null`, minimum: `0`

      Length of the speech in seconds, or null.
    - **`speech_url` (required)**: `string | null`, format: `uri`

      WAV of the speech heard in the shot, or null for a silent shot.
    - **`thumbnail_url` (required)**: `string | null`, format: `uri`

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

      MP4 link to the shot.
    - **`width` (required)**: `integer`, minimum: `1`

      The video width in pixels.
  - `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", "video_render"`

**Example:**

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

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

### VideoRenderResult

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

Links to the rendered shot. The files are kept for 24 hours after the job was created.

*Schema `VideoRenderResult` is shown above.*

**Example:**

```json
{
  "video_url": "https://files.example.com/shot.mp4",
  "thumbnail_url": "https://files.example.com/shot.jpg",
  "speech_url": null,
  "duration_s": 4.04,
  "width": 1280,
  "height": 720,
  "fps": 24,
  "model": "ltx-2.5",
  "lead_s": 0,
  "speech_s": null,
  "prompt": "A woman walks into a sunny kitchen.",
  "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.
- `VideoRenderBody` — shown above.
