# AI clipping
Source: https://developers.heygen.com/ai-clipping
Turn a long-form video into short, ready-to-share highlight clips with the HeyGen AI Clipping API — the model picks the best moments, cuts them to your target durations, and scores each clip's virality.
## Create a Clip Job
* Endpoint: [`POST /v3/ai-clipping`](/reference/create-ai-clipping)
* Purpose: Start a clipping job for a source video. Returns an `ai_clipping_id` to poll.
### Quick Example
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/ai-clipping" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"video": { "type": "url", "url": "https://example.com/interview.mp4" },
"title": "Founder interview",
"output_settings": {
"duration_types": ["30", "60"],
"aspect_ratio": "portrait",
"captions": false,
"prompt": "Pull the moments where the founder talks about pricing and growth."
}
}'
```
```json Response theme={null}
{
"data": {
"ai_clipping_id": "edf8d2c44ba441b89f395072b3ef7e34"
}
}
```
### Request Body
| Parameter | Type | Required | Default | Description |
| ----------------- | ------ | -------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `video` | object | Yes | — | Source video. Provide as `{ "type": "url", "url": "https://..." }` or `{ "type": "asset_id", "asset_id": "..." }` (from [`POST /v3/assets`](/reference/upload-asset) — see [Upload Assets](/docs/upload-assets)). |
| `title` | string | No | — | Title for the job. Defaults to the source video's title when omitted. |
| `input_language` | string | No | auto-detect | ISO-639-1 source language code (e.g. `en`, `es`). Omit to auto-detect. |
| `output_settings` | object | No | — | Configuration for the produced clips — see [Output settings](#output-settings). |
| `callback_url` | string | No | — | [Webhook](/docs/webhooks) URL — receives a POST when the job completes or fails. |
| `callback_id` | string | No | — | Arbitrary ID echoed back in the webhook payload. |
### Output settings
| Field | Type | Required | Default | Description |
| ---------------- | ---------------- | -------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `duration_types` | array of strings | No | — | Target clip durations to produce: `"30"`, `"60"`, `"180"`, or `"long"` (1–4 entries). Each duration produces a separate clip. |
| `aspect_ratio` | string | No | `portrait` | Framing for all produced clips: `portrait` (9:16, social-ready), `landscape` (16:9), or `square` (1:1). |
| `captions` | boolean | No | `true` | Captions are burned into the clips by default. Set `false` for clean, caption-free footage — as in the example above. |
| `caption_style` | string | No | — | Named caption style preset (e.g. `classic`, `bold`) when captions are on. Omit for the default style. |
| `prompt` | string | No | — | Editorial guidance for the highlight model — which speaker, what topics (max 500 chars). When omitted, the model selects highlights on its own. |
Want captions on some clips and clean footage on others? Captions are a per-job setting, so submit two jobs from the same source `video` — one with `"captions": false` — and pick per platform. To style burned-in captions on other footage, `caption_style` presets like `classic` and `bold` keep the look consistent.
## Get a Clip Job
* Endpoint: [`GET /v3/ai-clipping/{job_id}`](/reference/get-ai-clipping)
* Purpose: Fetch a clip job's live status and, as they render, its finished clips.
### Quick Example
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/ai-clipping/edf8d2c44ba441b89f395072b3ef7e34" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### Path Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------------------------------------------------------------------------- |
| `job_id` | string | Yes | Unique job identifier returned by `POST /v3/ai-clipping` as `ai_clipping_id`. |
### Response
```json theme={null}
{
"data": {
"id": "edf8d2c44ba441b89f395072b3ef7e34",
"title": "Founder interview",
"status": "completed",
"input_language": "en",
"source_duration": 1264.4,
"progress": 100,
"clips": [
{
"id": "92799db89cdf444eb40a3d3db4378f4c",
"status": "completed",
"title": "Why we changed our pricing",
"duration_seconds": 26.4,
"aspect_ratio": "portrait",
"virality_score": 65,
"thumbnail_url": "https://resource2.heygen.ai/video_repurpose/.../1280x720.jpeg",
"video_url": "https://resource2.heygen.ai/video_repurpose/.../1280x720.mp4?..."
}
],
"created_at": 1784649990
}
}
```
### Response Fields
| Field | Type | Description |
| -------------------------- | --------------- | ------------------------------------------------------------------------------------ |
| `id` | string | Unique job identifier. |
| `title` | string or null | Display title for the job. |
| `status` | string | Job lifecycle status: `pending`, `running`, `completed`, `failed`, or `cancelled`. |
| `input_language` | string or null | Detected or supplied source language code. |
| `source_duration` | number or null | Duration of the source video in seconds. |
| `progress` | integer | Approximate progress (0–100). `100` when all clips are completed. |
| `clips` | array | Produced clips. Populates as each clip renders — empty until the first one finishes. |
| `clips[].id` | string | Unique clip identifier. |
| `clips[].status` | string | Per-clip status: `pending`, `completed`, or `failed`. |
| `clips[].title` | string or null | Model-generated clip title. |
| `clips[].duration_seconds` | number or null | Final clip length in seconds. Populated when the clip completes. |
| `clips[].aspect_ratio` | string or null | Framing of the finished clip. |
| `clips[].virality_score` | integer or null | Model-predicted virality score (0–100). |
| `clips[].thumbnail_url` | string or null | Pre-signed thumbnail URL. |
| `clips[].video_url` | string or null | Pre-signed MP4 download URL. |
| `callback_id` | string or null | Client-provided callback ID. |
| `created_at` | integer | Unix timestamp (seconds) of job creation. |
| `failure_message` | string or null | Error description. Only present when status is `failed`. |
Each `video_url` and `thumbnail_url` is a pre-signed link with a limited lifetime. Download the file (or hand the URL to a downstream step) soon after the job completes rather than caching it for later.
## List Clip Jobs
* Endpoint: [`GET /v3/ai-clipping`](/reference/list-ai-clipping)
* Purpose: List clip jobs with cursor-based pagination.
### Quick Example
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/ai-clipping?limit=10" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### Query Parameters
| Parameter | Type | Required | Default | Description |
| --------- | ------- | -------- | ------- | ---------------------------------------------------------------------------------------- |
| `limit` | integer | No | `10` | Results per page. |
| `token` | string | No | — | Opaque cursor token. Pass the `next_token` from a prior response to fetch the next page. |
### Response
```json theme={null}
{
"data": [
{
"id": "edf8d2c44ba441b89f395072b3ef7e34",
"title": "Founder interview",
"status": "completed",
"progress": 100,
"created_at": 1784649990
}
],
"has_more": false,
"next_token": null
}
```
The list endpoint reports coarse `progress`; poll [`GET /v3/ai-clipping/{job_id}`](#get-a-clip-job) for live in-flight progress on a specific job.
## Delete a Clip Job
* Endpoint: [`DELETE /v3/ai-clipping/{job_id}`](/reference/delete-ai-clipping)
* Purpose: Permanently delete a clip job and its clips.
### Quick Example
```bash theme={null}
curl -X DELETE "https://api.heygen.com/v3/ai-clipping/edf8d2c44ba441b89f395072b3ef7e34" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### Response
```json theme={null}
{
"data": {
"id": "edf8d2c44ba441b89f395072b3ef7e34"
}
}
```
## Polling Pattern
Clip jobs are processed asynchronously. Poll until status reaches `completed` or `failed`.
Status transitions: `pending` → `running` → `completed` | `failed`
```bash theme={null}
while true; do
STATUS=$(curl -s "https://api.heygen.com/v3/ai-clipping/$JOB_ID" \
-H "X-Api-Key: $HEYGEN_API_KEY" | jq -r '.data.status')
echo "Status: $STATUS"
[ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] && break
sleep 10
done
```
For long recordings, prefer a [`callback_url`](/docs/webhooks) over tight polling — HeyGen will POST you the finished job instead (`ai_clipping.success` / `ai_clipping.fail` [webhook events](/docs/webhook-events)).
## Asset Inputs
The `video` field accepts two input formats:
**By URL** — any publicly accessible HTTPS link:
```json theme={null}
{ "type": "url", "url": "https://example.com/recording.mp4" }
```
**By asset ID** — reference a file previously uploaded via [`POST /v3/assets`](/reference/upload-asset) (see [Upload Assets](/docs/upload-assets)):
```json theme={null}
{ "type": "asset_id", "asset_id": "asset_xyz789" }
```
## Full Example
```python theme={null}
import time
import requests
API_KEY = "YOUR_API_KEY"
BASE = "https://api.heygen.com"
HEADERS = {"x-api-key": API_KEY, "Content-Type": "application/json"}
def clip_video(video_url, prompt=None):
"""Start a clip job, wait for it, and return the finished clips."""
body = {
"video": {"type": "url", "url": video_url},
"output_settings": {
"duration_types": ["30", "60"],
"aspect_ratio": "portrait",
"captions": False,
},
}
if prompt:
body["output_settings"]["prompt"] = prompt
job_id = requests.post(
f"{BASE}/v3/ai-clipping", headers=HEADERS, json=body
).json()["data"]["ai_clipping_id"]
while True:
job = requests.get(f"{BASE}/v3/ai-clipping/{job_id}", headers=HEADERS).json()["data"]
if job["status"] in ("completed", "failed"):
break
time.sleep(10)
if job["status"] == "failed":
raise RuntimeError(job.get("failure_message") or "clip job failed")
return job["clips"]
for clip in clip_video("https://example.com/interview.mp4", prompt="Best product moments"):
print(f"{clip['title']} ({clip['duration_seconds']}s) -> {clip['video_url']}")
```
Pairing clips with audio? The same [Tools](/background-music) suite covers [background music](/background-music) and [sound effects](/sound-effects) to score your cuts.
# Assets
Source: https://developers.heygen.com/assets
Upload, list, and manage images, audio, and video files via the HeyGen Assets API. Reference uploaded assets in any avatar video, translation, or lipsync.
Upload images, videos, audio, or PDFs to get an `asset_id` you can reference in other endpoints — like `POST /v3/video-agents`, `POST /v3/videos`, or `POST /v3/avatars`.
## Upload an Asset
```bash theme={null}
curl -X POST https://api.heygen.com/v3/assets \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@./my-photo.png"
```
```json Response theme={null}
{
"data": {
"asset_id": "ast_abc123",
"url": "https://files.heygen.com/asset/ast_abc123.png",
"mime_type": "image/png",
"size_bytes": 204800
}
}
```
## Supported File Types
| Category | Formats |
| -------- | --------- |
| Image | PNG, JPEG |
| Video | MP4, WebM |
| Audio | MP3, WAV |
| Document | PDF |
Max file size: **32 MB**. MIME type is auto-detected from file bytes. For larger files, use the direct upload flow below.
## Direct Upload for Large Files
`POST /v3/assets` proxies file bytes through the API, which is why it's capped at 32 MB. For larger files, use the presigned direct upload flow — three required steps:
1. [`POST /v3/assets/direct-uploads`](/reference/create-asset-upload) with `filename`, `content_type`, and exact `size_bytes` → returns `asset_id`, a presigned `upload_url`, and `upload_headers`.
2. `PUT` the raw file bytes to `upload_url`, sending `upload_headers` verbatim, before the URL expires (`expires_in_seconds`).
3. [`POST /v3/assets/{asset_id}/complete`](/reference/complete-asset-upload) to finalize. Idempotent. The `asset_id` is not usable until this step succeeds.
```bash theme={null}
# 1. Initialize
curl -X POST https://api.heygen.com/v3/assets/direct-uploads \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename": "footage.mp4", "content_type": "video/mp4", "size_bytes": 134217728}'
# 2. PUT the file bytes to the returned upload_url (with upload_headers)
curl -X PUT "UPLOAD_URL" -H "Content-Type: video/mp4" --upload-file ./footage.mp4
# 3. Complete
curl -X POST https://api.heygen.com/v3/assets/ASSET_ID/complete \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
```
The per-upload cap for this flow is returned as `max_bytes` in the initialize response. See [Upload Assets](/docs/upload-assets#upload-large-files-direct-upload) for full examples in Python and Node.js. To upload up to 100 files in one call, use the [batch variant](/batch-assets) of this flow.
## Using Assets
Once uploaded, reference the `asset_id` anywhere the API accepts asset inputs:
```json theme={null}
// In POST /v3/video-agents (file attachments)
{
"prompt": "Explain this diagram",
"files": [{ "type": "asset_id", "asset_id": "ast_abc123" }]
}
```
```json theme={null}
// In POST /v3/avatars (photo avatar)
{
"type": "photo",
"name": "My Avatar",
"file": { "type": "asset_id", "asset_id": "ast_abc123" }
}
```
Anywhere that accepts an asset also accepts a direct URL (`{"type": "url", "url": "https://..."}`) or base64 (`{"type": "base64", "media_type": "image/png", "data": "..."}`). The 32 MB per-file limit applies to URL inputs too — for larger files, upload via the direct upload flow and pass the `asset_id`. Use `asset_id` when you need to reuse the same file across multiple requests.
# Automated Broadcast
Source: https://developers.heygen.com/automated-broadcast
Run scheduled video broadcasts - news roundups, daily briefings, market updates. The HeyGen API generates each clip on a cron schedule from fresh data inputs.
## The Problem
Publishing regular video content — daily news roundups, weekly company updates, recurring educational series — is unsustainable without a production team. But consistency is what builds an audience.
## How It Works
```
Schedule triggers → Aggregate content → LLM writes script → Video Agent renders → Auto-distribute
```
A fully automated pipeline that runs on a schedule, collects fresh content from your sources, generates a video, and delivers it to your audience — no human in the loop.
## Build It
Pull content from whatever sources feed your broadcast.
```python theme={null}
import requests
from datetime import datetime
def aggregate_content():
stories = []
# RSS feeds
import feedparser
feed = feedparser.parse("https://news.ycombinator.com/rss")
for entry in feed.entries[:5]:
stories.append({
"title": entry.title,
"summary": entry.get("summary", ""),
"source": "Hacker News",
"url": entry.link,
})
# APIs (example: your internal metrics)
metrics = requests.get("https://api.yourapp.com/weekly-stats").json()
stories.append({
"title": f"This week: {metrics['new_users']} new users, {metrics['revenue']} revenue",
"summary": f"Growth of {metrics['growth_pct']}% week over week",
"source": "Internal",
})
return stories
stories = aggregate_content()
```
```python theme={null}
import anthropic
client = anthropic.Anthropic()
story_text = "\n".join(
f"- {s['title']} ({s['source']}): {s['summary']}"
for s in stories
)
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1500,
messages=[{
"role": "user",
"content": f"""Create a HeyGen Video Agent prompt for a 60-second
news/update video.
Date: {datetime.now().strftime('%B %d, %Y')}
Stories to cover:
{story_text}
Structure:
- Intro (5s): "Here's your [daily/weekly] update for [date]"
- Stories (45s): Cover the top 3 stories with text overlays for key stats
- Sign-off (10s): "That's your update. See you [tomorrow/next week]."
Tone: Authoritative but approachable. Clean, news-desk style background.
Keep pacing brisk — one story every 15 seconds."""
}],
)
video_prompt = message.content[0].text
```
```python theme={null}
resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={
"X-Api-Key": HEYGEN_API_KEY,
"Content-Type": "application/json",
},
json={"prompt": video_prompt},
)
video_id = resp.json()["data"]["video_id"]
# Poll until complete
import time
while True:
status = requests.get(
f"https://api.heygen.com/v3/videos/{video_id}",
headers={"X-Api-Key": HEYGEN_API_KEY},
).json()["data"]
if status["status"] == "completed":
video_url = status["video_url"]
break
elif status["status"] == "failed":
raise Exception(f"Video failed: {status.get('failure_message')}")
time.sleep(15)
```
Deliver the video to your audience wherever they are.
```python theme={null}
# Telegram
import telegram
bot = telegram.Bot(token=TELEGRAM_TOKEN)
bot.send_video(chat_id=CHANNEL_ID, video=video_url, caption="Daily Update")
# Slack
requests.post(SLACK_WEBHOOK, json={
"text": f"Daily update is ready: {video_url}",
})
# Email (via your ESP)
send_email(
to=subscriber_list,
subject=f"Your Daily Update — {datetime.now().strftime('%B %d')}",
html=f'',
)
```
Run the pipeline on a schedule using cron, GitHub Actions, or a cloud function.
```yaml theme={null}
# .github/workflows/daily-broadcast.yml
name: Daily Video Broadcast
on:
schedule:
- cron: '0 17 * * 1-5' # 5 PM UTC, weekdays
jobs:
broadcast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install -r requirements.txt
- run: python broadcast.py
env:
HEYGEN_API_KEY: ${{ secrets.HEYGEN_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
```
## Real-World Example
STUDIO 47, a German broadcaster, reported these results after adopting HeyGen for automated video production (via [HeyGen customer stories](https://www.heygen.com/customer-stories/studio-47)):
* Significantly faster content creation
* 24/7 production capability
* Substantial cost reduction vs traditional production
* Expanded into multilingual content that wasn't feasible before
## Resilient Delivery
Build fallbacks for when things go wrong:
```python theme={null}
def deliver(video_url, caption):
try:
# Try primary: send video by URL
bot.send_video(chat_id=CHANNEL_ID, video=video_url, caption=caption)
except Exception:
try:
# Fallback: download and upload as file
video_data = requests.get(video_url).content
bot.send_video(chat_id=CHANNEL_ID, video=video_data, caption=caption)
except Exception:
# Last resort: send text with link
bot.send_message(chat_id=CHANNEL_ID, text=f"{caption}\n\n{video_url}")
```
## Broadcast Types
| Type | Schedule | Content source | Duration |
| --------------------- | -------------- | -------------------------------- | -------- |
| **Daily news** | Every morning | RSS, APIs, web scrape | 45–60s |
| **Weekly roundup** | Monday morning | Internal metrics + industry news | 90s |
| **Product changelog** | Each release | Git commits, release notes | 30–45s |
| **Company all-hands** | Weekly/monthly | Meeting notes, updates | 60–90s |
| **Social digest** | Daily | Trending topics in your niche | 30s |
## Variations
* **Multi-language:** Generate once, [translate](/cookbook/video-agent/multilingual-content) for regional audiences
* **Different avatars per topic:** Use different presenters for different content categories
* **Audience segmentation:** Generate different versions for different subscriber segments
***
## Next Steps
Repurpose existing content instead of aggregating new content.
Trigger video generation from code changes instead of a schedule.
# Automated Video Pipeline
Source: https://developers.heygen.com/automated-pipeline
Build end-to-end automated video pipelines - data in, finished video out. HeyGen API integrates with your CMS, CRM, or data warehouse to render videos on demand.
## The Problem
You need to generate the same type of video repeatedly with different data — weekly reports, personalized onboarding videos, per-customer dashboards, changelog announcements. Doing this manually doesn't scale.
## How It Works
```
Data event → Template composition + injected data → Hyperframes render → Distribute
```
Hyperframes compositions are just HTML files. You can template them, inject data, and render programmatically — no browser, no human, no AI agent in the loop.
## Build It
Build one great composition with your AI agent, then extract the variable parts:
```html theme={null}
{{ACTIVE_USERS}}active users this week
{{REVENUE}}revenue
```
```python theme={null}
import subprocess
import shutil
from pathlib import Path
def generate_report_video(data: dict, output_path: str):
"""Generate a weekly report video from data."""
# Copy template
work_dir = Path(f"/tmp/report-{data['week']}")
shutil.copytree("templates/weekly-report", work_dir, dirs_exist_ok=True)
# Inject data into template
html = (work_dir / "index.html").read_text()
html = html.replace("{{ACTIVE_USERS}}", f"{data['active_users']:,}")
html = html.replace("{{REVENUE}}", f"${data['revenue']:,.0f}")
html = html.replace("{{GROWTH}}", f"{data['growth_pct']:.1f}%")
(work_dir / "index.html").write_text(html)
# Render
subprocess.run([
"npx", "hyperframes", "render",
"--output", output_path,
"--quality", "standard",
"--fps", "30",
], cwd=str(work_dir), check=True)
# Cleanup
shutil.rmtree(work_dir)
return output_path
```
**GitHub Actions:**
```yaml theme={null}
# .github/workflows/weekly-report.yml
name: Weekly Report Video
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 9am
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: sudo apt-get install -y ffmpeg
- run: python scripts/generate_report.py
- uses: actions/upload-artifact@v4
with:
name: weekly-report
path: renders/*.mp4
```
**Webhook-triggered:**
```python theme={null}
from flask import Flask, request
app = Flask(__name__)
@app.route("/webhook/new-signup", methods=["POST"])
def on_new_signup():
user = request.json
generate_welcome_video(
name=user["name"],
company=user["company"],
output=f"renders/welcome-{user['id']}.mp4"
)
# Upload to CDN, send via email, etc.
return {"status": "ok"}
```
## Pipeline Patterns
| Trigger | Data source | Output | Example |
| ----------------- | ---------------------- | --------------------------- | ------------------------- |
| **Cron schedule** | Database query | Weekly/monthly report video | Monday metrics recap |
| **Webhook** | Event payload | Per-user personalized video | Welcome onboarding |
| **Git push** | Changelog / commit log | Release announcement | "What's new in v2.4" |
| **API call** | Request parameters | On-demand custom video | Customer dashboard export |
## Combine with Video Agent
For the best of both worlds — motion graphics + avatar narration:
```python theme={null}
def generate_narrated_report(data):
# Step 1: Render the motion graphics with Hyperframes
graphics_path = generate_report_video(data, "renders/graphics.mp4")
# Step 2: Generate avatar narration with Video Agent
narration = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={"X-Api-Key": HEYGEN_API_KEY},
json={
"prompt": f"""Narrate this weekly report: {data['active_users']:,} active
users (up {data['growth_pct']:.0f}%), ${data['revenue']:,.0f} revenue.
Keep it under 15 seconds, upbeat and concise.""",
},
).json()
# Step 3: Composite in Hyperframes (avatar PiP over graphics)
# ... or use ffmpeg to overlay
```
Start simple — get one template working end-to-end, then add automation. A working pipeline that generates one video type reliably is more valuable than a complex system that handles everything.
***
## Next Steps
Build the animated visualizations that feed into your pipeline.
Similar automation pattern using Video Agent for avatar-based content.
# Avatar III
Source: https://developers.heygen.com/avatar-iii
Avatar III is a HeyGen rendering engine on the v3 API, built around a dedicated photo-to-video pipeline — a higher-quality re-engineered model for photo avatars and video avatars (digital twins and studio avatars).
A single `engine` value resolves to the right product based on the avatar look type — mirroring how `avatar_iv` already serves both photo and video avatars:
| Look type | Resolves to | Max resolution |
| --------------- | ------------ | -------------- |
| `digital_twin` | Digital Twin | 4K |
| `studio_avatar` | Digital Twin | 4K |
| `photo_avatar` | Photo Avatar | 1080p |
## Supported avatar types
`digital_twin`, `studio_avatar`, `photo_avatar` — pass the look's `avatar_id` in the request.
Studio avatar looks are video avatars, so they take the same Digital Twin pipeline as `digital_twin` looks — same 4K support and the same [Digital Twin rate](/docs/pricing#video-generation-avatar-iii).
For `motion_prompt`, `expressiveness`, or animating an arbitrary image, use [Avatar IV](/avatar-iv).
## Example request
Select Avatar III by passing `"engine": { "type": "avatar_iii" }` in your [`POST /v3/videos`](/reference/create-video) request:
```bash theme={null}
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_PHOTO_AVATAR_LOOK_ID",
"script": "Hello from Avatar III.",
"voice_id": "YOUR_VOICE_ID",
"resolution": "1080p",
"engine": { "type": "avatar_iii" }
}'
```
Video generation is asynchronous — the response returns a `video_id` you poll with `GET /v3/videos/{video_id}`. For pricing, see [Self-Serve Pricing](/docs/pricing#video-generation-avatar-iii) and [Enterprise Pricing](/docs/enterprise-pricing#video-generation-avatar-iii). To compare engines, see [Models](/models).
# Avatar IV
Source: https://developers.heygen.com/avatar-iv
Avatar IV is the default HeyGen v3 rendering engine - the broadest avatar support, arbitrary image animation, motion_prompt, and expressiveness control.
## Supported avatar types
`studio_avatar`, `digital_twin`, `photo_avatar`, `image` (arbitrary), `prompt`
## Exclusive features
* `motion_prompt` — a natural-language string controlling body motion and hand gestures (e.g. `"walk towards the camera slowly"`). Available for photo avatars and arbitrary images.
* `expressiveness` — controls energy and range of movement: `high`, `medium`, or `low`. Available for photo avatars and arbitrary images. Defaults to `low`.
* **Arbitrary image support** — animate any image by setting `type: "image"`, with no registered avatar required.
## Example request
Avatar IV is the default engine, so you can omit `engine` entirely in your [`POST /v3/videos`](/reference/create-video) request:
```bash theme={null}
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_LOOK_ID",
"script": "Hello from Avatar IV.",
"voice_id": "YOUR_VOICE_ID",
"resolution": "1080p"
}'
```
To request it explicitly — or to be unambiguous in code that switches engines — pass `"engine": { "type": "avatar_iv" }`:
```bash theme={null}
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_LOOK_ID",
"script": "Hello from Avatar IV.",
"voice_id": "YOUR_VOICE_ID",
"resolution": "1080p",
"engine": { "type": "avatar_iv" }
}'
```
Video generation is asynchronous — the response returns a `video_id` you poll with `GET /v3/videos/{video_id}`. For a full walkthrough of creating a Digital Twin video and polling for completion, see the [Digital Twin guide](/generate-avatar-video). To compare engines, see [Models](/models); for the highest-fidelity motion, see [Avatar V](/avatar-v).
# Avatar Realtime
Source: https://developers.heygen.com/avatar-realtime
Stream a HeyGen avatar that speaks in real time. Drive speech from a script, an audio file, or a live text stream at 720p.
Avatar Realtime opens a **live streaming session** where an avatar speaks in real time — useful for live agents, kiosks, and voice assistants with a face. You create a session, poll for the playback URL, and play it.
Avatar Realtime is **agent-agnostic**. Your application owns speech-to-text (STT) and the LLM — Avatar Realtime is only responsible for the face and voice. You stream text or audio to HeyGen, and HeyGen renders the avatar and publishes the video to an **HLS stream**. Because playback is plain HLS, there is no frontend dependency on LiveKit (or any other WebRTC stack): any HLS player can consume the output. This makes Avatar Realtime the right choice when you already have your own agent orchestration and just need to give it a talking face. If you want HeyGen to handle the full conversational loop — STT, LLM, and turn-taking — use [Live Avatar](/live-avatar) instead.
Avatar Realtime streams at **720p only**.
## Create a session
[`POST /v3/avatar-realtime`](/reference/create-avatar-realtime-session) — choose how to drive speech with the `type` field:
* `tts` — speak a script (`avatar_id`, `voice_id`, `text`)
* `audio` — lip-sync to your own audio (`avatar_id`, `audio`)
* `text_stream` — stream text live, e.g. from an LLM (`avatar_id`, `voice_id`, `text`)
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/avatar-realtime" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "tts",
"avatar_id": "Daisy-inskirt-20220818",
"voice_id": "1bd001e7e50f421d891986aad5158bc8",
"text": "Hi there — welcome to HeyGen Avatar Realtime."
}'
```
```json Response theme={null}
{ "data": { "stream_id": "a1b2c3d4-..." } }
```
## Get the playback URL
`GET /v3/avatar-realtime/{stream_id}` — poll until the session is ready, then play the HLS `url` in any HLS player.
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/avatar-realtime/a1b2c3d4-..." \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{ "data": { "stream_id": "a1b2c3d4-...", "status": "ready", "url": "https://.../stream.m3u8" } }
```
## Stream more text
For `text_stream` sessions, append text as it becomes available — the avatar keeps speaking on the open stream.
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/avatar-realtime/a1b2c3d4-.../text" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "text": "Here are the results I found." }'
```
## Limits
| Limit | Default | Notes |
| ----------------------------- | ------- | --------------------------------------------------------- |
| Idle timeout (`text_stream`) | 30 sec | The session closes if no new text arrives for 30 seconds. |
| Max session length | 1 hour | Sessions are capped at one hour. |
| Concurrent sessions per space | 3 | Maximum simultaneous realtime sessions. |
All three limits are adjustable — [reach out to us](https://www.heygen.com/contact-us/sales) to raise them.
## Pricing
Billed per second of session duration (720p only): **\$0.05 / sec** self-serve, **0.05 credits / sec** on Enterprise. See [Self-Serve Pricing](/docs/pricing) and [Enterprise Pricing](/docs/enterprise-pricing).
# Avatar V
Source: https://developers.heygen.com/avatar-v
Avatar V is HeyGen's highest-fidelity v3 rendering engine, using cross-reference-driven animation for the most natural motion and lip-sync. Opt-in per look.
## Supported avatar types
`digital_twin` — for eligible looks.
## Supported parameters
* `motion_prompt` — natural-language control of body motion and hand gestures.
* `reference_look_id` *(optional)* — an `instant_avatar` look to use as the animation reference. Must belong to the same avatar group as `avatar_id`. When omitted, the digital twin self-references.
For `expressiveness` control, use [Avatar IV](/avatar-iv).
## Checking eligibility
Avatar V is opt-in per look. Before using it, fetch the look and check `supported_api_engines`:
```bash theme={null}
GET /v3/avatars/looks/{look_id}
```
```json theme={null}
{
"id": "lk_abc123",
"name": "My Digital Twin",
"avatar_type": "digital_twin",
"supported_api_engines": ["avatar_iv", "avatar_v"]
}
```
Avatar V is available when `"avatar_v"` appears in `supported_api_engines`.
## Example request
Select Avatar V by passing `"engine": { "type": "avatar_v" }` in your [`POST /v3/videos`](/reference/create-video) request:
```bash theme={null}
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_LOOK_ID",
"script": "Hello from Avatar V.",
"voice_id": "YOUR_VOICE_ID",
"resolution": "1080p",
"engine": { "type": "avatar_v" }
}'
```
Video generation is asynchronous — the response returns a `video_id` you poll with `GET /v3/videos/{video_id}`. See the [Digital Twin guide](/generate-avatar-video) for the end-to-end request and polling flow, [Models](/models) for an engine comparison, or [Avatar IV](/avatar-iv) for the default engine.
# Background music
Source: https://developers.heygen.com/background-music
Search HeyGen's background-music catalog with natural language and get ready-to-use, pre-signed audio URLs for your videos via the HeyGen API.
Find background music by describing the vibe you want — "upbeat lofi hip-hop", "tense cinematic riser", "subtle ambient corporate" — and get back ranked tracks, each with a pre-signed download URL. Search is semantic, not keyword-based, so plain-language descriptions work best.
## Search the Catalog
Send a `GET` request to `/v3/audio/sounds` with a natural-language `query`:
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/audio/sounds?query=upbeat%20corporate%20background%20music&limit=3" \
-H "x-api-key: YOUR_API_KEY"
```
```json Response theme={null}
{
"data": [
{
"id": "4cbcca493220487bbae26a2c42dba5e9",
"name": "Astral Generated Music: 4cbcca49",
"description": "upbeat professional background music",
"audio_url": "https://heygen-product.s3-accelerate.amazonaws.com/astral_generated_music/4cbcca49...wav?X-Amz-Algorithm=...",
"duration": 30.0,
"score": 0.933,
"type": "music"
},
{
"id": "93a98c35d9654029be397d8d27a06da0",
"name": "Astral Generated Music: 93a98c35",
"description": "Modern, upbeat, and inspiring corporate background music with a light electronic beat.",
"audio_url": "https://heygen-product.s3-accelerate.amazonaws.com/astral_generated_music/93a98c35...wav?X-Amz-Algorithm=...",
"duration": 60.0,
"score": 0.911,
"type": "music"
}
],
"has_more": true,
"next_token": "eyJvZmZzZXQiOiAzLCAiX3R5cGUiOiAibXVzaWMifQ=="
}
```
## Query Parameters
| Parameter | Type | Default | Description |
| ----------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `query` | string | — | **Required.** Natural-language description of the audio you want, e.g. `tense cinematic riser`. Results are ranked by semantic similarity to this text. |
| `limit` | integer | `10` | Maximum number of results to return (1–50). |
| `min_score` | number | `0.7` | Minimum semantic similarity score (0–1). Tracks scoring below this are omitted. Raise it for tighter matches; lower it to widen the net. |
| `type` | string | `music` | Audio content type. `music` (the default) searches the background-music catalog; set `sound_effects` to search [sound effects](/sound-effects). |
| `token` | string | — | Opaque pagination cursor. Pass the `next_token` from a prior response to fetch the next page. |
## Response Fields
| Field | Type | Description |
| -------------------- | ------- | ----------------------------------------------------------------------------------------------------- |
| `data[].id` | string | Stable identifier for the track. |
| `data[].name` | string | Display name of the track. |
| `data[].description` | string | Human-readable description of the track's mood and instrumentation. |
| `data[].audio_url` | string | Pre-signed download URL for the audio file (WAV). |
| `data[].duration` | number | Track length in seconds. |
| `data[].score` | number | Semantic similarity score (0–1) against your `query`. Results are returned highest-first. |
| `data[].type` | string | `music` for results from the background-music catalog. |
| `has_more` | boolean | `true` if more results are available beyond this page. |
| `next_token` | string | Cursor for the next page. Pass it as `token` on your next request. Absent when `has_more` is `false`. |
Each `audio_url` is a pre-signed link with a limited lifetime. Download the file (or hand the URL to a downstream step) soon after searching rather than caching it for later.
## Paginate Through Results
When `has_more` is `true`, pass the returned `next_token` as the `token` parameter to fetch the next page:
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/audio/sounds?query=upbeat%20corporate%20background%20music&limit=3&token=eyJvZmZzZXQiOiAzLCAiX3R5cGUiOiAibXVzaWMifQ==" \
-H "x-api-key: YOUR_API_KEY"
```
## Full Example
```python theme={null}
import requests
from urllib.parse import urlencode
API_KEY = "YOUR_API_KEY"
BASE = "https://api.heygen.com"
HEADERS = {"x-api-key": API_KEY}
def search_music(query, limit=10, min_score=0.7):
"""Yield every matching track, following pagination."""
token = None
while True:
params = {"query": query, "limit": limit, "min_score": min_score}
if token:
params["token"] = token
resp = requests.get(
f"{BASE}/v3/audio/sounds?{urlencode(params)}",
headers=HEADERS,
)
page = resp.json()
for track in page["data"]:
yield track
if not page.get("has_more"):
break
token = page["next_token"]
# Grab the single best-matching track
best = next(search_music("calm ambient piano for a product walkthrough", limit=1))
print(f"{best['name']} ({best['duration']}s, score {best['score']:.2f})")
print(f"Download: {best['audio_url']}")
```
Searching from an AI agent instead of code? The same catalog is available through the [HeyGen MCP](/mcp/overview) via the `search_audio_sounds` tool.
# Assets
Source: https://developers.heygen.com/batch-assets
Upload up to 100 files in one batch: request presigned S3 slots in a single call, PUT the bytes in parallel, then finalize and poll one batch id.
## Overview
Asset batches parallelize the [direct upload flow](/assets#direct-upload-for-large-files): instead of presigning, uploading, and completing files one by one, you request up to 100 presigned upload slots in a single call, PUT all the files in parallel, and finalize the whole batch with one request.
The flow has three steps:
1. [`POST /v3/assets/direct-uploads/batches`](/reference/create-asset-upload-batch) — get an `asset_id` + presigned `upload_url` per file.
2. `PUT` each file's raw bytes to its `upload_url`, sending `upload_headers` verbatim.
3. [`POST /v3/assets/complete/batches`](/reference/complete-asset-upload-batch) — finalize, then poll [`GET /v3/assets/batches/{batch_id}`](/reference/get-asset-batch).
A batch holds up to **100** files. No file bytes flow through the HeyGen API — uploads go straight to S3. On completion, each file is validated and ingested independently, so one bad file does not fail the rest.
## Step 1 — Create a Batch of Upload Slots
* Endpoint: `POST /v3/assets/direct-uploads/batches`
* Purpose: Presign up to 100 direct-to-S3 upload slots and return them synchronously with a `batch_id`.
### Quick Example
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/assets/direct-uploads/batches" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Webinar recordings — October",
"files": [
{ "filename": "session-1.mp4", "content_type": "video/mp4", "size_bytes": 734003200 },
{ "filename": "session-2.mp4", "content_type": "video/mp4", "size_bytes": 812646400 }
]
}'
```
```json Response theme={null}
{
"data": {
"batch_id": "batch_as_abc123",
"items": [
{
"asset_id": "asset_9f2c...",
"upload_url": "https://heygen-uploads.s3.amazonaws.com/...",
"upload_headers": { "Content-Type": "video/mp4" },
"expires_in_seconds": 3600,
"max_bytes": 734003200,
"status": "pending_upload"
},
{
"asset_id": "asset_81aa...",
"upload_url": "https://heygen-uploads.s3.amazonaws.com/...",
"upload_headers": { "Content-Type": "video/mp4" },
"expires_in_seconds": 3600,
"max_bytes": 812646400,
"status": "pending_upload"
}
]
}
}
```
Slots are returned **in the submitted order**, one per file.
### Request Body
| Parameter | Type | Required | Default | Description |
| -------------- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `files` | array | Yes | — | Files to presign, each the same shape as [`POST /v3/assets/direct-uploads`](/reference/create-asset-upload). Between 1 and 100 items. |
| `title` | string | No | `null` | Display name for the batch, shown in the HeyGen app. |
| `callback_url` | string | No | `null` | Reserved for parity with the other batch APIs — track completion by [polling the batch](#step-3--complete-and-poll). |
Each entry in `files`:
| Field | Type | Required | Description |
| ----------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `filename` | string | Yes | Original filename for reference/metadata. The stored object's extension is derived from `content_type`. |
| `content_type` | string | Yes | Declared MIME type (e.g. `video/mp4`, `image/png`, `audio/mpeg`, `application/pdf`). Verified against the stored bytes when the batch is completed. |
| `size_bytes` | integer | Yes | Exact byte size of the file. Signed into the upload URL so it cannot be exceeded. |
| `checksum_sha256` | string | No | SHA-256 of the file as hex. When provided, S3 enforces it on upload. |
### Idempotency
Pass an `Idempotency-Key` header to make retries safe — replaying the same key returns the same batch and the same slots.
## Step 2 — Upload the Files
`PUT` each file's raw bytes to its `upload_url`, sending the returned `upload_headers` verbatim. Uploads are plain S3 PUTs, so you can run them in parallel from any HTTP client:
```bash theme={null}
curl -X PUT "https://heygen-uploads.s3.amazonaws.com/..." \
-H "Content-Type: video/mp4" \
--data-binary @session-1.mp4
```
Each `upload_url` expires after `expires_in_seconds` — request a fresh batch if a slot lapses before you upload.
## Step 3 — Complete and Poll
* Endpoint: `POST /v3/assets/complete/batches`
* Purpose: Finalize every uploaded file in the batch. Call once, after all PUTs return `200`.
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/assets/complete/batches" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "batch_id": "batch_as_abc123" }'
```
```json Response theme={null}
{
"data": {
"batch_id": "batch_as_abc123"
}
}
```
The call returns `202 Accepted` — validation and ingestion run asynchronously per file. The call is idempotent: repeating it re-drives the same batch.
Poll [`GET /v3/assets/batches/{batch_id}`](/reference/get-asset-batch) for per-item progress:
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/assets/batches/batch_as_abc123?limit=100" \
-H "x-api-key: YOUR_API_KEY"
```
```json Response theme={null}
{
"data": {
"batch_id": "batch_as_abc123",
"title": "Webinar recordings — October",
"status": "processing",
"total_items": 2,
"counts_by_status": {
"completed": 1,
"processing": 1
},
"created_at": 1783891200,
"items": [
{
"item_index": 0,
"status": "completed",
"video_id": "asset_9f2c...",
"error": null
},
{
"item_index": 1,
"status": "processing",
"video_id": null,
"error": null
}
],
"has_more": false,
"next_token": null
}
}
```
The batch read model is shared across the batch APIs, so the per-item id field is named `video_id` — for asset batches it holds the **asset id**. Once an item is `completed`, that asset id is usable anywhere assets are accepted, e.g. as `audio_asset_id` in [video creation](/generate-avatar-video) or as an `asset_id` source in [translation](/batch-video-translations) and [lipsync](/batch-lipsyncs) batches.
### Query Parameters
| Parameter | Type | Required | Default | Description |
| --------- | ------- | -------- | ------- | ----------------------------------------------------------------- |
| `limit` | integer | No | `100` | Items per page, `1`–`100`. |
| `token` | string | No | — | Opaque pagination cursor from a previous response's `next_token`. |
The response shape matches the [video batch](/batch-videos#response-fields): aggregate `status` (`processing`, `completed`, or `failed`), `total_items`, `counts_by_status`, and a paged `items` array where each item carries `item_index`, `status` (`queued`, `processing`, `completed`, or `failed`), its id, and `error` details when failed.
## Bulk Statuses
* Endpoint: `GET /v3/assets/statuses`
* Purpose: Check up to 100 assets in one request — across batches, or for assets uploaded individually.
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/assets/statuses?batch_ids=batch_as_abc123" \
-H "x-api-key: YOUR_API_KEY"
```
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------------------------------------------- |
| `asset_ids` | string | No | Comma-separated asset ids to look up. |
| `batch_ids` | string | No | Comma-separated batch ids; each expands to its member assets. |
Statuses are `queued`, `processing`, `completed`, or `failed`, plus `not_found` for unknown or unowned ids.
# Lipsyncs
Source: https://developers.heygen.com/batch-lipsyncs
Submit up to 100 lipsync requests in a single call. Re-sync many videos to new audio tracks together, then poll one batch id for per-item progress.
## Overview
Lipsync batches let you submit many lipsync jobs in one request instead of calling [`POST /v3/lipsyncs`](/reference/create-lipsync) once per job. You send an array of lipsync payloads, get back a single `batch_id` right away, and poll that one id for the status and id of every item.
Each item in a batch is a standard lipsync payload — the exact shape [`POST /v3/lipsyncs`](/reference/create-lipsync) accepts — so anything you can lipsync on its own can be batched, in either [Speed](/lipsync-speed) or [Precision](/lipsync-precision) mode.
A batch holds up to **100** items. Each payload becomes exactly one batch item, and each item is created and processed independently — one bad source does not fail the rest.
## Create a Batch
* Endpoint: `POST /v3/lipsyncs/batches`
* Purpose: Queue up to 100 lipsync payloads and return a `batch_id` immediately.
### Quick Example
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/lipsyncs/batches" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Course module redubs",
"callback_url": "https://example.com/hooks/heygen",
"lipsyncs": [
{
"video": { "type": "url", "url": "https://example.com/module-1.mp4" },
"audio": { "type": "url", "url": "https://example.com/module-1-v2.mp3" },
"title": "Module 1 — updated narration",
"mode": "speed"
},
{
"video": { "type": "asset_id", "asset_id": "asset_abc123" },
"audio": { "type": "asset_id", "asset_id": "asset_def456" },
"title": "Module 2 — updated narration",
"mode": "precision"
}
]
}'
```
```json Response theme={null}
{
"data": {
"batch_id": "batch_ls_abc123"
}
}
```
The call returns `202 Accepted` — the batch is queued, not finished. Use the returned `batch_id` to [poll for progress](#get-a-batch).
### Request Body
| Parameter | Type | Required | Default | Description |
| -------------- | ------ | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------- |
| `lipsyncs` | array | Yes | — | Lipsync payloads, each identical in shape to [`POST /v3/lipsyncs`](/reference/create-lipsync). Between 1 and 100 items. |
| `title` | string | No | `null` | Display name for the batch, shown in the HeyGen app. |
| `callback_url` | string | No | `null` | [Webhook](/docs/webhooks) URL invoked once when every item in the batch reaches a terminal state. |
Each entry in `lipsyncs` takes the full set of lipsync options — `video` and `audio` (each as a `url` or `asset_id`), `mode` (`speed` or `precision`), plus captions, partial-range, and format controls. See [`POST /v3/lipsyncs`](/reference/create-lipsync) for every field.
### Idempotency
Pass an `Idempotency-Key` header to make retries safe — replaying the same key returns the original batch instead of creating a duplicate.
## Get a Batch
* Endpoint: `GET /v3/lipsyncs/batches/{batch_id}`
* Purpose: Return the batch's aggregate status plus one page of items with their id and per-item status.
### Quick Example
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/lipsyncs/batches/batch_ls_abc123?limit=100" \
-H "x-api-key: YOUR_API_KEY"
```
```json Response theme={null}
{
"data": {
"batch_id": "batch_ls_abc123",
"title": "Course module redubs",
"status": "processing",
"total_items": 2,
"counts_by_status": {
"completed": 1,
"processing": 1
},
"created_at": 1783891200,
"items": [
{
"item_index": 0,
"status": "completed",
"video_id": "ls_9f2c...",
"error": null
},
{
"item_index": 1,
"status": "processing",
"video_id": null,
"error": null
}
],
"has_more": false,
"next_token": null
}
}
```
The batch read model is shared across the batch APIs, so the per-item id field is named `video_id` — for lipsync batches it holds the **lipsync id**. Use it with [`GET /v3/lipsyncs/{lipsync_id}`](/reference/get-lipsync) for the full record and download URL.
### Query Parameters
| Parameter | Type | Required | Default | Description |
| --------- | ------- | -------- | ------- | ----------------------------------------------------------------- |
| `limit` | integer | No | `100` | Items per page, `1`–`100`. |
| `token` | string | No | — | Opaque pagination cursor from a previous response's `next_token`. |
The response shape matches the [video batch](/batch-videos#response-fields): aggregate `status` (`processing`, `completed`, or `failed`), `total_items`, `counts_by_status`, and a paged `items` array where each item carries `item_index`, `status` (`queued`, `processing`, `completed`, or `failed`), its id, and `error` details when failed.
## Bulk Statuses
* Endpoint: `GET /v3/lipsyncs/statuses`
* Purpose: Check up to 100 lipsyncs in one request — across batches, or for jobs created individually.
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/lipsyncs/statuses?batch_ids=batch_ls_abc123" \
-H "x-api-key: YOUR_API_KEY"
```
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | --------------------------------------------------------------- |
| `lipsync_ids` | string | No | Comma-separated lipsync ids to look up. |
| `batch_ids` | string | No | Comma-separated batch ids; each expands to its member lipsyncs. |
Statuses are `queued`, `processing`, `completed`, or `failed`, plus `not_found` for unknown or unowned ids.
## Tracking completion
* **Webhook (push).** Set `callback_url` on the batch to be notified once, when the last item reaches a terminal state. See [Webhooks](/docs/webhooks) to register an endpoint and verify signatures.
* **Polling (pull).** Call `GET /v3/lipsyncs/batches/{batch_id}` and read `counts_by_status`. Items surface their lipsync id as soon as the job is created, so you can start fetching finished renders with [`GET /v3/lipsyncs/{lipsync_id}`](/reference/get-lipsync) while the rest of the batch is still processing.
# Video Translations
Source: https://developers.heygen.com/batch-video-translations
Submit up to 100 video translation requests in a single call. Fan one source video out to many languages, then poll one batch id for per-item progress.
## Overview
Translation batches let you submit many translations in one request instead of calling [`POST /v3/video-translations`](/reference/create-video-translation) once per job. You send an array of translation payloads, get back a single `batch_id` right away, and poll that one id for the status and id of every item.
Each item in a batch is a standard translation payload — the exact shape [`POST /v3/video-translations`](/reference/create-video-translation) accepts — so anything you can translate on its own can be batched, in either [Speed](/docs/video-translate) or [Precision](/docs/video-translation-precision) mode.
A batch holds up to **100** items. A single payload targeting multiple `output_languages` expands to **one batch item per language**, and the expanded count is what the 100-item cap applies to. Each item is created and processed independently, so one bad source does not fail the rest.
## Create a Batch
* Endpoint: `POST /v3/video-translations/batches`
* Purpose: Queue up to 100 translation payloads and return a `batch_id` immediately.
### Quick Example
One source video fanned out to three languages — this creates three batch items:
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/video-translations/batches" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Q3 keynote — localized cuts",
"callback_url": "https://example.com/hooks/heygen",
"video_translations": [
{
"video": { "type": "url", "url": "https://example.com/keynote.mp4" },
"title": "Q3 keynote",
"output_languages": ["Spanish (Spain)", "French", "Japanese"],
"mode": "speed"
}
]
}'
```
```json Response theme={null}
{
"data": {
"batch_id": "batch_tr_abc123"
}
}
```
The call returns `202 Accepted` — the batch is queued, not finished. Use the returned `batch_id` to [poll for progress](#get-a-batch).
### Request Body
| Parameter | Type | Required | Default | Description |
| -------------------- | ------ | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `video_translations` | array | Yes | — | Translation payloads, each identical in shape to [`POST /v3/video-translations`](/reference/create-video-translation). Up to 100 items after language expansion. |
| `title` | string | No | `null` | Display name for the batch, shown in the HeyGen app. |
| `callback_url` | string | No | `null` | [Webhook](/docs/webhooks) URL invoked once when every item in the batch reaches a terminal state. |
Each entry in `video_translations` takes the full set of translation options — `video` (as a `url` or `asset_id`), `output_languages` (use [`GET /v3/video-translations/languages`](/reference/list-supported-translation-languages) for valid values), `mode` (`speed` or `precision`), plus captions, brand glossary, stock voice, partial-range, and format controls. See [`POST /v3/video-translations`](/reference/create-video-translation) for every field.
### Idempotency
Pass an `Idempotency-Key` header to make retries safe — replaying the same key returns the original batch instead of creating a duplicate.
## Get a Batch
* Endpoint: `GET /v3/video-translations/batches/{batch_id}`
* Purpose: Return the batch's aggregate status plus one page of items with their id and per-item status.
### Quick Example
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/video-translations/batches/batch_tr_abc123?limit=100" \
-H "x-api-key: YOUR_API_KEY"
```
```json Response theme={null}
{
"data": {
"batch_id": "batch_tr_abc123",
"title": "Q3 keynote — localized cuts",
"status": "processing",
"total_items": 3,
"counts_by_status": {
"completed": 2,
"processing": 1
},
"created_at": 1783891200,
"items": [
{
"item_index": 0,
"status": "completed",
"video_id": "vt_9f2c...",
"error": null
},
{
"item_index": 1,
"status": "completed",
"video_id": "vt_81aa...",
"error": null
},
{
"item_index": 2,
"status": "processing",
"video_id": null,
"error": null
}
],
"has_more": false,
"next_token": null
}
}
```
The batch read model is shared across the batch APIs, so the per-item id field is named `video_id` — for translation batches it holds the **video translation id**. Use it with [`GET /v3/video-translations/{video_translation_id}`](/reference/get-video-translation) for the full record and download URL.
### Query Parameters
| Parameter | Type | Required | Default | Description |
| --------- | ------- | -------- | ------- | ----------------------------------------------------------------- |
| `limit` | integer | No | `100` | Items per page, `1`–`100`. |
| `token` | string | No | — | Opaque pagination cursor from a previous response's `next_token`. |
The response shape matches the [video batch](/batch-videos#response-fields): aggregate `status` (`processing`, `completed`, or `failed`), `total_items`, `counts_by_status`, and a paged `items` array where each item carries `item_index`, `status` (`queued`, `processing`, `completed`, or `failed`), its id, and `error` details when failed.
## Bulk Statuses
* Endpoint: `GET /v3/video-translations/statuses`
* Purpose: Check up to 100 translations in one request — across batches, or for jobs created individually.
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/video-translations/statuses?batch_ids=batch_tr_abc123" \
-H "x-api-key: YOUR_API_KEY"
```
| Parameter | Type | Required | Description |
| ----------------------- | ------ | -------- | ------------------------------------------------------------------- |
| `video_translation_ids` | string | No | Comma-separated translation ids to look up. |
| `batch_ids` | string | No | Comma-separated batch ids; each expands to its member translations. |
Statuses are `queued`, `processing`, `completed`, or `failed`, plus `not_found` for unknown or unowned ids.
## Tracking completion
* **Webhook (push).** Set `callback_url` on the batch to be notified once, when the last item reaches a terminal state, or subscribe to `video_translate.success` / `video_translate.fail` [webhook events](/docs/webhook-events) for per-item notifications.
* **Polling (pull).** Call `GET /v3/video-translations/batches/{batch_id}` and read `counts_by_status`. Items surface their translation id as soon as the job is created, so you can start fetching finished translations with [`GET /v3/video-translations/{video_translation_id}`](/reference/get-video-translation) while the rest of the batch is still processing.
# Videos
Source: https://developers.heygen.com/batch-videos
Submit up to 100 video creation requests in a single call. Queue avatar, image, and cinematic avatar videos together, then poll one batch id for per-item progress.
## Overview
Video batches let you submit many videos in one request instead of calling [`POST /v3/videos`](/reference/create-video) once per video. You send an array of video payloads, get back a single `batch_id` right away, and poll that one id for the status and `video_id` of every item.
Each item in a batch is a standard video creation payload — the exact shape [`POST /v3/videos`](/reference/create-video) accepts — so any [Digital Twin](/generate-avatar-video), [image](/image-to-video), or [Cinematic Avatar](/cinematic-avatar) video you can create on its own can be batched.
A batch holds up to **100** items. Submission is asynchronous: the response acknowledges the batch, and each video renders on its own. Poll the batch to collect video ids as they become available.
## Create a Batch
* Endpoint: `POST /v3/videos/batches`
* Purpose: Queue up to 100 video creation payloads and return a `batch_id` immediately.
### Quick Example
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/videos/batches" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "October product update — regional cuts",
"callback_url": "https://example.com/hooks/heygen",
"videos": [
{
"type": "avatar",
"avatar_id": "YOUR_DIGITAL_TWIN_LOOK_ID",
"script": "Hello from the North America team!",
"voice_id": "YOUR_VOICE_ID"
},
{
"type": "avatar",
"avatar_id": "YOUR_DIGITAL_TWIN_LOOK_ID",
"script": "Hello from the EMEA team!",
"voice_id": "YOUR_VOICE_ID"
}
]
}'
```
```json Response theme={null}
{
"data": {
"batch_id": "batch_abc123"
}
}
```
The call returns `202 Accepted` — the batch is queued, not finished. Use the returned `batch_id` to [poll for progress](#get-a-batch).
### Request Body
| Parameter | Type | Required | Default | Description |
| -------------- | ------ | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------- |
| `videos` | array | Yes | — | Video creation requests, each identical in shape to [`POST /v3/videos`](/reference/create-video). Between 1 and 100 items. |
| `title` | string | No | `null` | Display name for the batch, shown in the HeyGen app. |
| `callback_url` | string | No | `null` | [Webhook](/docs/webhooks) URL invoked once when every item in the batch reaches a terminal state. |
Each entry in `videos` is discriminated by `type`:
| `type` | Item shape | Guide |
| ------------------ | ------------------------------------ | -------------------------------------- |
| `avatar` | Avatar video from a script and voice | [Digital Twin](/generate-avatar-video) |
| `image` | Video from a still image | [Image to Video](/image-to-video) |
| `cinematic_avatar` | Cinematic Avatar video | [Cinematic Avatar](/cinematic-avatar) |
### Idempotency
Pass an `Idempotency-Key` header to make retries safe — replaying the same key returns the original batch instead of creating a duplicate. If a request with that key is still being processed, the API responds with `409`.
## Get a Batch
* Endpoint: `GET /v3/videos/batches/{batch_id}`
* Purpose: Return the batch's aggregate status plus one page of items with their `video_id` and per-item status.
### Quick Example
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/videos/batches/batch_abc123?limit=100" \
-H "x-api-key: YOUR_API_KEY"
```
```json Response theme={null}
{
"data": {
"batch_id": "batch_abc123",
"title": "October product update — regional cuts",
"status": "processing",
"total_items": 2,
"counts_by_status": {
"completed": 1,
"processing": 1
},
"created_at": 1711929600,
"items": [
{
"item_index": 0,
"status": "completed",
"video_id": "vid_9f2c...",
"error": null
},
{
"item_index": 1,
"status": "processing",
"video_id": null,
"error": null
}
],
"has_more": false,
"next_token": null
}
}
```
### Query Parameters
| Parameter | Type | Required | Default | Description |
| --------- | ------- | -------- | ------- | ----------------------------------------------------------------- |
| `limit` | integer | No | `100` | Items per page, `1`–`100`. |
| `token` | string | No | — | Opaque pagination cursor from a previous response's `next_token`. |
### Response Fields
| Field | Type | Description |
| ------------------ | ------- | ---------------------------------------------------------------------------------- |
| `batch_id` | string | Batch identifier. |
| `title` | string | Batch display name, if one was set. |
| `status` | string | Aggregate status derived from item states: `processing`, `completed`, or `failed`. |
| `total_items` | integer | Number of items submitted in this batch. |
| `counts_by_status` | object | Item counts keyed by item status. |
| `created_at` | integer | Batch creation time as a Unix timestamp. |
| `items` | array | One page of batch items, ordered by `item_index`. |
| `has_more` | boolean | Whether more items exist beyond this page. |
| `next_token` | string | Cursor for the next page; `null` on the last page. |
Each entry in `items`:
| Field | Type | Description |
| ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `item_index` | integer | Zero-based position of this item in the submitted `videos` array. |
| `status` | string | Item status: `queued`, `processing`, `completed`, or `failed`. |
| `video_id` | string | Video id, present once the underlying video has been created. Use it with [`GET /v3/videos/{video_id}`](/reference/get-video) for the full record and download URL. |
| `error` | object | Failure details, present when `status` is `failed`. |
## Tracking completion
You have two ways to know when work finishes, and they pair well:
* **Webhook (push).** Set `callback_url` on the batch to be notified once, when the last item reaches a terminal state. See [Webhooks](/docs/webhooks) to register an endpoint and verify signatures.
* **Polling (pull).** Call `GET /v3/videos/batches/{batch_id}` and read `counts_by_status`. Individual items surface a `video_id` the moment their video is created, so you can start downloading finished videos while the rest of the batch is still rendering.
For large batches, page through `items` with `limit` and `next_token` until `has_more` is `false`, then fetch each finished video with [`GET /v3/videos/{video_id}`](/reference/get-video).
# Changelog
Source: https://developers.heygen.com/changelog
Track every HeyGen API change, new endpoint, deprecation, and version bump in the API changelog. Subscribe via RSS or webhook to get notified on the next.
**Compose avatar clips, images, and video footage into one video with a single API call**
[`POST /v3/videos`](/reference/create-video) accepts a new creation mode, `"type": "studio"` — send an ordered list of whole-frame scenes and get back one rendered MP4. HeyGen owns the layout: each scene is center-cropped to a global output canvas, scene durations are derived server-side, and a single `video_id` tracks the all-or-nothing render.
* New guide: [HeyGen Studio](/studio-videos) under Video Composition.
* **Three scene types**, mixable in one request (1–50 scenes): `avatar_video` (an avatar or lip-synced image speaking a script or audio track, with engine selection, motion prompts, and solid-color backgrounds), `image` (a still held for a set `duration` or narrated), and `video` (an existing clip, with `playback` volume/mute control).
* **Global output settings** set once per request: `aspect_ratio`, `resolution` (up to `4k`), captions (sidecar `subtitle_url` plus optional burn-in), and webhooks.
* **Batches:** studio payloads work as items in [`POST /v3/videos/batches`](/reference/create-video-batch), alongside the other creation modes.
**Every high-volume workflow now has a native batch API**
Batch creation, introduced for [videos](/batch-videos), now covers video translations, lipsyncs, and asset uploads. Each accepts up to 100 payloads per call, returns a single `batch_id`, processes items independently (one bad item does not fail the rest), and supports `Idempotency-Key` retries.
* New guides under Batches: [Video Translations](/batch-video-translations), [Lipsyncs](/batch-lipsyncs), and [Assets](/batch-assets).
* **Translations:** [`POST /v3/video-translations/batches`](/reference/create-video-translation-batch) — a payload targeting multiple `output_languages` expands to one item per language. Replaces the CSV-script workflow previously documented as Bulk Video Translation.
* **Lipsyncs:** [`POST /v3/lipsyncs/batches`](/reference/create-lipsync-batch) — payloads identical in shape to `POST /v3/lipsyncs`.
* **Assets:** [`POST /v3/assets/direct-uploads/batches`](/reference/create-asset-upload-batch) presigns up to 100 direct-to-S3 slots in one call; finalize with [`POST /v3/assets/complete/batches`](/reference/complete-asset-upload-batch).
* **Bulk status checks:** [`GET /v3/video-translations/statuses`](/reference/bulk-video-translation-statuses), [`GET /v3/lipsyncs/statuses`](/reference/bulk-lipsync-statuses), [`GET /v3/assets/statuses`](/reference/bulk-asset-statuses), and [`GET /v3/videos/statuses`](/reference/bulk-video-statuses) — up to 100 ids per request, by id or by batch.
**Discover Studio templates and generate videos from them over the API**
You can now drive [HeyGen Studio](https://app.heygen.com/avatar/studio) templates programmatically — list them, read each one's variable schema, and render a video by filling the variables.
* New guide: [HeyGen Studio Template](/templates) under Video Composition.
* **List & inspect:** [`GET /v3/templates`](/reference/list-templates) and [`GET /v3/templates/{template_id}`](/reference/get-template) return each template's variable schema (text, image, video, audio, character, voice) and scenes.
* **Generate:** [`POST /v3/templates/{template_id}`](/reference/generate-video-from-template) renders a video from your variable values, with options for `scene_ids`, `dimension`, `fps`, captions/subtitles, sharing, and webhooks.
**Submit up to 100 videos in a single request**
Queue many videos at once instead of calling [`POST /v3/videos`](/reference/create-video) per video.
* New guide: [Videos](/batch-videos) under Batches.
* **Create:** [`POST /v3/videos/batches`](/reference/create-video-batch) accepts up to 100 payloads — each identical in shape to `POST /v3/videos` (`avatar`, `image`, or `cinematic_avatar`) — and returns a single `batch_id`.
* **Track:** [`GET /v3/videos/batches/{batch_id}`](/reference/get-video-batch) returns aggregate status and per-item `video_id`s; a `callback_url` fires once when the whole batch finishes.
**Submit a pre-recorded consent video for a digital twin**
[`POST /v3/avatars/{group_id}/consent`](/reference/create-avatar-consent) now accepts an optional `consent_video` field (as a `url`, `asset_id`, or `base64` asset). When supplied, the video is submitted directly for review — the avatar subject skips the hosted webcam page, and the response contains the avatar group without a consent `url`.
* Available to Enterprise API accounts with your account whitelisted — please [reach out to sales](https://www.heygen.com/contact-us/sales) or your account team to request access. The same applies to skipping the consent flow entirely.
* The webcam flow is unchanged and remains the default — see [Avatar Consent](/docs/avatar-consent) for the full breakdown of consent levels.
**Compose your avatar videos into finished, produced videos**
You can now combine a HeyGen avatar video with background music, sound effects, and Hyperframes graphics into a single composed render — Hyperframes acts as the compositor that lays an avatar clip into a designed scene, scores it, and renders it to one MP4.
* New guide: [Hyperframes + HeyGen](/hyperframes-heygen) — an end-to-end pipeline wiring [`POST /v3/videos`](/reference/create-video), [`GET /v3/audio/sounds`](/background-music) (music and sound effects), and [`POST /v3/hyperframes/renders`](/hyperframes) together.
* New page: [Introduction to Hyperframes](/hyperframes-overview) — what the framework is, its core concepts, and the ways to render.
* Pass the avatar `video_url` and audio `audio_url`s into the render as `variables`, so one composition bundle produces many finished videos.
**Support for the Avatar III engine**
We have expanded the capabilities of `POST /v3/videos` to include support for the Avatar III engine.
* **`POST /v3/videos`:**
* Added support for the `avatar_iii` engine configuration.
* Updated the `engine` property to include `AvatarIIIEngineConfig` in the `oneOf` schema definition, allowing for more flexible engine selection during video creation.
**Expanded API functionality and improved asset management**
We have introduced several new endpoints and configuration options to enhance your workflow:
* **Realtime Avatar Streaming**: Added comprehensive support for streaming interactions via `POST /v3/avatar-realtime`, including endpoints to manage stream state, send text, and fetch word timing (`GET /v3/avatar-realtime/{stream_id}/words`).
* **Background Removals**: New dedicated endpoints to programmatically manage background removal jobs: `GET/POST /v3/background-removals` and `DELETE/GET /v3/background-removals/{job_id}`.
* **Voice Management**: Added `DELETE /v3/voices/{voice_id}` for better voice library control.
* **Avatar & Video Configuration**:
* `POST /v3/avatars`: Added `avatar_id` as an optional property for more granular prompt-based avatar creation.
* `POST /v3/videos`: Added `reference_look_id` support within the `AvatarVEngineConfig` to refine video generation.
**Clarified Stock Voice configuration for Video Translation**
Updated `POST /v2/video_translate` and `POST /v3/video-translations` to better reflect Enterprise capabilities.
* The `stock_voice_config` documentation has been updated to clarify that this feature uses preset HeyGen voices instead of cloning the original speaker's voice.
* `preferred_stock_voice_ids` is now explicitly defined as an optional pinning mechanism for stock voices.
* Note: Stock voice usage is an Enterprise feature gated by account permissions; please contact your HeyGen account team for access.
**4K output for Avatar IV & V is temporarily deprecated**
4K rendering for Avatar IV & V video generation is temporarily unavailable. The 4K rate has been removed from the [pricing page](/pricing) for these tiers — 720p / 1080p output and pricing are unaffected.
* This is a temporary pause; 4K support is expected to return in a future update.
**`avatar_id` is now the visual reference for prompt avatars**
[`POST /v3/avatars`](/reference/create-avatar) with `type: "prompt"` now accepts an optional `avatar_id` — an existing look used as the visual reference for the generation. `avatar_group_id` no longer drives the reference image.
* Added `avatar_id` (optional): the look whose image conditions the generation. The new look is saved to the referenced avatar's group; if `avatar_group_id` is also provided, the avatar must belong to that group and the result is saved there. Returns 404 `AVATAR_NOT_FOUND` if the avatar doesn't exist, 400 `INVALID_PARAMETER` if it has no usable image or doesn't belong to the given group.
* Changed `avatar_group_id`: now only selects the group the generated avatar is saved to. It no longer conditions the generation on one of the group's looks.
* Changed `reference_images`: no longer requires `avatar_group_id` — it can be used on its own.
* **Migration Note:** if you passed `avatar_group_id` to keep a character's identity consistent across prompt-generated looks, pass the base look's ID as `avatar_id` instead. Requests with only `avatar_group_id` still succeed, but generate purely from the prompt.
* `type: "digital_twin"` and `type: "photo"` are unchanged.
See [Create Avatar](/docs/create-avatar#prompt-to-avatar) for the full behavior matrix.
**Expanded Audio Search with Sound Effects**
The audio search functionality has been updated to include support for sound effects, allowing for more versatile audio retrieval beyond just background music.
* Updated `GET /v3/audio/sounds` to include `sound_effects` as a searchable type.
* The `type` query parameter now supports `sound_effects` in addition to the default `music`.
* API responses for `GET /v3/audio/sounds` will now include items with the `sound_effects` type.
* **Migration Note:** Clients that strictly validate the `type` field in the response against an enum list may need to update their schema definitions to include the new `sound_effects` value to prevent parsing errors.
**Expanded usage insights for user accounts**
We have updated the user profile endpoints to provide better visibility into your account's credit consumption. You can now track your allocation and remaining balance directly through the API.
* Added `included_credits` and `remaining_credits` fields to the response objects for:
* `GET /v1/user/me`
* `GET /v3/users/me`
**Updated motion prompt capabilities for video generation**
The `motion_prompt` parameter for video generation now supports additional configurations for hand gestures and broader engine compatibility.
* Updated `motion_prompt` in `POST /v3/videos`:
* Now supports natural-language control for both body motion and hand gestures.
* Expanded support for photo avatars across both engines.
* Added support for video avatars specifically when using `engine.type: 'avatar_v'`.
**Generate cinematic avatar video from a single prompt**
[`POST /v3/videos`](/reference/create-video) now supports **Cinematic Avatar** — a prompt-driven video type that composes scene, motion, and framing from a natural-language prompt plus one to three avatar looks (with optional reference media). No script or voice required.
* Added the `CreateVideoFromCinematicAvatar` schema to `POST /v3/videos` via an additive request discriminator (`cinematic_avatar`). Existing `CreateVideoFromAvatar` and `CreateVideoFromImage` requests are unaffected — this is a **non-breaking** change.
* Billed at a flat **\$7.00 per video** (not by duration). Choose any length from **4 to 15 seconds** via the `duration` parameter.
* Supports **720p** and **1080p** output.
No migration required. See the [Cinematic Avatar guide](/avatar-shots) for the full parameter list.
**Expansion of Asset Management and Audio capabilities**
We have introduced new endpoints to streamline asset handling and provide better access to audio resources.
* `POST /v3/assets/direct-uploads`: Initialize a direct upload process for your custom assets.
* `POST /v3/assets/{asset_id}/complete`: Finalize the upload process for a specific asset.
* `GET /v3/audio/sounds`: Retrieve a list of available audio sounds for your projects.
**HyperFrames render resolution is now strictly typed**
We have updated the `POST /v3/hyperframes/renders` endpoint to enforce strict resolution settings.
* **Breaking Change:** The `resolution` property in `POST /v3/hyperframes/renders` is now strictly typed as a string enum (`1080p`, `4k`). Null values are no longer accepted.
* The `resolution` property now defaults to `1080p`.
* Added optional `aspect_ratio` support for `POST /v3/hyperframes/renders` and response schemas for `GET /v3/hyperframes/renders` and `GET /v3/hyperframes/renders/{render_id}`.
**Migration Steps:**
Ensure that any hardcoded `resolution` values sent to `POST /v3/hyperframes/renders` match the new `1080p` or `4k` enum strings. If you were previously sending `null` to indicate a default, you can now safely omit the property.
**Breaking change: `resolution` is now a tier; `aspect_ratio` is a separate field**
`POST /v3/hyperframes/renders` previously took a single `resolution` value that conflated aspect ratio and resolution tier into one of six presets (`landscape`, `landscape-4k`, `portrait`, `portrait-4k`, `square`, `square-4k`). The same flat shape lived on `GET /v3/hyperframes/renders/{render_id}`.
Effective immediately, both endpoints decompose that single field into two:
* `resolution` ∈ `1080p` | `4k` — output resolution tier. Defaults to `1080p`. 4K renders are billed at 1.5× the 1080p rate.
* `aspect_ratio` ∈ `16:9` | `9:16` | `1:1` — output aspect ratio. Defaults to `16:9` (landscape). `9:16` is portrait; `1:1` is square.
The new shape matches the existing `/v3/videos` endpoint's `resolution` + `aspect_ratio` fields. Aspect ratio does not affect pricing.
**Migration table**
Map each legacy preset to the new pair:
| OLD `resolution` | NEW |
| ---------------- | --------------------------------------------------- |
| `landscape` | `{ "resolution": "1080p", "aspect_ratio": "16:9" }` |
| `landscape-4k` | `{ "resolution": "4k", "aspect_ratio": "16:9" }` |
| `portrait` | `{ "resolution": "1080p", "aspect_ratio": "9:16" }` |
| `portrait-4k` | `{ "resolution": "4k", "aspect_ratio": "9:16" }` |
| `square` | `{ "resolution": "1080p", "aspect_ratio": "1:1" }` |
| `square-4k` | `{ "resolution": "4k", "aspect_ratio": "1:1" }` |
Requests using the legacy preset strings now return a 422 validation error. Pricing for 4K renders is unchanged — only the field shape moves.
**Not yet supported in this enum surface**: `720p`, `4:5`, `5:4`, and `auto`. These will follow in a separate update once the render pipeline supports the additional values.
**CLI**
`npx hyperframes cloud render` is updated in lockstep: the `--resolution` flag now accepts `1080p` | `4k`, and a new `--aspect-ratio` flag accepts `16:9` | `9:16` | `1:1`. Legacy values are rejected at the CLI layer with the same migration mapping.
**Introducing Brand Glossaries for Video Translation**
We have introduced Brand Glossaries to help maintain consistency in your video translations. You can now define custom term mappings to ensure specific terminology is translated accurately according to your brand guidelines.
* Added `GET /v3/brand-glossaries` to list and discover your available brand glossaries.
* Added `brand_glossary_id` as an optional request parameter to `POST /v2/video_translate` and `POST /v3/video-translations/proofreads`.
* The `brand_voice_id` parameter in these endpoints now acts as a legacy alias for `brand_glossary_id`, ensuring backward compatibility for your existing integrations.
**Refined API documentation and aspect ratio defaults**
We have updated our API documentation across various endpoints to provide clearer guidance on usage and functionality. Additionally, we have updated the default behavior for aspect ratio selection.
* Updated `POST /v2/videos` and `POST /v3/videos` to clarify that the `aspect_ratio` defaults to `'16:9'` if not specified.
* Documentation descriptions for endpoints across Avatars, Lipsync, Video Agents, and Webhooks have been streamlined for improved clarity.
* Updated API tags for `GET /v3/brand-kits`, moving them under the `Brand` category.
**Generate videos in square, portrait, landscape, and source-matched ratios**
`POST /v2/videos` and [`POST /v3/videos`](/reference/create-video) now accept four additional values for `aspect_ratio`:
* `1:1` — square, great for feed posts
* `4:5` — portrait, optimized for Instagram and LinkedIn feeds
* `5:4` — landscape variant for feed placements
* `auto` — detects the source's dimensions (avatar footage or uploaded image) and preserves the original ratio, falling back to `16:9` when the source can't be read
Combine these with the existing `16:9` and `9:16` options to target multiple placements from a single integration.
**Retrieve metadata for an individual asset**
[`GET /v3/assets/{asset_id}`](/reference/get-asset) returns metadata for an asset in your workspace — including owner, upload timestamp, file type, and a publicly accessible URL. Use it to look up uploads on demand instead of paginating through the full asset list.
**Record the exact consent language presented to the avatar subject**
[`POST /v3/avatars/{group_id}/consent`](/reference/create-avatar-consent) now accepts an optional `consent_text` field. Pass the wording the subject agreed to so you have a clear audit trail alongside the recorded consent. The field is optional — existing integrations continue to work unchanged.
**Clearer signal when a user-supplied URL can't be fetched**
Requests that include a URL for video, image, audio, or any other resource can now return a dedicated `download_failed` error (HTTP `400`) when the URL can't be downloaded. The `message` field tells you which URL failed and why.
Common causes:
* URL isn't publicly accessible (auth required, private video, restricted sharing).
* URL is malformed or points to a page rather than a direct file.
* Remote server refused the connection or returned an error.
* Google Drive links must be shared with **Anyone with the link**.
* YouTube/Vimeo videos must be **public** — unlisted or private videos aren't supported.
The `resource_limit_exceeded` error message also now covers instant-avatar redo attempts and verified avatar group slots, with guidance to wait for limits to reset where applicable.
See the [error codes reference](/docs/error-codes) for the full list.
**Avatar V is billed at the same rates as Avatar IV**
Per-second video generation rates for Avatar V now match Avatar IV across both [self-serve](/docs/pricing) and [enterprise](/docs/enterprise-pricing) plans. No action required — pricing tables have been updated to reflect the combined Avatar IV & V rates.
**Added Idempotency support and expanded API capabilities**
We have introduced support for the `Idempotency-Key` header across key endpoints to ensure safe retries for POST requests. Additionally, several endpoints now return a `409 Conflict` status to handle concurrent requests or state conflicts.
* **Idempotency-Key Support:** Added to `POST /v3/assets`, `POST /v3/avatars`, `POST /v3/avatars/{group_id}/consent`, `POST /v3/lipsyncs`, `POST /v3/video-translations`, `POST /v3/video-translations/proofreads`, `POST /v3/video-translations/proofreads/{proofread_id}/generate`, `POST /v3/videos`, `POST /v3/webhooks/endpoints`, and `POST /v3/webhooks/endpoints/{endpoint_id}/rotate-secret`.
* **New Endpoints:** Added `DELETE /v3/assets/{asset_id}`, `DELETE /v3/avatars/looks/{look_id}`, and `DELETE /v3/avatars/{group_id}`.
* **Default Values:** The `aspect_ratio` parameter now defaults to `16:9` in `POST /v2/videos` and `POST /v3/videos`.
**Strict schema validation for frame rate modes**
The `fps_mode` property in `POST /v3/video-translations` has been updated to use a strict enum, ensuring more predictable behavior for video output.
* **Breaking Change:** The `fps_mode` property is now restricted to an enum. Supported values are now explicitly defined as `vfr`, `cfr`, and `passthrough`.
* **Migration:** Ensure your integration passes one of these three strings. Previously provided custom values may now be rejected.
**Webhook payload field names now match what's actually delivered**
The documented payload for the `avatar_video.success` webhook event has been corrected. If you wired up handlers from the previous documentation, double-check the field names you're reading — the live payload uses these keys:
* `video_id`, `url`, `gif_download_url`, `video_page_url`, `video_share_page_url`, `folder_id`, `callback_id`
No change to the webhook delivery itself — this aligns the [webhook events reference](/docs/webhook-events) with the payload the API has been sending.
**Video engine updates and new brand kit integration**
We have updated the video generation workflow and introduced support for Brand Kits across Video Agents. Note that the `engine` parameter structure for `POST /v3/videos` has changed, which is a breaking change for existing integrations.
* **Breaking Change:** The `engine` property in `POST /v3/videos` now requires an object structure (e.g., `{"type": "avatar_v"}`) instead of a string. `ApiAvatarEngine` has been removed.
* **Video Scaling:** Added a new optional `fit` parameter to `POST /v3/videos` for both `CreateVideoFromAvatar` and `CreateVideoFromImage` request types.
* **Brand Kits:** Added `brand_kit_id` as an optional parameter to `POST /v3/video-agents` and `POST /v3/video-agents/{session_id}`.
* **New Endpoint:** Added `GET /v3/brand-kits` to retrieve available brand kit resources.
* **Parameter Constraints:** Note that `expressiveness` and `motion_prompt` in `POST /v3/videos` are now strictly for Avatar IV and are not supported when `engine.type` is set to `avatar_v`.
**New configuration options for Video Translation and Video Generation**
We have introduced support for Stock TTS in video translations and added explicit engine selection for avatar-based video generation.
* **Video Translation:** Added `stock_voice_config` to `POST /v2/video_translate` and `POST /v3/video-translations`. This allows users to opt into Stock TTS instead of using Voice Cloning.
* **Video Generation:** Added an optional `engine` field to `POST /v3/videos` (for `CreateVideoFromAvatar` requests). You can now explicitly select between Avatar IV and Avatar V engines. If omitted, the system defaults to Avatar IV.
**Updated schema definitions for Avatar engine support**
Metadata for supported API engines has been updated across the avatar look endpoints to ensure consistency when retrieving avatar configurations.
* Updated `supported_api_engines` fields in:
* `POST /v3/avatars`
* `GET /v3/avatars/looks`
* `GET /v3/avatars/looks/{look_id}`
* `PATCH /v3/avatars/looks/{look_id}`
**Fine-tune watermark size, transparency, and position**
The `watermark` object on `POST /v3/videos` now accepts three new optional fields for finer control over how your watermark renders:
* `scale` (number, `0`–`2`, default `1.0`) — adjust the watermark size relative to its native resolution.
* `opacity` (number, `0`–`1`, default `1.0`) — control transparency.
* `placement` — choose an anchor corner (`top_left`, `top_right`, `bottom_left`, `bottom_right`) and apply fractional `offset_x` / `offset_y` values for precise positioning.
All fields are optional and backward-compatible. Omitting them preserves the existing bottom-right default behavior.
**Programmatic voice cloning is now available**
You can now create and manage voice clones directly from the API, no dashboard step required.
* `POST /v3/voices/clone` initiates a clone from a reference audio sample.
* `GET /v3/voices/{voice_id}` returns clone status and details so you can poll until processing completes.
* Use the resulting `voice_id` anywhere a voice is accepted (`POST /v3/videos`, `POST /v3/voices/speech`, etc.).
See the [voices overview](/docs/voices/overview) for details.
**Render captions directly into your videos**
`POST /v2/videos` and `POST /v3/videos` now accept a `caption.style` field. Set it to burn captions into the rendered video instead of (or in addition to) consuming the sidecar subtitle file at `subtitle_url`.
Useful for social platforms where viewers watch with sound off and you want captions baked into the asset.
**Apply your own watermark to generated videos**
`POST /v3/videos` accepts a new optional `watermark` property on both `CreateVideoFromAvatar` and `CreateVideoFromImage` requests. Pass a PNG or JPEG image to overlay it onto the rendered output — handy for branding, attribution, or moderation marks.
Available as a premium option for select Enterprise customers — [contact support](https://help.heygen.com) to request access.
**`watermark` now uses the `WatermarkInput` schema**
The inline schema previously used for the `watermark` field on video generation requests has been replaced with the dedicated `WatermarkInput` type. The shape of the property has changed — update existing payloads to match the new schema before upgrading.
**Empty `title` queries are no longer accepted**
`GET /v2/videos` and `GET /v3/videos` now require at least one character for the `title` query parameter (`minLength` increased from 0 to 1). Omit the parameter entirely if you don't want to filter by title — sending an empty string will return a validation error.
**Better error feedback when the voice clone limit is reached**
`POST /v3/voices/clone` now returns the `resource_limit_reached` error code (HTTP 400) when your account has hit its voice clone quota, instead of a generic validation error. The response message tells you to delete unused clones or contact support to raise the limit. See the [error codes reference](/docs/error-codes#resource-limit-reached) for handling guidance.
**Expanded tool coverage for the HeyGen MCP server**
The HeyGen Remote MCP server now includes tools for managing avatars, videos, lipsync, and video translation — making more of the API accessible to AI agents like Claude, Cursor, Gemini CLI, and Manus.
* Added avatar management tools: `create_digital_twin`, `create_photo_avatar`, `create_prompt_avatar`, `create_avatar_consent`, `list_avatar_looks`, `get_avatar_look`, `update_avatar_look`, and more.
* Added full video CRUD: `create_video_from_avatar`, `create_video_from_image`, `list_videos`, `get_video`, `delete_video`.
* Added lipsync and video translation management tools.
* Added `design_voice` for finding voices from a natural-language description.
* See the [MCP overview](/mcp/overview) for the full tool list.
**CLI command surface synced with v3 API**
The HeyGen CLI now covers all v3 endpoints, including Video Agent, Lipsync, Video Translation (with Proofreads), Webhooks, and Assets.
* Added `--wait` flag for blocking until async operations complete, with configurable `--timeout`.
* Added `--request-schema` and `--response-schema` flags to inspect API schemas without authentication.
* Added `--force` flag for non-interactive destructive operations in CI.
* See the [CLI commands](/commands) and [features](/features) pages for usage details.
**v1 and v2 endpoints sunset on October 31, 2026**
A formal deprecation timeline is now in place for the v1 and v2 API. Both versions remain fully operational through October 31, 2026, after which they will be retired.
* Studio API (multi-scene) and Template API will continue to be supported on v2 until a v3 equivalent is available.
* See the [endpoint version comparison](/endpoint-version-comparison) for a full migration checklist and feature comparison.
**More granular error responses across the API**
New error codes provide clearer feedback when requests fail, making it easier to handle edge cases in your integration.
* `ai_vendor_access_restricted` — workspace AI vendor policy blocks the request.
* `unlimited_mode_disabled` — avatar doesn't support unlimited mode.
* `voice_unavailable` — cloned voice failed processing or expired.
* `ephemeral_upload_disabled` — eager upload temporarily disabled for the account.
* `gateway_timeout` — external resource could not be fetched in time.
* See the full [error codes reference](/docs/error-codes) for details and troubleshooting.
**New requirement for Starfish engine compatibility**
Text-to-speech generation endpoints now require the use of voices that support the Starfish engine.
* Updated `POST /v1/audio/text_to_speech` and `POST /v3/voices/speech` documentation.
* Developers should filter for compatible voices by passing `engine=starfish` when calling the voice listing endpoints.
**Standardized Asset ID descriptions**
Documentation across multiple endpoints has been clarified to consistently refer to asset IDs originating from the HeyGen asset upload endpoint. No functional changes were made to the API behavior.
* Applies to request bodies for:
* `POST /v3/avatars`
* `POST /v3/lipsyncs`
* `POST /v3/video-agents` and `POST /v3/video-agents/{session_id}`
* `POST /v3/video-translations`, `POST /v3/video-translations/proofreads`, and `PUT /v3/video-translations/proofreads/{proofread_id}/srt`
* `POST /v3/videos`
**New endpoint for listing Video Agents**
We have introduced a new endpoint to allow developers to retrieve a list of all existing video agents associated with their account.
* Added `GET /v3/video-agents`: Use this endpoint to fetch your video agents, enabling easier integration and management of your agent instances.
**Updated error codes for Avatar endpoints**
We have updated the error response codes for avatar-related endpoints to provide more specific feedback when a group cannot be located.
* `GET /v3/avatars/{group_id}`: The 404 response error code has been updated from `not_found` to `avatar_group_not_found`.
* `POST /v3/avatars/{group_id}/consent`: The 404 response error code has been updated from `not_found` to `avatar_group_not_found`.
**Advanced voice customization and output formatting**
We have introduced new parameters to provide finer control over generated audio and video output quality.
* Added `volume` and `engine_settings` to `voice_settings` for `POST /v2/videos` and `POST /v3/videos`. These settings apply when using text-to-speech (`script` + `voice_id`).
* Added `output_format` to `POST /v3/videos` for both `CreateVideoFromAvatar` and `CreateVideoFromImage` request schemas.
**Improved error handling for webhook management**
We have updated our webhook endpoints to provide more consistent and descriptive error responses.
* Added a `409` conflict response to `POST /v3/webhooks/endpoints` to better handle registration errors.
* Standardized error codes for `404` responses across `DELETE`, `PATCH`, and `POST /v3/webhooks/endpoints/{endpoint_id}/rotate-secret` by updating the error code to `webhook_not_found`.
**Support for custom output formats in video generation**
You can now specify a preferred output format when creating videos. The API response now includes the `output_format` field to confirm the format used for your generated video.
* Added optional `output_format` request property to `POST /v2/videos`.
* Added `output_format` to the response body of `POST /v2/videos` (200 OK).
* Added `output_format` to the response body of `POST /v3/videos` (200 OK).
**Comprehensive API Documentation Updates**
We have updated the endpoint descriptions across our entire V3 API to provide clearer guidance, better parameter context, and more precise functionality definitions. While the underlying API logic remains consistent, the improved documentation clarifies how to integrate with our latest engine versions and features.
* **Video Generation**: `POST /v3/videos` now officially documents support for the Avatar IV engine and upcoming Avatar V.
* **Avatars**: Clarified workflows for `POST /v3/avatars` (asynchronous training) and added guidance on the mandatory consent flow for private avatars via `POST /v3/avatars/{group_id}/consent`.
* **Video Agent**: Streamlined descriptions for session-based interactions, clearly distinguishing between `generate` (one-shot) and `chat` (multi-turn) modes.
* **Lipsync & Translation**: Updated documentation for `POST /v3/lipsyncs` and `POST /v3/video-translations` to emphasize the `speed` vs. `precision` mode selection for output quality.
* **Webhooks**: Clarified that `PATCH /v3/webhooks/endpoints/{endpoint_id}` performs a full replacement of the event types array.
* **Assets**: Updated supported MIME types for `POST /v3/assets` to include refined file type lists.
**Added caption\_url to Lipsync and Video Translation responses**
You can now retrieve the `caption_url` for generated lipsyncs and video translations, providing direct access to the generated caption files.
* `GET /v3/lipsyncs` and `GET /v3/lipsyncs/{lipsync_id}`
* `PATCH /v3/lipsyncs/{lipsync_id}`
* `GET /v3/video-translations` and `GET /v3/video-translations/{video_translation_id}`
* `PATCH /v3/video-translations/{video_translation_id}`
**Updated documentation for avatar consent**
Clarified the implementation details for the avatar consent flow to ensure a smoother user experience.
* `POST /v3/avatars/{group_id}/consent`: Updated documentation to clarify that the returned URL must be presented directly to the user in a browser to complete the consent process.
**Support for avatar-default voices**
You can now generate videos using an avatar's default voice without explicitly specifying a `voice_id`. When creating a video, if `voice_id` is omitted while `avatar_id` is present, the system will automatically use the avatar's default voice.
* Updated `POST /v3/videos`: The `voice_id` requirement has been relaxed for both `CreateVideoFromAvatar` and `CreateVideoFromImage` schemas, allowing the system to fall back to the avatar's default voice.
**Enhanced capabilities for Video Agent interactions**
We have updated the description and scope of the `POST /v3/video-agents/{session_id}` endpoint to better reflect its versatility in managing agent-led workflows.
* Updated the endpoint description to clarify support for answering agent-posed questions and requesting specific edits or revisions.
* The request body schema has been updated to better align with these extended conversational and editing capabilities.
**New 'thinking' status for Video Agents**
We have introduced a new `thinking` state to the Video Agent response object to provide better visibility into agent processing workflows.
* Updated `POST /v3/video-agents`
* The `status` field in the response now includes the `thinking` enum value.
* Integration note: Ensure your client-side parsers are prepared to handle this new status value in the response body.
**Updated Video Agent session retrieval and new video listing**
We have refactored how resource data is handled in Video Agent sessions to improve performance. Additionally, we have introduced a new endpoint to fetch videos associated with a session.
* **Breaking Change:** The `resources` property has been removed from the response body of `GET /v3/video-agents/{session_id}`.
* **Migration:** To access resource details previously found in the session object, please use the new `GET /v3/video-agents/{session_id}/resources/{resource_id}` endpoint.
* **New Endpoint:** Added `GET /v3/video-agents/{session_id}/videos` to retrieve a list of videos generated within a specific agent session.
**Breaking change: Restructured Video Agent session management**
We have updated the Video Agent API to simplify session handling. Please note that the previous `/v3/video-agents/sessions` path structure is deprecated and removed.
* **Removed endpoints:** `POST /v3/video-agents/sessions`, `GET /v3/video-agents/sessions/{session_id}`, `POST /v3/video-agents/sessions/{session_id}/messages`, `GET /v3/video-agents/sessions/{session_id}/resources`, and `POST /v3/video-agents/sessions/{session_id}/stop` have been removed.
* **Migration:** Replace existing calls with the new flattened endpoints under `/v3/video-agents/{session_id}`.
* **New endpoints added:**
* `GET /v3/video-agents/{session_id}`
* `POST /v3/video-agents/{session_id}`
* `GET /v3/video-agents/{session_id}/resources/{resource_id}`
* `POST /v3/video-agents/{session_id}/stop`
**New configuration options for Video Agent sessions**
The `POST /v3/video-agents` endpoint now supports advanced control over session flow.
* Added `mode`: Supports `generate` (default, one-shot) and `chat` (multi-turn, allows revisions and follow-ups).
* Added `auto_proceed`: Enables automated progression through storyboards.
* Added `skip_agentic_stop`: Provides granular control over agent stopping behavior.
**API Operation ID update**
The operation ID for `GET /v3/users/me` has been updated from `getUserMeV3` to `getCurrentUserV3` to maintain consistency across our SDKs.
**Added support for custom voice creation**
We have introduced a new endpoint to allow developers to programmatically create and add new voices to their HeyGen account.
* Added `POST /v3/voices` to the API.
**Refactored POST /v3/videos request body**
We have updated the `POST /v3/videos` endpoint to use a discriminated union for improved type safety and flexibility. This change replaces the legacy flat request structure with dedicated schemas for creating videos from avatars versus images.
* **Breaking Change:** The request body structure has been completely overhauled. You must now specify a type discriminator: use `CreateVideoFromAvatar` for digital twins/avatars or `CreateVideoFromImage` for custom image animation.
* **Migration:** All properties previously passed at the top level of the request (e.g., `avatar_id`, `image_url`, `voice_id`, `script`) must now be nested within the appropriate schema based on the video source.
* The operation ID for this endpoint has been updated from `createAvatarVideoV3` to `createVideo`.
**Enhanced error messaging across all endpoints**
We have updated the error response schemas and examples across the entire API suite. Developers can now expect more consistent and detailed error responses for common issues, including:
* Improved `400 Bad Request` messages with clearer parameter validation feedback.
* Standardized `401 Unauthorized` responses when API keys are missing or expired.
* Consistent `429 Rate Limited` responses that align with standard retry headers.
* Better descriptive error messages for resource-specific failures (e.g., `404 Not Found` for specific IDs).
These updates ensure that your integrations can better handle exceptions and debugging.
**HeyGen for Developers — New v3 API Surface**
We've launched a new set of v3 endpoints across the HeyGen API, bringing a consistent interface, cursor-based pagination, and a unified asset input model to all major resources.
What's new:
* All v3 endpoints share a standard error format, cursor-based pagination (`has_more` / `next_token`), and consistent authentication via `X-Api-Key` or OAuth bearer token.
* Asset inputs now use a type-discriminated union — pass files as `{ "type": "url", "url": "..." }`, `{ "type": "asset_id", "asset_id": "..." }`, or `{ "type": "base64", "media_type": "...", "data": "..." }` across all endpoints.
* New and updated endpoints include: Video Agent (`POST /v3/video-agents`), Videos (`POST /v3/videos`), Voices (`GET /v3/voices`, `POST /v3/voices/speech`), Video Translations (`POST /v3/video-translations`), Overdub (`POST /v3/overdubs`), Avatars (`POST /v3/avatars`), Assets (`POST /v3/assets`), Webhooks (`/v3/webhooks/*`), and User (`GET /v3/users/me`).
The v1/v2 endpoints continue to work, but we recommend migrating to v3 for all new integrations.
# Cinematic Avatar
Source: https://developers.heygen.com/cinematic-avatar
Generate cinematic avatar video from a single prompt with the HeyGen API. Combine up to three avatar looks with reference videos and images — no script or voice required.
Cinematic Avatar is a prompt-driven variant of [`POST /v3/videos`](/reference/create-video). Instead of a script and a voice, you describe the shot you want in natural language and hand HeyGen up to three avatar looks (plus optional reference media). The Seedance pipeline composes the scene, motion, and framing for you.
## Prerequisites
One to three avatar look IDs. Use `GET /v3/avatars/looks` to browse your looks and copy the `id` field for each one you want in the shot.
A prompt describing the scene, action, and framing. This replaces the `script` + `voice_id` you'd use for a [Digital Twin video](/generate-avatar-video).
## Step 1 — Write your prompt
The `prompt` (1–10,000 characters) is the creative brief for the shot. Describe what the avatar is doing, the setting, the camera, and the mood — e.g. *"A founder walks through a sunlit startup office, gesturing toward a whiteboard, shot handheld in a documentary style."* See [Writing Effective Video Prompts](/writing-effective-video-prompts) for guidance.
## Step 2 — Pick your avatar looks
Pass `avatar_id` as an **array** of 1–3 look IDs. Multiple looks let HeyGen feature more than one avatar in the same shot:
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/avatars/looks?ownership=private" \
-H "x-api-key: YOUR_API_KEY"
```
Copy the `id` of each look you want into the array.
## Step 3 — Create the video
Send a `POST` to `/v3/videos` with `type: "cinematic_avatar"`:
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/videos" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "cinematic_avatar",
"prompt": "A founder walks through a sunlit startup office, gesturing toward a whiteboard, shot handheld in a documentary style.",
"avatar_id": ["YOUR_LOOK_ID"],
"aspect_ratio": "16:9",
"resolution": "1080p",
"duration": 10,
"title": "Founder office walkthrough"
}'
```
The response is the same shape as any other `/v3/videos` job:
```json theme={null}
{
"data": {
"video_id": "abc123",
"status": "pending",
"output_format": "mp4"
}
}
```
### Adding reference media
Use `references` to steer style, motion, or composition with your own videos and images. Each reference is a `url`, `asset_id`, or `base64` asset input:
```json theme={null}
{
"type": "cinematic_avatar",
"prompt": "Match the lighting and camera movement of the reference clip.",
"avatar_id": ["YOUR_LOOK_ID"],
"references": [
{ "type": "url", "url": "https://your-cdn.com/reference-clip.mp4" },
{ "type": "asset_id", "asset_id": "YOUR_UPLOADED_ASSET_ID" }
]
}
```
Avatar looks and references share a combined media budget: **at most 3 videos and 9 images** total across `avatar_id` and `references`. Upload your own files first with [Assets](/assets) to get an `asset_id`.
## Step 4 — Poll for completion
Generation is asynchronous. Poll [`GET /v3/videos/{video_id}`](/reference/create-video) until `status` is `completed`:
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/videos/YOUR_VIDEO_ID" \
-H "x-api-key: YOUR_API_KEY"
```
| Status | Meaning |
| ------------ | ---------------------------------------------- |
| `pending` | Queued for processing |
| `processing` | Video is being generated |
| `completed` | Ready — `video_url` is available |
| `failed` | Something went wrong — check `failure_message` |
## Full example
```python theme={null}
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 Cinematic Avatar video
resp = requests.post(f"{BASE}/v3/videos", headers=HEADERS, json={
"type": "cinematic_avatar",
"prompt": "A founder walks through a sunlit startup office, gesturing toward a whiteboard, shot handheld in a documentary style.",
"avatar_id": ["YOUR_LOOK_ID"],
"aspect_ratio": "16:9",
"resolution": "1080p",
"duration": 10,
})
video_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)
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------- | ------- | -------- | -------------------------------------------------------------------------------------------- |
| `type` | string | Yes | Must be `"cinematic_avatar"` |
| `prompt` | string | Yes | 1–10,000 characters describing the shot |
| `avatar_id` | array | Yes | 1–3 avatar look IDs |
| `references` | array | No | Up to 3 videos / 9 images (shared with `avatar_id`) as `url`, `asset_id`, or `base64` inputs |
| `aspect_ratio` | string | No | `16:9` (default), `9:16`, or `1:1` |
| `resolution` | string | No | `720p` (default) or `1080p` |
| `duration` | integer | No | 4–15 seconds, default `10`. Omit when `auto_duration` is `true` |
| `auto_duration` | boolean | No | Let HeyGen pick the duration. Default `false` |
| `enhance_prompt` | boolean | No | Auto-expand a short prompt into a richer description. Default `false` |
| `title` | string | No | Display name in the HeyGen dashboard |
## Using webhooks instead of polling
Pass a `callback_url` when creating the video and HeyGen will POST to it when the job finishes — register an endpoint via `POST /v3/webhooks/endpoints` and subscribe to `avatar_video.success` and `avatar_video.fail` as covered in [Webhooks](/docs/webhooks).
# Overview
Source: https://developers.heygen.com/cli
Generate AI avatar videos from your terminal with the HeyGen CLI. Authenticate once, then script video creation, translation, and lipsync into any local.
The HeyGen CLI gives developers and AI agents command-line access to HeyGen's video platform. It wraps the v3 API, outputs structured JSON by default, and works out of the box in scripts, CI pipelines, and agent workflows.
## 1. Install the CLI
```bash theme={null}
curl -fsSL https://static.heygen.ai/cli/install.sh | bash
```
This installs the latest stable release into `~/.local/bin`.
Verify the installation:
```bash theme={null}
heygen --version
```
The CLI ships as a single binary with no runtime prerequisites. macOS (Apple Silicon and Intel) and Linux (x64 and arm64) are supported. Windows support is coming soon — WSL is recommended in the meantime.
## 2. Authenticate
Log in with your API key from [API dashboard](https://app.heygen.com/settings/api?nav=API):
```bash theme={null}
heygen auth login
```
Paste your API key when prompted. The key is stored locally at `~/.heygen/credentials`.
For CI/Docker/agent environments, set the environment variable instead — it takes precedence over stored credentials:
```bash theme={null}
export HEYGEN_API_KEY=your-api-key
```
Verify your credentials:
```bash theme={null}
heygen auth status
```
### Log in with OAuth
As an alternative to an API key, log in with your HeyGen account via OAuth:
```bash theme={null}
heygen auth login --oauth
```
This opens your browser to sign in at HeyGen.com.
Logging in one way replaces the other stored credential. Non-interactive shells (piped input, `CI=true`, or `HEYGEN_NONINTERACTIVE=1`) default to the API-key flow — pass `--oauth` explicitly in automation. Set `BROWSER=none` or `HEYGEN_NO_BROWSER=1` to print the sign-in URL instead of opening a browser.
## 3. Create a Video
Send a prompt to the Video Agent and let it handle avatar, voice, and layout:
```bash theme={null}
heygen video-agent create --prompt "A presenter explaining our product launch in 30 seconds"
```
```json Output theme={null}
{
"data": {
"session_id": "sess_abc123",
"status": "generating",
"video_id": "vid_xyz789",
"created_at": 1711288320
}
}
```
The CLI returns immediately with structured JSON. Your video is generating in the background.
For full control over every parameter, use `video create` with a JSON body:
```bash theme={null}
heygen video create -d '{
"type": "avatar",
"avatar_id": "avt_angela_01",
"script": "Welcome to our Q4 earnings call.",
"voice_id": "1bd001e7e50f421d891986aad5e3e5d2"
}'
```
Use `--request-schema` on any command to discover the expected JSON fields — no auth required:
```bash theme={null}
heygen video create --request-schema
heygen video-agent create --request-schema
```
## 4. Check Status
Poll for the result using the `video_id` returned from step 3:
```bash theme={null}
heygen video get vid_xyz789
```
```json Output theme={null}
{
"data": {
"id": "vid_xyz789",
"title": "Product launch explainer",
"status": "completed",
"video_url": "https://files.heygen.com/video/vid_xyz789.mp4",
"thumbnail_url": "https://files.heygen.com/thumb/vid_xyz789.jpg",
"duration": 32.5,
"created_at": 1711288320,
"completed_at": 1711288452
}
}
```
Status moves through `pending` → `processing` → `completed` or `failed`. If the video fails, the response includes `failure_code` and `failure_message` fields.
**Tip:** Add `--wait` to the create command to block until the video is ready instead of polling manually. The default timeout is 20 minutes — override with `--timeout 30m`. On timeout, the CLI exits with code `4` and prints the last known resource state along with a hint to resume polling manually.
## 5. Download the Video
Once complete, download to a local file:
```bash theme={null}
heygen video download vid_xyz789 --output-path ./launch-video.mp4
```
```json Output theme={null}
{
"asset": "video",
"message": "Downloaded video to ./launch-video.mp4",
"path": "./launch-video.mp4"
}
```
If the video was created with captions enabled, you can download the captioned version:
```bash theme={null}
heygen video download vid_xyz789 --asset captioned --output-path ./launch-captioned.mp4
```
# Commands
Source: https://developers.heygen.com/commands
Reference every HeyGen CLI command with flags, examples, and expected output. Covers video, avatar, voice, translate, lipsync, and config commands in one place.
All commands follow the pattern `heygen `. The command surface is auto-generated from HeyGen's OpenAPI specification — when new v3 endpoints ship, the CLI picks them up automatically.
Run `heygen --help` for detailed usage and examples on any command. Use `--request-schema` or `--response-schema` on any command to see the full JSON schema for its request or response — no auth required.
## Ai Clipping
Turn long-form videos into ready-to-share short clips with captions
| Command | API Endpoint | Description |
| ------------------------------------ | --------------------------------- | ------------------ |
| `heygen ai-clipping create` | `POST /v3/ai-clipping` | Create AI Clipping |
| `heygen ai-clipping delete ` | `DELETE /v3/ai-clipping/{job_id}` | Delete AI Clipping |
| `heygen ai-clipping get ` | `GET /v3/ai-clipping/{job_id}` | Get AI Clipping |
| `heygen ai-clipping list` | `GET /v3/ai-clipping` | List AI Clipping |
### Flags for `ai-clipping create`
| Flag | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--callback-id ` | Opaque client identifier echoed verbatim in webhook payloads. Mirrors /v3/video-translations callback\_id. |
| `--callback-url ` | HTTPS URL to receive per-job webhook callbacks. Mirrors /v3/video-translations callback\_url. Per-job callback\_url deliveries are NOT HMAC-signed: authenticate them by verifying TLS and matching the echoed callback\_id, and do not trust an unverified body. To receive a signed payload, register a webhook endpoint with a secret (the signature header is sent only to registered endpoints). |
| `--input-language ` | ISO-639-1 source language code (e.g. 'en', 'es'). Omit for auto-detect. |
| `--title ` | Title for the job. Defaults to the source video's title if omitted. |
### Flags for `ai-clipping list`
| Flag | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `--limit ` | Maximum number of items per page. Defaults to 10 (lower than other v3 lists) because each item embeds its full clips array. |
| `--token ` | Opaque cursor token for the next page. |
## Asset
Upload files for use in video creation
| Command | API Endpoint | Description |
| ----------------------------------------- | ------------------------------------- | --------------------- |
| `heygen asset complete create ` | `POST /v3/assets/{asset_id}/complete` | Complete Asset Upload |
| `heygen asset create` | `POST /v3/assets` | Upload Asset |
| `heygen asset delete ` | `DELETE /v3/assets/{asset_id}` | Delete Asset |
| `heygen asset direct-uploads create` | `POST /v3/assets/direct-uploads` | Create Asset Upload |
| `heygen asset get ` | `GET /v3/assets/{asset_id}` | Get Asset |
| `heygen asset list` | `GET /v3/assets` | List Assets |
### Flags for `asset complete create`
| Flag | Description |
| ---------------------------- | ---------------------------------- |
| `--checksum-sha-256 ` | Optional SHA256 (hex) cross-check. |
### Flags for `asset create`
| Flag | Description |
| ---------------- | --------------------------------------------------------------------------------- |
| `--file ` | File to upload (image, video, audio, PDF, or SRT subtitle). Max 32 MB. (required) |
### Flags for `asset direct-uploads create`
| Flag | Description |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--checksum-sha-256 ` | Optional SHA256 of the file as hex. When provided, S3 enforces it on upload. |
| `--content-type ` | Declared MIME type (e.g. 'video/mp4', 'image/png', 'audio/mpeg', 'application/pdf', 'application/zip'). Verified against the stored bytes at completion. (required) |
| `--filename ` | Original filename for reference/metadata. The stored object's extension is derived from content\_type. (required) |
| `--size-bytes ` | Exact byte size of the file. Signed into the upload URL so it cannot be exceeded. (required) |
### Flags for `asset list`
| Flag | Description |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--username ` | Username of the workspace member whose assets to list — the same value as asset items' 'owner' field. Required while this endpoint is in beta; it will become an optional filter in a future release. (required) |
| `--limit ` | Maximum number of assets to return per page (1-100). |
| `--token ` | Opaque cursor from a previous response's next\_token. Omit for the first page. |
| `--folder-id ` | Optional folder filter. Omit to list ALL workspace assets across folders. Pass a folder id to list that folder only, or an empty value (folder\_id=) for root-level assets (assets not filed into any folder). |
## Asset Batch
Create and track batches of direct-to-S3 asset uploads.
| Command | API Endpoint | Description |
| -------------------------------------------------- | ---------------------------------------- | --------------------------- |
| `heygen asset-batch batches get ` | `GET /v3/assets/batches/{batch_id}` | Get Asset Batch |
| `heygen asset-batch complete batches create` | `POST /v3/assets/complete/batches` | Complete Asset Upload Batch |
| `heygen asset-batch direct-uploads batches create` | `POST /v3/assets/direct-uploads/batches` | Create Asset Upload Batch |
| `heygen asset-batch statuses list` | `GET /v3/assets/statuses` | Bulk Asset Statuses |
### Flags for `asset-batch batches get`
| Flag | Description |
| ----------------- | -------------------------------------------------- |
| `--limit ` | Items per page (1-100). |
| `--token ` | Opaque pagination cursor from a previous response. |
### Flags for `asset-batch complete batches create`
| Flag | Description |
| -------------------- | ------------------------------------------------------------------------- |
| `--batch-id ` | Identifier returned by POST /v3/assets/direct-uploads/batches. (required) |
### Flags for `asset-batch direct-uploads batches create`
| Flag | Description |
| ------------------------ | ---------------------------------------------------------------------------------------- |
| `--callback-url ` | Reserved for parity with the other batch APIs; asset completion does not emit a webhook. |
| `--title ` | Display name for the batch, shown in the HeyGen app. |
### Flags for `asset-batch statuses list`
| Flag | Description |
| --------------------- | ------------------------------------------------------------- |
| `--asset-ids ` | Comma-separated asset ids to look up. |
| `--batch-ids ` | Comma-separated batch ids; each expands to its member assets. |
## Audio
Search the background-music and sound-effects catalog
| Command | API Endpoint | Description |
| -------------------------- | ---------------------- | ------------------------------------- |
| `heygen audio sounds list` | `GET /v3/audio/sounds` | Search audio (music or sound effects) |
### Flags for `audio sounds list`
| Flag | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--query ` | Natural-language description of the audio you want, e.g. 'upbeat lofi hip-hop' or 'tense cinematic riser'. Results are ranked by semantic similarity to this text. (required) |
| `--type ` | Audio content type to search: 'music' (background-music catalog) or 'sound\_effects' (SFX catalog). Defaults to 'music'. |
| `--limit ` | Maximum number of results to return (1-50). |
| `--min-score ` | Minimum semantic similarity score (0-1). Tracks scoring below this are omitted. |
| `--token ` | Opaque cursor token for the next page, taken from 'next\_token' in a prior response. |
## Avatar
List and manage avatars and looks
| Command | API Endpoint | Description |
| ----------------------------------------- | ------------------------------------- | --------------------- |
| `heygen avatar consent create ` | `POST /v3/avatars/{group_id}/consent` | Create Avatar Consent |
| `heygen avatar create` | `POST /v3/avatars` | Create Avatar |
| `heygen avatar delete ` | `DELETE /v3/avatars/{group_id}` | Delete Avatar Group |
| `heygen avatar get ` | `GET /v3/avatars/{group_id}` | Get Avatar Group |
| `heygen avatar list` | `GET /v3/avatars` | List Avatar Groups |
| `heygen avatar looks delete ` | `DELETE /v3/avatars/looks/{look_id}` | Delete Avatar Look |
| `heygen avatar looks get ` | `GET /v3/avatars/looks/{look_id}` | Get Avatar Look |
| `heygen avatar looks list` | `GET /v3/avatars/looks` | List Avatar Looks |
| `heygen avatar looks update ` | `PATCH /v3/avatars/looks/{look_id}` | Update Avatar Look |
### Flags for `avatar consent create`
| Flag | Description |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `--reroute-url ` | Callback URL where the user is redirected after completing consent. Defaults to HeyGen's consent completion page. |
### Flags for `avatar create`
This command takes a structured request body. Pass it with `-d` and run the command with `--request-schema` to see all fields.
### Flags for `avatar list`
| Flag | Description |
| --------------------- | ------------------------------------------------------------------------------------------ |
| `--ownership ` | Filter by ownership: 'public' for preset avatars, or 'private' for your own. Omit for all. |
| `--limit ` | Maximum number of items to return per page (1-50). |
| `--token ` | Opaque cursor token for the next page. |
### Flags for `avatar looks list`
| Flag | Description |
| ----------------------- | ------------------------------------------------------------------------------------------ |
| `--group-id ` | Filter looks to a specific avatar group. Returns only looks belonging to this group. |
| `--avatar-type ` | Filter by avatar type: 'studio\_avatar', 'digital\_twin', or 'photo\_avatar'. |
| `--ownership ` | Filter by ownership: 'public' for preset avatars, or 'private' for your own. Omit for all. |
| `--limit ` | Maximum number of items to return per page (1-50). |
| `--token ` | Opaque cursor token for the next page. |
### Flags for `avatar looks update`
| Flag | Description |
| ---------------- | ------------------------------ |
| `--name ` | New display name for the look. |
## Background Removal
| Command | API Endpoint | Description |
| ------------------------------------------- | ----------------------------------------- | ------------------------- |
| `heygen background-removal create` | `POST /v3/background-removals` | Create Background Removal |
| `heygen background-removal delete ` | `DELETE /v3/background-removals/{job_id}` | Delete Background Removal |
| `heygen background-removal get ` | `GET /v3/background-removals/{job_id}` | Get Background Removal |
| `heygen background-removal list` | `GET /v3/background-removals` | List Background Removals |
### Flags for `background-removal create`
| Flag | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--layers ` | Output layers to return. Any of: 'foreground' (subject on a transparent background), 'mask' (grayscale alpha matte), 'background' (the scene with the subject removed). Defaults to all three. |
| `--request-id ` | Client-provided idempotency key — *the* idempotency mechanism for this endpoint. Re-sending the same `request_id` returns the *original* job (same `id`, same `status`, same charge) instead of creating a new one — the dedup is over (`space_id`, `request_id`), not over the response body, so a duplicate `request_id` with different content still collides on the original job. The HTTP `Idempotency-Key` header is *not* honored here; pick a per-content `request_id` if you need per-content dedup. |
| `--title ` | Optional human-readable title for the job. |
### Flags for `background-removal list`
| Flag | Description |
| ----------------- | ---------------------------------------------------------------- |
| `--limit ` | Maximum number of jobs to return (1-100). |
| `--token ` | Opaque pagination cursor from a previous response's next\_token. |
## Brand
Brand-related resources — brand kits (colors, fonts, logos) and brand glossaries (custom term translations)
| Command | API Endpoint | Description |
| ------------------------------ | -------------------------- | --------------------- |
| `heygen brand glossaries list` | `GET /v3/brand-glossaries` | List Brand Glossaries |
| `heygen brand kits list` | `GET /v3/brand-kits` | List Brand Kits |
### Flags for `brand glossaries list`
| Flag | Description |
| ----------------- | ------------------------------------------------------------------------------------------ |
| `--limit ` | Maximum number of brand glossaries to return (1-100). Default 10. |
| `--token ` | Opaque pagination cursor from a previous response's `next_token`. Omit for the first page. |
### Flags for `brand kits list`
| Flag | Description |
| ----------------- | ---------------------------------------------------------------------------------------- |
| `--limit ` | Results per page (1-100). |
| `--token ` | Opaque cursor token for the next page. Obtained from next\_token in a previous response. |
## Lipsync
Dub or replace audio on existing videos
| Command | API Endpoint | Description |
| ------------------------------------ | ---------------------------------- | -------------- |
| `heygen lipsync create` | `POST /v3/lipsyncs` | Create Lipsync |
| `heygen lipsync delete ` | `DELETE /v3/lipsyncs/{lipsync_id}` | Delete Lipsync |
| `heygen lipsync get ` | `GET /v3/lipsyncs/{lipsync_id}` | Get Lipsync |
| `heygen lipsync list` | `GET /v3/lipsyncs` | List Lipsyncs |
| `heygen lipsync update ` | `PATCH /v3/lipsyncs/{lipsync_id}` | Update Lipsync |
### Flags for `lipsync create`
| Flag | Description |
| ----------------------------- | ------------------------------------------------------------------------------------- |
| `--callback-id ` | ID included in webhook payload |
| `--callback-url ` | Webhook URL for completion notifications |
| `--disable-music-track` | Remove background music |
| `--enable-caption` | Generate captions for the output video |
| `--enable-dynamic-duration` | Allow dynamic duration adjustment |
| `--enable-speech-enhancement` | Enhance speech quality |
| `--enable-watermark` | Add watermark to output |
| `--end-time ` | End time in seconds for partial lipsync |
| `--folder-id ` | Project/folder ID to organize lipsync into |
| `--fps-mode ` | Frame rate mode: 'vfr', 'cfr', or 'passthrough'. |
| `--keep-the-same-format` | Preserve the source video's encoding specs (resolution, bitrate). |
| `--mode ` | Quality mode: 'speed' (faster) or 'precision' (higher quality, uses avatar inference) |
| `--start-time ` | Start time in seconds for partial lipsync |
| `--title ` | Title for the lipsync job |
### Flags for `lipsync list`
| Flag | Description |
| ----------------- | ------------------------------------- |
| `--limit ` | Maximum number of items per page |
| `--token ` | Opaque cursor token for the next page |
### Flags for `lipsync update`
| Flag | Description |
| ----------------- | ------------------------------------ |
| `--title ` | New title for the lipsync (required) |
## Lipsync Batch
Create and track batches of lipsyncs.
| Command | API Endpoint | Description |
| --------------------------------------------- | ------------------------------------- | --------------------- |
| `heygen lipsync-batch batches create` | `POST /v3/lipsyncs/batches` | Create Lipsync Batch |
| `heygen lipsync-batch batches get ` | `GET /v3/lipsyncs/batches/{batch_id}` | Get Lipsync Batch |
| `heygen lipsync-batch statuses list` | `GET /v3/lipsyncs/statuses` | Bulk Lipsync Statuses |
### Flags for `lipsync-batch batches create`
| Flag | Description |
| ------------------------ | ------------------------------------------------------------------------------- |
| `--callback-url ` | Webhook URL invoked once when every item in the batch reaches a terminal state. |
| `--title ` | Display name for the batch, shown in the HeyGen app. |
### Flags for `lipsync-batch batches get`
| Flag | Description |
| ----------------- | -------------------------------------------------- |
| `--limit ` | Items per page (1-100). |
| `--token ` | Opaque pagination cursor from a previous response. |
### Flags for `lipsync-batch statuses list`
| Flag | Description |
| ----------------------- | --------------------------------------------------------------- |
| `--lipsync-ids ` | Comma-separated lipsync ids to look up. |
| `--batch-ids ` | Comma-separated batch ids; each expands to its member lipsyncs. |
## Template
Generate videos from reusable templates by replacing their variables
| Command | API Endpoint | Description |
| ---------------------------------------- | ---------------------------------- | ---------------------------- |
| `heygen template generate ` | `POST /v3/templates/{template_id}` | Generate Video from Template |
| `heygen template get ` | `GET /v3/templates/{template_id}` | Get Template |
| `heygen template list` | `GET /v3/templates` | List Templates |
### Flags for `template generate`
| Flag | Description |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--brand-voice-id ` | Brand voice ID controlling pronunciation |
| `--callback-id ` | Opaque ID echoed back in webhook events for this video |
| `--callback-url ` | URL called with the video result in addition to registered webhook endpoints |
| `--caption` | Whether to burn captions into the video |
| `--enable-sharing` | Whether the generated video's share page is publicly accessible |
| `--folder-id ` | Folder to place the generated video in |
| `--fps ` | Output frame rate. One of 25, 30, or 60. |
| `--include-gif` | Whether to include a GIF preview in the webhook payload |
| `--keep-text-vertically-centered` | When true, replaced text elements are vertically re-centered based on their rendered height |
| `--reorder-music` | When true (default), background audio tracks move with their scenes. When false, tracks stay pinned to layout positions. |
| `--scene-ids ` | Scene IDs to render, in order (repeats allowed). Scenes must already exist in the template; the API can select, reorder, and repeat scenes but cannot create new ones. Omit to render all scenes in template order. |
| `--title ` | Title for the generated video |
### Flags for `template list`
| Flag | Description |
| ----------------- | -------------------------------------------------------------- |
| `--limit ` | Maximum number of templates to return per page |
| `--token ` | Opaque pagination token from a previous response's next\_token |
## User
Account information and billing
| Command | API Endpoint | Description |
| -------------------- | ------------------ | ---------------- |
| `heygen user me get` | `GET /v3/users/me` | Get Current User |
## Video
Create, list, retrieve, and delete videos
| Command | API Endpoint | Description |
| ------------------------------------- | ----------------------------------- | ------------------- |
| `heygen video batches create` | `POST /v3/videos/batches` | Create Video Batch |
| `heygen video batches get ` | `GET /v3/videos/batches/{batch_id}` | Get Video Batch |
| `heygen video create` | `POST /v3/videos` | Create Video |
| `heygen video delete ` | `DELETE /v3/videos/{video_id}` | Delete Video |
| `heygen video get ` | `GET /v3/videos/{video_id}` | Get Video |
| `heygen video list` | `GET /v3/videos` | List Videos |
| `heygen video statuses list` | `GET /v3/videos/statuses` | Bulk Video Statuses |
### Flags for `video batches create`
| Flag | Description |
| ------------------------ | ------------------------------------------------------------------------------- |
| `--callback-url ` | Webhook URL invoked once when every item in the batch reaches a terminal state. |
| `--title ` | Display name for the batch, shown in the HeyGen app. |
### Flags for `video batches get`
| Flag | Description |
| ----------------- | -------------------------------------------------- |
| `--limit ` | Items per page (1-100). |
| `--token ` | Opaque pagination cursor from a previous response. |
### Flags for `video create`
This command takes a structured request body. Pass it with `-d` and run the command with `--request-schema` to see all fields.
### Flags for `video list`
| Flag | Description |
| --------------------- | ------------------------------------------------- |
| `--limit ` | Maximum number of items to return per page |
| `--token ` | Opaque pagination cursor from a previous response |
| `--folder-id ` | Filter videos by folder ID |
| `--title ` | Filter videos by title substring |
### Flags for `video statuses list`
| Flag | Description |
| --------------------- | ------------------------------------------------------------- |
| `--video-ids ` | Comma-separated video ids to look up. |
| `--batch-ids ` | Comma-separated batch ids; each expands to its member videos. |
## Video Agent
Create videos from text prompts using AI
| Command | API Endpoint | Description |
| ------------------------------------------------------------- | ----------------------------------------------------------- | -------------------------------- |
| `heygen video-agent create` | `POST /v3/video-agents` | Create Video Agent Session |
| `heygen video-agent get ` | `GET /v3/video-agents/{session_id}` | Get Video Agent Session |
| `heygen video-agent list` | `GET /v3/video-agents` | List Video Agent Sessions |
| `heygen video-agent resources get ` | `GET /v3/video-agents/{session_id}/resources/{resource_id}` | Get Session Resource |
| `heygen video-agent send ` | `POST /v3/video-agents/{session_id}` | Send Message or Request Revision |
| `heygen video-agent stop ` | `POST /v3/video-agents/{session_id}/stop` | Stop Video Agent Session |
| `heygen video-agent styles list` | `GET /v3/video-agents/styles` | List Video Agent Styles |
| `heygen video-agent videos list ` | `GET /v3/video-agents/{session_id}/videos` | List Session Videos |
### Flags for `video-agent create`
| Flag | Description |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--avatar-id ` | Specific avatar ID to use |
| `--brand-kit-id ` | Brand kit ID to apply brand colors, fonts, and logos to the generated video. |
| `--callback-id ` | Optional callback ID included in webhook payload |
| `--callback-url ` | Webhook URL for completion/failure notifications |
| `--incognito-mode` | When enabled, disables memory injection and extraction for this session |
| `--mode ` | Session mode. 'generate' produces one video (fire-and-forget). 'chat' enables multi-turn interaction — the agent may pause for decisions and allows revisions. |
| `--orientation ` | Video orientation. If not provided, auto-detected from content. |
| `--prompt ` | The message/prompt for video generation (1-10000 characters) (required) |
| `--style-id ` | Style ID from GET /v3/video-agents/styles. Applies a curated visual template to the generated video. |
| `--voice-id ` | Specific voice ID to use for narration |
### Flags for `video-agent list`
| Flag | Description |
| ----------------- | ---------------------------------------------------------------------------------------- |
| `--limit ` | Results per page (1-100). |
| `--token ` | Opaque cursor token for the next page. Obtained from next\_token in a previous response. |
### Flags for `video-agent send`
| Flag | Description |
| ------------------------ | -------------------------------------- |
| `--avatar-id ` | Override avatar for this message |
| `--brand-kit-id ` | Brand kit ID to apply for this message |
| `--message ` | Text message to the agent (required) |
| `--voice-id ` | Override voice for this message |
### Flags for `video-agent stop`
This command takes a structured request body. Pass it with `-d` and run the command with `--request-schema` to see all fields.
### Flags for `video-agent styles list`
| Flag | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------- |
| `--tag ` | Filter by tag (e.g., 'cinematic', 'retro-tech', 'iconic-artist', 'pop-culture', 'handmade', 'print'). |
| `--limit ` | Results per page (1-100). |
| `--token ` | Opaque cursor token for the next page. Obtained from next\_token in a previous response. |
## Video Translate
Translate videos into other languages
| Command | API Endpoint | Description |
| ------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------ |
| `heygen video-translate create` | `POST /v3/video-translations` | Create Video Translation |
| `heygen video-translate delete ` | `DELETE /v3/video-translations/{video_translation_id}` | Delete Video Translation |
| `heygen video-translate get ` | `GET /v3/video-translations/{video_translation_id}` | Get Video Translation |
| `heygen video-translate languages list` | `GET /v3/video-translations/languages` | List Supported Translation Languages |
| `heygen video-translate list` | `GET /v3/video-translations` | List Video Translations |
| `heygen video-translate proofreads create` | `POST /v3/video-translations/proofreads` | Create Proofread Session |
| `heygen video-translate proofreads generate ` | `POST /v3/video-translations/proofreads/{proofread_id}/generate` | Generate Video from Proofread |
| `heygen video-translate proofreads get ` | `GET /v3/video-translations/proofreads/{proofread_id}` | Get Proofread Session |
| `heygen video-translate proofreads srt get ` | `GET /v3/video-translations/proofreads/{proofread_id}/srt` | Download Proofread SRT |
| `heygen video-translate proofreads srt update ` | `PUT /v3/video-translations/proofreads/{proofread_id}/srt` | Upload Proofread SRT |
| `heygen video-translate update ` | `PATCH /v3/video-translations/{video_translation_id}` | Update Video Translation |
### Flags for `video-translate create`
| Flag | Description |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `--brand-glossary-id ` | Brand glossary ID for custom term translations (e.g. translate 'Reformer' as the Pilates equipment, not 'political activist'). Alias for the legacy `brand_voice_id` field. Discover IDs via GET /v3/brand-glossaries. |
| `--brand-voice-id ` | Brand glossary ID for custom term translations. Legacy field name for `brand_glossary_id` — both are accepted and resolve to the same workspace record. Discover IDs via GET /v3/brand-glossaries. |
| `--callback-id ` | ID included in webhook payload |
| `--callback-url ` | Webhook URL for completion notifications |
| `--disable-music-track` | Remove background music |
| `--enable-caption` | Generate captions for translated video |
| `--enable-dynamic-duration` | Allow dynamic duration adjustment |
| `--enable-speech-enhancement` | Enhance speech quality |
| `--enable-watermark` | Add watermark to output |
| `--end-time ` | End time in seconds for partial translation |
| `--folder-id ` | Project/folder ID to organize translation into |
| `--fps-mode ` | Frame rate mode for the output video. 'vfr' = variable frame rate, 'cfr' = constant frame rate, 'passthrough' = match the source. Only takes effect when a custom 'audio' track is provided. |
| `--input-language ` | Source language code (auto-detected if omitted) |
| `--keep-the-same-format` | Preserve the source video's encoding specs (resolution, bitrate). |
| `--mode ` | Translation quality mode: 'speed' (faster) or 'precision' (higher quality, uses avatar inference) |
| `--output-languages ` | Target language names (e.g. 'Chinese (Cantonese, Traditional)', 'Spanish (Spain)', 'English'). Use GET /v3/video-translations/languages for valid values. Use one for single translation, multiple for batch. (required) |
| `--speaker-num ` | Number of speakers (improves speaker separation) |
| `--srt-role ` | Which video the subtitle applies to: 'input' (source) or 'output' (translated). |
| `--start-time ` | Start time in seconds for partial translation |
| `--title ` | Title for the translation job |
| `--translate-audio-only` | Only translate audio, keep original video |
### Flags for `video-translate list`
| Flag | Description |
| ----------------- | ------------------------------------- |
| `--limit ` | Maximum number of items per page |
| `--token ` | Opaque cursor token for the next page |
### Flags for `video-translate proofreads create`
| Flag | Description |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--brand-glossary-id ` | Brand glossary ID for custom term translations (e.g. translate 'Reformer' as 'Pilates equipment', not 'political activist'). Alias for the legacy `brand_voice_id` field. Discover IDs via GET /v3/brand-glossaries. |
| `--brand-voice-id ` | Brand glossary ID for custom term translations. Legacy field name for `brand_glossary_id` — both are accepted and resolve to the same workspace record. Discover IDs via GET /v3/brand-glossaries. |
| `--disable-music-track` | Remove background music |
| `--enable-speech-enhancement` | Enhance speech quality |
| `--enable-video-stretching` | Allow dynamic duration adjustment |
| `--folder-id ` | Project/folder ID to organize proofread into |
| `--keep-the-same-format` | Preserve the source video's encoding specs (resolution, bitrate) |
| `--mode ` | Translation quality mode: 'speed' (faster) or 'precision' (higher quality) |
| `--output-languages ` | Target language codes. Use one for single proofread, multiple for batch. (required) |
| `--speaker-num ` | Number of speakers (improves speaker separation) |
| `--title ` | Title for the proofread job (required) |
### Flags for `video-translate proofreads generate`
| Flag | Description |
| ------------------------ | ------------------------------------------ |
| `--callback-id ` | ID included in webhook payload |
| `--callback-url ` | Webhook URL for completion notifications |
| `--captions` | Generate captions for the translated video |
| `--translate-audio-only` | Only translate audio, keep original video |
### Flags for `video-translate proofreads srt update`
This command takes a structured request body. Pass it with `-d` and run the command with `--request-schema` to see all fields.
### Flags for `video-translate update`
| Flag | Description |
| ----------------- | ---------------------------------------------- |
| `--title ` | New title for the video translation (required) |
## Video Translation Batch
Create and track batches of video translations.
| Command | API Endpoint | Description |
| ------------------------------------------------------- | ----------------------------------------------- | ------------------------------- |
| `heygen video-translation-batch batches create` | `POST /v3/video-translations/batches` | Create Video Translation Batch |
| `heygen video-translation-batch batches get ` | `GET /v3/video-translations/batches/{batch_id}` | Get Video Translation Batch |
| `heygen video-translation-batch statuses list` | `GET /v3/video-translations/statuses` | Bulk Video Translation Statuses |
### Flags for `video-translation-batch batches create`
| Flag | Description |
| ------------------------ | ------------------------------------------------------------------------------- |
| `--callback-url ` | Webhook URL invoked once when every item in the batch reaches a terminal state. |
| `--title ` | Display name for the batch, shown in the HeyGen app. |
### Flags for `video-translation-batch batches get`
| Flag | Description |
| ----------------- | -------------------------------------------------- |
| `--limit ` | Items per page (1-100). |
| `--token ` | Opaque pagination cursor from a previous response. |
### Flags for `video-translation-batch statuses list`
| Flag | Description |
| --------------------------------- | ------------------------------------------------------------------------- |
| `--video-translation-ids ` | Comma-separated video translation ids to look up. |
| `--batch-ids ` | Comma-separated batch ids; each expands to its member video translations. |
## Voice
Create speech audio and manage voices
| Command | API Endpoint | Description |
| -------------------------------- | ------------------------------ | --------------- |
| `heygen voice clone create` | `POST /v3/voices/clone` | Clone a Voice |
| `heygen voice create` | `POST /v3/voices` | Design a Voice |
| `heygen voice delete ` | `DELETE /v3/voices/{voice_id}` | Delete a Voice |
| `heygen voice get ` | `GET /v3/voices/{voice_id}` | Get Voice |
| `heygen voice list` | `GET /v3/voices` | List Voices |
| `heygen voice speech create` | `POST /v3/voices/speech` | Generate Speech |
### Flags for `voice clone create`
| Flag | Description |
| --------------------------- | ------------------------------------------------------------------------- |
| `--language ` | Language hint for the voice (e.g., 'en', 'es'). Auto-detected if omitted. |
| `--remove-background-noise` | Remove background noise from the audio before cloning. |
| `--voice-name ` | Display name for the cloned voice. (required) |
### Flags for `voice create`
| Flag | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--gender ` | Filter by gender: 'male' or 'female'. |
| `--locale ` | BCP-47 locale tag to filter by (e.g., 'en-US', 'pt-BR'). |
| `--prompt ` | Natural language description of the desired voice (e.g., 'warm, confident female narrator'). (required) |
| `--seed ` | Controls which batch of results to return. seed=0 returns the top matches, seed=1 the next batch, etc. Same prompt + seed always returns the same voices. |
### Flags for `voice list`
| Flag | Description |
| -------------------- | --------------------------------------------------------------------------------------------------------- |
| `--type ` | Voice type: 'public' for the shared library or 'private' for your cloned voices. |
| `--engine ` | Filter by voice engine (e.g. 'starfish'). When set, only voices compatible with that engine are returned. |
| `--language ` | Filter by language (e.g. 'English'). |
| `--gender ` | Filter by gender ('male' or 'female'). |
| `--limit ` | Results per page (1-100). |
| `--token ` | Opaque cursor token for the next page. |
### Flags for `voice speech create`
| Flag | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--input-type ` | Type of the input: 'text' for plain text, 'ssml' for SSML markup. Defaults to 'text'. |
| `--language ` | Base language code (e.g. 'en', 'pt', 'zh'). Optional — auto-detected from text when omitted. |
| `--locale ` | BCP-47 locale tag (e.g. 'en-US', 'pt-BR'). When set, language is inferred from locale. |
| `--speed ` | Speed multiplier (0.5-2.0). |
| `--text ` | Text to synthesize (1-5000 characters). (required) |
| `--voice-id ` | Voice ID to use. The voice must support the starfish engine. Filter compatible voices by passing engine=starfish to the voice listing endpoint. (required) |
## Webhook
Create, list, and manage webhook endpoints and events
| Command | API Endpoint | Description |
| ------------------------------------------------------ | --------------------------------------------------------- | ----------------------------- |
| `heygen webhook endpoints create` | `POST /v3/webhooks/endpoints` | Create Webhook Endpoint |
| `heygen webhook endpoints delete ` | `DELETE /v3/webhooks/endpoints/{endpoint_id}` | Delete Webhook Endpoint |
| `heygen webhook endpoints list` | `GET /v3/webhooks/endpoints` | List Webhook Endpoints |
| `heygen webhook endpoints rotate-secret ` | `POST /v3/webhooks/endpoints/{endpoint_id}/rotate-secret` | Rotate Webhook Signing Secret |
| `heygen webhook endpoints update ` | `PATCH /v3/webhooks/endpoints/{endpoint_id}` | Update Webhook Endpoint |
| `heygen webhook event-types list` | `GET /v3/webhooks/event-types` | List Webhook Event Types |
| `heygen webhook events list` | `GET /v3/webhooks/events` | List Webhook Events |
### Flags for `webhook endpoints create`
| Flag | Description |
| --------------------- | ----------------------------------------------------------------------------------------------------- |
| `--entity-id ` | Optional entity ID to scope this endpoint to a specific resource (e.g. a personalized video project). |
| `--events ` | Event types to subscribe to. Omit or set to null to receive all events. |
| `--url ` | Publicly accessible HTTPS URL that will receive webhook POST requests. (required) |
### Flags for `webhook endpoints list`
| Flag | Description |
| ----------------- | ---------------------------------------------------------------- |
| `--limit ` | Maximum number of endpoints to return (1-100). Default: 10. |
| `--token ` | Opaque pagination cursor from a previous response's next\_token. |
### Flags for `webhook endpoints update`
| Flag | Description |
| ------------------- | -------------------------------------------------------------------- |
| `--events ` | New list of event types to subscribe to. Replaces the existing list. |
| `--url ` | New URL for the endpoint. Must be publicly accessible HTTPS. |
### Flags for `webhook events list`
| Flag | Description |
| ---------------------- | ---------------------------------------------------------------- |
| `--event-type ` | Filter events by type, e.g. 'avatar\_video.success'. |
| `--entity-id ` | Filter events by entity ID. |
| `--limit ` | Maximum number of events to return (1-100). Default: 10. |
| `--token ` | Opaque pagination cursor from a previous response's next\_token. |
## Authentication
| Command | Description |
| --------------------------- | --------------------------------------------------------------------------------------------------- |
| `heygen auth login` | Authenticate interactively (prompts for API key) |
| `heygen auth login --oauth` | Log in via browser OAuth — uses subscription credits ([free usage](/docs/for-ai-agents#free-usage)) |
| `heygen auth status` | Verify stored credentials and show account info |
For CI/Docker, use the `HEYGEN_API_KEY` environment variable instead. It takes precedence over stored credentials.
## Utility Commands
| Command | Description |
| --------------------------------- | -------------------------------------------- |
| `heygen config set ` | Set a persistent config value |
| `heygen config get ` | Read a config value |
| `heygen config list` | Show all config values and their sources |
| `heygen update` | Self-update to the latest version |
| `heygen update --version ` | Update to a specific version (e.g. `v0.1.0`) |
### Config keys
| Key | Values | Description |
| ----------- | --------------- | ------------------------------------------- |
| `output` | `json`, `human` | Default output format (default: `json`) |
| `analytics` | `true`, `false` | Enable or disable anonymous usage analytics |
# Content Repurposing
Source: https://developers.heygen.com/content-repurposing
Repurpose blog posts, podcasts, and long-form video into avatar-led short clips with the HeyGen API. One source, dozens of platform-ready outputs.
## The Problem
You invest hours writing a great blog post. It reaches your readers — but misses the much larger audience that consumes content through video. Manually converting articles to video takes almost as long as writing them.
## How It Works
```
Written content → LLM extracts key points → Video Agent renders → Distribute on video platforms
```
An LLM reads your content and writes a production-quality video prompt — extracting the most compelling points and restructuring them for video. The same article can become a 90-second YouTube explainer, a 30-second TikTok, and a 60-second LinkedIn post.
## Build It
Pull the article from your CMS, a URL, or a local file.
```python theme={null}
# From a file
with open("article.md") as f:
article = f.read()
# Or from a URL (use a proper extraction library for production)
import requests
article = requests.get("https://yourblog.com/posts/your-article").text
```
The LLM acts as a producer — extracting the most engaging points and structuring them for video.
```python theme={null}
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"""You are a video producer converting a written article
into a HeyGen Video Agent prompt.
Read this article and create a 60-second video prompt that:
1. Opens with the most compelling insight or stat (hook)
2. Covers the 3 most important points — not everything, the best bits
3. Uses specific visual descriptions — what the viewer sees on screen
4. Ends with a CTA to read the full article
5. Matches the tone of the original
Article:
{article}
Output ONLY the Video Agent prompt."""
}],
)
video_prompt = message.content[0].text
```
**Don't summarize — adapt.** The LLM shouldn't just compress the article. It should identify the most *visual* and *engaging* points and restructure them for video. A great blog point might be boring on video, and vice versa.
Submit the prompt. Attach any images or charts from the article as file inputs.
```python theme={null}
resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={
"X-Api-Key": HEYGEN_API_KEY,
"Content-Type": "application/json",
},
json={
"prompt": video_prompt,
"files": [
{"type": "url", "url": "https://yourblog.com/images/chart.png"},
],
},
)
video_id = resp.json()["data"]["video_id"]
```
Then poll for completion — see [Video Agent docs](/docs/video-agent).
One article can become multiple videos for different platforms:
```python theme={null}
formats = [
{"platform": "YouTube", "duration": "90s", "orientation": "landscape", "style": "in-depth"},
{"platform": "TikTok/Reels", "duration": "30s", "orientation": "portrait", "style": "hook-driven"},
{"platform": "LinkedIn", "duration": "60s", "orientation": "landscape", "style": "professional"},
]
for fmt in formats:
# Regenerate the LLM prompt with platform-specific instructions
platform_prompt = generate_prompt_for(article, fmt)
# Submit to Video Agent with the right orientation
submit_video(platform_prompt, orientation=fmt["orientation"])
```
## Content Types That Convert Well
| Content type | Video style | Tips |
| -------------------- | -------------------- | ------------------------------------------------- |
| **How-to articles** | Tutorial walkthrough | Step-by-step with text overlays |
| **Listicles** | Quick tips | One point every 5–7 seconds, great for short-form |
| **Opinion/analysis** | Thought leadership | Presenter-driven, conversational |
| **Case studies** | Story-driven | Before/after structure, stats as highlights |
| **Newsletters** | Weekly digest | Cover 3–5 highlights, keep it breezy |
## Automating the Pipeline
```
Blog CMS webhook → "New post published"
↓
Fetch article content
↓
LLM generates video prompt
↓
Video Agent renders
↓
Upload to YouTube / post to social
↓
Add video embed to original article
```
Trigger from a CMS webhook, cron job, or CI/CD. See [Automated Broadcast](/cookbook/video-agent/automated-broadcast) for scheduling and distribution patterns.
## Variations
* **Teaser + full:** 15-second teaser for social, 90-second deep dive for YouTube
* **Multi-language:** Generate in English, then [translate](/cookbook/video-agent/multilingual-content) for global audiences
* **Podcast-to-video:** Extract audio highlights → write visual prompt → avatar presents the key takeaways
***
## Next Steps
Generate original social content, not just repurposed articles.
Automate the entire content → video → distribute pipeline.
# Data Visualization Videos
Source: https://developers.heygen.com/data-to-video
Convert spreadsheets, dashboards, and analytics into avatar-led explainer videos via the HeyGen API. The agent narrates the data and surfaces the key findings.
## Examples
9 sorting algorithms on 100 bars — bubble sort through merge sort. Each comparison plays a pitched tone. 76 seconds with synthesized audio.
A 75-year life as 3,900 weekly squares. They fill in with an accelerating heartbeat. The empty ones are what's left.
A full Flappy Bird game playing itself — pixel art, auto-pilot AI, wing flap sounds, score dings. 33 seconds. No game engine, just HTML + math.
## The Problem
Data tells a story, but spreadsheets and static charts don't. Animated visualizations are compelling — but building them as shareable video (not just an interactive webpage) usually means screen recording with all its artifacts.
## How It Works
```
Data source → Generate visualization HTML → Animate with GSAP → Render to MP4
```
Hyperframes renders anything a browser can display. D3 charts, Canvas graphics, SVG diagrams, CSS animations — they all become pixel-perfect video frames.
## Build It
Your data can come from anywhere — a CSV, an API, a database, or generated programmatically.
```python theme={null}
# Example: pull GitHub stats
import requests
repos = requests.get(
"https://api.github.com/users/your-username/repos",
headers={"Authorization": f"token {GITHUB_TOKEN}"}
).json()
stats = {
"total_repos": len(repos),
"languages": {},
"total_stars": sum(r["stargazers_count"] for r in repos),
}
for r in repos:
lang = r.get("language") or "Other"
stats["languages"][lang] = stats["languages"].get(lang, 0) + 1
```
```
I have GitHub data for a developer: 13 repos, 472 commits,
top languages are TypeScript (5), Dart (3), JavaScript (1).
Create a "GitHub Wrapped" style video — vertical 9:16, 45 seconds.
Show the stats one by one with animated counters, a bar chart of
languages that grows, and end with a highlight reel of project names.
Use a dark theme with green (#00ff88) accents like GitHub's contribution graph.
```
The AI agent writes the HTML composition with the data baked into the animation.
For data visualizations, synthesized audio often works better than voiceover. You can generate tones programmatically:
```python theme={null}
import wave, struct, math
# Generate pitched tones for a sorting visualizer
sample_rate = 44100
samples = []
for value in data_points:
freq = 200 + (value / max_value) * 1000 # pitch = data value
for i in range(int(0.03 * sample_rate)): # 30ms per tone
env = 1.0 - (i / (0.03 * sample_rate)) * 0.7
s = env * 0.25 * math.sin(2 * math.pi * freq * i / sample_rate)
samples.append(s)
with wave.open("data-sound.wav", "w") as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(sample_rate)
for s in samples:
wf.writeframes(struct.pack("
```
```bash theme={null}
npx hyperframes dev # preview at localhost:3002
npx hyperframes render # export to MP4
```
## Visualization Ideas
| Type | What it looks like | Complexity |
| --------------------------- | ------------------------------------------------ | ------------------------------------- |
| **Animated bar chart** | Bars growing, sorting, racing | Simple — CSS + GSAP |
| **Counter/ticker** | Numbers rolling up from 0 to target | Simple — GSAP snap |
| **Line chart drawing** | SVG polyline with stroke-dashoffset animation | Medium — SVG + GSAP |
| **Dashboard** | Multiple panels updating simultaneously | Medium — layout + timing |
| **Algorithm visualization** | Sorting bars, pathfinding grids, tree traversals | Complex — pre-compute states, animate |
| **Physics simulation** | Bouncing balls, pendulum waves, particle systems | Complex — math-driven positions |
## Automate It
The real power: **data in, video out** as a pipeline.
```python theme={null}
import subprocess
def generate_data_video(data, template_dir, output_path):
"""Generate a video from data using Hyperframes."""
# 1. Write data into the composition
with open(f"{template_dir}/data.json", "w") as f:
json.dump(data, f)
# 2. Render
subprocess.run([
"npx", "hyperframes", "render",
"--output", output_path,
"--quality", "standard"
], cwd=template_dir)
return output_path
# Generate weekly report videos from database
for week in get_weekly_metrics():
generate_data_video(
data=week,
template_dir="templates/weekly-report",
output_path=f"renders/report-{week['date']}.mp4"
)
```
Combine with [Docs to Video](/cookbook/video-agent/docs-to-video) for a fully automated pipeline: data changes → Hyperframes renders visualization → Video Agent adds avatar narration.
***
## Next Steps
Animated title cards, product launches, and brand content.
CI/CD integration for continuous video generation from data.
# Design a Custom Voice
Source: https://developers.heygen.com/design-a-voice
Generate a new HeyGen voice from a natural-language description. No audio sample needed; describe the voice and HeyGen synthesizes it for use in your videos.
## Steps
Use `voice create` with a natural language prompt:
```bash theme={null}
heygen voice create --prompt "warm, confident female narrator with a slight British accent"
```
```json theme={null}
{
"data": {
"seed": 0,
"voices": [
{
"voice_id": "BDfLWYibC6on6hn2IqEC",
"name": "Warm Confident Narrator",
"gender": "female",
"language": "English",
"preview_audio_url": "https://files2.heygen.ai/voice-design/previews/..."
},
{
"voice_id": "1jgmj3JDxkh9ybd7CRzS",
"name": "Warm Confident Narrator",
"preview_audio_url": "..."
},
{
"voice_id": "Db84ogyBT4thl08lVok8",
"name": "Warm Pro Narrator",
"preview_audio_url": "..."
}
]
}
}
```
You get up to 3 voice options. Each includes a `preview_audio_url` you can listen to before committing.
The same prompt with the same seed always returns the same voices. Increment `--seed` to explore new batches:
```bash theme={null}
heygen voice create --prompt "warm, confident female narrator" --seed 1
heygen voice create --prompt "warm, confident female narrator" --seed 2
```
Take the `voice_id` and pass it to any video creation command.
```bash Video Agent (prompt-based) theme={null}
heygen video-agent create \
--prompt "A presenter introducing our new product line" \
--voice-id "BDfLWYibC6on6hn2IqEC"
```
```bash Video Create (full control) theme={null}
heygen video create -d '{
"type": "avatar",
"avatar_id": "avt_angela_01",
"script": "Welcome to the future of video creation.",
"voice_id": "BDfLWYibC6on6hn2IqEC"
}'
```
## Prompt tips
The quality of your voice depends on the quality of your description:
| Prompt | Result |
| ----------------------------------------------------------------- | ------------------------- |
| `"deep male voice with authority, like a movie trailer narrator"` | Dramatic, resonant bass |
| `"friendly young woman, upbeat and energetic, American accent"` | Casual, approachable |
| `"calm, measured British male, BBC documentary style"` | Professional, trustworthy |
| `"enthusiastic tech reviewer, fast-paced, excited"` | High energy, engaging |
| `"soft-spoken female, ASMR-like, soothing"` | Gentle, intimate delivery |
## Optional flags
| Flag | Description |
| ---------- | -------------------------------------------------------------- |
| `--gender` | `male` or `female` — narrows results |
| `--locale` | BCP-47 locale tag (e.g. `en-US`, `pt-BR`) for accent targeting |
| `--seed` | Increment to get different batches (default: `0`) |
## Browsing existing voices instead
If you'd rather use a stock voice:
```bash theme={null}
# All English female voices
heygen voice list --language English --gender female --limit 20
# Private voices (ones you've created)
heygen voice list --type private
```
# Docs to Video
Source: https://developers.heygen.com/docs-to-video
Convert README files, knowledge base articles, and documentation into avatar-led explainer videos via the HeyGen API. One command, one rendered video.
## The Problem
Documentation is essential but most people don't read it. Video walkthroughs get significantly more engagement — but recording, editing, and keeping them in sync with doc changes costs more than most teams can justify.
## How It Works
```
Doc changes → LLM writes a video prompt → Video Agent renders → Embed or distribute
```
You don't send docs directly to Video Agent. An LLM converts documentation into a structured video prompt — acting as a video producer who reads the source material and writes production direction.
## Build It
```python theme={null}
# From a file
with open("README.md") as f:
content = f.read()
# Or from a URL
import requests
content = requests.get(
"https://raw.githubusercontent.com/your-org/repo/main/README.md"
).text
```
```python theme={null}
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"""You are a video producer. Convert this documentation
into a HeyGen Video Agent prompt.
Structure as 3–5 scenes with timing. Open with a hook explaining what this
does and why it matters. Walk through key points visually. End with a next
step. Target: 60 seconds. Be specific about visuals.
Documentation:
{content}
Output ONLY the Video Agent prompt."""
}],
)
video_prompt = message.content[0].text
```
**The two-stage pattern:** Content → LLM (writes production prompt) → Video Agent (renders). The LLM bridges the gap between "what the docs say" and "what the video should show."
```python theme={null}
resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={
"X-Api-Key": HEYGEN_API_KEY,
"Content-Type": "application/json",
},
json={"prompt": video_prompt},
)
video_id = resp.json()["data"]["video_id"]
```
Optionally attach screenshots or diagrams as [file inputs](/docs/video-agent#file-input-formats). Then poll for completion.
## CI/CD Integration
Trigger video generation automatically when documentation changes:
```yaml theme={null}
# .github/workflows/docs-video.yml
name: Generate Doc Video
on:
push:
paths: ['docs/**', 'README.md', 'CHANGELOG.md']
jobs:
generate-video:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate video
env:
HEYGEN_API_KEY: ${{ secrets.HEYGEN_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: python scripts/generate-doc-video.py
```
Store API keys as [GitHub encrypted secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets). Never commit them.
## Variations
* **Changelog videos:** "Here's what's new in v2.3" — generate for each release
* **API docs:** Walk through new endpoints or breaking changes visually
* **Onboarding:** Auto-generate "Getting Started" videos from quickstart guides
* **Multi-language:** Generate, then [translate](/cookbook/video-agent/multilingual-content) for international docs
***
## Next Steps
Apply the same pattern to blog posts and articles.
Schedule video generation pipelines.
# API Key
Source: https://developers.heygen.com/docs/api-key
Generate your HeyGen API key in under a minute. Authenticate requests for avatar video generation, translation, and streaming.
## Getting Your API Key
1. Go to the [HeyGen API dashboard](https://app.heygen.com/home?from=\&nav=API)
2. Click to generate your API key.
## Configuring Your API Key
### Environment variable (recommended)
```bash bash theme={null}
export HEYGEN_API_KEY="your-api-key-here"
```
### `.env` file
If your project uses a `.env` file (common with Node.js, Python, or frameworks like Next.js):
```text theme={null}
HEYGEN_API_KEY=your-api-key-here
```
### Claude Code
If you're using Claude Code or any terminal-based workflow, set the key in your shell before starting:
```bash bash theme={null}
export HEYGEN_API_KEY="your-api-key-here"
claude # or whatever command starts your session
```
Alternatively, add it to your shell profile (`~/.bashrc`, `~/.zshrc`) so it persists across sessions:
```bash bash theme={null}
echo 'export HEYGEN_API_KEY="your-api-key-here"' >> ~/.zshrc
source ~/.zshrc
```
### HeyGen Skills (in Claude)
When using HeyGen through the Skills integration in Claude's computer environment, the API key is read from the environment. Make sure `HEYGEN_API_KEY` is set before the skill executes any API calls.
## Using the Key in Requests
All HeyGen API requests authenticate via the `X-Api-Key` header. The base URL for all endpoints is `https://api.heygen.com`. When auth fails, the API returns [`unauthorized` (401)](/docs/error-codes#unauthorized).
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/avatars" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```javascript Node.js theme={null}
const response = await fetch("https://api.heygen.com/v3/avatars", {
headers: { "X-Api-Key": process.env.HEYGEN_API_KEY },
});
```
```python Python theme={null}
import os, requests
response = requests.get(
"https://api.heygen.com/v3/avatars",
headers={"X-Api-Key": os.environ["HEYGEN_API_KEY"]}
)
```
### Quick verification
You can verify your key is working by fetching your account info. Full schema: [`GET /v3/users/me`](/reference/get-current-user).
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/users/me" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"data": {
"username": "jane_doe",
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"billing_type": "wallet",
"wallet": {
"currency": "usd",
"remaining_balance": 42.50,
"auto_reload": { "enabled": false }
}
}
}
```
A `200` response with your account details confirms your key is valid. The `billing_type` and corresponding billing field (`wallet`, `subscription`, or `usage_based`) show your current balance and billing model.
## Security Best Practices
* **Never commit your API key to version control.** Add `.env` to your `.gitignore`.
* **Never expose the key in client-side / browser code.** Always call the API from a backend or server environment.
* **Rotate your key periodically** via the API dashboard.
* **Monitor usage** in your [API dashboard](https://app.heygen.com/home?from=\&nav=API)
# Avatar Consent
Source: https://developers.heygen.com/docs/avatar-consent
How HeyGen verifies that the person in a digital twin agreed to be cloned — the three levels of consent access, and the API flow for collecting it.
Before you can generate video with a **digital twin** ([`type: "digital_twin"`](/docs/create-avatar#digital-twin)), HeyGen needs proof that the person depicted agreed to be cloned. This protects the avatar subject, your account, and HeyGen — and it's what lets you use a real person's likeness responsibly at scale.
Consent applies only to **digital twin** avatars. Photo avatars (`type: "photo"`) and prompt-to-avatar characters (`type: "prompt"`) depict no real, identifiable person and do **not** require consent.
## The three levels of access
Consent is offered as **three increasing levels of access**. Each level removes friction from the flow, and each is unlocked for a progressively narrower set of accounts. Higher levels are more powerful and correspondingly more restricted.
| Level | Flow | Who it's for | Availability |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -------------------------------------------------------------------------------- |
| **1. Record via webcam** | The avatar subject records a short consent statement on camera through HeyGen's hosted consent page. | All customers | Available today in v3 |
| **2. Upload a consent video** | You supply a pre-recorded consent video instead of recording live — more flexible, but consent is still explicitly captured. | Enterprise only, whitelisted accounts | Available today in v3 — [contact sales](https://www.heygen.com/contact-us/sales) |
| **3. Skip the consent flow** | Consent collection is waived entirely for accounts that have signed an indemnity agreement. | Enterprise only, whitelisted accounts | [Contact sales](https://www.heygen.com/contact-us/sales) |
## Collect consent via API
One endpoint drives both **Level 1** and **Level 2**: [`POST /v3/avatars/{group_id}/consent`](/reference/create-avatar-consent). By default it starts the webcam flow and returns a URL for the avatar subject; whitelisted enterprise accounts can pass a pre-recorded [`consent_video`](#upload-a-consent-video-level-2) instead.
### Record via webcam (Level 1)
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/avatars/group_xyz789/consent" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reroute_url": "https://heygen.com/consent-done"
}'
```
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `reroute_url` | string | No | Redirect URL after the subject completes consent. Defaults to HeyGen's own completion page. Applies to the webcam flow only. |
#### Response
```json theme={null}
{
"data": {
"avatar_group": {
"id": "group_xyz789",
"name": "My Digital Twin",
"consent_status": "pending",
"looks_count": 1,
"created_at": 1717000000
},
"url": "https://heygen.com/consent/abc123..."
}
}
```
| Field | Type | Description |
| ----------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url` | string | Consent page URL. Send this to the avatar subject to complete approval. Returned for the webcam flow; when you upload a `consent_video`, the group is all you need, so the field is omitted. |
| `avatar_group.consent_status` | string | Current consent status (e.g. `"pending"`). |
### Upload a consent video (Level 2)
Whitelisted enterprise accounts can submit a pre-recorded consent statement directly — same endpoint, with `consent_video` in place of the flow parameters:
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/avatars/group_xyz789/consent" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"consent_video": { "type": "url", "url": "https://example.com/consent.mp4" }
}'
```
| Parameter | Type | Required | Description |
| --------------- | ------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `consent_video` | object | Yes, for this flow | The pre-recorded consent video, as `{"type": "url", "url": "https://..."}` or `{"type": "asset_id", "asset_id": "..."}` (see [Upload Assets](/docs/upload-assets) for uploading). |
The video is submitted immediately for review — the subject never visits a consent page, so the response contains the `avatar_group` alone and no `url`. Because there is no hosted page in this flow, send `consent_video` on its own, without the webcam-flow parameters. This flow requires a whitelisted account — please [reach out to sales](https://www.heygen.com/contact-us/sales) or your account team to request access.
#### What the consent video must contain
Because the upload flow has no hosted page prompting the subject, you provide the statement yourself. Consent is validated **semantically** — the subject names themselves and explicitly allows HeyGen to use their footage — so minor wording differences are fine. A clear version of the statement:
> "I, \[Full Name], hereby allow HeyGen to use the footage of me to build a HeyGen avatar."
| Requirement | Detail |
| ----------------- | ----------------------------------------------------------------------------------------------------- |
| Consent statement | The subject says the line above: their name plus explicit permission for HeyGen to use their footage. |
| Same person | Must be the same person as the training footage (identity-matched). |
| One visible face | A single, clearly visible face on camera. |
| Audio | Clearly audible. |
| Length | A short clip is enough. |
| Format | A standard video file (for example MP4), supplied as a public HTTPS URL or an uploaded `asset_id`. |
No passcode is required for the upload path — a spoken code applies only to the interactive webcam flow (Level 1).
Check `consent_status` on the avatar group via [`GET /v3/avatars/{group_id}`](/reference/get-avatar-group) to know when consent is complete. It is `null` for photo and prompt avatars, which never require consent.
## Where consent fits
Consent is one step in the digital twin flow. To create the avatar itself, see [Create Avatar](/docs/create-avatar); to browse the resulting characters and looks, see [Avatars](/docs/avatars) and [Avatar Looks](/docs/avatar-looks).
# Avatar Looks
Source: https://developers.heygen.com/docs/avatar-looks
Browse and select avatar looks (outfits, poses, hair styles) for any HeyGen avatar group via the Avatar Looks API.
A **look** is one outfit/pose/style for a character ([avatar group](/docs/avatars)). It's the value you pass as `avatar_id` to video creation. List via [`GET /v3/avatars/looks`](/reference/list-avatar-looks), fetch one via [`GET /v3/avatars/looks/{look_id}`](/reference/get-avatar-look), or [Update](/reference/update-avatar-look) / [Delete](/reference/delete-avatar-look) your own. To create a new look, see [Create Avatar](/docs/create-avatar).
## Quick Example
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/avatars/looks?avatar_type=photo_avatar&ownership=public&limit=5" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"data": [
{
"id": "look_def456",
"name": "Monica - Business Casual",
"avatar_type": "photo_avatar",
"group_id": "group_abc123",
"gender": "female",
"preview_image_url": "https://files.heygen.ai/look/preview_def456.jpg",
"preview_video_url": "https://files.heygen.ai/look/preview_def456.mp4",
"default_voice_id": "voice_xyz789",
"tags": ["business", "casual", "female"],
"supported_api_engines": ["avatar_iv", "avatar_v"],
"status": "completed"
}
],
"has_more": true,
"next_token": "eyJsYXN0X2lkIjoiNDU2In0"
}
```
## Query Parameters
| Parameter | Type | Required | Default | Description |
| ------------- | ------- | -------- | ------- | ---------------------------------------------------------------------------------------- |
| `group_id` | string | No | — | Filter looks to a specific avatar group. Returns only looks belonging to this character. |
| `avatar_type` | string | No | all | `"studio_avatar"`, `"digital_twin"`, or `"photo_avatar"`. |
| `ownership` | string | No | all | `"public"` for HeyGen presets or `"private"` for your own. Omit for both. |
| `limit` | integer | No | `20` | Results per page (1–50). |
| `token` | string | No | — | Opaque cursor token for the next page. |
## Response Fields
Each look in the `data` array contains:
| Field | Type | Description |
| ----------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id` | string | Unique look identifier. **This is the value to pass as `avatar_id`** to [`POST /v3/videos`](/reference/create-video) or [`POST /v3/video-agents`](/reference/create-video-agent-session). |
| `name` | string | Display name of the look. |
| `avatar_type` | string | One of `"studio_avatar"`, `"digital_twin"`, or `"photo_avatar"`. Determines engine and parameter compatibility. |
| `group_id` | string or null | ID of the avatar group (character) this look belongs to. |
| `gender` | string or null | Gender of the avatar. |
| `preview_image_url` | string or null | URL to a preview image. |
| `preview_video_url` | string or null | URL to a preview video. |
| `default_voice_id` | string or null | Default voice ID for this look — see [Browse Voices](/docs/voices/search-voices). |
| `tags` | array | Tags associated with the look (e.g. `["business", "casual"]`). |
| `supported_api_engines` | array | Engine values this look accepts for [`POST /v3/videos`](/reference/create-video): `"avatar_iii"`, `"avatar_iv"`, and/or `"avatar_v"`. Check this array before requesting a specific `engine` — requesting an engine that isn't listed returns `invalid_parameter`. |
| `status` | string or null | Training status: `"processing"`, `"completed"`, or `"failed"`. Only present for private avatars. |
## Avatar Types
The `avatar_type` field determines what features and parameters are available when creating a video:
| Type | Description |
| --------------- | ------------------------------------------------------------------------------------------------------ |
| `studio_avatar` | Pre-built HeyGen studio avatars with fixed poses and backgrounds. |
| `digital_twin` | Avatars created from video footage. Support background removal by default on new twins. |
| `photo_avatar` | Avatars generated from a single photo. Support `motion_prompt` and `expressiveness` in video creation. |
## Get a Single Look
Full schema: [`GET /v3/avatars/looks/{look_id}`](/reference/get-avatar-look). To rename or retag, use [`PATCH /v3/avatars/looks/{look_id}`](/reference/update-avatar-look); to remove, [`DELETE`](/reference/delete-avatar-look).
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/avatars/looks/look_def456" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"data": {
"id": "look_def456",
"name": "Monica - Business Casual",
"avatar_type": "photo_avatar",
"group_id": "group_abc123",
"gender": "female",
"preview_image_url": "https://files.heygen.ai/look/preview_def456.jpg",
"preview_video_url": "https://files.heygen.ai/look/preview_def456.mp4",
"default_voice_id": "voice_xyz789",
"tags": ["business", "casual", "female"],
"supported_api_engines": ["avatar_iv", "avatar_v"],
"status": "completed"
}
}
```
## Filtering by Group
To see all outfits and styles for a specific character, pass its `group_id`:
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/avatars/looks?group_id=group_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
## Pagination
If `has_more` is `true`, pass the `next_token` value as the `token` query parameter to fetch the next page.
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/avatars/looks?token=eyJsYXN0X2lkIjoiNDU2In0" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
## Using a Look in Video Creation
Once you have a look `id`, pass it as `avatar_id`. When you request a specific rendering engine, pick one from the look's `supported_api_engines` array — for example, pass `"engine": {"type": "avatar_v"}` when the array includes `"avatar_v"`. Omitting `engine` uses Avatar IV. See [Models](/models) for how the engines compare.
```bash "Video Agent" theme={null}
curl -X POST "https://api.heygen.com/v3/video-agents" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A product demo for our new app",
"avatar_id": "look_def456"
}'
```
```bash "Avatar Video" theme={null}
curl -X POST "https://api.heygen.com/v3/videos" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "avatar",
"avatar_id": "look_def456",
"voice_id": "voice_xyz789",
"script": "Welcome to our product walkthrough."
}'
```
# Avatar Groups
Source: https://developers.heygen.com/docs/avatars
Generate AI avatar videos via the HeyGen API. Choose from 500+ stock avatars or train custom digital twins. Includes avatar looks, voices, and instant.
Avatars come in two layers: a **group** is a character (e.g. "Monica"); each group has one or more **looks** (outfits, poses, styles). This page is about listing and inspecting groups via [`GET /v3/avatars`](/reference/list-avatar-groups) and [`GET /v3/avatars/{group_id}`](/reference/get-avatar-group). To pick a specific look to render with, see [Avatar Looks](/docs/avatar-looks). To train your own avatar, see [Create Avatar](/docs/create-avatar).
## Quick Example
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/avatars?ownership=public&limit=5" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"data": [
{
"id": "group_abc123",
"name": "Monica",
"gender": "female",
"preview_image_url": "https://files.heygen.ai/avatar/preview_abc123.jpg",
"preview_video_url": "https://files.heygen.ai/avatar/preview_abc123.mp4",
"looks_count": 3,
"default_voice_id": "voice_xyz789",
"consent_status": null,
"status": "completed",
"created_at": 1711382400
}
],
"has_more": true,
"next_token": "eyJsYXN0X2lkIjoiMTIzIn0"
}
```
## Query Parameters
| Parameter | Type | Required | Default | Description |
| ----------- | ------- | -------- | ------- | ---------------------------------------------------------------------------------- |
| `ownership` | string | No | all | `"public"` for HeyGen's preset avatars or `"private"` for your own. Omit for both. |
| `limit` | integer | No | `20` | Results per page (1–50). |
| `token` | string | No | — | Opaque cursor token for the next page. |
## Response Fields
Each avatar group in the `data` array contains:
| Field | Type | Description |
| ------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | Unique group identifier. Pass to [`GET /v3/avatars/{group_id}`](/reference/get-avatar-group) for details, or use as `group_id` in [`GET /v3/avatars/looks`](/reference/list-avatar-looks) to filter. |
| `name` | string | Display name of the avatar character. |
| `gender` | string or null | Gender of the avatar. |
| `preview_image_url` | string or null | URL to a preview image. |
| `preview_video_url` | string or null | URL to a preview video. |
| `looks_count` | integer | Number of looks (outfits/styles) available for this character. |
| `default_voice_id` | string or null | Default voice ID for this avatar — pair with [Browse Voices](/docs/voices/search-voices) or [`GET /v3/voices`](/reference/list-voices). |
| `consent_status` | string or null | Consent status for the group. `null` means consent is not required. |
| `status` | string or null | Training status: `"processing"`, `"pending_consent"`, `"completed"`, or `"failed"`. Only present for private avatars. |
| `created_at` | integer | Unix timestamp of creation. |
## Get a Single Avatar Group
Full schema: [`GET /v3/avatars/{group_id}`](/reference/get-avatar-group).
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/avatars/group_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"data": {
"id": "group_abc123",
"name": "Monica",
"gender": "female",
"preview_image_url": "https://files.heygen.ai/avatar/preview_abc123.jpg",
"preview_video_url": "https://files.heygen.ai/avatar/preview_abc123.mp4",
"looks_count": 3,
"default_voice_id": "voice_xyz789",
"consent_status": null,
"status": "completed",
"created_at": 1711382400
}
}
```
## Pagination
If `has_more` is `true`, pass the `next_token` value as the `token` query parameter to fetch the next page.
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/avatars?token=eyJsYXN0X2lkIjoiMTIzIn0" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
## Avatars vs. Looks
Avatar **groups** represent characters (e.g. "Monica"). Each group has one or more **looks** — different outfits, poses, or styles for that character. When creating a video, you pass a **look ID** (not a group ID) as the `avatar_id`. See [Avatar Looks](/docs/avatar-looks) for how to browse and select looks.
# Choosing the Right Video API
Source: https://developers.heygen.com/docs/choosing-the-right-video-api
Compare HeyGen's video APIs - Video Agent, Direct Video, and Cinematic Avatar. Pick the right endpoint for your use case in this side-by-side decision.
HeyGen offers three ways to create videos programmatically. The right choice depends on how much control you need and whether you want a spoken script or a prompt-composed cinematic shot.
| | Video Agent | Direct Video | Cinematic Avatar |
| ------------------------- | ----------------------------- | ----------------- | ---------------------------------------------- |
| **Endpoint** | `POST /v3/video-agents` | `POST /v3/videos` | `POST /v3/videos` (`type: "cinematic_avatar"`) |
| **Input** | Natural language prompt | Structured JSON | Prompt + 1–3 avatar looks |
| **Script writing** | Agent writes it | You write it | None — motion driven by the prompt |
| **Avatar selection** | Agent picks (or you override) | You specify | You specify 1–3 looks |
| **Voice selection** | Agent picks (or you override) | You specify | None — no spoken voice |
| **Interactive iteration** | ✅ Via chat mode | ❌ | ❌ |
| **Webhook support** | ✅ `callback_url` | ✅ `callback_url` | ✅ `callback_url` |
| **Control level** | Low (prompt-driven) | High (explicit) | Medium (prompt + your looks) |
## Video Agent — best for speed
Send a text prompt, get a video. The agent handles scripting, avatar selection, and scene composition automatically.
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/video-agents" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A 60-second onboarding video for our SaaS product. Friendly tone.",
"callback_url": "https://yourapp.com/webhook/heygen"
}'
```
**Use when:**
* You want a video fast without managing avatars or scripts
* You're building a product where end users describe videos in natural language
* You want to iterate interactively — use `mode: "chat"` to review the storyboard before rendering
**Trade-off:** Less control over exact scene composition and creative choices.
## Direct Video — best for control
Explicitly specify the avatar, voice, and script. Predictable, repeatable output for automated pipelines.
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/videos" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "avatar",
"avatar_id": "your_look_id",
"voice_id": "your_voice_id",
"script": "Hi there! This video was created just for you.",
"aspect_ratio": "auto",
"resolution": "1080p",
"callback_url": "https://yourapp.com/webhook/heygen"
}'
```
**Use when:**
* Building automated pipelines (personalized sales videos, daily reports)
* You need exact control over avatar, voice, and script
* Generating videos programmatically from data (CRM records, form submissions)
**Trade-off:** You handle all creative decisions — avatar IDs and voice IDs must be known upfront.
## Cinematic Avatar — best for cinematic shots
A prompt-driven variant of `POST /v3/videos`. Hand HeyGen 1–3 avatar looks plus a natural-language prompt and the Seedance pipeline composes the scene, motion, and framing — no script or voice. See the full [Cinematic Avatar guide](/cinematic-avatar).
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/videos" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "cinematic_avatar",
"prompt": "A founder walks through a sunlit startup office, gesturing toward a whiteboard, shot handheld in a documentary style.",
"avatar_id": ["your_look_id"],
"aspect_ratio": "16:9",
"resolution": "1080p",
"duration": 10,
"callback_url": "https://yourapp.com/webhook/heygen"
}'
```
**Use when:**
* You want cinematic b-roll or motion of an avatar rather than a talking-head script
* You want to feature up to three looks in one composed shot
* You want to steer style and motion with your own reference videos and images
**Trade-off:** No spoken script or voice, and output is capped at **720p / 1080p** (4K is not supported). Clips run 4–15 seconds.
## Not sure which to pick?
Start with Video Agent. If you need precise control over the script, avatar, or timing, switch to `POST /v3/videos`. If you want a prompt-composed cinematic shot with no script, reach for [Cinematic Avatar](/cinematic-avatar).
You can also combine them — use Video Agent to explore ideas and find the right style, then recreate with explicit parameters for the final production version.
# Create Avatar
Source: https://developers.heygen.com/docs/create-avatar
Create a HeyGen avatar from video footage, a single photo, or a text prompt. Covers all three creation types, generating new looks for an existing avatar, the consent flow, and how to render videos with your trained avatar.
Three creation modes all go through [`POST /v3/avatars`](/reference/create-avatar) — distinguished by the `type` field. The result is a new **look** that you pass as `avatar_id` to video creation endpoints. To browse existing avatars and looks instead, see [Avatars](/docs/avatars) and [Avatar Looks](/docs/avatar-looks).
## Pick your flow
| You have... | You want... | Use |
| ------------------------- | -------------------------------------------------- | ------------------------------------------------------- |
| Video footage of a person | A reusable digital twin of that person | [`type: "digital_twin"`](#digital-twin) |
| A photo of a person | An avatar from that photo, fast | [`type: "photo"`](#photo-avatar) |
| Just a text description | A fully synthetic AI character | [`type: "prompt"`](#prompt-to-avatar) |
| An existing HeyGen avatar | A **new look** for it (new outfit, setting, style) | [`type: "prompt"`](#prompt-to-avatar) **+** `avatar_id` |
The last row is the most-overlooked path: once you have any HeyGen avatar (digital twin, photo, or prompt), you can use the prompt endpoint to generate additional looks for that same character — see [Generate new looks for an existing avatar](#generate-new-looks-for-an-existing-avatar).
## Creation Methods
### Digital Twin (`type: "digital_twin"`)
Create an avatar from video footage. The speaker in the video becomes a reusable digital twin.
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/avatars" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "digital_twin",
"name": "My Digital Twin",
"file": { "type": "url", "url": "https://example.com/training-footage.mp4" }
}'
```
| Parameter | Type | Required | Description |
| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type` | string | Yes | Must be `"digital_twin"`. |
| `name` | string | Yes | Display name for the avatar. |
| `file` | object | Yes | Training video. `{ "type": "url", "url": "..." }`, `{ "type": "asset_id", "asset_id": "..." }` (from [`POST /v3/assets`](/reference/upload-asset)), or `{ "type": "base64", "media_type": "video/mp4", "data": "..." }`. |
| `avatar_group_id` | string | No | Attach to an existing character identity. Omit to create a new one. |
### Photo Avatar (`type: "photo"`)
Create an avatar from a single photo. Quick to set up — no video recording needed.
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/avatars" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "photo",
"name": "My Photo Avatar",
"file": { "type": "url", "url": "https://example.com/headshot.png" }
}'
```
| Parameter | Type | Required | Description |
| ----------------- | ------ | -------- | ------------------------------------------------------------------- |
| `type` | string | Yes | Must be `"photo"`. |
| `name` | string | Yes | Display name for the avatar. |
| `file` | object | Yes | Photo image. Same format options as digital twin `file`. |
| `avatar_group_id` | string | No | Attach to an existing character identity. Omit to create a new one. |
**Want to generate new outfits or settings from a photo using a prompt?** Create the photo avatar first, then call `type: "prompt"` with the returned `avatar_item.id` as `avatar_id` — see [Generate new looks for an existing avatar](#generate-new-looks-for-an-existing-avatar). The image you upload here becomes the visual reference that prompt-driven variations are conditioned on.
### Prompt-to-Avatar (`type: "prompt"`) — Tokyo Pipeline
Generate an entirely new AI avatar from a text description. No photo or video needed — describe the character you want and the Tokyo pipeline creates it.
Prompt-to-Avatar uses HeyGen's Tokyo pipeline to generate a unique AI character from your text description. The avatar is fully synthetic — no real person is depicted.
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/avatars" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "prompt",
"name": "Space Commander",
"prompt": "Young woman, early 30s, confident expression, short silver hair, warm brown eyes, wearing a dark blue space suit with mission patches, standing in a modern spacecraft bridge with holographic displays"
}'
```
#### Parameters
| Parameter | Type | Required | Description |
| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type` | string | Yes | Must be `"prompt"`. |
| `name` | string | Yes | Display name for the avatar. |
| `prompt` | string | Yes | Text description of the avatar's appearance, clothing, setting, and style. Max 1000 chars. Be specific for best results. |
| `avatar_id` | string | No | An existing look ID to use as the **visual reference** for the generation. The new look is saved to the referenced avatar's group; if `avatar_group_id` is also provided, the avatar must belong to that group and the result is saved there. The referenced avatar must exist and have a usable image, otherwise the request is rejected. Use this to create new looks (outfits, settings, styles) for an avatar you already created — see [Generate new looks for an existing avatar](#generate-new-looks-for-an-existing-avatar). |
| `avatar_group_id` | string | No | The identity (group) to **save** the generated avatar to. By default a new identity is created. If `avatar_id` is also provided, it must belong to this group. This field only controls where the result is saved — it does not drive the visual reference; use `avatar_id` for that. |
| `reference_images` | array | No | Up to **3** reference images for additional style/setting guidance. Each entry is `{ "type": "url", "url": "..." }`, `{ "type": "asset_id", "asset_id": "..." }` (from [`POST /v3/assets`](/reference/upload-asset)), or `{ "type": "base64", "media_type": "image/png", "data": "..." }`. Can be used on their own or layered on top of an `avatar_id` reference. |
#### How `avatar_id` and `avatar_group_id` combine
| `avatar_id` | `avatar_group_id` | Visual reference | Saved to |
| ----------- | ----------------- | --------------------------------------- | ---------------------------------------------------- |
| ✗ | ✗ | none — generated purely from the prompt | a new group |
| ✗ | ✓ | none — generated purely from the prompt | that group |
| ✓ | ✗ | the referenced avatar's image | the referenced avatar's group |
| ✓ | ✓ | the referenced avatar's image | `avatar_group_id` (the avatar **must** belong to it) |
`reference_images` layer additional guidance on top of any of these combinations; with no `avatar_id` they are used on their own.
**Changed behavior (June 2026):** `avatar_group_id` previously conditioned the generation on one of the group's looks. It now only controls where the result is saved. If you relied on it for character consistency, pass the base look's ID as `avatar_id` instead — see the [changelog](/changelog).
**Prompting tips for best results:**
* Be specific about age, gender, expression, and clothing
* Describe the setting/background
* Mention lighting or mood (e.g., `"warm studio lighting"`, `"cinematic"`)
* Reference images help with style consistency but are optional
#### Generate new looks for an existing avatar
If you already have a HeyGen avatar — from any creation type — you can generate additional looks for that same character by calling `type: "prompt"` with the existing look's ID as `avatar_id`. The referenced look's image is used as the visual reference that conditions the new generation, so the character's identity stays consistent across the new outfit, setting, or style described in your prompt. The new look is saved to the referenced avatar's group automatically — no `avatar_group_id` needed.
Typical sequence — say you have a photo of a person and want several prompt-driven variations:
1. Create the photo avatar once:
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/avatars" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "photo",
"name": "Sarah",
"file": { "type": "url", "url": "https://example.com/sarah-headshot.png" }
}'
```
Save the returned `avatar_item.id` (e.g. `look_abc123`).
2. Generate as many additional looks as you need, each conditioned on Sarah's identity:
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/avatars" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "prompt",
"name": "Sarah — Navy Blazer Office",
"prompt": "Wearing a navy blazer, modern office background with plants, warm natural light",
"avatar_id": "look_abc123"
}'
```
Each call returns a new `avatar_item.id` (a new look) attached to the same `avatar_group.id`. Browse all looks for an avatar via [`GET /v3/avatars/looks?group_id=...`](/reference/list-avatar-looks) — and pass any look's ID as `avatar_id` to use it as the reference for the next variation.
Passing only `avatar_group_id` (without `avatar_id`) saves the new look to that group but does **not** use any of the group's images as a visual reference — the result is generated purely from the prompt. To keep a character's identity consistent, pass the base look's ID as `avatar_id`.
#### With reference images
`reference_images` (up to 3) add style or setting guidance to the generation. They can be used on their own — to guide a brand-new identity — or layered on top of an `avatar_id` reference for extra control over the new look:
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/avatars" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "prompt",
"name": "Brand Ambassador — Plant Studio",
"prompt": "Professional woman in her 40s, warm smile, wearing a navy blazer, modern office background with plants",
"avatar_id": "look_abc123",
"reference_images": [
{ "type": "url", "url": "https://example.com/style-reference.png" },
{ "type": "url", "url": "https://example.com/setting-reference.jpg" }
]
}'
```
#### Errors
| Condition | HTTP status | Error code |
| --------------------------------------------------------------------------------------- | ----------- | ------------------- |
| `avatar_id` not found | 404 | `AVATAR_NOT_FOUND` |
| `avatar_id` exists but has no usable image (e.g. generation not completed, or rejected) | 400 | `INVALID_PARAMETER` |
| `avatar_id` does not belong to the provided `avatar_group_id` | 400 | `INVALID_PARAMETER` |
## Response
All three creation types return the same response shape:
```json theme={null}
{
"data": {
"avatar_item": {
"id": "look_abc123",
"name": "Space Commander",
"avatar_type": "studio_avatar",
"group_id": "group_xyz789",
"preview_image_url": "https://files.heygen.ai/...",
"supported_api_engines": ["avatar_iv", "avatar_v"],
"tags": []
},
"avatar_group": {
"id": "group_xyz789",
"name": "Space Commander",
"looks_count": 1,
"consent_status": null,
"created_at": 1717000000
}
}
}
```
| Field | Type | Description |
| ----------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `avatar_item.id` | string | The look ID — pass this as `avatar_id` to [`POST /v3/videos`](/reference/create-video) or [Create Video Agent Session](/reference/create-video-agent-session). |
| `avatar_item.avatar_type` | string | `"digital_twin"`, `"photo_avatar"`, or `"studio_avatar"`. |
| `avatar_item.supported_api_engines` | array | Engine values compatible with this look for [`POST /v3/videos`](/reference/create-video). |
| `avatar_item.group_id` | string | The character identity this look belongs to. |
| `avatar_group.id` | string | Group ID. Use to add more looks or initiate consent. |
| `avatar_group.consent_status` | string or null | `null` for photo and prompt avatars. Digital twins may require consent — see below. |
## Avatar Consent
Before you can generate video with a **digital twin**, the person depicted must consent to being cloned. Photo avatars and prompt-to-avatar characters do not require consent (their `consent_status` is `null`).
HeyGen offers three increasing levels of consent access — recording via webcam (all customers), uploading a consent video through the same API endpoint, and skipping consent entirely. The two higher levels require a whitelisted enterprise account — reach out to sales or your account team for access. The API flows, the full breakdown of the three levels, and how to request them all live on the dedicated page.
How consent works, who each level is for, and the `POST /v3/avatars/{group_id}/consent` flow.
## Avatars vs. Looks
An **avatar group** is a character identity (e.g. "Sarah"). Each group can have multiple **looks** — different outfits, poses, or styles. When creating a video, you pass a look ID (not a group ID) as the `avatar_id`. Use [`GET /v3/avatars/looks`](/reference/list-avatar-looks) to browse looks (see [Avatar Looks](/docs/avatar-looks)), or pass `avatar_group_id` when creating a new avatar to add a look to an existing character.
# Discord
Source: https://developers.heygen.com/docs/discord
Join the HeyGen developer Discord for API help, feature announcements, and community recipes. Talk to the API team and other developers building on HeyGen.
Engineers and PMs are active in the community every day.
Got a question the docs didn't fully answer? Stuck on an integration? Want to see what others are building? Our [Discord](https://discord.gg/mGKRsCtSC3) is the fastest way to get real help — from the engineers and PMs who build the product, and from developers who've probably hit the same wall you're hitting.
Ask questions, get direct access to engineers and PMs, and connect with developers building with HeyGen. Free to join, no waitlist.
No ticket queues. Post in the right channel and get answers from engineers who built the feature — often within hours.
Share feedback, report rough edges, and request features. PMs actively monitor the community and feed insights back to the team.
Browse what others are building, share your own projects, and find patterns and workarounds not in the docs yet.
| Channel | What it's for |
| :-------------------- | :-------------------------------------------------------------- |
| `#general` | General questions about the API and docs |
| `#heygen-api` | Questions, integrations, and troubleshooting for the HeyGen API |
| `#hyperframes` | Showcase and discuss HyperFrames projects |
| `#heygen-cli-and-mcp` | Support and discussions for the HeyGen CLI and MCP tools |
| `#hackathon` | project ideas, submissions, and hackathon updates |
| `#announcements` | Release notes and major updates |
# Enterprise Pricing
Source: https://developers.heygen.com/docs/enterprise-pricing
Get HeyGen Enterprise API pricing for high-volume video generation. Custom rate limits, SLAs, dedicated support, and per-second billing. Contact sales.
## Overview
Enterprise plans work the same way as [self-serve](/docs/pricing) — the difference is the unit. Where self-serve bills in USD, Enterprise plans are billed in **credits**, and **1 credit costs \$0.50**. To map an enterprise rate to the self-serve page, multiply the credit rate by \$0.50: for example, Photo Avatar video at 0.1 credits / sec is the equivalent of \$0.05 / sec.
## Pricing
### Video Generation — Avatar V
Highest-fidelity engine with cross-reference-driven animation — see the [Avatar V model page](/avatar-v) for engine details. Avatar V supports Digital Twins only.
| Avatar Type | Rate |
| ------------ | ----------------- |
| Digital Twin | 0.1 credits / sec |
### Video Generation — Avatar IV
Default v3 engine — see the [Avatar IV model page](/avatar-iv) for engine details.
| Avatar Type | Rate |
| ------------- | ----------------- |
| Photo Avatar | 0.1 credits / sec |
| Digital Twin | 0.1 credits / sec |
| Studio Avatar | 0.1 credits / sec |
### Video Generation — Avatar III
Dedicated photo-to-video pipeline — see the [Avatar III model page](/avatar-iii) for engine details.
| Avatar Type | Rate |
| ------------- | -------------------- |
| Digital Twin | 0.0167 credits / sec |
| Studio Avatar | 0.0167 credits / sec |
| Photo Avatar | 0.0433 credits / sec |
### Video Generation — Avatar III (Legacy v1/v2)
This is the **older Avatar III engine** on the legacy **v1/v2 endpoints** — a different pipeline from the `avatar_iii` engine above. It is available to existing customers only and is not offered on the new developer platform. For all new integrations use the v3 engines: [Avatar IV](/avatar-iv), [Avatar V](/avatar-v), or [Avatar III](/avatar-iii).
| Avatar Type | Rate |
| ------------- | -------------------- |
| Photo Avatar | 0.0033 credits / sec |
| Digital Twin | 0.0033 credits / sec |
| Studio Avatar | 0.0033 credits / sec |
### Cinematic Avatar
Flat rate per video (4–15 seconds, 720p/1080p only), not billed by duration. See the [Cinematic Avatar guide](/cinematic-avatar).
| Item | Rate |
| ---------------------- | ----------------- |
| Cinematic Avatar video | 7 credits / video |
### Avatar Realtime
Live streaming avatar session, billed per second of session duration (720p only). See the [Avatar Realtime guide](/avatar-realtime).
| Feature | Rate |
| ----------------------- | ------------------ |
| Avatar Realtime session | 0.05 credits / sec |
### Video Agent
| Feature | Rate |
| --------------- | -------------------- |
| Prompt to Video | 0.0667 credits / sec |
### HyperFrames
Billed per minute of output video; the rate scales with `resolution` and `fps`. See the [HyperFrames guide](/hyperframes).
| Resolution / Frame Rate | Rate |
| ----------------------- | ------------------ |
| 1080p / 30 fps | 0.1 credits / min |
| 1080p / 60 fps | 0.2 credits / min |
| 4K / 30 fps | 0.15 credits / min |
| 4K / 60 fps | 0.3 credits / min |
### Video Translation
| Mode | Rate |
| -------------------- | --------------------- |
| Speed — Audio Only | 0.05 credits / sec |
| Speed — Lip Sync | 0.05 credits / sec |
| Precision — Lip Sync | 0.1 credits / sec |
| Proofread | 0.00833 credits / sec |
### Lipsync
| Mode | Rate |
| --------- | ------------------ |
| Speed | 0.05 credits / sec |
| Precision | 0.1 credits / sec |
### AI Clipping
Turn a long-form video into short highlight clips, billed per clip produced. See the [AI Clipping guide](/ai-clipping).
| Item | Rate |
| -------------- | ------------------- |
| Highlight clip | 0.15 credits / clip |
### Text-to-Speech
| Model | Rate |
| ----------------- | ---------------------- |
| Speech — Starfish | 0.000333 credits / sec |
### Avatar Creation
| Operation | Rate |
| ------------ | --------------- |
| Digital Twin | 1 credit / call |
| Photo Avatar | 1 credit / call |
***
## Concurrency Limits
| Plan | Max Concurrent Video Jobs |
| ---------- | ------------------------- |
| Enterprise | 20+ (varies by contract) |
Concurrent jobs include any asynchronous generation in progress: Video Agent sessions, avatar video renders, and video translations. Exceeding the limit returns `429 Too Many Requests` with a `Retry-After` header.
***
## Endpoint Limits
### Video Generation Input
Resources provided to `POST /v3/videos` must meet these limits. Invalid resources will cause render failures.
| Resource Type | Supported Formats | Max File Size | Max Resolution |
| ------------- | ----------------- | ------------- | -------------- |
| Video | MP4, WebM | 100 MB | \< 2K |
| Image | JPG, PNG | 50 MB | \< 2K |
| Audio | WAV, MP3 | 50 MB | — |
Requirements:
* Resource URLs must be **publicly accessible** (no authentication required).
* The file extension must **match the actual file format**.
* Files must not be **corrupted or malformed**.
### Avatar Input
* **Script text:** Maximum 5,000 characters.
* **Audio input:** Maximum 10 minutes (600 seconds).
### Video Agent Input
* **Prompt:** 1–10,000 characters.
* **File attachments:** Up to 20 files. Supported types: image (PNG, JPEG), video (MP4, WebM), audio (MP3, WAV), and PDF.
* Files can be provided as an `asset_id` (from `POST /v3/assets`), an HTTPS URL, or base64-encoded content.
### Asset Upload (`POST /v3/assets`)
* **Maximum file size:** 32 MB. The same limit applies to files provided by URL. For larger files, use the [direct upload flow](/docs/upload-assets#upload-large-files-direct-upload) (`POST /v3/assets/direct-uploads`).
* **Supported types:** Image (PNG, JPEG), video (MP4, WebM), audio (MP3, WAV), and PDF.
### Text-to-Speech Input (`POST /v3/voices/speech`)
* **Text length:** 1–5,000 characters.
* **Speed multiplier:** 0.5× to 2.0×.
* **Input type:** Plain text or SSML markup.
### Output Video Specifications
* **Frame rate:** 25 fps for videos containing avatars.
* **Resolution:** Width and height must each be between 128 and 4,096 pixels. Default output is 1080p (up to 4K on Enterprise).
* **Aspect ratio:** 16:9 or 9:16.
* **Maximum scenes:** 50 per video.
* **Maximum duration:** Custom (contact your account team).
***
## Pagination
Most list endpoints use cursor-based pagination with a `limit` parameter and `next_token` for the next page.
| Endpoint | Default | Max |
| ---------------------------------------------- | ------- | --- |
| `GET /v3/videos` | 10 | 100 |
| `GET /v3/avatars` | 20 | 50 |
| `GET /v3/avatars/looks` | 20 | 50 |
| `GET /v3/voices` | 20 | 100 |
| `GET /v3/video-agents/styles` | 20 | 100 |
| `GET /v3/video-translations` | 10 | 100 |
| `GET /v3/webhooks/endpoints` | 10 | 100 |
| `GET /v3/webhooks/events` | 10 | 100 |
| `GET /v3/video-agents/sessions/{id}/resources` | 8 | 100 |
***
## Rate Limiting
All endpoints enforce rate limits. When exceeded, the API returns `429 Too Many Requests` with a `Retry-After` header indicating the number of seconds to wait before retrying.
# Enterprise Billing — Dollar-Based
Source: https://developers.heygen.com/docs/enterprise-pricing-dollar-base
Buy HeyGen API capacity in dollars, not credits. Enterprise dollar-based billing lets your team forecast spend across avatars, video agent, translation, and.
| Model | How It Works | Best For |
| ------------------- | ----------------------------------------------------------- | ------------------------------------------------------------- |
| Usage-Based Billing | Monthly Minimum Commitment (MMC) with overage billing | Teams with predictable, recurring API usage |
| Dollar Packages | Purchase an annual pool of dollars upfront under a contract | Teams that prefer a fixed annual spend with flexible drawdown |
Both models authenticate with an **API Key** (`x-api-key` header). Check your balance at any time with `GET /v3/users/me → wallet`.
## Usage-Based Billing
Usage-based billing pairs a flat Monthly Minimum Commitment (MMC) with per-second usage billing. If you exceed the included usage in a given month, overage is billed at a slightly higher rate.
### How It Works
1. **Monthly Minimum Commitment (MMC):** A flat fee charged monthly, regardless of usage.
2. **Included Usage:** Each tier includes a pool of usage dollars per month at your contracted rate.
3. **Overage:** Usage beyond the included pool is billed at the overage rate per second.
For pricing details and available tiers, [contact our sales team](https://www.heygen.com/contact-us/sales).
## Dollar Packages
Dollar packages let you purchase a fixed annual pool of dollars under a contract. Your balance is drawn down as you use the API throughout the year.
### How It Works
1. **Annual Contract:** You agree to a total dollar amount for the contract term (typically 12 months).
2. **Drawdown:** Your balance is consumed per second as you use HeyGen's API.
3. **Balance Tracking:** Monitor your remaining balance at any time via `GET /v3/users/me → wallet`.
For packaging options and contract terms, [contact our sales team](https://www.heygen.com/contact-us/sales).
# Error Codes
Source: https://developers.heygen.com/docs/error-codes
Error codes, HTTP status codes, and troubleshooting for the HeyGen API
HeyGen uses conventional HTTP response codes to indicate the success or failure of an API request. Codes in the `2xx` range indicate success. Codes in the `4xx` range indicate an error with the information provided (e.g., a missing parameter, insufficient credits, or a resource not found). Codes in the `5xx` range indicate an error on HeyGen's servers.
Every error response includes a machine-readable `code`, a human-readable `message`, and a `doc_url` linking to the relevant section below. Some errors that relate to a specific request field also include a `param` attribute.
## Error response format
```json theme={null}
{
"error": {
"code": "insufficient_credit",
"message": "Your account has 5 credits but this video requires 10 credits.",
"doc_url": "https://developers.heygen.com/docs/error-codes#insufficient-credit"
}
}
```
| Attribute | Type | Description |
| --------- | ------ | ----------------------------------------------------------------------------------- |
| `code` | string | A short, machine-readable identifier for the error. See the full list below. |
| `message` | string | A human-readable description of what went wrong and, where possible, how to fix it. |
| `param` | string | The request field that caused the error. Only present for validation errors. |
| `doc_url` | string | A link to the documentation for this specific error code. |
## HTTP status code summary
| Status | Meaning |
| --------------------------- | ------------------------------------------------------------------------- |
| `200 OK` | Everything worked as expected. |
| `400 Bad Request` | The request was malformed or contained invalid parameters. |
| `401 Unauthorized` | No valid API key was provided. |
| `402 Payment Required` | The request requires additional credits or a plan upgrade. |
| `403 Forbidden` | The API key doesn't have permission to perform the request. |
| `404 Not Found` | The requested resource doesn't exist. |
| `429 Too Many Requests` | Too many requests hit the API too quickly, or a usage quota was exceeded. |
| `500 Internal Server Error` | Something went wrong on HeyGen's end. |
***
## Error codes
### `unauthorized`
**HTTP status:** `401`
The API key provided is invalid, expired, or missing. Verify that you are sending your API key in the `X-Api-Key` header and that the key is active in your [HeyGen account settings](https://app.heygen.com/settings).
### `forbidden`
**HTTP status:** `403`
The API key is valid but does not have permission to perform the requested action. This can occur when accessing organization-level resources with a member-level key.
### `resource_access_denied`
**HTTP status:** `403`
The authenticated user does not have access to the specific resource referenced in the request. The resource may belong to a different user or organization. Verify that the resource ID is correct and belongs to your account.
### `ai_vendor_access_restricted`
**HTTP status:** `403`
The workspace has restricted which AI vendor companies may be used. The action or model you requested relies on a vendor that is not allowed under the workspace’s AI vendor access policy. Ask a workspace administrator to update the policy if this vendor should be permitted.
### `voice_not_usable`
**HTTP status:** `403`
The voice referenced in the request cannot currently be used to generate this video. The voice is in a state that blocks generation and will not resolve on retry. Select a different voice.
### `rate_limit_exceeded`
**HTTP status:** `429`
You are sending requests too frequently. Back off and retry with exponential backoff. Check the `Retry-After` response header for the number of seconds to wait before retrying. See our [rate limits documentation](https://docs.heygen.com/reference/rate-limits) for per-endpoint limits.
### `quota_exceeded`
**HTTP status:** `429`
You have exceeded a usage quota (e.g., the free-tier limit for video agent or AI clip requests). Upgrade your plan or wait for your quota to reset. Check your current usage in the [HeyGen dashboard](https://app.heygen.com).
### `insufficient_credit`
**HTTP status:** `402`
Your account does not have enough credits to complete this request. The error message includes how many credits you have and how many are required. Purchase additional credits or reduce the scope of your request (e.g., shorter video duration, fewer scenes).
### `trial_limit_exceeded`
**HTTP status:** `402`
You have reached the video generation limit for trial accounts. Upgrade to a paid plan to continue creating videos.
### `plan_upgrade_required`
**HTTP status:** `402`
The requested feature or resource requires a higher subscription tier than your current plan. This can occur when:
* Using a premium avatar that is not available on your plan.
* Accessing an integration that requires a higher tier.
* Requesting a resolution or feature gated by plan level.
Upgrade your plan in the [HeyGen dashboard](https://app.heygen.com/pricing) to access this feature.
### `video_not_found`
**HTTP status:** `404`
No video, draft, or video translation was found matching the provided ID. Verify that:
* The `video_id` is correct and was not mistyped.
* The video has not been deleted.
* The video belongs to your account.
### `avatar_not_found`
**HTTP status:** `404`
No avatar was found matching the provided ID. This applies to all avatar types — standard avatars, photo avatars (photars), instant avatars, and avatar kits. Verify that:
* The `avatar_id` is correct.
* The avatar has finished training (if recently created).
* The avatar belongs to your account or is a public avatar.
### `voice_not_found`
**HTTP status:** `404`
No voice was found matching the provided ID. Verify that the `voice_id` is correct and that the voice is available in your account. If using a cloned voice, ensure it has finished processing.
### `template_not_found`
**HTTP status:** `404`
No template was found matching the provided ID. Verify that the `template_id` is correct and that the template is shared with your account or is publicly available.
### `asset_not_found`
**HTTP status:** `404`
No asset was found matching the provided ID. Assets may have been deleted or may not have finished uploading. Verify that the `asset_id` was returned from a successful `POST /v1/asset` call and that the asset has not been removed.
### `webhook_not_found`
**HTTP status:** `404`
No webhook endpoint was found matching the provided ID. Verify that the `endpoint_id` is correct and that the webhook has not been deleted. List your existing webhooks with `GET /v3/webhooks/endpoints` to find valid endpoint IDs.
### `batch_not_found`
**HTTP status:** `404`
No batch was found matching the provided ID. Verify that the `batch_id` is correct and that the batch belongs to your account.
### `resource_not_found`
**HTTP status:** `404`
The requested resource was not found. This is a generic not-found error for resources that do not have a more specific error code (e.g., streaming sessions, audio records). Verify that the resource ID is correct and belongs to your account.
### `invalid_parameter`
**HTTP status:** `400`
One or more request parameters are invalid, missing, or in the wrong format. The `message` field describes which parameter failed validation and why. The `param` field, when present, identifies the specific field.
Common causes:
* A required field is missing from the request body.
* A field value is the wrong type (e.g., string instead of number).
* A field value is outside the allowed range or not in the set of accepted values.
* The request body is not valid JSON or is not a JSON object.
### `conflict`
**HTTP status:** `409`
The request conflicts with existing state. For example, attempting to create a webhook endpoint with a URL that is already registered for your account. Use a different value or delete the existing resource first.
### `resource_not_ready`
**HTTP status:** `409`
The requested resource exists but is not yet in a ready state. This can occur when a video translation is still processing or an instant avatar has not finished training. Poll the resource status and retry once it reaches a ready state. Also returned when the uploaded object is not yet present in storage (e.g. `POST /v3/assets/{asset_id}/complete` called before the upload PUT landed); safe to retry.
### `request_in_progress`
**HTTP status:** `409`
A prior request with this `Idempotency-Key` is still in progress. Wait for the original request to complete and retry. Once the original request finishes, subsequent retries with the same key within 24 hours replay the original response.
### `content_policy_violation`
**HTTP status:** `400`
The request was rejected for violating HeyGen's content policy. This can occur when an instant avatar does not pass the moderation review or when submitted content contains inappropriate content. Create a new resource that complies with our [usage policy](https://www.heygen.com/policy).
### `unlimited_mode_disabled`
**HTTP status:** `400`
The avatar does not support unlimited mode. Use a different avatar, or use Avatar IV or Avatar V.
### `avatar_consent_required`
**HTTP status:** `400`
The avatar group used in the request requires consent before it can be used to generate a video (its instant-avatar consent was skipped, rejected, or never completed). Complete the consent flow for the avatar group (e.g. `POST /v3/avatars/{group_id}/consent`), then retry the request.
### `resource_limit_reached`
**HTTP status:** `400`
You have reached the maximum number of a resource allowed for your account (e.g., voice clone slots, instant avatar redo attempts, verified avatar group slots). Delete unused resources to free up capacity, wait for limits to reset, or contact [HeyGen support](https://help.heygen.com) to request a higher limit.
### `voice_unavailable`
**HTTP status:** `400`
The requested voice exists but is not in a usable state. This occurs when a cloned voice failed processing, expired, or was canceled. Delete the voice and create a new voice clone, or use a different voice.
### `script_too_short`
**HTTP status:** `400`
The script provided is too short to generate a video. HeyGen requires the text-to-speech audio to be at least 1.0 second long. Very short scripts (a single word, a period, or a few characters) will not produce enough audio. Add more content to your script and retry the request.
### `tts_text_invalid`
**HTTP status:** `400`
The text provided for text-to-speech conversion is invalid or cannot produce speech. Check that the script is not empty and contains speakable words or valid pauses, then retry.
### `download_failed`
**HTTP status:** `400`
A URL provided in your request could not be downloaded. This applies to video URLs, image URLs, audio URLs, and any other user-supplied resource link. Common causes:
* The URL is not publicly accessible (authentication required, private video, restricted sharing settings).
* The URL is malformed or points to a page rather than a direct file.
* The remote server refused the connection or returned an error.
* For Google Drive links, the file must be shared with "Anyone with the link" access.
* For YouTube/Vimeo, the video must be public (unlisted or private videos are not supported).
Check the `message` field for details about which URL failed and why.
### `batch_too_large`
**HTTP status:** `400`
The batch request contains more items than the maximum allowed. Split the request into smaller batches and retry.
### `batch_item_invalid`
**HTTP status:** `400`
One or more items in the batch request failed validation. Check the `message` field for details about which item failed and why, correct the payload, and retry.
### `too_many_ids`
**HTTP status:** `400`
The request specified more IDs than the maximum allowed in a single call. Reduce the number of IDs and split the request across multiple calls.
### `video_delete_failed`
**HTTP status:** `500`
The video could not be deleted due to an internal error. Retry the request. If the error persists, contact [HeyGen support](https://help.heygen.com) with the `video_id`.
### `internal_error`
**HTTP status:** `500`
An unexpected error occurred on HeyGen's servers. This is not caused by your request. If the error persists, contact [HeyGen support](https://help.heygen.com) and include the full error response for faster debugging.
### `voice_provider_error`
**HTTP status:** `502`
An upstream voice provider that HeyGen relies on (for text-to-speech, voice cloning, or voice design) returned an error or was temporarily unavailable. This is not caused by your request. Retry after a short delay. If the error persists, contact [HeyGen support](https://help.heygen.com) and include the full error response for faster debugging.
### `gateway_timeout`
**HTTP status:** `504`
A resource referenced in your request (e.g., a URL for background audio or an image) could not be downloaded within the time limit. Verify that the URL is publicly accessible, responds quickly, and is not blocked by firewall or geo-restrictions. Retry if the target server was temporarily slow.
### `service_unavailable`
**HTTP status:** `503`
A downstream service needed to fulfill your request (e.g., the text-to-speech synthesis backend) is temporarily overloaded and did not respond in time. This is transient and not caused by your request. Back off and retry after a short delay.
### `hyperframes_project_invalid`
**HTTP status:** `400`
The HyperFrames project zip you supplied isn't a valid composition. Make sure the zip contains an `index.html` (or the `composition` entry file you specified) at the root or in a single top-level directory, and that it opens correctly with the `hyperframes` CLI before submitting.
### `hyperframes_project_too_large`
**HTTP status:** `413`
The HyperFrames project zip exceeds the maximum allowed size for the ingestion method you used. Use `asset_id` (pre-upload via `POST /v1/asset`) for projects larger than the `url` / `base64` caps.
### `hyperframes_render_not_found`
**HTTP status:** `404`
No HyperFrames render with that `render_id` exists for your space, or the render has been soft-deleted. Check that the `render_id` is correct and was created under the same API key / space.
# For AI Agents
Source: https://developers.heygen.com/docs/for-ai-agents
Agent-first onboarding for AI coding agents — detect auth before writing code, follow the MCP → CLI → raw API ladder, and go from a blank session to a finished video.
**Who this is for.** AI coding agents acting on a user's behalf — Claude Code, Codex, Cursor, Gemini CLI, Manus, and similar. Humans should start with the [Quick Start](/docs/quick-start) instead.
The most common failure mode for an agent generating a HeyGen video is: read the API reference → write a `curl` script → fail at auth → hand the user a "ready-to-run" file that never actually runs. This page exists to make that failure impossible. **Detect auth before writing any integration code.**
## 1. Detect what's already wired up
Before writing code, check what HeyGen tooling is available in the current environment. Use the **first** one that resolves — don't combine them.
If `mcp__heygen__*` tools are visible in your toolset, the user is already connected to the [HeyGen Remote MCP](/mcp/overview) over OAuth. **Use these tools directly.** No API key, no key handling, no separate billing — videos consume the user's existing HeyGen plan credits.
Verify with `mcp__heygen__get_current_user`. If it returns a profile, you're authenticated. Skip to [step 3](#3-generate-the-video).
Not connected yet? Point the user at the host-specific setup guide: [Claude Code](/mcp/claude-code) · [Claude Web](/mcp/claude-web) · [Gemini CLI](/mcp/gemini-cli) · [Manus](/mcp/manus) · [OpenAI](/mcp/open-ai) · [Superhuman](/mcp/superhuman). For Cursor and other hosts, use the generic endpoint instructions in the [MCP overview](/mcp/overview).
**Not installed?** Offer to install it for the user — it's a single binary, no runtime dependencies, headless-friendly:
```bash theme={null}
curl -fsSL https://static.heygen.ai/cli/install.sh | bash
```
Installs to `~/.local/bin`. macOS (Apple Silicon and Intel) and Linux (x64 and arm64) supported. Verify with `heygen --version`. See the [CLI Overview](/cli) for full details.
**Already installed?** Authenticate it.
For agent environments, **prefer the environment variable** — it takes precedence over stored credentials and works headlessly without an interactive prompt:
```bash theme={null}
export HEYGEN_API_KEY=
```
Then verify with `heygen auth status`. If `HEYGEN_API_KEY` is already set, you're done — no further action.
Interactive login (`heygen auth login`) is fine for a developer at a terminal, but as an agent you should not depend on it: it blocks on a TTY paste prompt and writes to `~/.heygen/credentials`, neither of which composes well with scripted flows. Only suggest it as a fallback if the user explicitly prefers it.
If neither auth source is available, ask once and stop:
```text theme={null}
HeyGen CLI installed but no credentials found. Set one of:
export HEYGEN_API_KEY= (preferred for agent use)
heygen auth login (interactive, stores in ~/.heygen)
Get your key from https://app.heygen.com/settings?nav=API — it's shown once.
Reply "ready" when done and I'll generate the video.
```
Once authenticated, drive the workflow via [`heygen` commands](/commands) — JSON output by default, no need to hand-roll requests.
No MCP, no CLI — fall back to direct HTTPS calls to `api.heygen.com`. Check for `HEYGEN_API_KEY` in the environment first. If it's missing, **do not** invent a key, hardcode one, or prompt the user to paste one into chat. Tell them:
```text theme={null}
HeyGen API key not found. Set it with:
export HEYGEN_API_KEY=
Get the key from https://app.heygen.com/settings?nav=API (shown once on creation).
Reply "ready" when set and I'll generate the video.
```
Once `HEYGEN_API_KEY` is set, follow the [Quick Start](/docs/quick-start) flow: `POST /v3/video-agents` → poll `GET /v3/videos/{video_id}`. Prefer the [Video Agent](/docs/video-agent) endpoint over assembling avatars/voices manually — it handles scripting, casting, and composition for you.
## 2. Install HeyGen skills (recommended)
For agent hosts that support installable skills (Claude Code, Codex, Cursor, OpenClaw, Hermes), install the official [`heygen-com/skills`](https://github.com/heygen-com/skills) repo. It ships a runtime contract, mode-detection ladder, and producer pipelines for video, avatar creation, and translation — so each new session starts from the same baseline instead of re-deriving it from the API reference.
Agent-facing install guide: [`INSTALL_FOR_AGENTS.md`](https://github.com/heygen-com/skills/blob/master/INSTALL_FOR_AGENTS.md). Hand the URL to the agent and it will pick the right install path for the host.
| Agent host | Default skill install path |
| ----------- | ----------------------------------------------------------------------- |
| Claude Code | `~/.claude/skills/heygen-skills` |
| Codex | `~/.codex/skills/heygen-skills` |
| Cursor | `~/.cursor/skills/heygen-skills` |
| OpenClaw | `~/.openclaw/skills/heygen-skills` (or `clawhub install heygen-skills`) |
| Hermes | `~/.hermes/skills/heygen-skills` |
## 3. Generate the video
Once authentication is confirmed, prefer one direct action over a multi-file scaffold. For most prompts:
* **MCP**: call `mcp__heygen__create_video_from_avatar` or the Video Agent variant.
* **CLI**: `heygen video-agent create --prompt ""` — JSON output by default, see [Output Modes](/output-modes).
* **API**: `POST /v3/video-agents` with `{ "prompt": "" }`, then poll `GET /v3/videos/{video_id}` until `status` is `completed` (1–5 minutes). In production, pass a `callback_url` and skip polling.
For prompt structure (scenes, pacing, visual style, casting), see [Writing Effective Video Prompts](/writing-effective-video-prompts). For visual styles, see [Video Agent Styles](/video-agent-with-styles).
## Rules for agents
* **Check auth before writing code.** Run the verifier for whichever path resolved (`mcp__heygen__get_current_user`, `heygen auth status`, or a probe of `GET /v3/users/me`). Don't write integration code on the assumption that auth will work.
* **Never ask the user to paste an API key into chat.** Direct them to the [dashboard](https://app.heygen.com/settings?nav=API), have them `export HEYGEN_API_KEY=` (preferred — works headlessly for both CLI and raw API), then continue. `heygen auth login` is only a fallback for users at an interactive terminal.
* **If the CLI isn't installed, offer to install it.** It's a single binary (`curl -fsSL https://static.heygen.ai/cli/install.sh | bash`) and is strictly a better surface than raw `curl` for agent flows — structured JSON output, fewer lines of code, and the same `HEYGEN_API_KEY` env-var path.
* **Never invent a key, account, or URL.** If something is missing, say so and ask.
* **Prefer Video Agent over manual assembly.** A single `POST /v3/video-agents` produces a finished video; chaining avatar + voice + composition endpoints by hand is slower and more error-prone unless the user explicitly needs that control. See [Choosing the Right Video API](/docs/choosing-the-right-video-api).
* **Polling has a ceiling.** Most videos complete in 1–5 minutes. Use exponential backoff, respect `Retry-After` on `429`s ([Usage Limits](/docs/usage-limits)), and prefer `callback_url` in production.
* **Don't hand the user "ready-to-run" scripts as a substitute for actually generating the video.** If you're blocked, name the blocker in one sentence and ask for the one thing you need.
## When things break
* Auth errors → [API Key guide](/docs/api-key).
* Video failed (non-2xx, or `status: failed` with `failure_code`) → [Error Codes](/docs/error-codes).
* Rate-limited → [Usage Limits](/docs/usage-limits).
* Translation, lipsync, avatars, voices → start at the [Cookbook overview](/overview) and follow the per-product card.
# Interactive Sessions
Source: https://developers.heygen.com/docs/interactive-sessions
Build realtime avatar conversations with the HeyGen Interactive Avatar API. Stream live video, low-latency speech, and bidirectional audio over LiveKit-backed.
Interactive sessions give you a multi-turn conversation with the Video Agent. Instead of going straight to rendering, the agent pauses at checkpoints (like storyboard review) so you can provide feedback, adjust direction, and approve before the final video is generated. For the fire-and-forget alternative, see [Prompt to Video](/docs/video-agent).
## Session lifecycle
[`POST /v3/video-agents`](/reference/create-video-agent-session) with `"mode": "chat"` — Send your initial prompt. The agent begins processing.
[`GET /v3/video-agents/{session_id}`](/reference/get-video-agent-session) — Check progress and read agent messages. The session pauses at `reviewing` status.
[`POST /v3/video-agents/{session_id}`](/reference/send-message-or-request-revision) — Send feedback or approve the storyboard. Repeat as needed.
Send a message with `auto_proceed: true` or approve the storyboard. The session moves to `generating`, then `completed`. Fetch the final video with [`GET /v3/videos/{video_id}`](/reference/get-video).
### Session statuses
| Status | Description |
| ------------------- | ------------------------------------------------------------------------------------ |
| `thinking` | Agent is working (scripting, composing scenes, preparing storyboard). |
| `waiting_for_input` | Agent is paused, waiting for your input. |
| `reviewing` | Agent is paused at a review checkpoint. Review the storyboard and messages. |
| `generating` | Storyboard approved — video is rendering. |
| `completed` | Video is ready. Retrieve it via [`GET /v3/videos/{video_id}`](/reference/get-video). |
| `failed` | Something went wrong. Check messages for error details. |
## Create a session
Full schema: [`POST /v3/video-agents`](/reference/create-video-agent-session). Pass `"mode": "chat"` to enable interactive mode.
### Request body
| Parameter | Type | Required | Description |
| -------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `prompt` | string | **Yes** | Initial message to the agent (1–10,000 characters). |
| `mode` | string | No | Set to `"chat"` for interactive sessions. Defaults to `"generate"` (one-shot). |
| `avatar_id` | string | No | Specific avatar look ID. |
| `voice_id` | string | No | Specific voice ID for narration. |
| `orientation` | string | No | `"landscape"` or `"portrait"`. Auto-detected if omitted. |
| `files` | array | No | Up to 20 file attachments (asset\_id, url, or base64). See [Upload Assets](/docs/upload-assets) and [`POST /v3/assets`](/reference/upload-asset). |
| `auto_proceed` | boolean | No | If `true`, skip interactive review and go straight to video generation. Default: `false`. |
| `callback_url` | string | No | [Webhook](/docs/webhooks) URL for completion/failure notifications. |
| `callback_id` | string | No | Caller-defined ID echoed back in the webhook payload. |
Set `auto_proceed: true` to skip the review step entirely — the session behaves like the one-shot mode but you still get a `session_id` to track.
### Example
```bash curl theme={null}
curl -X POST "https://api.heygen.com/v3/video-agents" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Create a 2-minute onboarding video for new engineering hires. Cover team culture, dev tools, and first-week checklist.",
"mode": "chat",
"orientation": "landscape"
}'
```
```python Python theme={null}
import requests
resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={"X-Api-Key": HEYGEN_API_KEY},
json={
"prompt": "Create a 2-minute onboarding video for new engineering hires.",
"mode": "chat",
"orientation": "landscape",
},
)
session = resp.json()["data"]
session_id = session["session_id"]
```
### Response
```json theme={null}
{
"data": {
"session_id": "sess_abc123",
"status": "thinking",
"video_id": null,
"created_at": 1711382400
}
}
```
## Poll session status
Full schema: [`GET /v3/video-agents/{session_id}`](/reference/get-video-agent-session). Returns the current session status, progress percentage, chat messages, and the `video_id` once generation starts.
### Response
```json theme={null}
{
"data": {
"session_id": "sess_abc123",
"status": "reviewing",
"progress": 45,
"title": "Engineering Onboarding Video",
"video_id": null,
"created_at": 1711382400,
"messages": [
{
"role": "model",
"content": "I've drafted a storyboard with 4 scenes covering team culture, dev environment setup, key tools, and the first-week checklist. Would you like to review it or should I proceed?",
"type": "text",
"created_at": 1711382450,
"resource_ids": ["res_storyboard_001"]
},
{
"role": "user",
"content": "Create a 2-minute onboarding video for new engineering hires.",
"type": "text",
"created_at": 1711382400,
"resource_ids": null
}
]
}
}
```
### Response fields
| Field | Type | Description |
| ------------ | -------------- | -------------------------------------------------------------------------------------------------- |
| `session_id` | string | Session identifier. |
| `status` | string | Current status: `thinking`, `waiting_for_input`, `reviewing`, `generating`, `completed`, `failed`. |
| `progress` | integer | Progress percentage (0–100). |
| `title` | string \| null | Agent-generated session title. |
| `video_id` | string \| null | Video ID once generation starts. Use with [`GET /v3/videos/{video_id}`](/reference/get-video). |
| `created_at` | integer | Unix timestamp of session creation. |
| `messages` | array | Most recent visible messages (max 40, newest-first). |
### Message object
| Field | Type | Description |
| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `role` | string | `"user"` or `"model"`. |
| `content` | string | Message text. |
| `type` | string | `"text"`, `"resource"`, or `"error"`. |
| `created_at` | integer \| null | Unix timestamp. |
| `resource_ids` | array \| null | Resource IDs resolvable via [`GET /v3/video-agents/{session_id}/resources/{resource_id}`](/reference/get-session-resource). |
## Send a follow-up message
Full schema: [`POST /v3/video-agents/{session_id}`](/reference/send-message-or-request-revision). Send feedback, request changes, or approve the storyboard. The agent processes your message and updates the session.
### Request body
| Parameter | Type | Required | Description |
| -------------- | ------- | -------- | -------------------------------------------------------------------------------------------- |
| `message` | string | **Yes** | Your message to the agent (1–10,000 characters). |
| `avatar_id` | string | No | Override avatar for this message. |
| `voice_id` | string | No | Override voice for this message. |
| `files` | array | No | Additional file attachments (max 20). |
| `auto_proceed` | boolean | No | If `true`, skip remaining review steps and generate the video immediately. Default: `false`. |
### Example: Request changes
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/video-agents/sess_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "Looks great, but add a scene about our code review process before the checklist scene."
}'
```
### Example: Approve and generate
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/video-agents/sess_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "Looks perfect, go ahead and generate the video.",
"auto_proceed": true
}'
```
### Response
```json theme={null}
{
"data": {
"session_id": "sess_abc123",
"run_id": "run_def456",
"title": "Engineering Onboarding Video"
}
}
```
After sending a message, poll [`GET /v3/video-agents/{session_id}`](/reference/get-video-agent-session) to see the agent's response and updated status.
## Get a session resource
Full schema: [`GET /v3/video-agents/{session_id}/resources/{resource_id}`](/reference/get-session-resource). Retrieve a specific resource by ID — storyboard images, draft videos, selected avatars, and voices are all exposed as resources. Resource IDs are referenced in message `resource_ids` arrays. To list all videos generated by a session, use [`GET /v3/video-agents/{session_id}/videos`](/reference/list-session-videos).
### Example
```bash theme={null}
curl "https://api.heygen.com/v3/video-agents/sess_abc123/resources/res_storyboard_001" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### Response
```json theme={null}
{
"data": {
"resource_id": "res_storyboard_001",
"resource_type": "image",
"source_type": "generated",
"url": "https://files.heygen.ai/resources/res_storyboard_001.png",
"thumbnail_url": "https://files.heygen.ai/resources/res_storyboard_001_thumb.png",
"created_at": 1711382450,
"metadata": {}
}
}
```
### Resource object
| Field | Type | Description |
| --------------- | --------------- | -------------------------------------------------------- |
| `resource_id` | string | Unique identifier. Referenced in message `resource_ids`. |
| `resource_type` | string | Type: `image`, `video`, `draft`, `avatar`, `voice`, etc. |
| `source_type` | string \| null | `"generated"` or `"user_uploaded"`. |
| `url` | string \| null | Primary media URL. |
| `thumbnail_url` | string \| null | Thumbnail URL. |
| `preview_url` | string \| null | Preview URL. |
| `created_at` | integer \| null | Unix timestamp. |
| `metadata` | object \| null | Type-specific metadata. |
## Stop a session
Full schema: [`POST /v3/video-agents/{session_id}/stop`](/reference/stop-video-agent-session). Stop an in-progress agent run — the agent halts at the next checkpoint, and partial results are preserved.
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/video-agents/sess_abc123/stop" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
```
```json Response theme={null}
{
"data": {
"session_id": "sess_abc123"
}
}
```
# Overview
Source: https://developers.heygen.com/docs/overview
Get the lay of the land on the HeyGen developer platform. See what's available across avatars, video agent, translation, streaming, MCP, and SDKs in one.
Video Agent is the fastest way to create videos programmatically. Describe what you want in plain text, and the agent handles avatar selection, scripting, scene composition, and production — all in a single API call.
## How It Works
POST a text description to `POST /v3/video-agents`. Optionally attach files, pick an avatar, or apply a style.
The agent writes a script, selects visuals, and renders the video asynchronously. You receive a `session_id` immediately, and a `video_id` once generation begins.
Poll `GET /v3/videos/{video_id}` until `status` is `completed`, then download via `video_url`. Or use a `callback_url` to get notified automatically.
## Quick Start
```bash curl theme={null}
curl -X POST "https://api.heygen.com/v3/video-agents" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Create a 30-second product walkthrough for a new project management app"
}'
```
```python Python theme={null}
import requests
resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={"X-Api-Key": HEYGEN_API_KEY},
json={
"prompt": "Create a 30-second product walkthrough for a new project management app"
},
)
data = resp.json()["data"]
print(data["session_id"], data["status"])
```
```javascript Node.js theme={null}
const resp = await fetch("https://api.heygen.com/v3/video-agents", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "Create a 30-second product walkthrough for a new project management app",
}),
});
const { data } = await resp.json();
console.log(data.session_id, data.status);
```
```json Response theme={null}
{
"data": {
"session_id": "sess_abc123",
"status": "generating",
"video_id": null,
"created_at": 1711382400
}
}
```
`video_id` is `null` on creation and is populated once the agent begins rendering. Poll `GET /v3/video-agents/{session_id}` to track progress and retrieve the `video_id`.
## Two Modes of Operation
Video Agent supports two workflows depending on how much control you need:
| Mode | How to use | Best for |
| --------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **Generate** (`mode: "generate"`) | `POST /v3/video-agents` — default | Fire-and-forget. Send a prompt, get a video. The agent auto-proceeds through the storyboard. |
| **Chat** (`mode: "chat"`) | `POST /v3/video-agents` with `"mode": "chat"` | Multi-turn interaction. The agent may pause for decisions (e.g. picking a voice), supports revisions and follow-up videos. |
Both modes support the same file inputs, avatar/voice overrides, and style options.
```json Chat mode example theme={null}
{
"prompt": "Create a product walkthrough for our new app",
"mode": "chat"
}
```
Use `POST /v3/video-agents/{session_id}` to send follow-up messages, answer the agent's questions, or request revisions in a chat session.
## Processing Time
Video generation is asynchronous. Processing times depend on video length, complexity, and your plan tier.
| Factor | Typical Range |
| -------------------- | ------------------------------------------------------------------- |
| **Standard plans** | 5x–10x the final video length (e.g. a 1-min video takes \~5–10 min) |
| **Enterprise plans** | Faster processing with priority queue access |
| **Multi-scene** | Each scene adds to total processing time |
| **Peak hours** | Processing may take longer during high-traffic periods |
If a video has been processing for more than 24 hours, something is likely wrong. Contact [HeyGen Support](https://help.heygen.com) with your `video_id`.
**Best practices:**
* Use `callback_url` instead of polling to reduce unnecessary API calls
* Set reasonable poll intervals (10–30 seconds) if polling
* Display a progress indicator to end users based on the 5x–10x benchmark
## Choosing the Right Video API
| Feature | Video Agent | Direct Video (`v3`) |
| -------------------- | ------------------------------- | ---------------------- |
| **Endpoint** | `POST /v3/video-agents` | `POST /v3/videos` |
| **Input** | Natural language prompt | Structured JSON |
| **Avatar selection** | Agent chooses (or you override) | You specify |
| **Script writing** | Agent writes it | You write it |
| **Best for** | Quick prototypes, simple videos | Programmatic pipelines |
| **Control level** | Low (prompt-driven) | High (explicit) |
Start with Video Agent. If you need precise control over script, avatar, or timing, use `POST /v3/videos` directly.
## Key Concepts
**Session** — Every Video Agent request creates a session (`session_id`). Sessions track the agent's work: prompt, storyboard, generated assets, and final video. Retrieve session state via `GET /v3/video-agents/{session_id}`.
**Video ID** — The `video_id` is populated once rendering begins. Poll `GET /v3/videos/{video_id}` for status and the final download URL.
**Styles** — Curated visual templates that control scene composition, pacing, and aesthetics. Browse them via `GET /v3/video-agents/styles` and pass a `style_id` to your request.
**File attachments** — Images, videos, audio, and PDFs you provide as context. The agent uses these as visual references or content sources. Pass them via the `files` array as `url`, `asset_id`, or `base64` inputs.
**Incognito mode** — Set `incognito_mode: true` to disable memory injection and extraction for a session.
## Error Handling
All Video Agent endpoints return errors in a consistent format:
```json theme={null}
{
"error": {
"code": "invalid_parameter",
"message": "'prompt' is required and must be 1-10000 characters.",
"param": "prompt",
"doc_url": null
}
}
```
| Status | Meaning |
| ------ | ------------------------------------------------------------------------------------- |
| `400` | Invalid request parameters. Check the `param` field for which field caused the error. |
| `401` | Authentication failed. Verify your API key or Bearer token. |
| `429` | Rate limit exceeded. Retry after the seconds specified in the `Retry-After` header. |
For video-specific failures (e.g. rendering errors), check `failure_code` and `failure_message` on the video status response.
# Self-Serve Pricing
Source: https://developers.heygen.com/docs/pricing
Compare HeyGen API pricing across self-serve plans. See per-minute video costs, monthly credit allocations, and free tier limits. No credit card needed.
HeyGen's self-serve (Pay-As-You-Go) plan lets you purchase USD balance when you need it — no monthly subscription, no commitments.
## How Billing Works
When you authenticate with an **API Key** (`x-api-key` header), you are billed under the **API tier**. Usage is deducted from your prepaid USD wallet.
Check your balance at any time:
```text theme={null}
GET /v3/users/me → wallet
```
## Pricing
### Video Generation — Avatar V
Highest-fidelity engine with cross-reference-driven animation — see the [Avatar V model page](/avatar-v) for engine details. Avatar V supports Digital Twins only.
| Avatar Type | 720p / 1080p |
| ------------ | -------------- |
| Digital Twin | \$0.0667 / sec |
### Video Generation — Avatar IV
Default v3 engine — see the [Avatar IV model page](/avatar-iv) for engine details.
| Avatar Type | 720p / 1080p |
| ------------- | -------------- |
| Photo Avatar | \$0.05 / sec |
| Digital Twin | \$0.0667 / sec |
| Studio Avatar | \$0.0667 / sec |
### Video Generation — Avatar III
Dedicated photo-to-video pipeline — see the [Avatar III model page](/avatar-iii) for engine details.
| Avatar Type | 720p / 1080p |
| ------------- | -------------- |
| Digital Twin | \$0.0167 / sec |
| Studio Avatar | \$0.0167 / sec |
| Photo Avatar | \$0.0433 / sec |
### Cinematic Avatar
Flat rate per video (4–15 seconds, 720p/1080p only), not billed by duration. See the [Cinematic Avatar guide](/cinematic-avatar).
| Item | Rate |
| ---------------------- | -------------- |
| Cinematic Avatar video | \$7.00 / video |
### Avatar Realtime
Live streaming avatar session, billed per second of session duration (720p only). See the [Avatar Realtime guide](/avatar-realtime).
| Feature | Rate |
| ----------------------- | ------------ |
| Avatar Realtime session | \$0.05 / sec |
### Video Agent
| Feature | Rate |
| --------------- | -------------- |
| Prompt to Video | \$0.0333 / sec |
### HyperFrames
Billed per minute of output video; the rate scales with `resolution` and `fps`. See the [HyperFrames guide](/hyperframes).
| Resolution / Frame Rate | Rate |
| ----------------------- | ------------ |
| 1080p / 30 fps | \$0.10 / min |
| 1080p / 60 fps | \$0.20 / min |
| 4K / 30 fps | \$0.15 / min |
| 4K / 60 fps | \$0.30 / min |
### Video Translation
| Mode | Rate |
| -------------------- | -------------- |
| Speed — Audio Only | \$0.0333 / sec |
| Speed — Lip Sync | \$0.0333 / sec |
| Precision — Lip Sync | \$0.0667 / sec |
> **Note:** Proofread mode is available on Enterprise plans only.
### Lipsync
| Mode | Rate |
| --------- | -------------- |
| Speed | \$0.0333 / sec |
| Precision | \$0.0667 / sec |
### AI Clipping
Turn a long-form video into short highlight clips, billed per clip produced. See the [AI Clipping guide](/ai-clipping).
| Item | Rate |
| -------------- | ------------- |
| Highlight clip | \$0.15 / clip |
### Text-to-Speech
| Model | Rate |
| ----------------- | ---------------- |
| Speech — Starfish | \$0.000667 / sec |
### Avatar Creation
| Operation | Rate |
| ------------ | --------------- |
| Digital Twin | \$1.00 per call |
| Photo Avatar | \$1.00 per call |
# Quick Start
Source: https://developers.heygen.com/docs/quick-start
Generate your first AI video with the HeyGen API in minutes — authenticate, send one request, and get back an MP4.
Create a key in Settings → API. Every request below needs it.
Fire a real request from your browser — before writing any code.
**Base URL** `https://api.heygen.com` · **Auth header** `X-Api-Key: `
## Your first video
Create a key in [Settings → API](https://app.heygen.com/home?from=\&nav=API). For key rotation, see the [API Key guide](/docs/api-key).
```bash theme={null}
export HEYGEN_API_KEY="your-api-key-here"
```
Send a prompt to the [Video Agent](/reference/create-video-agent-session) — it scripts, picks the avatar and voice, and renders.
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/video-agents" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "A presenter explaining our product launch in 30 seconds"}'
```
```python theme={null}
import requests
resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={"X-Api-Key": HEYGEN_API_KEY},
json={"prompt": "A presenter explaining our product launch in 30 seconds"},
)
session_id = resp.json()["data"]["session_id"]
```
```javascript theme={null}
const resp = await fetch("https://api.heygen.com/v3/video-agents", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "A presenter explaining our product launch in 30 seconds",
}),
});
const { session_id } = (await resp.json()).data;
```
```json Response theme={null}
{ "data": { "session_id": "sess_abc123", "status": "generating", "video_id": null } }
```
Watch it build live at `https://app.heygen.com/video-agent/{session_id}`.
Poll the session for a `video_id`, then the video for its `video_url` — or skip polling with a [webhook](/docs/webhooks) via `callback_url`.
```python theme={null}
import time
# 1. Wait for the video_id to be assigned
video_id = None
while not video_id:
sess = requests.get(
f"https://api.heygen.com/v3/video-agents/{session_id}",
headers={"X-Api-Key": HEYGEN_API_KEY},
).json()["data"]
video_id = sess.get("video_id")
if not video_id:
time.sleep(5)
# 2. Poll the video until it's done
while True:
video = requests.get(
f"https://api.heygen.com/v3/videos/{video_id}",
headers={"X-Api-Key": HEYGEN_API_KEY},
).json()["data"]
if video["status"] in ("completed", "failed"):
break
time.sleep(10)
print(video["video_url"])
```
```bash theme={null}
# 1. Session → video_id
curl "https://api.heygen.com/v3/video-agents/sess_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY"
# 2. video_id → video_url
curl "https://api.heygen.com/v3/videos/vid_xyz789" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response (completed) theme={null}
{ "data": { "id": "vid_xyz789", "status": "completed", "video_url": "https://files.heygen.ai/video/vid_xyz789.mp4", "duration": 32.5 } }
```
## Pick your endpoint
Prompt → finished video. **Flagship.**
Pick the avatar, voice, and script yourself.
Prompt-driven cinematic shots from avatar looks.
30+ languages with voice cloning and lip-sync.
Text → natural speech audio.
HTML/CSS/JS → motion-graphics video. **New.**
## Working from an agent or terminal?
Skip the raw HTTP — both surfaces wrap the same API.
Plug HeyGen into Claude, Cursor, or any MCP-capable agent — it handles the calls for you.
`heygen video create`, `heygen video download` — scriptable from any shell or CI job.
## Build with your stack
Migrating from **v1/v2**? Supported until **October 31, 2026** — see the [version comparison](/endpoint-version-comparison).
## Troubleshooting
Inspect `failure_code` and `failure_message` on [`GET /v3/videos/{video_id}`](/reference/get-video). Full catalog in [Error Codes](/docs/error-codes).
Confirm the `X-Api-Key` header is set and the key is active in [Settings → API](https://app.heygen.com/home?from=\&nav=API).
Rate or concurrency limit hit. Respect `Retry-After` and back off. See [Usage Limits](/docs/usage-limits).
A URL you passed couldn't be fetched — it must be publicly accessible and point directly at the file.
# Slack Integration
Source: https://developers.heygen.com/docs/slack
Connect HeyGen to Slack for video render notifications, agent triggers, and team-level activity feeds. Setup takes a few minutes with no code required.
Transform your Slack messages into professional AI-generated videos, instantly.
## What is HeyGen for Slack?
The HeyGen Slack app brings the power of AI video generation directly into your workspace. Create professional videos from text prompts without leaving Slack — perfect for team updates, tutorials, announcements, and more.
## Features
* **Instant video creation** - @mention HeyGen with your video idea and get a video in minutes
* **Emoji reactions** - React to any message with 🎥 to turn it into a video
* **Message curation** - Use `/heygen-curate` to find and compile top messages into videos
* **Personal accounts** - Connect your own HeyGen account to use your credits and avatars
* **Rich previews** - HeyGen video links automatically unfurl with thumbnails and metadata
## Installation
### Prerequisites
* **Slack workspace admin permissions** to install apps
* **A HeyGen account** with available video credits ([sign up here](https://app.heygen.com))
* Your HeyGen **username** and **space ID** ready
### Step 1: Install the app
1. Go to the [HeyGen Slack App](https://slack.com/oauth/v2/authorize?client_id=2341957757140.9185742217618\&scope=app_mentions:read,channels:history,channels:read,chat:write,commands,files:read,files:write,groups:history,groups:read,im:history,im:read,im:write,links:read,links:write,mpim:history,mpim:read,reactions:read,users:read\&user_scope=openid,profile) in the Slack App Directory
2. Click **Add to Slack**
3. Select your workspace and click **Allow**
### Step 2: Connect your HeyGen account
After installation, you'll be redirected to connect your HeyGen account:
* **If you're already logged into HeyGen**: Installation completes automatically. You're done!
* **If you're not logged in**: You'll be redirected to HeyGen to log in, then complete the setup by selecting which HeyGen space to use
That's it! The HeyGen bot is now available in your workspace.
## How to use
### Method 1: @mention the bot
Simply @mention HeyGen with your video idea:
```text theme={null}
@HeyGen Create a welcome video saying "Welcome to our team! We're excited to have you here."
```
The bot will:
1. Acknowledge your request
2. Generate the video using your HeyGen account
3. Post the finished video in the thread
### Method 2: React with 🎥 emoji
Convert any message into a video by reacting with the camera emoji:
1. Find a message you want to turn into a video
2. Click **Add reaction** (or press `R`)
3. Choose the 🎥 `:movie_camera:` emoji
The bot will use the message text as the video script.
**Tip:** You can also use a custom `:heygen-video:` emoji if your workspace has one.
**Note:** There's a 5-minute cooldown per message to prevent duplicate videos.
### Method 3: Curate channel messages
Use the `/heygen-curate` slash command to find and compile top messages:
```text theme={null}
/heygen-curate [#channel] [--notify] [--days N]
```
**Examples:**
```text theme={null}
/heygen-curate
/heygen-curate #marketing --days 7 --notify
```
This will:
* Analyze recent messages in the channel (default: last 7 days)
* Score messages based on reactions, replies, and engagement
* Present the top 3 messages
* Optionally notify the thread with `--notify`
## Personal account linking
By default, videos use the workspace's HeyGen account. Team members can link their personal HeyGen accounts to use their own credits and avatars.
### Why link your account?
* Videos you request will use and be saved on **your HeyGen account**
* You'll use **your video credits** and **your avatars**
* Other team members continue using the workspace default
### How to link your account
1. **Visit your HeyGen account settings** at [app.heygen.com](https://app.heygen.com/settings?from=\&nav=General)
2. Navigate to **Connections** → **Slack**
3. Click **Link your Slack account**
4. Sign in to Slack and authorize the connection
Once linked, all videos you create will use your personal HeyGen account.
### Check your link status
1. On the [settings](https://app.heygen.com/settings?from=\&nav=Connections) menu, Make sure you are on **Connections**
2. Check to see if the button is grayed out or says *unlink* on the Slack card
If the button is grayed out or says *unlink*, your heygen account and slack accounts are connected.
### Unlink your account
To stop using your personal account and switch back to the workspace default:
1. Go to your [**HeyGen account settings**](https://app.heygen.com/settings?from=\&nav=General)
2. **Connections** → **Slack**
3. Click **Unlink**
## Rate limits
To ensure fair usage, the following limits apply:
| Action | Limit |
| ----------------------- | ------------------------------------------- |
| Video creation | 50 per minute, 500 per hour (per workspace) |
| /heygen-curate command | 30 per minute, 300 per hour (per workspace) |
| Emoji reaction cooldown | 1 video per message every 5 minutes |
If you hit a rate limit, wait a few minutes and try again. You'll see a message like:
Rate limit reached. Please wait a moment and try again.
## Troubleshooting
### "Workspace not installed" error
**Problem:** The bot responds with "Workspace not installed. Please reinstall the HeyGen app."
**Solution:**
* The app may have been uninstalled or credentials revoked
* Reinstall the app following the [Installation](https://docs.heygen.com/docs/slack#installation) steps
* Make sure a workspace admin completes the HeyGen account connection
### Bot doesn't respond to @mentions
**Problem:** You @mentioned the bot but nothing happened.
**Check:**
* The bot must be invited to the channel (`/invite @HeyGen`)
* You have available HeyGen video credits
* You're not hitting rate limits (see [Rate limits](https://docs.heygen.com/docs/slack#rate-limits))
* Check the thread for error messages
### Video generation failed
**Problem:** The bot acknowledged your request but the video never arrived.
**Possible causes:**
* **Insufficient credits** - Check your HeyGen account balance
* **Invalid script** - Make sure your prompt is clear and complete
* **API errors** - Try again in a few minutes
**Get help:** Send a direct message to the bot for support information.
### Emoji reaction doesn't work
**Problem:** You reacted with 🎥 but no video was created.
**Check:**
* You're using the correct emoji: 🎥 `:movie_camera:` or `:heygen-video:`
* The message hasn't had a video generated in the last 5 minutes (cooldown)
* The message has enough text to create a video (minimum \~10 words recommended)
### "Invalid HeyGen credentials" error
**Problem:** Videos aren't generating and you see credential errors.
**Solution:**
* Your HeyGen username or space ID may be incorrect
* A workspace admin should:
1. Go to your Slack workspace settings
2. **Apps** → **HeyGen** → **Configuration**
3. Update the HeyGen credentials
4. Save changes
## FAQ
### How much does it cost?
The HeyGen Slack app is free to install. Video generation uses HeyGen credits from your account:
* **Workspace default**: Uses the account configured during installation
* **Personal linking**: Uses your own HeyGen account and credits
See [HeyGen pricing](https://heygen.com/pricing) for credit costs.
### Can I choose which avatar to use?
By default, videos use your HeyGen account's default avatar. To customize:
* Link your personal HeyGen account (see [Personal account linking](https://app.heygen.com/settings?from=\&nav=Connections))
* By default, Video Agent will auto-select most recently used avatar from your workspace
* The bot will automatically use that avatar for your videos
### Where are videos stored?
Videos are:
1. Created in your HeyGen workspace (visible in your [HeyGen dashboard](https://app.heygen.com))
2. Uploaded directly to Slack (stored in your Slack workspace files)
3. Accessible via the Slack message thread
### Can I use this in private channels?
Yes! Invite the HeyGen bot to any channel:
```text theme={null}
/invite @HeyGen
```
The bot works in:
* Public channels
* Private channels
* Direct messages
* Group messages
### Is my data secure?
* **Message content** is sent to HeyGen's API only when you explicitly request a video
* **Credentials** are encrypted and stored securely
* The bot only reads messages where it's @mentioned or reacted to
* See [HeyGen's security policies](https://heygen.com/security) for details
### How do I uninstall?
To remove the HeyGen app:
1. Go to your **Slack workspace settings**
2. **Apps** → **HeyGen**
3. Click **Remove App**
4. Confirm removal
Your workspace data will be marked as deactivated but not deleted (for potential reinstallation).
## Tips & best practices
### Writing great video prompts
**Do:**
* Be specific and clear: *"Create a welcome video introducing our new design system update"*
* Include context: *"Make a tutorial video explaining how to use the new login flow"*
* Keep it concise: Aim for 30-90 seconds of content
**Don't:**
* Be too vague: ~~"Make a video"~~
* Use very long scripts: Messages over \~500 words may be truncated
* Include formatting: The bot uses plain text, not markdown
### Using /heygen-curate effectively
The curate command works best with:
* **Active channels** with regular discussion
* **Time range**: Last 24-48 hours typically has the best content
* **Engagement metrics**: Reactions and replies indicate valuable messages
**Pro tip:** Use `--notify` in channels where you want to create visibility around the curation process.
### Managing workspace credits
To avoid surprise credit usage:
* Set up **usage alerts** in your HeyGen account
* Encourage personal account linking for team members who create many videos
* Monitor usage in your [HeyGen analytics dashboard](https://app.heygen.com/analytics)
## Support
Need help?
* **Documentation**: [docs.heygen.com/slack](https://docs.heygen.com/slack)
* **Email**: [support@heygen.com](mailto:support@heygen.com)
* **Community**: Join our community for tips and discussions
# Stripe Projects
Source: https://developers.heygen.com/docs/stripe-projects
Provision a HeyGen API key directly from the Stripe CLI. Stripe handles billing and identity; HeyGen creates the account, applies your budget for API credit, and returns the key — no sign-up screen, no card handed to the agent.
[Stripe Projects](https://docs.stripe.com/projects) lets an agent discover, provision, and pay for the services it needs to ship — entirely from the command line. HeyGen is available as one of those services, so an agent can stand up video generation the same way it provisions a database or a host.
You give the agent a budget, not your card. Stripe handles identity and billing; HeyGen creates (or links) the account, applies your budget for API credit, and returns an API key.
**What you'll get:** a HeyGen API key, provisioned and billed through Stripe, written to your project's `.env` and ready to use with the [HeyGen CLI](/cli) or API.
Provisioning a paid service spends real money against the budget you set. You control the spending cap, and charges are billed through your Stripe account.
## How it works
1. **Discover.** The agent finds `heygen/api` in the Stripe Projects service catalog.
2. **Authorize.** Stripe passes the user's identity to HeyGen, which creates a new account or links an existing workspace and returns API credentials.
3. **Pay.** A Stripe payment token funds the account up to a budget you set — the agent never sees a card number.
4. **Generate.** The agent uses the returned API key to call the HeyGen API and create videos.
## Prerequisites
A Stripe account with a valid payment method (you can also add a card during provisioning via Stripe Checkout). Then install the CLI and initialize a project:
```bash theme={null} theme={null}
brew install stripe/stripe-cli/stripe # Stripe CLI
stripe plugin install projects # Projects plugin
stripe login # authenticate (opens browser)
stripe projects init # initialize a project in the current directory
```
## Provision a key
### Interactive
Walks you through the service summary and pricing, accepting HeyGen's Terms and Privacy Policy, confirming the paid service, and setting up billing. It links your HeyGen account by your Stripe email, provisions the key, and writes it to `.env`.
```bash theme={null} theme={null}
stripe projects add heygen/api
```
On success the key is stored in `.env` (shown masked, never printed in full):
```text theme={null} theme={null}
✓ Connected HeyGen account (you@example.com)
○ Provisioning heygen/api...
├─ ✓ Resource requested
├─ ✓ Resource provisioned
├─ ✓ Credentials synced
└─ ✓ Project updated
● heygen/api ready
HEYGEN_HEYGEN_API_KEY=sk_••••••••
```
### Non-interactive (agents / CI)
Confirm the paid service and accept the terms of service up front. Billing must already be configured (`stripe projects billing add`) — it can't be set up non-interactively.
```bash theme={null} theme={null}
stripe projects add heygen/api --json --yes --confirm-paid-service --accept-tos
```
If no payment method is on file, the CLI returns `PAYMENT_METHOD_REQUIRED` with a Stripe Checkout URL to add one.
## Use the API key
The key lives in your project's `.env`. Load it and call HeyGen with the [CLI](/cli):
```bash theme={null} theme={null}
curl -fsSL https://static.heygen.ai/cli/install.sh | bash # install the HeyGen CLI
set -a && source .env && set +a # load the key from .env
export HEYGEN_API_KEY="$HEYGEN_HEYGEN_API_KEY" # the CLI reads HEYGEN_API_KEY
heygen avatar list
heygen video-agent create --prompt "30-second product demo" --wait
heygen video get
```
The key is read from `.env` and never shown on screen. In a new shell, re-run `set -a && source .env && set +a` to load it again.
## Manage the project
```bash theme={null} theme={null}
stripe projects status # providers, services, plans
stripe projects env # show injected env vars
stripe projects billing show # billing state
stripe projects rotate heygen-api # rotate the API key
stripe projects remove heygen-api # remove the service (revokes the key)
```
## Take it all the way to a video
Once your agent has a key, it has a production studio — not just a single clip. Pair HeyGen avatars and voices with [HyperFrames](/hyperframes), HeyGen's open-source framework for building videos as HTML and rendering them to MP4. HyperFrames runs locally with no account or API key required, so there's nothing extra to provision: point it at your HeyGen output and the agent authors the whole thing — presenter, captions synced to the audio, motion graphics, scenes, and B-roll — then renders a finished MP4 to share.
# Styles & References
Source: https://developers.heygen.com/docs/styles-and-references
Steer HeyGen AI video output with style presets and reference images. Apply cinematic looks, brand colors, and visual consistency across generations.
Styles are curated visual templates that control how the [Video Agent](/docs/video-agent) composes your video — scene layout, script structure, pacing, and overall aesthetic. Apply a style by passing its `style_id` when creating a video.
## List available styles
Full schema: [`GET /v3/video-agents/styles`](/reference/list-video-agent-styles). Returns a paginated list of styles — each includes a name, thumbnail, preview video, tags, and aspect ratio.
### Query parameters
| Parameter | Type | Default | Description |
| --------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------- |
| `tag` | string | — | Filter by tag. Available tags: `cinematic`, `retro-tech`, `iconic-artist`, `pop-culture`, `handmade`, `print`. |
| `limit` | integer | 20 | Results per page (1–100). |
| `token` | string | — | Opaque cursor from a previous response's `next_token` for pagination. |
### Example request
```bash curl theme={null}
curl "https://api.heygen.com/v3/video-agents/styles?tag=cinematic&limit=5" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```python Python theme={null}
import requests
resp = requests.get(
"https://api.heygen.com/v3/video-agents/styles",
headers={"X-Api-Key": HEYGEN_API_KEY},
params={"tag": "cinematic", "limit": 5},
)
styles = resp.json()["data"]
for style in styles:
print(style["style_id"], style["name"])
```
### Response
```json theme={null}
{
"data": [
{
"style_id": "style_noir_detective",
"name": "Noir Detective",
"thumbnail_url": "https://files.heygen.ai/styles/noir_thumb.jpg",
"preview_video_url": "https://files.heygen.ai/styles/noir_preview.mp4",
"tags": ["cinematic"],
"aspect_ratio": "16:9"
},
{
"style_id": "style_retro_crt",
"name": "Retro CRT",
"thumbnail_url": "https://files.heygen.ai/styles/retro_crt_thumb.jpg",
"preview_video_url": "https://files.heygen.ai/styles/retro_crt_preview.mp4",
"tags": ["retro-tech"],
"aspect_ratio": "16:9"
}
],
"has_more": true,
"next_token": "eyJsYXN0X2lkIjoic3R5bGVfcmV0cm9fY3J0In0="
}
```
### Style object
| Field | Type | Description |
| ------------------- | -------------- | --------------------------------------------------------------------------------------------------------------- |
| `style_id` | string | Unique identifier. Pass this to [`POST /v3/video-agents`](/reference/create-video-agent-session) as `style_id`. |
| `name` | string | Display name of the style. |
| `thumbnail_url` | string \| null | Thumbnail image URL (public CDN). |
| `preview_video_url` | string \| null | Preview video URL (public CDN, mp4). |
| `tags` | array \| null | Tags for categorization (e.g. `cinematic`, `retro-tech`, `handmade`). |
| `aspect_ratio` | string \| null | Aspect ratio the style is designed for: `"16:9"`, `"9:16"`, or `"1:1"`. |
## Apply a style to a video
Pass the `style_id` when creating a video with the Video Agent:
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/video-agents" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Explain the history of jazz music in 60 seconds",
"style_id": "style_noir_detective"
}'
```
The style influences the visual template the agent uses — scenes, transitions, text overlays, and pacing will follow the style's design system. Your prompt still controls the content and narration.
Preview styles before using them. The `preview_video_url` on each style object shows a sample video rendered in that style — use it to pick the right look before generating.
## Pagination
The styles endpoint uses cursor-based pagination. When `has_more` is `true`, pass the `next_token` value as the `token` query parameter in your next request:
```bash theme={null}
# Page 1
curl "https://api.heygen.com/v3/video-agents/styles?limit=10" \
-H "X-Api-Key: $HEYGEN_API_KEY"
# Page 2
curl "https://api.heygen.com/v3/video-agents/styles?limit=10&token=eyJsYXN0X2lkIjo..." \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
## Filter by tag
Use the `tag` parameter to narrow results to a specific category:
| Tag | Description |
| --------------- | --------------------------------------------------------------- |
| `cinematic` | Film-inspired looks with dramatic lighting and composition. |
| `retro-tech` | Vintage technology aesthetics (CRT screens, pixel art, etc.). |
| `iconic-artist` | Styles inspired by iconic artistic movements. |
| `pop-culture` | Bold, colorful styles drawn from pop culture. |
| `handmade` | Handcrafted, organic textures (paper, watercolor, stop-motion). |
| `print` | Magazine, newspaper, and print-inspired layouts. |
```bash theme={null}
curl "https://api.heygen.com/v3/video-agents/styles?tag=handmade" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
## Using file references with styles
Styles and file attachments work together. Attach reference images or documents alongside a style to combine the style's visual template with your own content:
```json theme={null}
{
"prompt": "Create a product demo using the attached screenshots",
"style_id": "style_retro_crt",
"files": [
{ "type": "url", "url": "https://example.com/screenshot-1.png" },
{ "type": "url", "url": "https://example.com/screenshot-2.png" }
]
}
```
The agent will render your screenshots within the retro CRT visual template, applying the style's transitions and framing to your content.
# Upload Assets
Source: https://developers.heygen.com/docs/upload-assets
Upload images, audio, and video to HeyGen via the Assets API. Use uploaded assets as backgrounds, voice references, lipsync inputs, or photo avatar sources.
The Assets API lets you upload files to HeyGen and receive an `asset_id` you can reference in other endpoints — including [Video Agent](/docs/video-agent), [Avatar creation](/docs/create-avatar), [Video Translation](/docs/video-translate), and [Lipsync](/lipsync-speed).
There are two ways to upload:
* [`POST /v3/assets`](/reference/upload-asset) — a single `multipart/form-data` request. Simplest option, capped at **32 MB**.
* [Direct upload](#upload-large-files-direct-upload) — a presigned-URL flow for files larger than 32 MB. The file bytes go straight to storage and never pass through the API.
## Upload via `POST /v3/assets`
Full schema: [`POST /v3/assets`](/reference/upload-asset). Upload a file using `multipart/form-data` — the MIME type is auto-detected from file bytes.
### Constraints
| Constraint | Value |
| ---------------- | -------------------------------------------------------------------------------- |
| Max file size | 32 MB — for larger files, use [direct upload](#upload-large-files-direct-upload) |
| Supported images | png, jpeg |
| Supported video | mp4, webm |
| Supported audio | mp3, wav |
| Other | pdf |
### Example request
```bash curl theme={null}
curl -X POST "https://api.heygen.com/v3/assets" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-F "file=@./product-screenshot.png"
```
```python Python theme={null}
import requests
with open("product-screenshot.png", "rb") as f:
resp = requests.post(
"https://api.heygen.com/v3/assets",
headers={"X-Api-Key": HEYGEN_API_KEY},
files={"file": ("product-screenshot.png", f, "image/png")},
)
asset = resp.json()["data"]
print(asset["asset_id"])
```
```javascript Node.js theme={null}
const fs = require("fs");
const FormData = require("form-data");
const form = new FormData();
form.append("file", fs.createReadStream("./product-screenshot.png"));
const resp = await fetch("https://api.heygen.com/v3/assets", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY,
...form.getHeaders(),
},
body: form,
});
const { data } = await resp.json();
console.log(data.asset_id);
```
### Response
```json theme={null}
{
"data": {
"asset_id": "asset_abc123def456",
"url": "https://files.heygen.ai/assets/asset_abc123def456.png",
"mime_type": "image/png",
"size_bytes": 245760
}
}
```
| Field | Type | Description |
| ------------ | ------- | ------------------------------------------------------------ |
| `asset_id` | string | Unique identifier to reference this file in other API calls. |
| `url` | string | Public URL of the uploaded file. |
| `mime_type` | string | Detected MIME type. |
| `size_bytes` | integer | File size in bytes. |
## Upload large files (direct upload)
For files larger than 32 MB, use the direct upload flow. It is a three-step process — **all three steps are required**; the `asset_id` is not usable until you call the complete endpoint:
Call [`POST /v3/assets/direct-uploads`](/reference/create-asset-upload) with the file's name, MIME type, and exact byte size. The response contains an `asset_id`, a presigned `upload_url`, and `upload_headers`.
Send the raw file bytes to `upload_url` with an HTTP `PUT`, including every header from `upload_headers` verbatim. The URL expires after `expires_in_seconds`, and the byte size is signed into it — the upload fails if the file doesn't match the declared `size_bytes`.
Call [`POST /v3/assets/{asset_id}/complete`](/reference/complete-asset-upload) to finalize the asset. This step is idempotent — repeated calls return the same finalized asset. The `asset_id` is now usable anywhere the API accepts assets.
### Example
```bash curl theme={null}
# Step 1: Initialize
SIZE=$(stat -f%z ./footage.mp4) # use `stat -c%s` on Linux
INIT=$(curl -s -X POST "https://api.heygen.com/v3/assets/direct-uploads" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"filename\": \"footage.mp4\", \"content_type\": \"video/mp4\", \"size_bytes\": $SIZE}")
ASSET_ID=$(echo "$INIT" | jq -r '.data.asset_id')
UPLOAD_URL=$(echo "$INIT" | jq -r '.data.upload_url')
# Step 2: PUT the raw bytes to the presigned URL,
# passing every header from data.upload_headers verbatim
HEADER_ARGS=$(echo "$INIT" | jq -r '.data.upload_headers | to_entries[] | "-H \"\(.key): \(.value)\""' | tr '\n' ' ')
eval curl -X PUT "$UPLOAD_URL" $HEADER_ARGS --upload-file ./footage.mp4
# Step 3: Complete
curl -X POST "https://api.heygen.com/v3/assets/$ASSET_ID/complete" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
```
```python Python theme={null}
import os
import requests
file_path = "footage.mp4"
# Step 1: Initialize
init = requests.post(
"https://api.heygen.com/v3/assets/direct-uploads",
headers={"X-Api-Key": HEYGEN_API_KEY},
json={
"filename": os.path.basename(file_path),
"content_type": "video/mp4",
"size_bytes": os.path.getsize(file_path),
},
).json()["data"]
# Step 2: PUT the raw bytes to the presigned URL
with open(file_path, "rb") as f:
put_resp = requests.put(init["upload_url"], data=f, headers=init["upload_headers"])
put_resp.raise_for_status()
# Step 3: Complete
asset = requests.post(
f"https://api.heygen.com/v3/assets/{init['asset_id']}/complete",
headers={"X-Api-Key": HEYGEN_API_KEY},
json={},
).json()["data"]
print(asset["asset_id"], asset["status"])
```
```javascript Node.js theme={null}
const fs = require("fs");
const filePath = "./footage.mp4";
const { size } = fs.statSync(filePath);
// Step 1: Initialize
const initResp = await fetch("https://api.heygen.com/v3/assets/direct-uploads", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
filename: "footage.mp4",
content_type: "video/mp4",
size_bytes: size,
}),
});
const init = (await initResp.json()).data;
// Step 2: PUT the raw bytes to the presigned URL
await fetch(init.upload_url, {
method: "PUT",
headers: init.upload_headers,
body: fs.readFileSync(filePath),
});
// Step 3: Complete
const completeResp = await fetch(
`https://api.heygen.com/v3/assets/${init.asset_id}/complete`,
{
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
}
);
const asset = (await completeResp.json()).data;
console.log(asset.asset_id, asset.status);
```
### Initialize response fields
| Field | Type | Description |
| -------------------- | ------- | ------------------------------------------------------------------ |
| `asset_id` | string | Reusable asset identifier. Becomes usable after the complete step. |
| `upload_url` | string | Presigned URL. `PUT` the raw file bytes here. |
| `upload_headers` | object | Headers that must be sent verbatim on the `PUT` request. |
| `expires_in_seconds` | integer | Seconds until `upload_url` expires. |
| `max_bytes` | integer | Maximum allowed upload size in bytes for this flow. |
| `status` | string | Always `pending_upload` at this stage. |
Calling complete before the `PUT` has finished returns a `409 conflict` ("Uploaded object not found yet"). Retry after the `PUT` returns `200`. You can optionally pass `checksum_sha256` (hex) at both the initialize and complete steps to have the stored bytes verified end to end.
## Use assets in Video Agent
Once uploaded, reference the `asset_id` in the `files` array when creating a video:
```json theme={null}
{
"prompt": "Create a product demo using the attached screenshots",
"files": [
{ "type": "asset_id", "asset_id": "asset_abc123def456" },
{ "type": "asset_id", "asset_id": "asset_ghi789jkl012" }
]
}
```
## Three ways to provide files
Video Agent and other endpoints accept files in three formats. Use whichever is most convenient for your workflow:
Upload once, reference by ID. Best for files you reuse across multiple videos.
Point to a publicly accessible URL. No upload step needed — HeyGen fetches the file directly. Same 32 MB per-file limit as uploads.
Inline the file content as a base64-encoded string. Useful for small files or when you want a self-contained request.
### Format comparison
| Format | Syntax | When to use |
| -------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Asset ID | `{ "type": "asset_id", "asset_id": "asset_..." }` | Pre-uploaded files, reusable across requests. Only option for files over 32 MB (via [direct upload](#upload-large-files-direct-upload)). |
| URL | `{ "type": "url", "url": "https://..." }` | Files up to 32 MB already hosted publicly. |
| Base64 | `{ "type": "base64", "media_type": "image/png", "data": "iVBOR..." }` | Small files, self-contained requests, no hosting needed. |
The 32 MB per-file limit also applies to URL inputs — pointing at a larger self-hosted file fails with `Maximum size for URL inputs of type 'video/mp4' is 32 MB`. For larger files, use [direct upload](#upload-large-files-direct-upload) and pass the resulting `asset_id`. Base64 encoding additionally inflates payload size by \~33%, so prefer the other two formats for anything beyond a few MB.
## Where assets can be used
The `asset_id` format is accepted anywhere the API takes file inputs:
| Endpoint | Use case |
| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| [`POST /v3/video-agents`](/reference/create-video-agent-session) | Attach reference files (images, slides, video clips, audio). |
| [`POST /v3/video-agents/{session_id}`](/reference/send-message-or-request-revision) | Send additional files in follow-up messages — see [Interactive Sessions](/docs/interactive-sessions). |
| [`POST /v3/avatars`](/reference/create-avatar) | Provide a photo or video for avatar creation — see [Create Avatar](/docs/create-avatar). |
| [`POST /v3/video-translations`](/reference/create-video-translation) | Provide source video or custom audio — see [Video Translation](/docs/video-translate). |
| [`POST /v3/lipsyncs`](/reference/create-lipsync) | Provide source video and/or replacement audio — see [Lipsync](/lipsync-speed). |
## Example: Upload then generate
A complete workflow — upload a PDF, then use it to generate a video:
```bash curl theme={null}
# Step 1: Upload the PDF
ASSET_ID=$(curl -s -X POST "https://api.heygen.com/v3/assets" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-F "file=@./quarterly-report.pdf" | jq -r '.data.asset_id')
echo "Uploaded asset: $ASSET_ID"
# Step 2: Generate a video using the uploaded PDF
curl -X POST "https://api.heygen.com/v3/video-agents" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"prompt\": \"Summarize the key findings from this quarterly report in a 60-second video\",
\"files\": [{ \"type\": \"asset_id\", \"asset_id\": \"$ASSET_ID\" }]
}"
```
```python Python theme={null}
import requests
# Step 1: Upload
with open("quarterly-report.pdf", "rb") as f:
upload_resp = requests.post(
"https://api.heygen.com/v3/assets",
headers={"X-Api-Key": HEYGEN_API_KEY},
files={"file": f},
)
asset_id = upload_resp.json()["data"]["asset_id"]
# Step 2: Generate
gen_resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={"X-Api-Key": HEYGEN_API_KEY},
json={
"prompt": "Summarize the key findings from this quarterly report in a 60-second video",
"files": [{"type": "asset_id", "asset_id": asset_id}],
},
)
session = gen_resp.json()["data"]
```
# Usage Limits
Source: https://developers.heygen.com/docs/usage-limits
See HeyGen API rate limits by endpoint and plan tier. Includes concurrent video render quotas, request-per-minute caps, and how to request a quota increase.
## Concurrency Limits
| Plan | Max Concurrent Video Jobs |
| :------------ | :------------------------ |
| Pay-As-You-Go | 10 |
Concurrent jobs include any asynchronous generation in progress: [Video Agent sessions](/docs/video-agent), avatar video renders, and [video translations](/docs/video-translate). Exceeding the limit returns `429 Too Many Requests` with a `Retry-After` header — see the [`rate_limit_exceeded` error](/docs/error-codes#rate-limit-exceeded).
## Endpoint Limits
### Video Generation Input
Resources provided to [`POST /v3/videos`](/reference/create-video) must meet these limits. Invalid resources will cause render failures with [`download_failed`](/docs/error-codes#download-failed).
| Resource Type | Supported Formats | Max File Size | Max Resolution |
| :------------ | :---------------- | :------------ | :------------- |
| Video | MP4, WebM | 100 MB | \< 2K |
| Image | JPG, PNG | 50 MB | \< 2K |
| Audio | WAV, MP3 | 50 MB | — |
Requirements:
* Resource URLs must be **publicly accessible** (no authentication required).
* The file extension must **match the actual file format**.
* Files must not be **corrupted or malformed**.
### Avatar Input
* **Script text:** Maximum 5,000 characters.
* **Audio input:** Maximum 10 minutes (600 seconds).
### Video Agent Input
* **Prompt:** 1–10,000 characters.
* **File attachments:** Up to 20 files. Supported types: image (PNG, JPEG), video (MP4, WebM), audio (MP3, WAV), and PDF.
* Files can be provided as an `asset_id` (from [`POST /v3/assets`](/reference/upload-asset)), an HTTPS URL, or base64-encoded content. See [Upload Assets](/docs/upload-assets).
### Asset Upload ([`POST /v3/assets`](/reference/upload-asset))
* **Maximum file size:** 32 MB. The same limit applies to files provided by URL. For larger files, use the [direct upload flow](/docs/upload-assets#upload-large-files-direct-upload) ([`POST /v3/assets/direct-uploads`](/reference/create-asset-upload)) — its per-upload cap is returned as `max_bytes` in the initialize response.
* **Supported types:** Image (PNG, JPEG), video (MP4, WebM), audio (MP3, WAV), and PDF.
### Text-to-Speech Input ([`POST /v3/voices/speech`](/reference/generate-speech))
* **Text length:** 1–5,000 characters.
* **Speed multiplier:** 0.5× to 2.0×.
* **Input type:** Plain text or SSML markup.
### Output Video Specifications
* **Frame rate:** 25 fps for videos containing avatars.
* **Resolution:** Width and height must each be between 128 and 4,096 pixels. Default output is 1080p.
* **Aspect ratio:** 16:9 or 9:16.
* **Maximum scenes:** 50 per video.
* **Maximum duration:** 30 minutes.
## Pagination
Most list endpoints use cursor-based pagination with a `limit` parameter and `next_token` for the next page.
| Endpoint | Default | Max |
| :------------------------------------------------------------------ | :------ | :-- |
| [`GET /v3/videos`](/reference/list-videos) | 10 | 100 |
| [`GET /v3/avatars`](/reference/list-avatar-groups) | 20 | 50 |
| [`GET /v3/avatars/looks`](/reference/list-avatar-looks) | 20 | 50 |
| [`GET /v3/voices`](/reference/list-voices) | 20 | 100 |
| [`GET /v3/video-agents/styles`](/reference/list-video-agent-styles) | 20 | 100 |
| [`GET /v3/video-translations`](/reference/list-video-translations) | 10 | 100 |
| [`GET /v3/webhooks/endpoints`](/reference/list-webhook-endpoints) | 10 | 100 |
| [`GET /v3/webhooks/events`](/reference/list-webhook-events) | 10 | 100 |
| `GET /v3/video-agents/sessions/{id}/resources` | 8 | 100 |
## Rate Limiting
All endpoints enforce rate limits. When exceeded, the API returns `429 Too Many Requests` with a `Retry-After` header indicating the number of seconds to wait before retrying. See [`rate_limit_exceeded`](/docs/error-codes#rate-limit-exceeded) for the error shape and recommended backoff strategy.
# Prompt to Video
Source: https://developers.heygen.com/docs/video-agent
Create AI avatar videos from a single text prompt with the HeyGen Video Agent API. The agent picks the avatar, voice, and style, then renders in minutes.
This is the **one-shot** workflow — send a prompt, get a video. For multi-turn collaboration with the agent, see [Interactive Sessions](/docs/interactive-sessions).
**API reference:** [Create Session](/reference/create-video-agent-session) · [Get Session](/reference/get-video-agent-session) · [List Sessions](/reference/list-video-agent-sessions) · [Stop Session](/reference/stop-video-agent-session) · [Get Video](/reference/get-video) · [List Videos](/reference/list-videos) · [Delete Video](/reference/delete-video)
[`POST /v3/video-agents`](/reference/create-video-agent-session)
Send a text prompt describing the video you want. The agent handles scripting, avatar selection, scene composition, and rendering. The video is generated asynchronously — use the returned `session_id` to track progress and retrieve the `video_id` once rendering begins.
### Request body
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `prompt` | string | **Yes** | Text description of the video you want (1–10,000 characters). |
| `avatar_id` | string | No | Specific avatar look ID. Omit to let the agent choose automatically. |
| `voice_id` | string | No | Specific voice ID for narration. Omit to let the agent choose automatically. |
| `style_id` | string | No | Style ID from [`GET /v3/video-agents/styles`](/reference/list-video-agent-styles). Applies a curated visual template. See [Styles & References](/docs/styles-and-references). |
| `orientation` | string | No | `"landscape"` or `"portrait"`. Auto-detected from content if omitted. |
| `files` | array | No | Up to 20 file attachments. See [File input formats](#file-input-formats) below. |
| `callback_url` | string | No | Webhook URL to receive a POST notification on completion or failure. |
| `callback_id` | string | No | Caller-defined ID echoed back in the webhook payload. |
### File input formats
Each item in the `files` array uses a `type` discriminator to specify how the file is provided:
```json URL theme={null}
{ "type": "url", "url": "https://example.com/slide-deck.pdf" }
```
```json Asset ID theme={null}
{ "type": "asset_id", "asset_id": "asset_abc123" }
```
```json Base64 theme={null}
{ "type": "base64", "media_type": "image/png", "data": "iVBORw0KGgo..." }
```
Supported file types: image (png, jpeg), video (mp4, webm), audio (mp3, wav), and pdf. Upload files in advance via [`POST /v3/assets`](/reference/upload-asset) to get an `asset_id` — see [Upload Assets](/docs/upload-assets).
### Example request
```bash curl theme={null}
curl -X POST "https://api.heygen.com/v3/video-agents" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Create a 45-second explainer about our Q3 product launch. Use a friendly, upbeat tone. Include the attached slides as visual context.",
"orientation": "landscape",
"files": [
{ "type": "url", "url": "https://example.com/q3-launch-deck.pdf" }
]
}'
```
```python Python theme={null}
import requests
resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={"X-Api-Key": HEYGEN_API_KEY},
json={
"prompt": "Create a 45-second explainer about our Q3 product launch. Use a friendly, upbeat tone.",
"orientation": "landscape",
"files": [
{"type": "url", "url": "https://example.com/q3-launch-deck.pdf"}
],
},
)
data = resp.json()["data"]
session_id = data["session_id"]
```
```javascript Node.js theme={null}
const resp = await fetch("https://api.heygen.com/v3/video-agents", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "Create a 45-second explainer about our Q3 product launch.",
orientation: "landscape",
files: [
{ type: "url", url: "https://example.com/q3-launch-deck.pdf" },
],
}),
});
const { data } = await resp.json();
const sessionId = data.session_id;
```
### Response
```json theme={null}
{
"data": {
"session_id": "sess_abc123",
"status": "generating",
"video_id": null,
"created_at": 1711382400
}
}
```
| Field | Type | Description |
| ------------ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `session_id` | string | Primary identifier for this Video Agent session. Use to track progress. |
| `status` | string | Session status: `"thinking"`, `"generating"`, `"completed"`, or `"failed"`. |
| `video_id` | string \| null | Video ID for polling via [`GET /v3/videos/{video_id}`](/reference/get-video). `null` until rendering begins — poll [`GET /v3/video-agents/{session_id}`](/reference/get-video-agent-session) to get the `video_id` once it's assigned. |
| `created_at` | integer | Unix timestamp of session creation. |
## Poll for completion
Video generation is asynchronous. First, poll the session to get the `video_id`, then poll the video for its final status:
* [`GET /v3/video-agents/{session_id}`](/reference/get-video-agent-session) — session status and assigned `video_id`
* [`GET /v3/videos/{video_id}`](/reference/get-video) — final render status and `video_url`
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/videos/vid_xyz789" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```python Python theme={null}
import time, requests
# Step 1: wait for video_id to be assigned
video_id = None
while not video_id:
sess = requests.get(
f"https://api.heygen.com/v3/video-agents/{session_id}",
headers={"X-Api-Key": HEYGEN_API_KEY},
).json()["data"]
video_id = sess.get("video_id")
if not video_id:
time.sleep(5)
# Step 2: poll video until complete
while True:
video = requests.get(
f"https://api.heygen.com/v3/videos/{video_id}",
headers={"X-Api-Key": HEYGEN_API_KEY},
).json()["data"]
if video["status"] in ("completed", "failed"):
break
time.sleep(10)
print(video["video_url"])
```
### Response (completed)
```json theme={null}
{
"data": {
"id": "vid_xyz789",
"title": "Q3 Product Launch Explainer",
"status": "completed",
"video_url": "https://files.heygen.ai/video/vid_xyz789.mp4",
"thumbnail_url": "https://files.heygen.ai/thumb/vid_xyz789.jpg",
"duration": 45.2,
"created_at": 1711382400,
"completed_at": 1711382680
}
}
```
### Video status transitions
The `status` field progresses through these values:
| Status | Description |
| ------------ | -------------------------------------------------------------- |
| `pending` | Video creation request accepted, queued for processing. |
| `processing` | The agent is generating the video. |
| `completed` | Video is ready. `video_url` contains the download link. |
| `failed` | Generation failed. Check `failure_code` and `failure_message`. |
### Response fields
| Field | Type | Description |
| --------------------- | --------------- | ------------------------------------------------------------------ |
| `id` | string | Unique video identifier. |
| `title` | string \| null | Video title. |
| `status` | string | Current status: `pending`, `processing`, `completed`, or `failed`. |
| `video_url` | string \| null | Presigned download URL. Present when `completed`. |
| `thumbnail_url` | string \| null | Thumbnail image URL. |
| `gif_url` | string \| null | Animated GIF preview URL. |
| `captioned_video_url` | string \| null | Video with burned-in captions. |
| `subtitle_url` | string \| null | SRT subtitle file download URL. |
| `duration` | number \| null | Video duration in seconds. |
| `created_at` | integer \| null | Unix timestamp of creation. |
| `completed_at` | integer \| null | Unix timestamp when generation finished. |
| `failure_code` | string \| null | Machine-readable failure reason. Only when `failed`. |
| `failure_message` | string \| null | Human-readable failure description. Only when `failed`. |
| `video_page_url` | string \| null | Link to the video in the HeyGen app. |
## Use webhooks instead of polling
Pass a `callback_url` in the creation request to receive a POST notification when the video completes or fails, instead of polling:
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/video-agents" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Create a short welcome video for new employees",
"callback_url": "https://your-server.com/webhooks/heygen",
"callback_id": "onboarding-video-001"
}'
```
The `callback_id` is echoed back in the webhook payload so you can correlate notifications with requests.
## List videos
Retrieve all videos in your account with pagination. Full schema: [`GET /v3/videos`](/reference/list-videos).
| Parameter | Type | Default | Description |
| ----------- | ------- | ------- | ------------------------------------------------------ |
| `limit` | integer | 10 | Results per page (1–100). |
| `token` | string | — | Opaque cursor from a previous response's `next_token`. |
| `folder_id` | string | — | Filter by folder ID. |
| `title` | string | — | Filter by title substring. |
```bash theme={null}
curl "https://api.heygen.com/v3/videos?limit=5" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
## Delete a video
Permanently remove a video. Full schema: [`DELETE /v3/videos/{video_id}`](/reference/delete-video).
```bash theme={null}
curl -X DELETE "https://api.heygen.com/v3/videos/vid_xyz789" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"data": {
"id": "vid_xyz789",
"deleted": true
}
}
```
## Tips for better results
1. **Be descriptive in your prompt.** Include details about tone, target audience, visual style, and pacing — the agent uses all of this to make better decisions.
2. **Attach reference files.** Pass slides, images, or documents in the `files` array to give the agent visual context.
3. **Use `orientation`** when you know the target platform (e.g. `"portrait"` for mobile/social, `"landscape"` for presentations).
4. **Apply a style** for consistent visual branding across videos. See [Styles & References](/docs/styles-and-references) and [`GET /v3/video-agents/styles`](/reference/list-video-agent-styles).
5. **Pin a specific avatar or voice** with `avatar_id` (see [Avatars](/docs/avatars)) and `voice_id` (see [Browse Voices](/docs/voices/search-voices)) for brand consistency, or omit them to let the agent choose.
6. **Need multi-turn revisions?** Use [Interactive Sessions](/docs/interactive-sessions) instead of one-shot prompts.
# Video Translation - Speed
Source: https://developers.heygen.com/docs/video-translate
Translate videos into 175+ languages with the HeyGen Video Translation API. Preserves the speaker's voice, applies lipsync to the target language, and exports.
**Mode:** `"speed"` (default) Best for: fast turnaround, batch jobs, and workflows where time matters more than perfect lip-sync. For higher fidelity at the cost of latency, see [Precision mode](/docs/video-translation-precision). For dozens or hundreds of videos in one job, see [Video Translation batches](/batch-video-translations).
## How Lip Sync and Video Translation Relate
Both APIs sit on top of **one lip sync engine** that runs in two modes — **Speed** and **Precision** — trading latency for fidelity. What differs is what each API does *around* that engine:
* **[Lip Sync API](/lipsync-speed)** — the engine on its own. You bring the video *and* your own audio; the engine redraws the mouth to match. No translation, no audio generation — dialogue replacement only.
* **[Video Translation API](/docs/video-translate)** — translation, optionally followed by the engine. It has two output modes:
* **Audio only** (`translate_audio_only: true`) — transcribe, translate, and generate the translated audio, then stop. No lip sync. The output is just the new audio track (or the original video with swapped audio and an untouched mouth).
* **Video (audio + visual)** — the same translation pipeline, then runs the lip sync engine so the mouth matches the translated speech.
The short version:
* **Lip Sync API** = engine only — you bring the audio.
* **Video Translation API** = translation, optionally followed by the engine. Audio-only mode skips the engine entirely; video mode adds it back.
## Quick Start
### 1. List Supported Languages
Before translating, fetch the available target language codes via [`GET /v3/video-translations/languages`](/reference/list-supported-translation-languages):
```bash theme={null}
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations/languages' \
--header 'accept: application/json' \
--header 'x-api-key: '
```
### 2. Submit a Translation (Single Language)
Full schema: [`POST /v3/video-translations`](/reference/create-video-translation).
```bash theme={null}
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations' \
--header 'accept: application/json' \
--header 'x-api-key: ' \
--header 'Content-Type: application/json' \
--data '{
"video": {
"type": "url",
"url": ""
},
"output_languages": ["Spanish"],
"mode": "speed",
"title": "My Translated Video"
}'
```
### Batch (Multiple Languages)
Translate into several languages in one request:
```bash theme={null}
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations' \
--header 'accept: application/json' \
--header 'x-api-key: ' \
--header 'Content-Type: application/json' \
--data '{
"video": {
"type": "url",
"url": ""
},
"output_languages": ["English", "Spanish", "French"],
"mode": "speed",
"title": "Global Campaign"
}'
```
Response returns one ID per language:
```json theme={null}
{
"data": {
"video_translation_ids": [
"tr_abc123-en",
"tr_abc123-es",
"tr_abc123-fr"
]
}
}
```
### 3. Poll for Status
Use [`GET /v3/video-translations/{video_translation_id}`](/reference/get-video-translation). Skip polling by passing `callback_url` — see [Webhooks](/docs/webhooks).
```bash theme={null}
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations/' \
--header 'accept: application/json' \
--header 'x-api-key: '
```
| Status | Meaning |
| ----------- | ------------------------------- |
| `pending` | Queued |
| `running` | In progress |
| `completed` | Done — `video_url` is available |
| `failed` | Check `failure_message` |
## Source Video Input
| Type | Example |
| -------- | ----------------------------------------------------------- |
| URL | `{ "type": "url", "url": "https://example.com/video.mp4" }` |
| Asset ID | `{ "type": "asset_id", "asset_id": "" }` |
> The URL must be publicly accessible (test by opening in an incognito browser). To use an `asset_id`, upload first via [`POST /v3/assets`](/reference/upload-asset) — see the [Upload Assets guide](/docs/upload-assets).
## Speed Mode Options
These parameters are particularly relevant for Speed mode:
| Parameter | Default | Description |
| --------------------------- | --------- | ------------------------------------------------------------------ |
| `mode` | `"speed"` | Set to `"speed"` for faster processing |
| `speaker_num` | auto | Number of speakers |
| `translate_audio_only` | `false` | When `true`, only audio is translated; original video is preserved |
| `enable_dynamic_duration` | `true` | Allows output duration to vary to match natural speech pacing |
| `disable_music_track` | `false` | Strips background music from output |
| `enable_speech_enhancement` | `false` | Improves speech audio quality |
| `enable_caption` | `false` | Generates captions alongside the video |
| `brand_voice_id` | — | Apply a custom brand voice (requires setup) |
| `callback_url` | — | [Webhook](/docs/webhooks) URL notified on completion or failure |
| `callback_id` | — | Your own ID, echoed back in the webhook payload |
## Stock Voice (Enterprise)
Use a preset "stock" voice for the translation instead of recreating the original speaker's voice.
By default, Video Translation clones the original speaker, so the translated video sounds like them. With this option enabled, the translation is spoken by a natural, preset voice that's optimized for clear pronunciation and accent in the target language. The trade-off: the result won't sound like the original speaker.
This is an Enterprise feature, available for selected accounts and languages and turned on by request. To use it, contact your HeyGen account team.
Pass `stock_voice_config` in the translation request:
| Field | Type | Default | Description |
| --------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
| `use_stock_voice` | boolean | `false` | Set to `true` to use a preset stock voice instead of cloning the original speaker. |
| `preferred_stock_voice_ids` | string\[] | — | Optional. Pin specific stock voice IDs to draw from. If omitted, the target language's default stock-voice pool is used. |
```bash theme={null}
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations' \
--header 'accept: application/json' \
--header 'x-api-key: ' \
--header 'Content-Type: application/json' \
--data '{
"video": {
"type": "url",
"url": ""
},
"output_languages": ["Spanish"],
"mode": "speed",
"title": "My Translated Video",
"stock_voice_config": {
"use_stock_voice": true
}
}'
```
## Captions
To enable captions, set `enable_caption: true` in the translation request. Once completed, download them:
```bash theme={null}
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations//caption?format=srt' \
--header 'accept: application/json' \
--header 'x-api-key: '
```
Supported formats: `srt`, `vtt`.
## Proofread Before Finalizing
Speed mode supports the proofread workflow — review and edit subtitles before spending credits on final generation. Reference: [Create](/reference/create-proofread-session) · [Get](/reference/get-proofread-session) · [Download SRT](/reference/download-proofread-srt) · [Upload SRT](/reference/upload-proofread-srt) · [Generate Final Video](/reference/generate-video-from-proofread).
### Step 1 — Create Proofread Session
Full schema: [`POST /v3/video-translations/proofreads`](/reference/create-proofread-session).
```bash theme={null}
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations/proofreads' \
--header 'x-api-key: ' \
--header 'Content-Type: application/json' \
--data '{
"video": { "type": "url", "url": "" },
"output_languages": ["Spanish"],
"title": "Review Before Publishing",
"mode": "speed"
}'
```
Returns `proofread_ids` — one per language.
### Step 2 — Poll Until `completed`
[`GET /v3/video-translations/proofreads/{proofread_id}`](/reference/get-proofread-session).
```bash theme={null}
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations/proofreads/' \
--header 'x-api-key: '
```
### Step 3 — Download & Edit the SRT
Download via [`GET /v3/video-translations/proofreads/{proofread_id}/srt`](/reference/download-proofread-srt); upload the revised file via [Upload Proofread SRT](/reference/upload-proofread-srt).
```bash theme={null}
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations/proofreads//srt' \
--header 'x-api-key: '
```
Edit the returned `srt_url` file locally, then upload the revised version:
```bash theme={null}
curl --request PUT \
--url 'https://api.heygen.com/v3/video-translations/proofreads//srt' \
--header 'x-api-key: ' \
--header 'Content-Type: application/json' \
--data '{ "srt": { "type": "url", "url": "" } }'
```
### Step 4 — Generate Final Video
[`POST /v3/video-translations/proofreads/{proofread_id}/generate`](/reference/generate-video-from-proofread).
```bash theme={null}
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations/proofreads//generate' \
--header 'x-api-key: ' \
--header 'Content-Type: application/json' \
--data '{ "captions": true }'
```
Returns a `video_translation_id` to poll via [`GET /v3/video-translations/{video_translation_id}`](/reference/get-video-translation).
## Other Operations
### List All Translations
[`GET /v3/video-translations`](/reference/list-video-translations).
```bash theme={null}
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations?limit=10' \
--header 'x-api-key: '
```
Uses `has_more` + `next_token` for pagination.
### Delete a Translation
[`DELETE /v3/video-translations/{video_translation_id}`](/reference/delete-video-translation).
```bash theme={null}
curl --request DELETE \
--url 'https://api.heygen.com/v3/video-translations/' \
--header 'x-api-key: '
```
## When to Use Speed vs. Precision
| | Speed | [Precision](/docs/video-translation-precision) |
| ---------------- | ---------------------------------------- | ---------------------------------------------------------------------------------- |
| Processing Time | Faster | Slower |
| Translation | Adequate | Context- and Gender-Aware |
| Lip-Sync Quality | Standard | High |
| Best For | Faces with little movement, quick drafts | Faces with significant movement, side angles, or occlusions; final delivery videos |
For high-volume jobs across many source videos, see [Video Translation batches](/batch-video-translations).
# Video Translation - Precision
Source: https://developers.heygen.com/docs/video-translation-precision
Use HeyGen Video Translation in precision mode for verbatim transcripts, terminology control, and proofread SRT inputs.
**Mode:** `"precision"` Best for: high-quality final delivery, talking-head videos, and content where accurate lip-sync is critical. For faster turnaround at lower fidelity, see [Speed mode](/docs/video-translate). For many videos in one job, see [Video Translation batches](/batch-video-translations).
## How Precision Mode Works
Precision mode uses avatar inference and multiple models to re-render the speaker's mouth movements to match the translated audio—producing significantly more realistic lip-sync than Speed mode. It requires longer processing time and is recommended for polished, client-facing, or broadcast-quality output.
## Quick Start
### 1. List Supported Languages
Fetch available target language codes via [`GET /v3/video-translations/languages`](/reference/list-supported-translation-languages):
```bash theme={null}
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations/languages' \
--header 'accept: application/json' \
--header 'x-api-key: '
```
### 2. Submit a Translation (Single Language)
Full schema: [`POST /v3/video-translations`](/reference/create-video-translation).
```bash theme={null}
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations' \
--header 'accept: application/json' \
--header 'x-api-key: ' \
--header 'Content-Type: application/json' \
--data '{
"video": {
"type": "url",
"url": ""
},
"output_languages": ["Spanish"],
"mode": "precision",
"title": "High Quality Translation"
}'
```
### Batch (Multiple Languages)
```bash theme={null}
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations' \
--header 'accept: application/json' \
--header 'x-api-key: ' \
--header 'Content-Type: application/json' \
--data '{
"video": {
"type": "url",
"url": ""
},
"output_languages": ["English", "Spanish", "French"],
"mode": "precision",
"title": "Global Campaign — High Quality"
}'
```
Response returns one ID per language:
```json theme={null}
{
"data": {
"video_translation_ids": [
"tr_abc123-en",
"tr_abc123-es",
"tr_abc123-fr"
]
}
}
```
### 3. Poll for Status
Use [`GET /v3/video-translations/{video_translation_id}`](/reference/get-video-translation). Skip polling by passing `callback_url` — see [Webhooks](/docs/webhooks).
```bash theme={null}
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations/' \
--header 'accept: application/json' \
--header 'x-api-key: '
```
| Status | Meaning |
| ----------- | ------------------------------- |
| `pending` | Queued |
| `running` | Avatar inference in progress |
| `completed` | Done — `video_url` is available |
| `failed` | Check `failure_message` |
> Precision mode takes longer than Speed mode — plan polling intervals accordingly (e.g. every 30–60 seconds for longer videos).
## Source Video Input
| Type | Example |
| -------- | ----------------------------------------------------------- |
| URL | `{ "type": "url", "url": "https://example.com/video.mp4" }` |
| Asset ID | `{ "type": "asset_id", "asset_id": "" }` |
> The URL must be publicly accessible (test by opening in an incognito browser). To use an `asset_id`, upload first via [`POST /v3/assets`](/reference/upload-asset) — see the [Upload Assets guide](/docs/upload-assets).
## Precision Mode Options
These parameters are particularly relevant for Precision mode:
| Parameter | Default | Description |
| --------------------------- | --------- | ----------------------------------------------------------------------------------- |
| `mode` | `"speed"` | **Set to `"precision"`** to enable avatar inference |
| `speaker_num` | auto | Number of speakers |
| `translate_audio_only` | `false` | When `true`, skips avatar inference and only dubs audio (negates precision benefit) |
| `enable_dynamic_duration` | `true` | Allows output duration to vary to match natural speech pacing |
| `disable_music_track` | `false` | Strips background music from output |
| `enable_speech_enhancement` | `false` | Improves speech audio quality |
| `enable_caption` | `false` | Generates captions alongside the video |
| `brand_voice_id` | — | Apply a custom brand voice (requires setup) |
| `srt` | — | Custom subtitle file — **Enterprise plan only** |
| `srt_role` | — | `"input"` or `"output"` — which video the SRT applies to. Enterprise only |
| `callback_url` | — | [Webhook](/docs/webhooks) URL notified on completion or failure |
| `callback_id` | — | Your own ID, echoed back in the webhook payload |
> **Tip:** Setting `speaker_num` is especially important in Precision mode — accurate speaker separation directly improves the quality of avatar inference per speaker.
## Captions
To enable captions, set `enable_caption: true` in the translation request. Once completed, download them:
```bash theme={null}
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations//caption?format=srt' \
--header 'accept: application/json' \
--header 'x-api-key: '
```
Supported formats: `srt`, `vtt`.
## Proofread Before Finalizing
Precision mode fully supports the proofread workflow — review and edit subtitles before committing to the full avatar inference render. **This is especially valuable in Precision mode** since generation takes longer and costs more. Reference: [Create](/reference/create-proofread-session) · [Get](/reference/get-proofread-session) · [Download SRT](/reference/download-proofread-srt) · [Upload SRT](/reference/upload-proofread-srt) · [Generate Final Video](/reference/generate-video-from-proofread).
### Step 1 — Create Proofread Session
Full schema: [`POST /v3/video-translations/proofreads`](/reference/create-proofread-session).
```bash theme={null}
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations/proofreads' \
--header 'x-api-key: ' \
--header 'Content-Type: application/json' \
--data '{
"video": { "type": "url", "url": "" },
"output_languages": ["Spanish"],
"title": "Review Before Publishing",
"mode": "precision"
}'
```
Returns `proofread_ids` — one per language.
### Step 2 — Poll Until `completed`
[`GET /v3/video-translations/proofreads/{proofread_id}`](/reference/get-proofread-session).
```bash theme={null}
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations/proofreads/' \
--header 'x-api-key: