> ## Documentation Index
> Fetch the complete documentation index at: https://heygen-1fa696a7.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Video Details

> Read everything the API knows about a video you've created — delivery URLs, lifecycle timestamps, and the full scene-by-scene composition.

<img className="w-full h-44 object-cover rounded-xl" src="https://mintcdn.com/heygen-1fa696a7/hfMXXwJzjE7vBSYZ/images/theme/research-3.webp?fit=max&auto=format&n=hfMXXwJzjE7vBSYZ&q=85&s=c8191ba4743fd85050f1af1a93ed4969" alt="" noZoom width="1400" height="788" data-path="images/theme/research-3.webp" />

## Overview

Creating a video returns a `video_id`. Two endpoints read everything back from that id:

| Endpoint                                                          | Answers               | Returns                                                                                      |
| ----------------------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------- |
| [`GET /v3/videos/{video_id}`](/reference/get-video)               | *What came out?*      | Status, timestamps, and every deliverable: MP4, thumbnail, GIF, captioned variant, subtitles |
| [`GET /v3/videos/{video_id}/scenes`](/reference/get-video-scenes) | *What is it made of?* | The composition: backgrounds, avatars, clips, and the script each scene speaks               |

The first is the endpoint you already poll after [creating a video](/reference/create-video) — it now returns far more than the status-and-URL pair it started with. The scenes endpoint is new.

## Video Details

```bash theme={null}
curl "https://api.heygen.com/v3/videos/{video_id}" \
  -H "x-api-key: YOUR_API_KEY"
```

```json Response theme={null}
{
  "data": {
    "id": "ca86dbcf9097461da11c42b1e4cd6efc",
    "title": "Field team FAQ — week one",
    "status": "completed",
    "created_at": 1787847881,
    "completed_at": 1787847968,
    "duration": 10.8669,
    "video_url": "https://files2.heygen.ai/.../ca86dbcf....mp4?Expires=...",
    "thumbnail_url": "https://files2.heygen.ai/.../ca86dbcf....jpeg?Expires=...",
    "gif_url": "https://resource2.heygen.ai/video/ca86dbcf.../gif.gif",
    "video_page_url": "https://app.heygen.com/videos/ca86dbcf9097461da11c42b1e4cd6efc"
  }
}
```

**Lifecycle**

* `status` — `pending` → `processing` → `completed` or `failed`
* `created_at`, `completed_at` — Unix timestamps; the difference is your render time
* `failure_code`, `failure_message` — on a failed video: branch on the code, log the message

**Deliverables** — up to five outputs on a completed video:

