## Prerequisites

A Digital Twin `avatar_id` (type: `digital_twin`). Use `GET /v3/avatars/looks?avatar_type=digital_twin` to find yours.

A `voice_id` for the voice you want. Use `GET /v3/voices` to browse available voices.

## Step 1 — Find your Digital Twin

List your private Digital Twin looks to get the `avatar_id`:

```
curl -X GET "https://api.heygen.com/v3/avatars/looks?avatar_type=digital_twin&ownership=private" \
  -H "x-api-key: YOUR_API_KEY"
```

From the response, copy the `id` field of the look you want. This is your `avatar_id`.

## Step 2 — Create the video

Send a `POST` request to `/v3/videos` with `type: "avatar"`, your Digital Twin ID, a script, and a voice:

```
curl -X POST "https://api.heygen.com/v3/videos" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "avatar",
    "avatar_id": "YOUR_DIGITAL_TWIN_LOOK_ID",
    "script": "Hello! I am your Digital Twin. This video was generated entirely through the HeyGen API.",
    "voice_id": "YOUR_VOICE_ID",
    "title": "My First Digital Twin Video",
    "resolution": "1080p",
    "aspect_ratio": "16:9"
  }'
```

## Step 3 — Poll for completion

Video generation is asynchronous. Poll `GET /v3/videos/{video_id}` until `status` is `completed`:

```
curl -X GET "https://api.heygen.com/v3/videos/YOUR_VIDEO_ID" \
  -H "x-api-key: YOUR_API_KEY"
```

### Status values

| Status     | Meaning                          |  |
|------------|---------------------------------|--|
| `pending`  | Queued for processing           |  |
| `processing` | Video is being generated       |  |
| `completed` | Ready — `video_url` is available |  |
| `failed`    | Something went wrong            |  |

Once completed, the response includes a `video_url` with a presigned download link.

## Full example

```
import requests
import time

API_KEY = "YOUR_API_KEY"
BASE = "https://api.heygen.com"
HEADERS = {"x-api-key": API_KEY, "Content-Type": "application/json"}

# 1. Create the video
resp = requests.post(f"{BASE}/v3/videos", headers=HEADERS, json={
    "type": "avatar",
    "avatar_id": "YOUR_DIGITAL_TWIN_LOOK_ID",
    "script": "Welcome to our product demo. Let me walk you through the new features.",
    "voice_id": "YOUR_VOICE_ID",
    "resolution": "1080p",
    "aspect_ratio": "16:9"
})
vide_id = resp.json()["data"]["video_id"]
print(f"Video created: {video_id}")

# 2. Poll until done
while True:
    status_resp = requests.get(f"{BASE}/v3/videos/{video_id}", headers=HEADERS)
    data = status_resp.json()["data"]
    print(f"Status: {data['status']}")
    if data["status"] == "completed":
        print(f"Download: {data['video_url']}")
        break
    elif data["status"] == "failed":
        print(f"Error: {data.get('failure_message')}")
        break
    time.sleep(10)
```

## Optional parameters

| Parameter           | Type    | Description  |
|---------------------|---------|--------------|
| `title`             | string  | Display name in the HeyGen dashboard |
| `resolution`        | string  | `4k`, `1080p`, or `720p` |
| `aspect_ratio`      | string  | `16:9` or `9:16` |
| `remove_background` | boolean | Removes the avatar background (twin must be trained with matting enabled) |
| `background`        | object  | Set a solid color or image background |
| `voice_settings`    | object  | Adjust `speed` (0.5–1.5), `pitch` (-50 to +50), and `locale` |
| `callback_url`      | string  | Webhook URL — receive a POST when the video is ready |

## Using webhooks instead of polling

Instead of polling, pass a `callback_url` when creating the video. HeyGen will send a POST request to that URL when the video completes or fails.

```
{
  "type": "avatar",
  "avatar_id": "YOUR_DIGITAL_TWIN_LOOK_ID",
  "script": "This video uses a webhook callback.",
  "voice_id": "YOUR_VOICE_ID",
  "callback_url": "https://your-server.com/webhooks/heygen"
}
```

Register a webhook endpoint via `POST /v3/webhooks/endpoints` and subscribe to `avatar_video.success` and `avatar_video.fail` events for production use.
