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.