| Field                 | What it is                                                                   |
| --------------------- | ---------------------------------------------------------------------------- |
| `video_url`           | The rendered MP4                                                             |
| `thumbnail_url`       | Poster frame                                                                 |
| `gif_url`             | Animated preview — for hover states and embeds                               |
| `subtitle_url`        | Sidecar subtitle file, when created with [captions](/studio-videos#captions) |
| `captioned_video_url` | Second MP4 with captions burned in — `video_url` stays clean                 |

**Placement**

* `title`, `folder_id` — where the video lives in your workspace
* `video_page_url` — deep link to the video in the HeyGen app
* `duration` — final length in seconds
* `output_language` — BCP-47 code; present only on [translated](/docs/video-translate) videos, which is also how you tell them apart

<Note>
  Media URLs are presigned and expire (the `Expires` parameter says when). Download what you need rather than storing the URLs — re-fetch the details for a fresh link at any time.
</Note>

Fields that don't apply are simply absent: a processing video has no `video_url` yet, and only a failed video carries failure fields.

## Video Scenes

[`GET /v3/videos/{video_id}/scenes`](/reference/get-video-scenes) opens the composition itself — for any video in your workspace, whichever surface created it: a [studio request](/studio-videos), a [template render](/templates), an [avatar video](/generate-avatar-video), or a video composed by hand in the editor. It describes the video **as it stands now**, including editor changes made after creation. When a [brand glossary](/docs/brand-glossary) shapes the video's speech, `video.brand_glossary_id` carries its id.

A video whose composition can't be read answers with an error rather than an empty document: `404` `video_not_found` when there is no editor document to read (some translated videos, for example), and `400` when the video predates the current editor format.

<img className="w-full rounded-xl" src="https://mintcdn.com/heygen-1fa696a7/VGKkCPx4-6bN7aBv/images/diagrams/video-scenes-anatomy.svg?fit=max&auto=format&n=VGKkCPx4-6bN7aBv&q=85&s=56f8a96c45b20d3d57059e22b18ac5cf" alt="Anatomy of the scenes response: video context plus a list of scenes, each with a background, elements, and script" width="1200" height="560" data-path="images/diagrams/video-scenes-anatomy.svg" />

```bash theme={null}
curl "https://api.heygen.com/v3/videos/{video_id}/scenes" \
  -H "x-api-key: YOUR_API_KEY"
```

```json Response theme={null}
{
  "data": {
    "video_id": "0d6116be51f3434e82d492db84535786",
    "video": {
      "title": "Orby Style Demo",
      "aspect_ratio": "16:9",
      "resolution": "1080p",
      "caption": { "enabled": false }
    },
    "scenes": [
      {
        "id": "42319b88-cfb8-4faa-8572-77391c78359f",
        "background": { "type": "color", "color": "#ffffff" },
        "elements": [
          {
            "id": "77448d08-499c-47c7-b542-adb2240711ba",
            "type": "avatar",
            "avatar_id": "Leos_standing_sofa_side",
            "engine": "avatar_v"
          },
          {
            "id": "a_roll_9cgnoo-design_element_kagzee-650f",
            "type": "motion_graphics"
          }
        ],
        "script": [
          {
            "id": "577c787f",
            "type": "text",
            "text": "Hi! I'm Orby. This is a quick test to show you your custom tech style in action.",
            "voice_id": "5d8c378ba8c3434586081a52ac368738",
            "voice_settings": { "speed": 1.0, "pitch": 0.0, "volume": 1.0 },
            "trim_to_speech": true
          }
        ]
      }
    ]
  }
}
```

### Roles, not kinds

A scene's three content fields split by the **role** a thing plays: `background` fills the frame, `elements` are placed within it, `script` is the audio over it.

<Warning>
  A whole-frame image or clip lands in `background` — code that only walks `elements` misses it.
</Warning>

A video background or video element reads back with the same playback controls a [studio scene](/studio-videos#playback-and-scene-duration) accepts: `playback` (`freeze` / `loop` / `fit_to_scene`), `trim`, `volume`, `speed_multiplier`.

### Reading elements

* **Every element appears** — the count is always truthful. `avatar`, `image`, and `video` are described in full; anything else is a placeholder with just an `id` and `type` (like `motion_graphics` above).
* **Skip unrecognized types** rather than erroring — the set is open and grows with the editor.
* **Recurse into containers** — a `group` or `mask` carries its children under `children`, each described like a top-level element.
* **Avatar elements report what a create request can set** — `avatar_id`, `engine`, `expressiveness`, `motion_prompt` — so what you read back is what you could send back. New fields are added additively.

### Reading scripts

| `type`    | Content                                                                                                       |
| --------- | ------------------------------------------------------------------------------------------------------------- |
| `text`    | Synthesized speech — verbatim script (inline markup included), `voice_id`, `voice_settings`, `trim_to_speech` |
| `audio`   | Uploaded audio — `audio_url`                                                                                  |
| `silence` | Held silence — `duration` in seconds                                                                          |

Walking `scenes[].script[].text` yields the full narration in playback order — for search indexing, compliance review, or driving a [translation](/docs/video-translate).

### Scope

* Asset URLs (`url`, `audio_url`) are returned when a live link exists at the time of the response; when the stored link has lapsed, the field is absent — the element itself is still reported. As with the deliverables above, download media you need rather than storing the URLs.
* The response is scoped to composition **content**. Visual styling — element geometry, text-element contents, animations, per-scene caption styling, background music — lives outside it, so a rebuild from this response reproduces the content and narration, not the pixel-exact layout.

## Putting the two together

<Steps>
  <Step title="Wait for the render">
    Poll [`GET /v3/videos/{video_id}`](/reference/get-video) until `status` is `completed` — or skip polling with a [webhook](/docs/webhooks).
  </Step>

  <Step title="Download the deliverables">
    Grab `video_url`, `subtitle_url`, and friends before their presigned links expire.
  </Step>

  <Step title="Index the composition">
    Call [`GET /v3/videos/{video_id}/scenes`](/reference/get-video-scenes) — scripts for search, avatar and voice ids for auditing, scene structure for your own UI.
  </Step>
</Steps>

Requesting an id that doesn't exist in your workspace returns `404` with code `video_not_found`, from either endpoint.
