` | Base language code (auto-detected if omitted) |
| `--locale ` | BCP-47 locale tag |
| `--input-type ` | `text` (default) or `ssml` |
## Lipsync
Dub or replace audio on existing videos.
| Command | API Endpoint | Description |
| ------------------------------------ | ---------------------------------- | ------------------------------ |
| `heygen lipsync create` | `POST /v3/lipsyncs` | Create a lipsync job |
| `heygen lipsync list` | `GET /v3/lipsyncs` | List lipsync jobs |
| `heygen lipsync get ` | `GET /v3/lipsyncs/{lipsync_id}` | Get lipsync details and status |
| `heygen lipsync update ` | `PATCH /v3/lipsyncs/{lipsync_id}` | Update a lipsync title |
| `heygen lipsync delete ` | `DELETE /v3/lipsyncs/{lipsync_id}` | Delete a lipsync |
`lipsync create` requires a complex request body (video and audio sources use discriminated unions). Use `-d`:
```bash theme={null}
cat request.json | heygen lipsync create -d -
```
Run `heygen lipsync create --request-schema` to see all available fields.
## Video Translate
Translate videos into other languages with lip-sync.
| Command | API Endpoint | Description |
| --------------------------------------------------- | ------------------------------------------------------ | ----------------------------- |
| `heygen video-translate create` | `POST /v3/video-translations` | Create a video translation |
| `heygen video-translate list` | `GET /v3/video-translations` | List translations |
| `heygen video-translate get ` | `GET /v3/video-translations/{id}` | Get translation details |
| `heygen video-translate update ` | `PATCH /v3/video-translations/{id}` | Update a translation title |
| `heygen video-translate delete ` | `DELETE /v3/video-translations/{id}` | Delete a translation |
| `heygen video-translate languages list` | `GET /v3/video-translations/languages` | List supported languages |
| `heygen video-translate proofreads create` | `POST /v3/video-translations/proofreads` | Create a proofread session |
| `heygen video-translate proofreads get ` | `GET /v3/video-translations/proofreads/{id}` | Get proofread status |
| `heygen video-translate proofreads generate ` | `POST /v3/video-translations/proofreads/{id}/generate` | Generate video from proofread |
| `heygen video-translate proofreads srt get ` | `GET /v3/video-translations/proofreads/{id}/srt` | Download proofread SRT |
| `heygen video-translate proofreads srt update ` | `PUT /v3/video-translations/proofreads/{id}/srt` | Upload edited SRT |
### Flags for `video-translate create`
| Flag | Description |
| ---------------------------- | -------------------------------------------------------------------------------------------------------- |
| `--output-languages ` | Target language names, comma-separated (required). Use `video-translate languages list` for valid values |
| `--mode ` | `speed` or `precision` |
| `--speaker-num ` | Number of speakers in source (improves separation) |
| `--translate-audio-only` | Translate audio without lip-sync |
| `--enable-caption` | Add captions to translated video |
| `--input-language ` | Source language code (auto-detected if omitted) |
| `--callback-url ` | Webhook URL for completion notifications |
| `--title ` | Title for the translation job |
## Webhooks
Manage webhook endpoints for event notifications.
| Command | API Endpoint | Description |
| --------------------------------------------- | ------------------------------------------------ | -------------------------- |
| `heygen webhook endpoints create` | `POST /v3/webhooks/endpoints` | Create a webhook endpoint |
| `heygen webhook endpoints list` | `GET /v3/webhooks/endpoints` | List webhook endpoints |
| `heygen webhook endpoints update ` | `PATCH /v3/webhooks/endpoints/{id}` | Update a webhook endpoint |
| `heygen webhook endpoints delete ` | `DELETE /v3/webhooks/endpoints/{id}` | Delete a webhook endpoint |
| `heygen webhook endpoints rotate-secret ` | `POST /v3/webhooks/endpoints/{id}/rotate-secret` | Rotate signing secret |
| `heygen webhook event-types list` | `GET /v3/webhooks/event-types` | List available event types |
| `heygen webhook events list` | `GET /v3/webhooks/events` | List delivered events |
### Flags for `webhook endpoints create`
| Flag | Description |
| ------------------ | ----------------------------------------------------------------- |
| `--url ` | Publicly accessible HTTPS URL (required) |
| `--events ` | Comma-separated event types to subscribe to (omit for all events) |
| `--entity-id ` | Scope this endpoint to a specific resource |
Store the `secret` returned by `endpoints create` and `endpoints rotate-secret` securely — it is used to verify webhook signatures and will not be shown again.
## Assets
Upload files for use in video creation.
| Command | API Endpoint | Description |
| --------------------- | ----------------- | -------------------------------- |
| `heygen asset create` | `POST /v3/assets` | Upload a file to get an asset ID |
### Flags for `asset create`
| Flag | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `--file ` | Local file to upload (required). Max 32 MB. Supported types: image (png, jpeg), video (mp4, webm), audio (mp3, wav), pdf |
## User
| Command | API Endpoint | Description |
| -------------------- | ------------------ | ------------------------------------------- |
| `heygen user me get` | `GET /v3/users/me` | Get current user info, credits, and billing |
## Authentication
| Command | Description |
| -------------------- | ------------------------------------------------ |
| `heygen auth login` | Authenticate interactively (prompts for API key) |
| `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`. For OAuth-based authentication, see [Connecting your app to HeyGen with OAuth 2.0](/docs/connecting-your-app-to-heygen-with-oauth-20). 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/v1/user/me" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"code": 100,
"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 }
}
},
"message": null
}
```
A successful response with `"code": 100` 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 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_4_quality", "avatar_4_turbo"],
"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 supports for video creation (e.g. `["avatar_4_quality", "avatar_4_turbo"]`). |
| `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 if trained with matting. |
| `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_4_quality", "avatar_4_turbo"],
"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`:
```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.
# Bulk Video Translation
Source: https://developers.heygen.com/docs/bulk-video-translation
Submit large batches of videos to the HeyGen Bulk Video Translation API. Translate hundreds of videos in parallel into multiple target languages from a single.
## Prerequisites
* Python 3.6+
* The `requests` library:
```bash theme={null}
pip install requests
```
* A HeyGen API key from the [Subscriptions & API](https://app.heygen.com/settings?from=\&nav=Subscriptions%20%26%20API) section of your dashboard — see the [API Key guide](/docs/api-key).
This page walks through a batch script. For one-off translations see [Speed mode](/docs/video-translate) or [Precision mode](/docs/video-translation-precision). Each row in the CSV becomes one call to [`POST /v3/video-translations`](/reference/create-video-translation).
## Prepare Your CSV
Create a file named `bulk_translation_input.csv` with these columns:
| Column | Required | Description |
| ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | Yes | Title for the translation job. |
| `output_language` | Yes | Target language code (use [`GET /v3/video-translations/languages`](/reference/list-supported-translation-languages) to discover valid codes). |
| `url` | Yes | Public video download URL. |
| `folder_id` | No | Folder ID to organize translations. |
Example:
```csv theme={null}
title,output_language,url,folder_id
"Product Demo","Spanish","https://example.com/demo.mp4","folder_123"
"Onboarding","French","https://example.com/onboard.mp4",""
"Q4 Earnings","Japanese","https://example.com/q4.mp4",""
```
The `url` field must be a publicly accessible video download URL. If you paste the URL into an incognito browser window, the video should play without any login or password.
You can also start from this [Google Sheet template](https://docs.google.com/spreadsheets/d/1ykARUasMW0tHFbrFYnGCIINhga-wJLiu39zCDXnhZIQ/edit?usp=sharing) and export it as CSV.
## The Script
Save the following as `bulk_translate.py`:
```python theme={null}
import os
import csv
import requests
API_URL = "https://api.heygen.com/v3/video-translations"
API_KEY = os.environ.get("HEYGEN_API_KEY", "YOUR_API_KEY_HERE")
HEADERS = {
"accept": "application/json",
"content-type": "application/json",
"x-api-key": API_KEY,
}
def main():
input_csv = "bulk_translation_input.csv"
output_csv = "bulk_translation_results.csv"
results = []
with open(input_csv, mode="r", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
fieldnames = list(reader.fieldnames) + ["video_translation_id"]
for row in reader:
payload = {
"video": {
"type": "url",
"url": row["url"],
},
"output_languages": [row["output_language"]],
"title": row["title"],
}
folder_id = row.get("folder_id", "").strip()
if folder_id:
payload["folder_id"] = folder_id
try:
resp = requests.post(API_URL, headers=HEADERS, json=payload)
resp.raise_for_status()
data = resp.json()
ids = data.get("data", {}).get("video_translation_ids", [])
translation_id = ids[0] if ids else ""
print(f"Success: '{row['title']}' -> {translation_id}")
except requests.exceptions.RequestException as e:
translation_id = ""
print(f"Error: '{row['title']}' -> {e}")
row["video_translation_id"] = translation_id
results.append(row)
with open(output_csv, mode="w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(results)
print(f"\nResults saved to {output_csv}")
if __name__ == "__main__":
main()
```
## Running the Script
1. **Set your API key** as an environment variable (recommended):
```bash theme={null}
export HEYGEN_API_KEY="your-api-key-here"
```
Alternatively, replace `YOUR_API_KEY_HERE` in the script directly.
2. **Place your CSV** (`bulk_translation_input.csv`) in the same directory as the script.
3. **Run the script:**
```bash theme={null}
python bulk_translate.py
```
On macOS or Linux, use `python3` if needed:
```bash theme={null}
python3 bulk_translate.py
```
4. **Review results.** The script creates `bulk_translation_results.csv` with all original columns plus a `video_translation_id` column.
## Checking Translation Status
Use the `video_translation_id` values from the results CSV to poll each translation's status via [`GET /v3/video-translations/{video_translation_id}`](/reference/get-video-translation). For a hands-off pattern, pass `callback_url` in the payload instead — 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: '
```
When the status is `completed`, the response includes a presigned `video_url` to download the translated video.
For better security, always store your API key in an environment variable rather than hardcoding it in the script.
# 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.
# OAuth 2.0
Source: https://developers.heygen.com/docs/connecting-your-app-to-heygen-with-oauth-20
Use OAuth 2.0 to connect your app to HeyGen users on their behalf. Covers authorization flow, scopes, token exchange, refresh, and revocation with code.
This guide will walk you through connecting your app to HeyGen using OAuth 2.0. We'll use curl commands to demonstrate each step, making it easy to test the process. HeyGen uses the Authorization Code Flow with PKCE (Proof Key for Code Exchange) for added security.
## Prerequisites
Before you begin, ensure you have:
* Your HeyGen Client ID
* Your Redirect URI *(This will be provided by the Partnerships team after you have submitted your [Integration Intake form](https://form.typeform.com/to/m3EYcOaM))*
## Step 1: Initiate User Authorization
To begin the OAuth process, redirect the user to HeyGen's authorization URL. Create this URL (Replace the placeholders with your own values):
```bash bash theme={null}
https://app.heygen.com/oauth/authorize?client_id=YOUR_CLIENT_ID&state=RANDOM_STATE&redirect_uri=YOUR_REDIRECT_URI&code_challenge=CODE_CHALLENGE&code_challenge_method=S256&response_type=code
```
* **YOUR\_CLIENT\_ID**: Client ID. (e.g., `abc123`)
* **RANDOM\_STATE**: A unique string to maintain state. (e.g., `xyz789`)
* **YOUR\_REDIRECT\_URI**: Your approved redirect URI, ensure URL-encoded. (e.g., `https://example.com/oauth/callback`)
* **CODE\_CHALLENGE** Corresponding `code_challenge` for a generated `code_verifier` using the PKCE flow (e.g. `E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM`). See: [RFC 7636 Appendix B](https://www.rfc-editor.org/rfc/rfc7636#appendix-B).
**Note:**
You can generate a *code\_verifier* and *code\_challenge* using online PKCE tools or standard libraries (e.g., Node.js, Python).
## Step 2: Handle the Authorization Callback
After approval, you'll be redirected to your Redirect URI with a `code` parameter. It'll look like this:
```bash bash theme={null}
https://yourapp.com/oauth/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATE
```
Verify that the state matches the one you sent in Step 1, then extract the `AUTHORIZATION_CODE`.
## Step 3: Exchange Authorization Code for Access Token
Exchange the authorization code for an access token using this curl command:
```bash bash theme={null}
curl -X POST https://api2.heygen.com/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "code=AUTHORIZATION_CODE" \
-d "client_id=YOUR_CLIENT_ID" \
-d "grant_type=authorization_code" \
-d "redirect_uri=YOUR_REDIRECT_URI" \
-d "code_verifier=YOUR_CODE_VERIFIER"
```
```json Response theme={null}
{
"token_type": "Bearer",
"access_token": "YyzWfeiqmklLsvzNsallQgUgfKHaNSnpv60BNgLGsC",
"expires_in": 864000,
"refresh_token": "dfU22fh6iD4aCkpy9GI32ulJXVe5eC8u4rt4SXedJHHich6L"
}
```
Save the `access_token` and `refresh_token` to use in the next steps.
## Step 4: Use the Access Token
Now you can make requests to HeyGen's API using the `Authorization` header with the Bearer scheme. Here's an example listing your avatar groups:
```bash bash theme={null}
curl -X GET https://api.heygen.com/v3/avatars \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
The **HeyGen API** endpoints support both authentication methods:
* **OAuth Access Tokens** — Use the `Authorization` header with the Bearer scheme. Format: `Authorization: Bearer YOUR_ACCESS_TOKEN`
* **API Keys** — Use the `X-Api-Key` header. Format: `X-Api-Key: YOUR_API_KEY`
## Step 5: Refresh the Access Token
Access tokens expire. Keep track of token expiration and refresh as needed. When the access token expires, use the refresh token to obtain a new one:
```bash bash theme={null}
curl -X POST https://api2.heygen.com/v1/oauth/refresh_token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=YOUR_CLIENT_ID" \
-d "grant_type=refresh_token" \
-d "refresh_token=REFRESH_TOKEN"
```
```json Response theme={null}
{
"token_type": "Bearer",
"access_token": "YyzWfeiqmklLsvzNsallQgUgfKHaNSnpv60BNgLGsC",
"expires_in": 864000,
"refresh_token": "dfU22fh6iD4aCkpy9GI32ulJXVe5eC8u4rt4SXedJHHich6L"
}
```
## Get User's Account Information
After successfully authenticating with HeyGen, you can retrieve account information — including remaining credits and billing details — using the `GET /v3/users/me` endpoint:
```bash bash theme={null}
curl -X GET https://api.heygen.com/v3/users/me \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
```json Response theme={null}
{
"code": 100,
"data": {
"username": "jane_doe",
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"billing_type": "subscription",
"subscription": {
"plan": "enterprise",
"credits": {
"premium_credits": {
"remaining": 250,
"resets_at": "2026-05-01T00:00:00Z"
},
"add_on_credits": {
"remaining": 50,
"resets_at": null
}
}
}
},
"message": null
}
```
Replace `YOUR_ACCESS_TOKEN` with the access token obtained during the OAuth process.
The `billing_type` field indicates which billing object is populated in the response:
| Billing Type | Field | Description |
| -------------- | -------------- | ------------------------------------------------------------------------------------------- |
| `wallet` | `wallet` | Prepaid balance in USD (or credits for Enterprise). Includes optional auto-reload settings. |
| `subscription` | `subscription` | Per-pool credit balances with plan tier and reset dates. Used with OAuth integration apps. |
| `usage_based` | `usage_based` | Metered billing with current spending and optional spending cap. |
Only the field matching `billing_type` will be populated; the others will be `null`.
## Best Practices and Security Considerations
* Always use **HTTPS** for all OAuth-related requests.
* Store tokens securely and never expose them client-side.
* Implement token rotation and regularly refresh access tokens. If a request fails, check if the access token has expired.
* Validate the `state` parameter to prevent CSRF attacks.
* Use short-lived access tokens and long-lived refresh tokens.
* Implement proper error handling for token expiration and other OAuth-related errors.
***
# 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_4_quality", "avatar_4_turbo"],
"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
Initiate a consent flow for an avatar group. Returns a URL the avatar subject must visit to approve usage. Full schema: [`POST /v3/avatars/{group_id}/consent`](/reference/create-avatar-consent).
Consent is only required for **digital twin** avatars (`type: "digital_twin"`). Photo avatars and prompt-to-avatar characters do not require consent.
### Request
```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. |
### 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. |
| `avatar_group.consent_status` | string | Current consent status (e.g. `"pending"`). |
Check `consent_status` on the avatar group via [`GET /v3/avatars/{group_id}`](/reference/get-avatar-group) to know when consent is complete.
## 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.
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 |
Ask questions, get direct access to engineers and PMs, and connect with developers building with HeyGen. Free to join, no waitlist.
# 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 are billed in **credits**. Credits are consumed when a video, translation, or speech job completes successfully — you are not charged for failed jobs.
Most rates are **per second of output video** (or audio) produced. The rate depends on the feature and the quality mode you select.
Credit balances and remaining usage are available via `GET /v3/users/me`. Contact your account team to purchase additional credits or adjust your credit pool.
**OAuth vs API Key:** If you authenticate with an OAuth bearer token, usage is billed against your **web plan**, not the API tier.
Using an **API Key** is recommended for automation and integration workflows. API key authentication provides higher concurrency limits and is more flexible and powerful for programmatic use.
## Pricing
### Video Generation — Avatar IV & V
| 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
Avatar III is available to existing customers only. It is not offered to new users. For all new integrations use Avatar IV or Avatar V.
| 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 |
### 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 |
### 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-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-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 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.
### `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.
### `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.
### `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`.
### `ephemeral_upload_disabled`
**HTTP status:** `400`
Eager upload (direct-to-S3 pre-upload followed by an `ephemeral` commit on the create request) is temporarily disabled for your account. Fall back to the standard upload flow by supplying `input_video_id` or `google_url` instead. If the error persists, retry later or contact [HeyGen support](https://help.heygen.com).
### `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.
### `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.
### `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
**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), [OAuth 2.0](/docs/connecting-your-app-to-heygen-with-oauth-20).
* 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
```
**OAuth vs API Key:** If you authenticate with an OAuth bearer token, usage is billed against your **web plan**, not the API tier. Check your web plan balance with `GET /v3/users/me → subscription`.
Using an **API Key** is recommended for automation and integration workflows. API key authentication provides higher concurrency limits and is more flexible and powerful for programmatic use.
## Pricing
All rates are billed in USD based on output duration.
### Video Generation — Avatar IV & V
| Avatar Type | 720p / 1080p | 4K |
| ------------- | -------------- | -------------- |
| Photo Avatar | \$0.05 / sec | \$0.0667 / sec |
| Digital Twin | \$0.0667 / sec | \$0.0833 / sec |
| Studio Avatar | \$0.0667 / sec | \$0.0833 / sec |
### Video Generation — Avatar III
Avatar III is available to existing customers only. It is not offered to new users. For all new integrations use Avatar IV or Avatar V.
| Avatar Type | 720p / 1080p | 4K |
| ------------- | -------------- | ------------ |
| Photo Avatar | \$0.0167 / sec | \$0.02 / sec |
| Digital Twin | \$0.0167 / sec | \$0.02 / sec |
| Studio Avatar | \$0.0167 / sec | \$0.02 / 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 |
### 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.0167 / 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 |
### 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 OAuth and 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 [Bulk Video Translation](/docs/bulk-video-translation).
## 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 |
## 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 [Bulk Video Translation](/docs/bulk-video-translation).
# 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 [Bulk Video Translation](/docs/bulk-video-translation).
## 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: '
```
### 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](/docs/video-translate) | 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 [Bulk Video Translation](/docs/bulk-video-translation).
# Design a Voice
Source: https://developers.heygen.com/docs/voices/design-voices
Generate a brand new custom voice from a natural-language description using the HeyGen Design a Voice API. No source audio needed; describe the voice you want.
Full schema: [`POST /v3/voices`](/reference/design-a-voice). To browse the existing catalog instead, see [Browse Voices](/docs/voices/search-voices). To clone an existing speaker from audio, see [`POST /v3/voices/clone`](/reference/clone-a-voice).
## Quick Example
```bash curl theme={null}
curl -X POST "https://api.heygen.com/v3/voices" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A warm, confident male voice with a slight British accent. Deep baritone, measured pace, suitable for tech product narration.",
"gender": "male"
}'
```
```python Python theme={null}
import requests
resp = requests.post(
"https://api.heygen.com/v3/voices",
headers={"X-Api-Key": HEYGEN_API_KEY},
json={
"prompt": "A warm, confident male voice with a slight British accent. Deep baritone, measured pace, suitable for tech product narration.",
"gender": "male",
},
)
result = resp.json()["data"]
for v in result["voices"]:
print(f"{v['voice_id']} — {v['name']}")
```
```javascript Node.js theme={null}
const resp = await fetch("https://api.heygen.com/v3/voices", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "A warm, confident male voice with a slight British accent. Deep baritone, measured pace, suitable for tech product narration.",
gender: "male",
}),
});
const { data } = await resp.json();
data.voices.forEach((v) => console.log(`${v.voice_id} — ${v.name}`));
```
```json Response theme={null}
{
"data": {
"voices": [
{
"voice_id": "1bd001e7e50f421d891986aad5c8bbd2",
"name": "James",
"language": "English",
"gender": "male",
"preview_audio_url": "https://files.heygen.ai/voice/preview/james.mp3",
"support_pause": true,
"support_locale": true,
"type": "public"
}
],
"seed": 0
}
}
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `prompt` | string | Yes | Text description of the desired voice. Max 1000 characters. |
| `gender` | string | No | Filter results by `"male"` or `"female"`. |
| `locale` | string | No | BCP-47 locale tag to filter by (e.g. `"en-US"`, `"pt-BR"`). |
| `seed` | integer | No | Controls which batch of results to return. `0` returns the top matches, `1` the next batch. Same prompt + seed always returns the same voices. |
## Response Fields
| Field | Type | Description |
| -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `voices` | array | Up to 3 matching voices, ordered by relevance. Each has the same shape as voices returned by [`GET /v3/voices`](/reference/list-voices). Use `voice_id` directly in [`POST /v3/voices/speech`](/reference/generate-speech) or video creation. |
| `seed` | integer | The seed used for this request. Increment to get a different batch of voices. |
## Getting Different Results
If the returned voices don't fit, increment `seed` to get the next batch — same prompt, different voices:
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/voices" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A warm, confident male voice with a slight British accent.",
"gender": "male",
"seed": 1
}'
```
**Prompting tips:**
* Specify gender and age (`"young woman in her 20s"`, `"mature male voice"`)
* Describe the accent (`"American Midwest"`, `"slight French accent"`)
* Set the tone (`"warm and friendly"`, `"authoritative"`, `"playful"`)
* Mention pacing (`"measured and calm"`, `"energetic and fast"`)
* Reference a use case (`"suitable for corporate training"`, `"good for storytelling"`)
# Voices
Source: https://developers.heygen.com/docs/voices/overview
Browse, search, and use voices on HeyGen via the Voices API. Covers stock voices, custom voice design, voice cloning, and how to pair voices with avatars.
HeyGen provides 300+ pre-built voices across dozens of languages, plus the ability to generate custom AI voices from a text description, clone an existing speaker, or synthesize speech directly. This guide walks the full **browse → design → use** workflow; deeper guides for each step live at [Browse Voices](/docs/voices/search-voices), [Design Voices](/docs/voices/design-voices), and [Text to Speech](/docs/voices/speech).
## Step 1: Browse Available Voices
Use [`GET /v3/voices`](/reference/list-voices) to list available voices with cursor-based pagination. Filter by language, gender, type, or engine.
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/voices?language=English&gender=female" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```python Python theme={null}
import requests
resp = requests.get(
"https://api.heygen.com/v3/voices",
headers={"X-Api-Key": HEYGEN_API_KEY},
params={"language": "English", "gender": "female"},
)
data = resp.json()
voices = data["data"]
for v in voices[:5]:
print(f"{v['voice_id']} — {v['name']} ({v['language']})")
```
```javascript Node.js theme={null}
const resp = await fetch(
"https://api.heygen.com/v3/voices?language=English&gender=female",
{ headers: { "X-Api-Key": process.env.HEYGEN_API_KEY } }
);
const { data } = await resp.json();
data.slice(0, 5).forEach((v) =>
console.log(`${v.voice_id} — ${v.name} (${v.language})`)
);
```
```json Response theme={null}
{
"data": [
{
"voice_id": "1bd001e7e50f421d891986aad5c8bbd2",
"name": "Sara",
"language": "English",
"gender": "female",
"preview_audio_url": "https://files.heygen.ai/voice/preview/sara.mp3",
"support_pause": true,
"support_locale": true,
"type": "public"
}
],
"has_more": true,
"next_token": "eyJsYXN0X2lkIjoiMTIzIn0"
}
```
### Query Parameters
| Parameter | Type | Description |
| ---------- | ------- | ------------------------------------------------------------------------------------------------- |
| `type` | string | `"public"` for the shared library or `"private"` for your cloned voices. Defaults to `"public"`. |
| `engine` | string | Filter by voice engine (e.g. `"starfish"`). Only voices compatible with that engine are returned. |
| `language` | string | Filter by language name (e.g. `"English"`, `"Spanish"`, `"Japanese"`). |
| `gender` | string | Filter by `"male"` or `"female"`. |
| `limit` | integer | Results per page (1–100). Defaults to `20`. |
| `token` | string | Opaque cursor token for the next page. |
### Response Fields
| Field | Type | Description |
| ------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `voice_id` | string | Pass this as `voice_id` to [`POST /v3/videos`](/reference/create-video) or [`POST /v3/video-agents`](/reference/create-video-agent-session). Inspect a single voice with [`GET /v3/voices/{voice_id}`](/reference/get-voice). |
| `name` | string | Display name of the voice. |
| `language` | string | Primary language. |
| `gender` | string | Gender of the voice. |
| `preview_audio_url` | string or null | URL to a short audio preview — play to audition the voice. |
| `support_pause` | boolean | Whether the voice supports SSML pause/break tags. |
| `support_locale` | boolean | Whether the voice supports locale variants. |
| `type` | string | `"public"` or `"private"`. |
Each voice includes a `preview_audio_url` — play these to audition voices before using one in your video.
## Step 2: Design a Custom Voice (Optional)
If none of the pre-built voices fit, use [`POST /v3/voices`](/reference/design-a-voice) to generate up to 3 AI voice options from a text description. The endpoint returns a ranked list — pick the one that fits best. For more detail and prompting patterns, see [Design Voices](/docs/voices/design-voices). To clone an existing speaker from audio samples instead, use [`POST /v3/voices/clone`](/reference/clone-a-voice).
```bash curl theme={null}
curl -X POST "https://api.heygen.com/v3/voices" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A warm, confident male voice with a slight British accent. Deep baritone, measured pace, suitable for tech product narration.",
"gender": "male"
}'
```
```python Python theme={null}
resp = requests.post(
"https://api.heygen.com/v3/voices",
headers={"X-Api-Key": HEYGEN_API_KEY},
json={
"prompt": "A warm, confident male voice with a slight British accent. Deep baritone, measured pace, suitable for tech product narration.",
"gender": "male",
},
)
result = resp.json()["data"]
for v in result["voices"]:
print(f"{v['voice_id']} — {v['name']}")
```
```javascript Node.js theme={null}
const resp = await fetch("https://api.heygen.com/v3/voices", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "A warm, confident male voice with a slight British accent. Deep baritone, measured pace, suitable for tech product narration.",
gender: "male",
}),
});
const { data } = await resp.json();
data.voices.forEach((v) => console.log(`${v.voice_id} — ${v.name}`));
```
```json Response theme={null}
{
"data": {
"voices": [
{
"voice_id": "1bd001e7e50f421d891986aad5c8bbd2",
"name": "James",
"language": "English",
"gender": "male",
"preview_audio_url": "https://files.heygen.ai/voice/preview/james.mp3",
"support_pause": true,
"support_locale": true,
"type": "public"
}
],
"seed": 0
}
}
```
### Parameters
| Parameter | Type | Required | Description |
| --------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `prompt` | string | Yes | Text description of the desired voice — accent, tone, pace, gender, personality. Max 1000 characters. |
| `gender` | string | No | Filter results by `"male"` or `"female"`. |
| `locale` | string | No | BCP-47 locale tag to filter by (e.g. `"en-US"`, `"pt-BR"`). |
| `seed` | integer | No | Controls which batch of results to return. `0` returns the top matches, `1` the next batch, etc. Same prompt + seed always returns the same voices. |
**Prompting tips for voice design:**
* Specify gender and approximate age (`"young woman in her 20s"`, `"mature male voice"`)
* Describe the accent (`"American Midwest"`, `"slight French accent"`)
* Set the tone (`"warm and friendly"`, `"authoritative"`, `"playful"`)
* Mention pacing (`"measured and calm"`, `"energetic and fast"`)
* Reference a use case (`"suitable for corporate training"`, `"good for storytelling"`)
## Step 3: Use a Voice in Video Creation
Once you have a `voice_id`, pass it when creating a video. To render speech directly without an avatar, use [Text to Speech](/docs/voices/speech) ([`POST /v3/voices/speech`](/reference/generate-speech)).
### With 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": "A 30-second explainer about cloud computing benefits",
"voice_id": "1bd001e7e50f421d891986aad5c8bbd2"
}'
```
### With Direct Video Creation
Set the voice alongside your avatar and script:
```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": "1bd001e7e50f421d891986aad5c8bbd2",
"script": "Welcome to our platform. Today I will walk you through the key features."
}'
```
### Voice Settings
When using [`POST /v3/videos`](/reference/create-video), you can fine-tune playback via `voice_settings`:
```json theme={null}
{
"type": "avatar",
"avatar_id": "your_look_id",
"voice_id": "1bd001e7e50f421d891986aad5c8bbd2",
"script": "Welcome to our platform.",
"voice_settings": {
"speed": 1.1,
"pitch": 0.0,
"locale": "en-US"
}
}
```
| Field | Type | Range | Description |
| -------- | ------ | ------------- | ------------------------------------------------- |
| `speed` | number | `0.5` – `1.5` | Playback speed multiplier. `1.0` is normal speed. |
| `pitch` | number | `-50` – `+50` | Pitch adjustment in semitones. |
| `locale` | string | BCP-47 | Locale/accent hint for multi-lingual voices. |
# Browse Voices
Source: https://developers.heygen.com/docs/voices/search-voices
List and filter HeyGen voices by language, gender, accent, and age. Returns voice IDs ready to use in video generation requests across all HeyGen video APIs.
List voices via [`GET /v3/voices`](/reference/list-voices); inspect one via [`GET /v3/voices/{voice_id}`](/reference/get-voice). To create a new voice instead, see [Design Voices](/docs/voices/design-voices) or [Clone a Voice](/reference/clone-a-voice). For TTS, see [Text to Speech](/docs/voices/speech).
## Quick Example
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/voices?language=English&gender=female&limit=5" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"data": [
{
"voice_id": "1bd001e7e50f421d891986aad5c8bbd2",
"name": "Sara",
"language": "English",
"gender": "female",
"type": "public",
"preview_audio_url": "https://files.heygen.ai/voice/preview_sara.mp3",
"support_pause": true,
"support_locale": true
}
],
"has_more": true,
"next_token": "eyJsYXN0X2lkIjoiMTIzIn0"
}
```
## Query Parameters
| Parameter | Type | Required | Default | Description |
| ---------- | ------- | -------- | ---------- | -------------------------------------------------------------------------------- |
| `type` | string | No | `"public"` | `"public"` for the shared library or `"private"` for your cloned voices. |
| `engine` | string | No | — | Filter by voice engine (e.g. `"starfish"`). Only compatible voices are returned. |
| `language` | string | No | — | Filter by language (e.g. `"English"`, `"Spanish"`). |
| `gender` | string | No | — | Filter by `"male"` or `"female"`. |
| `limit` | integer | No | `20` | Results per page (1–100). |
| `token` | string | No | — | Opaque cursor token for the next page (from a previous response's `next_token`). |
## Response Fields
Each voice object in the `data` array contains:
| Field | Type | Description |
| ------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `voice_id` | string | Unique identifier. Pass this to [`POST /v3/voices/speech`](/reference/generate-speech), [`POST /v3/videos`](/reference/create-video), or [`POST /v3/video-agents`](/reference/create-video-agent-session). |
| `name` | string | Display name of the voice. |
| `language` | string | Primary language (e.g. `"English"`). |
| `gender` | string | `"male"` or `"female"`. |
| `type` | string | `"public"` (shared library) or `"private"` (your cloned voice). |
| `preview_audio_url` | string or null | URL to a short audio preview. |
| `support_pause` | boolean | Whether the voice supports SSML pause/break tags. |
| `support_locale` | boolean | Whether the voice supports locale variants (e.g. `en-US` vs `en-GB`). |
## Filtering Examples
### By Language
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/voices?language=Spanish" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### By Gender
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/voices?gender=male" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### Your Private (Cloned) Voices
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/voices?type=private" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### TTS-Compatible Voices Only
To get voices that work with [`POST /v3/voices/speech`](/reference/generate-speech), filter by the `starfish` engine:
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/voices?engine=starfish" \
-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/voices?token=eyJsYXN0X2lkIjoiMTIzIn0" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
# Text to Speech
Source: https://developers.heygen.com/docs/voices/speech
Convert text to audio using any HeyGen voice via the Text-to-Speech API. Useful for previewing voices, generating standalone audio, or pre-rendering speech for.
Full schema: [`POST /v3/voices/speech`](/reference/generate-speech).
Starfish only works with **Starfish-compatible voices**. Not all HeyGen voices support this engine. Use [`GET /v3/voices?engine=starfish`](/reference/list-voices) — see [Browse Voices](/docs/voices/search-voices) — to get a list of compatible voices before calling this endpoint.
## Quick Example
```bash curl theme={null}
curl -X POST "https://api.heygen.com/v3/voices/speech" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello from HeyGen!",
"voice_id": "1bd001e7e50f421d891986aad5c8bbd2"
}'
```
```python Python theme={null}
import requests
resp = requests.post(
"https://api.heygen.com/v3/voices/speech",
headers={"X-Api-Key": HEYGEN_API_KEY},
json={
"text": "Hello from HeyGen!",
"voice_id": "1bd001e7e50f421d891986aad5c8bbd2",
},
)
data = resp.json()["data"]
print(data["audio_url"], data["duration"])
```
```javascript Node.js theme={null}
const resp = await fetch("https://api.heygen.com/v3/voices/speech", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "Hello from HeyGen!",
voice_id: "1bd001e7e50f421d891986aad5c8bbd2",
}),
});
const { data } = await resp.json();
console.log(data.audio_url, data.duration);
```
```json Response theme={null}
{
"data": {
"audio_url": "https://files.heygen.ai/audio/req_xyz789.mp3",
"duration": 2.4,
"request_id": "req_xyz789",
"word_timestamps": [
{ "word": "Hello", "start": 0.0, "end": 0.45 },
{ "word": "from", "start": 0.45, "end": 0.72 },
{ "word": "HeyGen!", "start": 0.72, "end": 1.35 }
]
}
}
```
## Finding a Compatible Voice
Before calling this endpoint, find a Starfish-compatible `voice_id`:
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/voices?engine=starfish&language=English&gender=female" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
See [Browse Voices](/docs/voices/search-voices) for full filtering and pagination details.
## Parameters
| Parameter | Type | Required | Default | Description |
| ------------ | ------ | -------- | ------------- | ----------------------------------------------------------------------------- |
| `text` | string | Yes | — | Text to synthesize (1–5,000 characters). |
| `voice_id` | string | Yes | — | A Starfish-compatible voice ID. |
| `input_type` | string | No | `"text"` | `"text"` for plain text or `"ssml"` for SSML markup. |
| `speed` | number | No | `1.0` | Speed multiplier (0.5–2.0). |
| `language` | string | No | auto-detected | Base language code (e.g. `"en"`, `"pt"`, `"zh"`). Auto-detected when omitted. |
| `locale` | string | No | — | BCP-47 locale tag (e.g. `"en-US"`, `"pt-BR"`). Overrides `language` when set. |
## Response Fields
| Field | Type | Description |
| ----------------- | -------------- | ------------------------------------------------------------------------------ |
| `audio_url` | string | URL of the generated audio file. |
| `duration` | number | Duration of the audio in seconds. |
| `request_id` | string or null | Unique identifier for this generation request. |
| `word_timestamps` | array or null | Word-level timing data — each entry has `word`, `start`, and `end` in seconds. |
## SSML Support
For finer control over pronunciation, pauses, and emphasis, set `input_type` to `"ssml"`. Check the `support_pause` field on the voice object returned by [`GET /v3/voices`](/reference/list-voices) to confirm whether the selected voice supports SSML `` tags.
This also works with [`POST /v3/videos`](/reference/create-video) when generating videos from scripts, letting you control pacing and pauses directly within the narration.\\
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/voices/speech" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Welcome to HeyGen. Let us get started. ",
"voice_id": "1bd001e7e50f421d891986aad5c8bbd2",
"input_type": "ssml"
}'
```
# Webhook Events
Source: https://developers.heygen.com/docs/webhook-events
Reference every HeyGen webhook event type with full payload schemas. Includes video.completed, video.failed, translation.completed, with example JSON.
HeyGen sends webhook events as POST requests to your registered endpoints. This page covers the available event types and how to browse delivered events.
## Authentication
| Header | Value |
| --------------- | ---------------------------------- |
| `X-Api-Key` | Your HeyGen API key |
| `Authorization` | `Bearer YOUR_ACCESS_TOKEN` (OAuth) |
## Event Types
Fetch the full list of supported event types and their descriptions:
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/webhooks/event-types" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"data": [
{ "event_type": "avatar_video.success", "description": "Fired when an avatar video completes successfully." },
{ "event_type": "avatar_video.fail", "description": "Fired when an avatar video generation fails." },
{ "event_type": "video_agent.success", "description": "Fired when a Video Agent session completes successfully." },
{ "event_type": "video_agent.fail", "description": "Fired when a Video Agent session fails." }
],
"has_more": false,
"next_token": null
}
```
### Available Event Types
| Event Type | Description |
| --------------------------------- | ------------------------------------------ |
| `avatar_video.success` | Avatar video completed successfully. |
| `avatar_video.fail` | Avatar video generation failed. |
| `avatar_video_gif.success` | Avatar video GIF generation completed. |
| `avatar_video_gif.fail` | Avatar video GIF generation failed. |
| `avatar_video_caption.success` | Avatar video caption generation completed. |
| `avatar_video_caption.fail` | Avatar video caption generation failed. |
| `video_translate.success` | Video translation completed. |
| `video_translate.fail` | Video translation failed. |
| `video_agent.success` | Video Agent session completed. |
| `video_agent.fail` | Video Agent session failed. |
| `personalized_video` | Personalized video event. |
| `instant_avatar.success` | Instant avatar creation completed. |
| `instant_avatar.fail` | Instant avatar creation failed. |
| `photo_avatar_generation.success` | Photo avatar generation completed. |
| `photo_avatar_generation.fail` | Photo avatar generation failed. |
| `photo_avatar_train.success` | Photo avatar training completed. |
| `photo_avatar_train.fail` | Photo avatar training failed. |
| `photo_avatar_add_motion.success` | Photo avatar motion addition completed. |
| `photo_avatar_add_motion.fail` | Photo avatar motion addition failed. |
| `proofread_creation.success` | Proofread creation completed. |
| `proofread_creation.fail` | Proofread creation failed. |
| `live_avatar.success` | Live avatar session completed. |
| `live_avatar.fail` | Live avatar session failed. |
## Event Data Payloads
The `event_data` shape varies by event type. Below are the fields for `avatar_video.success`:
### `avatar_video.success`
| Field | Type | Description |
| ---------------------- | -------------- | ----------------------------------------------------------------- |
| `video_id` | string | Unique ID for the completed video. |
| `url` | string | Pre-signed download URL for the video file. |
| `gif_download_url` | string | Pre-signed download URL for the GIF preview. |
| `video_page_url` | string | URL to the video in the HeyGen app. |
| `video_share_page_url` | string | Shareable URL for the video. |
| `folder_id` | string \| null | Folder the video belongs to, or `null`. |
| `callback_id` | string \| null | Value of `callback_id` passed when creating the video, or `null`. |
The video download URL (`url`) is a pre-signed URL with a limited expiry window. Fetch and store the file promptly, or re-fetch the URL via `GET /v3/videos/{video_id}` if it expires.
## List Delivered Events
Browse events that have been delivered to your endpoints. Filter by event type or entity ID.
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/webhooks/events?event_type=avatar_video.success&limit=5" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"data": [
{
"event_id": "evt_abc123",
"event_type": "avatar_video.success",
"event_data": {
"video_id": "vid_xyz789",
"url": "https://files.heygen.com/video/vid_xyz789.mp4",
"gif_download_url": "https://resource.heygen.ai/video/vid_xyz789/gif.gif",
"video_page_url": "https://app.heygen.com/videos/vid_xyz789",
"video_share_page_url": "https://app.heygen.com/share/vid_xyz789",
"folder_id": null,
"callback_id": null
},
"created_at": "2026-03-25T12:05:00Z"
}
],
"has_more": false,
"next_token": null
}
```
### Query Parameters
| Parameter | Type | Required | Default | Description |
| ------------ | ------- | -------- | ------- | ---------------------------------------------------------------- |
| `event_type` | string | No | all | Filter by a specific event type (e.g. `"avatar_video.success"`). |
| `entity_id` | string | No | — | Filter by entity ID (e.g. a video ID or session ID). |
| `limit` | integer | No | `10` | Results per page (1–100). |
| `token` | string | No | — | Opaque cursor for the next page. |
### Response Fields
Each event in the `data` array contains:
| Field | Type | Description |
| ------------ | ------ | ----------------------------------------------- |
| `event_id` | string | Unique identifier for this event delivery. |
| `event_type` | string | The event type (e.g. `"avatar_video.success"`). |
| `event_data` | object | The event payload. Contents vary by event type. |
| `created_at` | string | ISO 8601 timestamp when the event was created. |
## Subscribing to Events
When [creating a webhook endpoint](), pass the event types you want to receive in the `events` array:
```json "Subscribe to video events only" theme={null}
{
"url": "https://yourapp.com/webhooks/heygen",
"events": [
"avatar_video.success",
"avatar_video.fail",
"video_agent.success",
"video_agent.fail"
]
}
```
```json "Receive all events" theme={null}
{
"url": "https://yourapp.com/webhooks/heygen"
}
```
Omitting the `events` field (or setting it to `null`) subscribes the endpoint to all event types.
To change subscriptions later, use `PATCH /v3/webhooks/endpoints/{endpoint_id}` with an updated `events` array.
## Handling Events in Your Application
When an event is delivered, HeyGen sends a POST request to your endpoint URL with the event payload as JSON. A typical handler:
1. **Verify the signature** using your endpoint's signing secret.
2. **Parse `event_type`** to determine what happened.
3. **Process `event_data`** — for `avatar_video.success` events this includes `video_id`, `url` (the video download URL), `gif_download_url`, `video_page_url`, `video_share_page_url`, `folder_id`, and `callback_id`; for failures it includes error details.
4. **Return a 2xx response** promptly to acknowledge receipt.
# Webhooks
Source: https://developers.heygen.com/docs/webhooks
Receive HeyGen API webhook events when videos finish rendering, translations complete, or errors occur. Includes endpoint setup, signature verification, and.
Instead of polling [`GET /v3/videos/{video_id}`](/reference/get-video) for status, you can register a webhook endpoint to receive a POST notification when a video completes, fails, or other events occur. See [Webhook Events](/docs/webhook-events) for the full event catalog and payload shapes.
* **Base path:** `https://api.heygen.com/v3/webhooks/endpoints`
## Authentication
Same as the rest of the v3 API — see the [API Key guide](/docs/api-key).
| Header | Value |
| --------------- | ---------------------------------- |
| `X-Api-Key` | Your HeyGen API key |
| `Authorization` | `Bearer YOUR_ACCESS_TOKEN` (OAuth) |
## Create an Endpoint
Register a URL to receive webhook events. The response includes a `secret` for verifying payloads — store it securely, as it will not be shown again. Full schema: [`POST /v3/webhooks/endpoints`](/reference/create-webhook-endpoint).
```bash curl theme={null}
curl -X POST "https://api.heygen.com/v3/webhooks/endpoints" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/heygen",
"events": ["avatar_video.success", "avatar_video.fail"]
}'
```
```json Response theme={null}
{
"data": {
"endpoint_id": "ep_abc123",
"url": "https://yourapp.com/webhooks/heygen",
"events": ["avatar_video.success", "avatar_video.fail"],
"status": "enabled",
"created_at": "2026-03-25T12:00:00Z",
"secret": "whsec_k7x9m2..."
}
}
```
### Request Parameters
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url` | string | Yes | Publicly accessible HTTPS URL that will receive webhook POST requests. |
| `events` | array | No | Event types to subscribe to. Omit or set to `null` to receive all events. See [Webhook Events](/docs/webhook-events) and [`GET /v3/webhooks/event-types`](/reference/list-webhook-event-types) for the full list. |
| `entity_id` | string | No | Scope this endpoint to a specific resource (e.g. a personalized video project). |
## List Endpoints
Retrieve all registered endpoints with pagination. Full schema: [`GET /v3/webhooks/endpoints`](/reference/list-webhook-endpoints).
```bash curl theme={null}
curl -X GET "https://api.heygen.com/v3/webhooks/endpoints?limit=10" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"data": [
{
"endpoint_id": "ep_abc123",
"url": "https://yourapp.com/webhooks/heygen",
"events": ["avatar_video.success", "avatar_video.fail"],
"status": "enabled",
"created_at": "2026-03-25T12:00:00Z",
"secret": null
}
],
"has_more": false,
"next_token": null
}
```
| Parameter | Type | Required | Default | Description |
| --------- | ------- | -------- | ------- | -------------------------------- |
| `limit` | integer | No | `10` | Results per page (1–100). |
| `token` | string | No | — | Opaque cursor for the next page. |
The `secret` field is only returned when creating an endpoint or rotating the secret. It will be `null` in list responses.
## Update an Endpoint
Change the URL and/or subscribed event types. The `events` array is fully replaced — include all event types you want to keep. Full schema: [`PATCH /v3/webhooks/endpoints/{endpoint_id}`](/reference/update-webhook-endpoint).
```bash curl theme={null}
curl -X PATCH "https://api.heygen.com/v3/webhooks/endpoints/ep_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/heygen-v2",
"events": ["avatar_video.success", "avatar_video.fail", "video_agent.success"]
}'
```
```json Response theme={null}
{
"data": {
"endpoint_id": "ep_abc123",
"url": "https://yourapp.com/webhooks/heygen-v2",
"events": ["avatar_video.success", "avatar_video.fail", "video_agent.success"],
"status": "enabled",
"created_at": "2026-03-25T12:00:00Z",
"secret": null
}
}
```
Both fields are optional — include only what you want to change.
## Delete an Endpoint
Permanently remove an endpoint. Events will no longer be delivered to this URL. Full schema: [`DELETE /v3/webhooks/endpoints/{endpoint_id}`](/reference/delete-webhook-endpoint).
```bash curl theme={null}
curl -X DELETE "https://api.heygen.com/v3/webhooks/endpoints/ep_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"data": {}
}
```
## Rotate Signing Secret
Generate a new signing secret for an endpoint. The old secret is invalidated immediately on rotation; deploy the new secret from the response to your verifier promptly. Expect a brief window of failed verifications during rollover — handle this gracefully on the receiver. Full schema: [`POST /v3/webhooks/endpoints/{endpoint_id}/rotate-secret`](/reference/rotate-webhook-signing-secret).
```bash curl theme={null}
curl -X POST "https://api.heygen.com/v3/webhooks/endpoints/ep_abc123/rotate-secret" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
```json Response theme={null}
{
"data": {
"endpoint_id": "ep_abc123",
"secret": "whsec_n3w5ecr3t..."
}
}
```
## Verifying Payloads
Every webhook delivery includes a signature header derived from your endpoint `secret`. **Always verify before trusting the payload** — without verification, anyone who learns your URL can forge events.
HeyGen signs the **raw request body** with HMAC-SHA256 using the endpoint `secret` returned by [Create Endpoint](/reference/create-webhook-endpoint) or [Rotate Secret](/reference/rotate-webhook-signing-secret). Compute the same digest on your side and compare in constant time.
| Header | Description |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `Heygen-Signature` | Hex-encoded HMAC-SHA256 of the raw request body, computed with your endpoint `secret`. |
| `Heygen-Timestamp` | Unix timestamp (seconds) when the event was dispatched. Reject deliveries older than \~5 minutes to defend against replay. |
| `Heygen-Event-Id` | Unique ID for this delivery — use to de-duplicate on your side (events can be redelivered on retry). |
```javascript Node.js theme={null}
import crypto from "node:crypto";
import express from "express";
const app = express();
// IMPORTANT: keep the raw body intact for signature verification.
app.use("/webhooks/heygen", express.raw({ type: "application/json" }));
const SECRET = process.env.HEYGEN_WEBHOOK_SECRET;
const MAX_SKEW_SECONDS = 300;
app.post("/webhooks/heygen", (req, res) => {
const signature = req.header("Heygen-Signature");
const timestamp = req.header("Heygen-Timestamp");
if (!signature || !timestamp) return res.status(400).send("missing headers");
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > MAX_SKEW_SECONDS) {
return res.status(400).send("stale timestamp");
}
const expected = crypto
.createHmac("sha256", SECRET)
.update(req.body) // raw Buffer, not parsed JSON
.digest("hex");
const sigBuf = Buffer.from(signature, "hex");
const expBuf = Buffer.from(expected, "hex");
if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
return res.status(401).send("bad signature");
}
const event = JSON.parse(req.body.toString("utf8"));
// Handle event.event_type — see /docs/webhook-events for the catalog.
res.status(200).send("ok");
});
```
```python Python (Flask) theme={null}
import hashlib
import hmac
import os
import time
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["HEYGEN_WEBHOOK_SECRET"].encode()
MAX_SKEW_SECONDS = 300
@app.post("/webhooks/heygen")
def heygen_webhook():
signature = request.headers.get("Heygen-Signature", "")
timestamp = request.headers.get("Heygen-Timestamp", "")
if not signature or not timestamp:
abort(400, "missing headers")
if abs(time.time() - int(timestamp)) > MAX_SKEW_SECONDS:
abort(400, "stale timestamp")
raw = request.get_data() # bytes, before any JSON parsing
expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
abort(401, "bad signature")
event = request.get_json()
# Handle event["event_type"] — see /docs/webhook-events for the catalog.
return "", 200
```
Verify against the **raw body bytes**, not a re-serialized JSON object. Any whitespace or key-order change will break the HMAC. In Express, use `express.raw()`; in Flask, use `request.get_data()` before `request.get_json()`.
### Delivery, retries, and idempotency
* **Success criteria:** respond with `2xx` within 10 seconds. Slow handlers will be timed out and retried.
* **Retries:** failed deliveries are retried with exponential backoff for up to 24 hours.
* **Idempotency / replay defense:** a single event can be delivered more than once (retry after a transient failure). De-duplicate on `Heygen-Event-Id` — this is your **primary replay defense**, since the signature covers the body only.
* **Stale-delivery rejection:** treat `Heygen-Timestamp` as defense-in-depth — reject deliveries where it's more than \~5 minutes old. (Not a substitute for event-id dedup: an attacker who captured a valid body could replay it with a fresh timestamp header without breaking the signature.)
For the full list of event types and payload shapes, see [Webhook Events](/docs/webhook-events).
## Using Callbacks Instead
If you don't need a persistent webhook endpoint, pass a `callback_url` directly when creating a video — for example via [Create Video Agent Session](/reference/create-video-agent-session) or [Create Video Translation](/reference/create-video-translation). This sends a one-off notification for that specific request without registering an endpoint:
```json "Video Agent with callback" theme={null}
{
"prompt": "A product demo for our new app",
"callback_url": "https://yourapp.com/callbacks/heygen",
"callback_id": "my-custom-id-123"
}
```
The `callback_id` is echoed back in the webhook payload so you can correlate the notification with your request.
# E-commerce Product Videos
Source: https://developers.heygen.com/e-commerce-product-videos
Turn product catalogs into avatar-led e-commerce videos via the HeyGen API. One video per SKU, automatically refreshed when prices, photos, or copy change.
## The Problem
Product pages with video significantly outperform those without — higher engagement, longer time on page, and better conversion rates. But with thousands of SKUs, creating individual product videos is impossible with traditional production. Most e-commerce stores have great product images but zero product video.
## How It Works
```
Product catalog (name, description, images) → Prompt per product → Batch generate → Embed on product pages
```
Your product database already has everything Video Agent needs: name, description, features, and images. Turn that structured data into video at scale.
## Build It
```python theme={null}
# From your catalog API, database, or CSV
products = [
{
"name": "CloudWalk Pro Running Shoes",
"category": "Footwear",
"price": "$129",
"description": "Lightweight performance running shoe with responsive foam midsole and breathable knit upper.",
"features": [
"ResponsiveFoam midsole — 30% more energy return",
"Breathable knit upper — keeps feet cool on long runs",
"Carbon fiber plate — propels you forward",
"Only 7.2 oz — one of the lightest in its class",
],
"images": [
"https://cdn.store.com/products/cloudwalk-hero.jpg",
"https://cdn.store.com/products/cloudwalk-side.jpg",
"https://cdn.store.com/products/cloudwalk-sole.jpg",
],
},
# ... hundreds more
]
```
Consider using different video styles for different product categories. For example, fashion might benefit from energy and aspiration, while electronics might call for clarity and specs.
```python theme={null}
CATEGORY_STYLES = {
"Footwear": {
"tone": "energetic and aspirational, like a Nike ad",
"focus": "performance benefits, how it feels, lifestyle context",
"duration": "20 seconds",
},
"Electronics": {
"tone": "clear, knowledgeable, like a trusted tech reviewer",
"focus": "specs that matter, real-world use cases, comparisons",
"duration": "30 seconds",
},
"Home & Kitchen": {
"tone": "warm, practical, like a friend recommending a product",
"focus": "solving everyday problems, quality materials, ease of use",
"duration": "25 seconds",
},
}
def build_product_prompt(product):
style = CATEGORY_STYLES.get(product["category"], CATEGORY_STYLES["Electronics"])
features = "\n".join(f"- {f}" for f in product["features"])
return f"""Create a {style['duration']} product video for {product['name']}.
Product: {product['name']} — {product['price']}
{product['description']}
Key features:
{features}
Video structure:
- Hook (3s): Bold statement about the key benefit
- Features (70% of duration): Walk through 2-3 standout features
with text overlays. Reference the attached product images.
- CTA (3s): "Available now for {product['price']}"
Tone: {style['tone']}
Focus on: {style['focus']}
Use the attached product images as visual reference.
"""
```
```python theme={null}
import requests
import time
video_jobs = []
for product in products:
prompt = build_product_prompt(product)
files = [{"type": "url", "url": img} for img in product["images"][:5]]
resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={
"X-Api-Key": HEYGEN_API_KEY,
"Content-Type": "application/json",
},
json={"prompt": prompt, "files": files},
)
video_jobs.append({
"product_id": product["name"],
"video_id": resp.json()["data"]["video_id"],
})
time.sleep(5)
print(f"Submitted {len(video_jobs)} product videos")
```
Then poll for completion and match video URLs back to product IDs.
Once rendered, add video URLs to your product data and display on your store.
```python theme={null}
# After polling all videos to completion:
for job in video_jobs:
update_product_page(
product_id=job["product_id"],
video_url=job["video_url"],
thumbnail_url=job["thumbnail_url"],
)
```
## Video Types by Use Case
| Video type | Duration | When to use |
| ------------------------ | -------- | ---------------------------------------- |
| **Product showcase** | 15–30s | Product listing page — show key features |
| **How-to-use** | 30–60s | Complex products — demonstrate usage |
| **Comparison** | 30–45s | "Pro vs Standard" — help buyers choose |
| **Unboxing/first look** | 20–30s | New arrivals — build excitement |
| **Customer testimonial** | 30s | Social proof — pair with review text |
## Scaling to Thousands of Products
For large catalogs, consider prioritizing by business impact:
1. **Top sellers first** — highest traffic pages get the most ROI from video
2. **High-margin products** — the conversion lift matters most here
3. **New arrivals** — video helps customers understand unfamiliar products
4. **Products with high return rates** — better video = more informed purchases = fewer returns
```python theme={null}
# Prioritize by revenue impact
products.sort(key=lambda p: p["monthly_revenue"], reverse=True)
top_products = products[:100] # Start with top 100
```
## A/B Testing
Generate multiple video styles for the same product and measure which converts better:
```python theme={null}
variants = [
{"style": "presenter explaining features", "label": "explainer"},
{"style": "fast-paced montage with text overlays only", "label": "montage"},
{"style": "customer testimonial style, first-person", "label": "testimonial"},
]
```
## Variations
* **Seasonal campaigns:** Regenerate product videos with holiday-themed prompts
* **Multi-language:** Translate for international storefronts using [Video Translation](/cookbook/video-agent/multilingual-content)
* **Social ads:** Generate portrait (9:16) versions for social media advertising
* **Bundle videos:** Combine multiple products into "complete the look" or "bundle" showcase videos
***
## Next Steps
Same catalog-to-video pattern, applied to properties.
Distribute product videos across social platforms.
# Endpoint Version Comparison
Source: https://developers.heygen.com/endpoint-version-comparison
Compare HeyGen API v1, v2, and v3 side-by-side. See which endpoints moved between versions, what's deprecated, and how to migrate existing integrations to v3.
## Deprecation Timeline
The v1 and v2 API endpoints are entering a planned sunset period. Both versions will continue to operate normally through **October 31, 2026**, giving teams ample runway to migrate at their own pace.
| Phase | Window | What to Expect |
| --------------- | --------------------------------------- | --------------------------------------------------------------------------------------------- |
| **Current** | Now — October 31, 2026 | v1/v2 endpoints remain fully operational. v3 is live and recommended for all new development. |
| **End of Life** | November 1, 2026 — end of support date. | v1/v2 endpoints will be retired. All traffic should be routed to v3 by this date. |
> **Note:** If your integration relies on **Studio API (multi-scene)** or **Template API** features that are not yet available in v3, those v2 endpoints will continue to be supported until a v3 equivalent is in place. See the [v3 changelog](/changelog) for updates.
## Feature Comparison
| Feature | v1 | v2 | v3 |
| ------------------------------- | --------------------------------- | -------------------------------------- | ------------------------------- |
| **Avatar Video Generation** | ✅ `POST /v1/video/generate` | ✅ `POST /v2/video/generate` | ✅ `POST /v3/videos` |
| **Video Agent** | ✅ `POST /v1/video-agent/generate` | — | ✅ `POST /v3/video-agents` |
| **Studio API (multi-scene)** | — | ✅ `POST /v2/video/generate` | ⏳ Not yet available |
| **Template API (variables)** | — | ✅ `POST /v2/template/{id}/generate` | ⏳ Not yet available |
| **Video Translation** | — | ✅ `POST /v2/video_translate/translate` | ✅ `POST /v3/video-translations` |
| **Lipsync** | — | — | ✅ `POST /v3/lipsyncs` |
| **Voice Design (prompt-based)** | — | — | ✅ `POST /v3/voices` |
| **Text-to-Speech (Starfish)** | — | — | ✅ `POST /v3/voices/speech` |
| **Webhook Management** | — | callback\_url param only | ✅ Full CRUD + secret rotation |
| **Asset Upload** | ✅ `POST /v1/video/upload` | — | ✅ `POST /v3/assets` |
| **List Avatars** | ✅ `GET /v1/avatar.list` | ✅ `GET /v2/avatars` | ✅ `GET /v3/avatars` |
| **List Voices** | ✅ `GET /v1/voice.list` | ✅ `GET /v2/voices` | ✅ `GET /v3/voices` |
| **Poll Video Status** | ✅ `GET /v1/video_status.get` | — | ✅ `GET /v3/videos/{id}` |
| **User / Account Info** | ✅ `GET /v1/user/me` | — | ✅ `GET /v3/users/me` |
## v3-Only Features (No Legacy Equivalent)
| Feature | Endpoint | Notes |
| ------------------------- | ------------------------------------- | ------------------------------------------------- |
| Lipsync | `POST /v3/lipsyncs` | Speed and precision modes |
| Voice Design | `POST /v3/voices` | Design a voice from a text prompt |
| Text-to-Speech (Starfish) | `POST /v3/voices/speech` | New TTS engine |
| Proofread Workflow | `/v3/video-translations/proofreads/*` | SRT review/edit before final render |
| Webhook Management | `/v3/webhooks/endpoints/*` | Full CRUD, secret rotation, event log |
| Video Agent (full) | `/v3/video-agents/*` | Styles, references, chat mode |
| Avatar Consent Flow | `POST /v3/avatars/{group_id}/consent` | Required for custom avatar creation |
| Cursor-based Pagination | All v3 list endpoints | Standard across every resource |
| Unified Asset Input | All v3 creation endpoints | `url` / `asset_id` / `base64` discriminated union |
## v3 Platform Improvements
| Capability | v1/v2 | v3 |
| ------------------ | -------------------------- | --------------------------------------------------------- |
| Pagination | Offset-based or none | Cursor-based (all list endpoints) |
| Asset references | Varies by endpoint | Unified `url` / `asset_id` / `base64` union |
| Webhook delivery | `callback_url` param only | Managed endpoints, event types, signed payloads |
| Video request body | Flat object | Discriminated union (`type: "avatar"` or `type: "image"`) |
| Voice fallback | `voice_id` always required | Falls back to avatar's default voice when omitted. |
## Migration Checklist
* Inventory all v1/v2 endpoint calls in your codebase
* Replace `POST /v1/video/generate` and `POST /v2/video/generate` with `POST /v3/videos`
* Replace `GET /v1/video_status.get` with `GET /v3/videos/{id}`
* Replace avatar and voice listing endpoints with v3 equivalents
* Replace `POST /v1/video/upload` with `POST /v3/assets`
* Replace `GET /v1/user/me` with `GET /v3/users/me`
* Migrate video translation calls to `/v3/video-translations`
* Update webhook handling from `callback_url` to managed `/v3/webhooks/endpoints`
* Update pagination logic to cursor-based where applicable
* **Flag:** If using Studio API (multi-scene) or Template API — these remain v2-only for now; monitor the [v3 changelog](/changelog) for replacements
* Test all migrated endpoints in staging before October 31, 2026
# Examples
Source: https://developers.heygen.com/examples
Browse example HeyGen CLI workflows from one-off video generation to multi-step automation. Each example shows the prompt, command, and resulting video.
## Create a Video with the Agent
Let the AI pick the avatar, voice, and layout from a text prompt:
```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
}
}
```
Block until the video is ready:
```bash theme={null}
heygen video-agent create --prompt "A presenter explaining our product launch in 30 seconds" --wait
```
Browse available styles first, then apply one:
```bash theme={null}
heygen video-agent styles list
heygen video-agent create --prompt "Product launch" --style-id --wait
```
***
## Create a Video with Full Control
Skip the agent and specify every detail yourself using `-d`:
```bash theme={null}
heygen video create -d '{
"type": "avatar",
"avatar_id": "avt_angela_01",
"script": "Welcome to our Q4 earnings call.",
"voice_id": "1bd001e7e50f421d891986aad5e3e5d2",
"aspect_ratio": "auto",
"resolution": "1080p"
}' --wait
```
```json Output theme={null}
{
"data": {
"id": "vid_qr8821",
"status": "completed",
"video_url": "https://files.heygen.com/video/vid_qr8821.mp4",
"duration": 12.4,
"created_at": 1711288320,
"completed_at": 1711288422
}
}
```
Animate a custom image instead of a preset avatar:
```bash theme={null}
heygen video create -d '{
"type": "image",
"image": {"type": "url", "url": "https://example.com/photo.jpg"},
"script": "Hello from HeyGen.",
"voice_id": "1bd001e7e50f421d891986aad5e3e5d2"
}' --wait
```
Discover all available request fields:
```bash theme={null}
heygen video create --request-schema
```
***
## Download a Video
Download the video file once it's complete:
```bash theme={null}
heygen video download vid_qr8821 --output-path ./my-video.mp4
```
```json Output theme={null}
{
"asset": "video",
"message": "Downloaded video to ./my-video.mp4",
"path": "./my-video.mp4"
}
```
Download the captioned version (requires `enable_caption` at creation time):
```bash theme={null}
heygen video download vid_qr8821 --asset captioned --output-path ./my-video-captioned.mp4
```
***
## Text to Speech
Generate standalone audio using the `voice speech create` command:
```bash theme={null}
heygen voice speech create \
--text "Hello world, welcome to HeyGen." \
--voice-id 1bd001e7e50f421d891986aad5e3e5d2
```
```json Output theme={null}
{
"data": {
"audio_url": "https://files.heygen.com/audio/req_abc123.mp3",
"duration": 2.1,
"request_id": "req_abc123"
}
}
```
***
## Design a Voice
Find a voice by describing what you want:
```bash theme={null}
heygen voice create --prompt "warm, confident female narrator"
```
```json Output theme={null}
{
"data": {
"voices": [
{
"voice_id": "1bd001e7e50f421d891986aad5e3e5d2",
"name": "Jenny",
"language": "English",
"gender": "female",
"preview_audio_url": "https://files.heygen.com/voice/jenny_preview.mp3"
}
],
"seed": 0
}
}
```
Increment `--seed` to get a different batch of results with the same prompt:
```bash theme={null}
heygen voice create --prompt "warm, confident female narrator" --seed 1
```
***
## List and Filter Voices
Browse voices available for TTS:
```bash theme={null}
heygen voice list --language English --gender female --limit 5
```
```json Output theme={null}
{
"data": [
{
"voice_id": "1bd001e7e50f421d891986aad5e3e5d2",
"name": "Jenny",
"language": "English",
"gender": "female",
"type": "public",
"preview_audio_url": "https://files.heygen.com/voice/jenny_preview.mp3"
}
],
"has_more": true,
"next_token": "eyJsYXN0X2lkIjoiMWJkMDAxZTcifQ"
}
```
***
## Browse Avatar Looks
List all looks available for an avatar group:
```bash theme={null}
heygen avatar looks list --group-id avt_angela_01 --limit 5
```
```json Output theme={null}
{
"data": [
{
"id": "angela_business_01",
"name": "Business Suit",
"avatar_type": "studio_avatar",
"group_id": "avt_angela_01",
"gender": "female",
"tags": ["formal", "business"],
"default_voice_id": "1bd001e7e50f421d891986aad5e3e5d2",
"preview_image_url": "https://files.heygen.com/look/angela_business_01.jpg"
}
],
"has_more": false,
"next_token": null
}
```
The `id` from a look is what you pass as `avatar_id` in `video create`. Get details for a specific look:
```bash theme={null}
heygen avatar looks get angela_business_01
```
***
## Upload an Asset
Upload a file to use as an avatar image, audio source, or attachment in video-agent:
```bash theme={null}
heygen asset create --file ./my-photo.jpg
```
```json Output theme={null}
{
"data": {
"asset_id": "ast_abc123",
"url": "https://files.heygen.com/asset/ast_abc123.jpg",
"mime_type": "image/jpeg",
"size_bytes": 204800
}
}
```
Use the returned `asset_id` anywhere the API accepts an asset input:
```bash theme={null}
heygen video create -d '{
"type": "image",
"image": {"type": "asset_id", "asset_id": "ast_abc123"},
"script": "Hello from my photo.",
"voice_id": "1bd001e7e50f421d891986aad5e3e5d2"
}' --wait
```
***
## Translate a Video
Dub and lip-sync an existing video into Spanish:
```bash theme={null}
heygen video-translate create \
--output-languages es \
--mode precision \
--wait
```
For complex options (custom audio, SRT files), use `-d`:
```bash theme={null}
cat request.json | heygen video-translate create -d - --wait
```
Without `--wait`:
```json Output theme={null}
{
"data": {
"video_translation_ids": ["trl_55f"]
}
}
```
`--wait` only supports single-language translations. For batch (multiple `output_languages`), poll each ID individually with `heygen video-translate get`.
Manage existing translations:
```bash theme={null}
heygen video-translate list
heygen video-translate get trl_55f
heygen video-translate caption get trl_55f --format srt
heygen video-translate delete trl_55f --force
```
***
## Webhooks
Register an endpoint to receive event notifications:
```bash theme={null}
heygen webhook endpoints create \
--url "https://example.com/webhook" \
--events "avatar_video.success,avatar_video.fail"
```
```json Output theme={null}
{
"data": {
"endpoint_id": "ep_abc123",
"url": "https://example.com/webhook",
"events": ["avatar_video.success", "avatar_video.fail"],
"status": "enabled",
"created_at": "2025-03-24T14:32:00Z",
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxx"
}
}
```
Store the `secret` securely — it's used to verify webhook signatures and won't be shown again. Use `heygen webhook endpoints rotate-secret ` to generate a new one.
List all available event types:
```bash theme={null}
heygen webhook event-types list
```
Update or delete an endpoint:
```bash theme={null}
heygen webhook endpoints update ep_abc123 --events "avatar_video.success"
heygen webhook endpoints delete ep_abc123 --force
```
***
## Scripting and Agent Integration
Since JSON is the default output and all non-data output goes to stderr, piping into other tools works without any extra flags.
### Create a video and immediately open it in the browser
```bash theme={null}
VIDEO_ID=$(heygen video-agent create --prompt "Demo video" | jq -r '.data.video_id')
heygen video get "$VIDEO_ID" --wait | jq -r '.data.video_url' | xargs open
```
### Batch translate into multiple languages
```bash theme={null}
for lang in es fr de ja ko; do
heygen video-translate create \
--output-languages "$lang" \
--mode precision \
--wait --quiet
echo "✓ $lang done"
done
```
### Create a video, wait, then download it
```bash theme={null}
RESULT=$(heygen video create -d '{
"type": "avatar",
"avatar_id": "avt_angela_01",
"script": "Weekly update for the team.",
"voice_id": "1bd001e7e50f421d891986aad5e3e5d2"
}' --wait)
STATUS=$(echo "$RESULT" | jq -r '.data.status')
VIDEO_ID=$(echo "$RESULT" | jq -r '.data.id')
if [ "$STATUS" = "completed" ]; then
heygen video download "$VIDEO_ID" --output-path weekly-update.mp4
fi
```
### Pipe a JSON file into video create
```bash theme={null}
cat request.json | heygen video create -d - --wait
```
### Page through all your videos
```bash theme={null}
# Fetch first page
heygen video list --limit 50
# Fetch next page using the token from the previous response
heygen video list --limit 50 --token "eyJsYXN0X2lkIjoiYXZ0X21hcmN1c18wMiJ9"
```
### List all avatar names (human-readable)
```bash theme={null}
heygen avatar list --human
```
### Check your remaining credits
```bash theme={null}
heygen user me get | jq '.data.wallet'
```
# Features
Source: https://developers.heygen.com/features
Configure the HeyGen CLI with shared flags for output mode, model selection, agent style, and verbose debugging. Use these flags across any CLI command.
## Common Flags
These flags are supported across commands where applicable.
| Flag | Description | Default |
| ---------------------------- | --------------------------------------------------------------------------------------------------------- | ----------------- |
| `--human` | Enable rich TUI output (tables, colorized values, readable timestamps) | Off (JSON) |
| `--wait` | Block until async operation completes, subject to `--timeout`. Exits with code `4` if timeout is reached. | Off |
| `--timeout ` | Max wait time when using `--wait` (e.g. `10m`, `1h`) | `20m` |
| `--limit ` | Maximum items per page (1–100) | Endpoint-specific |
| `--token ` | Pagination cursor from a previous response's `next_token` | None |
| `--force` | Skip confirmation prompts for destructive operations | Off (prompt) |
| `-d, --data ` | JSON request body (inline, file path, or `-` for stdin) | None |
| `--request-schema` | Print the API request body JSON schema and exit (no auth required) | Off |
| `--response-schema` | Print the API response JSON schema and exit (no auth required) | Off |
***
## Async Operations and `--wait`
Commands that create videos or translations return immediately by default with an ID and status. The operation continues in the background.
Add `--wait` to block until the operation completes:
```bash theme={null}
heygen video-agent create --prompt "Welcome to our Q4 earnings call." --wait
```
Without `--wait`, you get the initial response:
```json theme={null}
{
"data": {
"session_id": "sess_abc123",
"status": "generating",
"video_id": "vid_qr8821"
}
}
```
With `--wait`, the CLI polls the status endpoint until completion and returns the full resource:
```json theme={null}
{
"data": {
"id": "vid_qr8821",
"status": "completed",
"video_url": "https://files.heygen.com/video/vid_qr8821.mp4",
"duration": 12.4,
"created_at": 1711288320,
"completed_at": 1711288422
}
}
```
The default timeout is 20 minutes. Override with `--timeout`:
```bash theme={null}
heygen video create -d '...' --wait --timeout 30m
```
If the timeout is reached, the CLI exits with code `4`. Stdout contains the last known resource state, and stderr contains a hint with the manual polling command:
```json theme={null}
{"error": {"code": "timeout", "message": "polling timed out after 20m0s", "hint": "heygen video get vid_qr8821"}}
```
If the operation reaches a terminal failure state, the CLI exits with code `1`. Stdout contains the failure response (which often includes error details) and stderr contains the error envelope.
`--wait` is supported on:
* `heygen video create`
* `heygen video-agent create`
* `heygen video-translate create`
* `heygen lipsync create`
***
## Complex Request Bodies (`-d` / `--data`)
Endpoints with nested inputs — discriminated unions, arrays of objects, nested configs — use `-d` for raw JSON instead of individual flags:
```bash theme={null}
# Inline JSON
heygen video create -d '{
"type": "avatar",
"avatar_id": "avt_angela_01",
"script": "Hello world",
"voice_id": "1bd001e7e50f421d891986aad5e3e5d2"
}'
# From a file
heygen video create -d request.json
# From stdin
cat request.json | heygen video create -d -
```
Flags and `-d` can be combined — **flags override matching fields in the JSON body**. This lets you keep a reusable JSON template and tweak individual fields per invocation:
```bash theme={null}
heygen video create -d base.json --wait
```
Use `--request-schema` to discover the expected JSON shape for any command — no auth required:
```bash theme={null}
heygen video create --request-schema
heygen lipsync create --request-schema
```
***
## Pagination
List commands return paginated results. Each response includes `has_more` and `next_token`:
```json theme={null}
{
"data": [...],
"has_more": true,
"next_token": "eyJsYXN0X2lkIjoiYXZ0X21hcmN1c18wMiJ9"
}
```
### Manual pagination
Use `--token` to fetch the next page:
```bash theme={null}
heygen avatar list --limit 10
heygen avatar list --limit 10 --token "eyJsYXN0X2lkIjoiYXZ0X21hcmN1c18wMiJ9"
```
If an agent needs multiple pages, it should read `next_token` from the JSON response and pass it to the next call explicitly. The CLI does not auto-paginate — each page is a separate request.
***
## Stdin Support
Flags that accept long text support reading from stdin with `-`:
```bash theme={null}
# Pipe a script file into video create
cat script.json | heygen video create -d -
# Here-doc
heygen video create -d - <
## Prerequisites
A Digital Twin `avatar_id` (type: `digital_twin`). Use `GET /v3/avatars/looks?avatar_type=digital_twin` to find yours.
A `voice_id` for the voice you want. Use `GET /v3/voices` to browse available voices.
## Step 1 — Find your Digital Twin
List your private Digital Twin looks to get the `avatar_id`:
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/avatars/looks?avatar_type=digital_twin&ownership=private" \
-H "x-api-key: YOUR_API_KEY"
```
From the response, copy the `id` field of the look you want. This is your `avatar_id`.
## Step 2 — Choose a rendering engine
HeyGen v3 supports two rendering engines for Digital Twins. You select the engine via the `engine` object in your video creation request.
| Engine | Key | Default? | Quality |
| --------- | ----------- | ----------------------------------- | ------------------------------------------------ |
| Avatar IV | `avatar_iv` | Yes — used when `engine` is omitted | Standard, widely supported |
| Avatar V | `avatar_v` | No — must be explicitly requested | Higher quality, cross-reference-driven animation |
Before requesting Avatar V, check the `supported_api_engines` array on the avatar look via `GET /v3/avatars/looks/{look_id}`. If `"avatar_v"` is not listed, the request will be rejected. See [Avatar Models](/avatar-models) for full details.
## Step 3 — Create the video
Send a `POST` request to `/v3/videos` with `type: "avatar"`, your Digital Twin ID, a script, and a voice.
### Using Avatar IV (default)
Omit the `engine` field or pass `{"type": "avatar_iv"}` — Avatar IV is the default:
```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_DIGITAL_TWIN_LOOK_ID",
"script": "Hello! I am your Digital Twin. This video was generated entirely through the HeyGen API.",
"voice_id": "YOUR_VOICE_ID",
"title": "My First Digital Twin Video",
"resolution": "1080p",
"aspect_ratio": "auto"
}'
```
### Using Avatar V (higher quality)
Pass `{"type": "avatar_v"}` in the `engine` field to request cross-reference-driven animation:
```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_DIGITAL_TWIN_LOOK_ID",
"script": "Hello! I am your Digital Twin.",
"voice_id": "YOUR_VOICE_ID",
"title": "My Digital Twin — Avatar V",
"resolution": "1080p",
"aspect_ratio": "auto",
"engine": { "type": "avatar_v" }
}'
```
`motion_prompt` is supported on both Avatar IV and Avatar V — pass a natural-language prompt to control body motion and hand gestures.
`expressiveness` remains an Avatar IV-only parameter. Do not include it when using Avatar V — it will cause a validation error.
## Step 4 — Poll for completion
Video generation is asynchronous. Poll `GET /v3/videos/{video_id}` 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 values
| Status | Meaning |
| ------------ | ---------------------------------------------- |
| `pending` | Queued for processing |
| `processing` | Video is being generated |
| `completed` | Ready — `video_url` is available |
| `failed` | Something went wrong — check `failure_message` |
Once completed, the response includes a `video_url` with a presigned download link.
## 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 video (Avatar V — swap engine for {"type": "avatar_iv"} or omit for default)
resp = requests.post(f"{BASE}/v3/videos", headers=HEADERS, json={
"type": "avatar",
"avatar_id": "YOUR_DIGITAL_TWIN_LOOK_ID",
"script": "Welcome to our product demo. Let me walk you through the new features.",
"voice_id": "YOUR_VOICE_ID",
"resolution": "1080p",
"aspect_ratio": "auto",
"engine": {"type": "avatar_v"} # omit entirely to use Avatar IV (default)
})
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)
```
## Optional parameters
| Parameter | Type | Engine | Description |
| ------------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------- |
| `title` | string | Both | Display name in the HeyGen dashboard |
| `resolution` | string | Both | `4k`, `1080p` (recommended), or `720p` |
| `aspect_ratio` | string | Both | `auto` (recommended — matches the source, falling back to `16:9`), `16:9`, `9:16`, `4:5`, `5:4`, or `1:1` |
| `engine` | object | — | `{"type": "avatar_iv"}` or `{"type": "avatar_v"}`. Defaults to Avatar IV when omitted |
| `remove_background` | boolean | Both | Removes the avatar background (twin must be trained with matting enabled) |
| `background` | object | Both | Set a solid color or image background |
| `voice_settings` | object | Both | Adjust `speed` (0.5–1.5), `pitch` (-50 to +50), and `locale` |
| `motion_prompt` | string | Both | Natural-language prompt controlling avatar body motion and hand gestures |
| `expressiveness` | string | IV only | `high`, `medium`, or `low`. Defaults to `low` |
| `output_format` | string | Both | `mp4` (default) or `webm` (transparent background / alpha channel) |
| `callback_url` | string | Both | Webhook URL — receive a POST when the video is ready |
## Using webhooks instead of polling
Instead of polling, pass a `callback_url` when creating the video. HeyGen will send a POST request to that URL when the video completes or fails.
```json theme={null}
{
"type": "avatar",
"avatar_id": "YOUR_DIGITAL_TWIN_LOOK_ID",
"script": "This video uses a webhook callback.",
"voice_id": "YOUR_VOICE_ID",
"callback_url": "https://your-server.com/webhooks/heygen"
}
```
Register a webhook endpoint via `POST /v3/webhooks/endpoints` and subscribe to `avatar_video.success` and `avatar_video.fail` events for production use.
# Hyperframes
Source: https://developers.heygen.com/hyperframes
Render programmatic, code-driven video with the HeyGen Hyperframes API. Upload an HTML project bundle, inject runtime variables, and HeyGen renders it to MP4, WebM, or MOV.
Hyperframes turns a self-contained HTML/CSS/JS project into a rendered video. You package your composition as a `.zip`, upload it, and HeyGen runs it through a headless renderer — ideal for motion graphics, data-driven visuals, and templated pipelines. Renders are billed per minute of output — rates are on the [self-serve](/docs/pricing#hyperframes) and [enterprise](/docs/enterprise-pricing#hyperframes) pricing pages. For worked examples, see the [Hyperframes cookbook](/motion-graphics).
## Prerequisites
A project `.zip` containing an `index.html` at the root (or at the path you set in `composition`), plus any CSS, JS, fonts, and assets it needs. The bundle must render standalone in a browser.
The zip available as a `url`, an uploaded `asset_id`, or `base64`. Use [Assets](/assets) to upload a file and get an `asset_id`.
## Step 1 — Build your composition
Author an HTML page that renders your scene. To make a render data-driven, read values from `data-composition-variables` on the document — HeyGen overrides these with the `variables` object you send at render time, so one bundle can produce many videos.
## Step 2 — Create a render
`POST /v3/hyperframes/renders` with your project bundle. The call returns `202 Accepted` with a `render_id`:
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/hyperframes/renders" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"project": { "type": "asset_id", "asset_id": "YOUR_PROJECT_ZIP_ASSET_ID" },
"fps": 30,
"quality": "standard",
"format": "mp4",
"resolution": "1080p",
"aspect_ratio": "16:9",
"composition": "index.html",
"variables": { "headline": "Q2 Revenue", "value": "$1.2M" },
"title": "Q2 revenue motion graphic"
}'
```
```json theme={null}
{
"data": {
"render_id": "rnd_abc123",
"status": "queued"
}
}
```
## Step 3 — Poll for completion
Poll `GET /v3/hyperframes/renders/{render_id}` until `status` is `completed`:
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/hyperframes/renders/YOUR_RENDER_ID" \
-H "x-api-key: YOUR_API_KEY"
```
| Status | Meaning |
| ----------- | ----------------------------------------------------- |
| `queued` | Accepted and waiting for a renderer |
| `rendering` | Render in progress |
| `completed` | Ready — `video_url` and `thumbnail_url` are available |
| `failed` | Something went wrong — check `failure_message` |
A completed render returns the full `HyperframesRenderDetail`, including `video_url`, `thumbnail_url`, `duration`, and the settings the render used.
## Managing renders
List your renders (paginated) with `GET /v3/hyperframes/renders`, or remove one with `DELETE /v3/hyperframes/renders/{render_id}`:
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/hyperframes/renders" \
-H "x-api-key: YOUR_API_KEY"
curl -X DELETE "https://api.heygen.com/v3/hyperframes/renders/YOUR_RENDER_ID" \
-H "x-api-key: YOUR_API_KEY"
```
## 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. Submit the render
resp = requests.post(f"{BASE}/v3/hyperframes/renders", headers=HEADERS, json={
"project": {"type": "asset_id", "asset_id": "YOUR_PROJECT_ZIP_ASSET_ID"},
"fps": 30,
"quality": "standard",
"format": "mp4",
"resolution": "1080p",
"aspect_ratio": "16:9",
"variables": {"headline": "Q2 Revenue", "value": "$1.2M"},
})
render_id = resp.json()["data"]["render_id"]
print(f"Render queued: {render_id}")
# 2. Poll until done
while True:
status_resp = requests.get(f"{BASE}/v3/hyperframes/renders/{render_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 |
| -------------- | ------- | -------- | ------------------------------------------------------------------------------------------ |
| `project` | object | Yes | The project `.zip` as a `url`, `asset_id`, or `base64` input |
| `fps` | integer | No | Frames per second, 1–240. Default `30` |
| `quality` | string | No | `draft`, `standard` (default), or `high` |
| `format` | string | No | `mp4` (default), `webm`, or `mov` |
| `resolution` | string | No | `1080p` (default) or `4k`. 4k is billed at 1.5× — see [pricing](/docs/pricing#hyperframes) |
| `aspect_ratio` | string | No | `16:9` (default), `9:16`, or `1:1` |
| `composition` | string | No | Entry HTML path inside the zip. Default `index.html` |
| `variables` | object | No | Key–value overrides for `data-composition-variables` |
| `title` | string | No | Display name, ≤500 characters |
| `callback_id` | string | No | Your own correlation ID, ≤256 characters, echoed back in the webhook |
| `callback_url` | string | No | Webhook URL — receive a POST when the render finishes |
## Using webhooks instead of polling
Pass a `callback_url` (and optionally a `callback_id` to correlate the response) when you submit the render. HeyGen posts to it on completion. Register an endpoint via `POST /v3/webhooks/endpoints` and subscribe to the `hyperframes_video.success` and `hyperframes_video.fail` events described in [Webhook Events](/docs/webhook-events).
# Image to Video
Source: https://developers.heygen.com/image-to-video
Animate any image into video with the HeyGen Image to Video API. Add motion, effects, and avatar-led narration to still images programmatically at scale.
## Prerequisites
An image of a person (PNG or JPEG) — accessible via a public URL or uploaded as an asset
A `voice_id` for the voice you want. Use `GET /v3/voices` to browse options.
## Step 1 — Generate the video
Use `POST /v3/videos` with `type: "image"` and an `image` object instead of `avatar_id`:
```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": "image",
"image": {
"type": "url",
"url": "https://example.com/person.jpg"
},
"script": "Hello! This video was generated directly from a photo, with no avatar setup needed.",
"voice_id": "YOUR_VOICE_ID",
"title": "Image to Video Demo",
"resolution": "1080p",
"aspect_ratio": "auto"
}'
```
First upload via `POST /v3/assets`, then reference the returned `asset_id`:
```bash theme={null}
# Upload the image
curl -X POST "https://api.heygen.com/v3/assets" \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@person.jpg"
# Generate the video
curl -X POST "https://api.heygen.com/v3/videos" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "image",
"image": {
"type": "asset_id",
"asset_id": "RETURNED_ASSET_ID"
},
"script": "This video was created from an uploaded image asset.",
"voice_id": "YOUR_VOICE_ID",
"title": "Image to Video Demo"
}'
```
`type: "image"` and `type: "avatar"` are mutually exclusive — use exactly one.
## Step 2 — Poll for completion
Video generation is asynchronous. Poll `GET /v3/videos/{video_id}` until the status reaches `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 |
## 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. Generate video from an image URL
resp = requests.post(f"{BASE}/v3/videos", headers=HEADERS, json={
"type": "image",
"image": {
"type": "url",
"url": "https://example.com/person.jpg"
},
"script": "Welcome! This entire video was created from a single photograph.",
"voice_id": "YOUR_VOICE_ID",
"title": "Image-to-Video Example",
"resolution": "1080p",
"aspect_ratio": "auto"
})
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)
```
## Using audio instead of a script
You can lip-sync to a custom audio file instead of generating speech from text. Pass `audio_url` or `audio_asset_id` instead of `script` + `voice_id`:
```json theme={null}
{
"type": "image",
"image": {
"type": "url",
"url": "https://example.com/person.jpg"
},
"audio_url": "https://example.com/narration.mp3",
"title": "Image-to-Video with custom audio"
}
```
`script` and `audio_url`/`audio_asset_id` are mutually exclusive. If you provide a `script`, you must also provide a `voice_id`.
## Optional parameters
| Parameter | Type | Description |
| ------------------- | ------- | --------------------------------------------------------------------------------------------------------------- |
| `title` | string | Display name in the HeyGen dashboard |
| `resolution` | string | `4k`, `1080p` (recommended), or `720p` |
| `aspect_ratio` | string | `auto` (recommended — matches the source image, falling back to `16:9`), `16:9`, `9:16`, `4:5`, `5:4`, or `1:1` |
| `remove_background` | boolean | Remove the image background from the video |
| `background` | object | Set a solid color or image background |
| `voice_settings` | object | Adjust `speed` (0.5–1.5), `pitch` (-50 to +50), `locale` |
| `callback_url` | string | Webhook URL for completion notification |
| `callback_id` | string | Your own ID echoed back in the webhook payload |
## Image-to-video vs. Photo Avatar
| Criteria | Image-to-Video | Photo Avatar |
| ------------------ | --------------------------------- | ------------------------------------------- |
| **Setup** | None — use `type: "image"` and go | Requires `POST /v3/avatars` first |
| **Reusability** | One-off per request | Reusable across many videos via `avatar_id` |
| **Motion prompt** | Not supported | Supported |
| **Expressiveness** | Not supported | `high` / `medium` / `low` |
| **Best for** | Quick tests, one-off content | Recurring brand content |
If you plan to generate multiple videos with the same person, create a Photo Avatar once and reuse its `avatar_id`. This saves processing time and unlocks motion and expressiveness controls.
# Lipsync - Precision
Source: https://developers.heygen.com/lipsync-precision
Run the HeyGen Lipsync API in precision mode for frame-accurate mouth movements on long-form video. Best for cinematic content where fidelity matters.
* Endpoint: [`POST /v3/lipsyncs`](/reference/create-lipsync)
* Purpose: Dub or replace audio on a video with high-accuracy lip-sync. The job runs asynchronously — poll via [Get Lipsync](/reference/get-lipsync) or use a `callback_url` ([Webhooks](/docs/webhooks)).
* For faster, lower-fidelity lipsync, see [Speed mode](/lipsync-speed).
### Quick Example
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/lipsyncs" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"video": { "type": "url", "url": "https://example.com/source.mp4" },
"audio": { "type": "url", "url": "https://example.com/new-audio.mp3" },
"mode": "precision"
}'
```
### 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)). |
| `audio` | object | Yes | — | Replacement audio. Same format options as `video`. |
| `mode` | string | No | — | Set to `"precision"` for avatar-inference lip-sync. |
| `title` | string | No | — | Display title for the lipsync in the HeyGen dashboard. |
| `enable_caption` | boolean | No | `false` | Generate captions for the output video. |
| `enable_dynamic_duration` | boolean | No | `true` | Allow output duration to adjust to match the new audio length. |
| `disable_music_track` | boolean | No | `false` | Strip background music from the source video. |
| `enable_speech_enhancement` | boolean | No | `false` | Enhance speech quality in the output. |
| `enable_watermark` | boolean | No | `false` | Add a watermark to the output. |
| `start_time` | number | No | — | Start time in seconds for partial lipsync. |
| `end_time` | number | No | — | End time in seconds for partial lipsync. |
| `keep_the_same_format` | boolean | No | — | Preserve the source video's resolution and bitrate. |
| `fps_mode` | string | No | — | Frame rate mode: `"vfr"`, `"cfr"`, or `"passthrough"`. |
| `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. |
| `folder_id` | string | No | — | Organize the lipsync into a specific project folder. |
### Response
```json theme={null}
{
"data": {
"lipsync_id": "ls_abc123"
}
}
```
| Field | Type | Description |
| ------------ | ------ | ----------------------------------------------------------------------------------------------------- |
| `lipsync_id` | string | Unique identifier. Use with [`GET /v3/lipsyncs/{lipsync_id}`](/reference/get-lipsync) to poll status. |
## Get Lipsync Details
* Endpoint: [`GET /v3/lipsyncs/{lipsync_id}`](/reference/get-lipsync)
* Purpose: Get detailed information about a lipsync including status, download URL, and metadata.
### Quick Example
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/lipsyncs/ls_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### Path Parameters
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | -------------------------- |
| `lipsync_id` | string | Yes | Unique lipsync identifier. |
### Response
```json theme={null}
{
"data": {
"id": "ls_abc123",
"title": "My Lipsync",
"status": "completed",
"duration": 42.5,
"video_url": "https://files.heygen.ai/...",
"callback_id": null,
"created_at": 1717000000,
"failure_message": null
}
}
```
### Response Fields
| Field | Type | Description |
| ----------------- | --------------- | --------------------------------------------------------------------------------------- |
| `id` | string | Unique lipsync identifier. |
| `title` | string or null | Display title. |
| `status` | string | Current status: `"pending"`, `"running"`, `"completed"`, or `"failed"`. |
| `duration` | number or null | Video duration in seconds. Present when completed. |
| `video_url` | string or null | Presigned download URL for the output video. Only present when status is `"completed"`. |
| `callback_id` | string or null | Client-provided callback ID. |
| `created_at` | integer or null | Unix timestamp of creation. |
| `failure_message` | string or null | Error description. Only present when status is `"failed"`. |
## List Lipsyncs
* Endpoint: [`GET /v3/lipsyncs`](/reference/list-lipsyncs)
* Purpose: List lipsyncs with cursor-based pagination.
### Quick Example
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/lipsyncs?limit=10" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### Query Parameters
| Parameter | Type | Required | Default | Description |
| --------- | ------- | -------- | ------- | -------------------------------------- |
| `limit` | integer | No | `10` | Results per page (1–100). |
| `token` | string | No | — | Opaque cursor token for the next page. |
### Response
```json theme={null}
{
"data": [
{
"id": "ls_abc123",
"title": "My Lipsync",
"status": "completed",
"duration": 42.5,
"video_url": "https://files.heygen.ai/...",
"created_at": 1717000000
}
],
"has_more": false,
"next_token": null
}
```
## Update Lipsync
* Endpoint: [`PATCH /v3/lipsyncs/{lipsync_id}`](/reference/update-lipsync)
* Purpose: Update a lipsync's title.
### Quick Example
```bash theme={null}
curl -X PATCH "https://api.heygen.com/v3/lipsyncs/ls_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "title": "Updated Title" }'
```
### Request Body
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | -------------------------- |
| `title` | string | Yes | New title for the lipsync. |
## Delete Lipsync
* Endpoint: [`DELETE /v3/lipsyncs/{lipsync_id}`](/reference/delete-lipsync)
* Purpose: Permanently delete a lipsync.
### Quick Example
```bash theme={null}
curl -X DELETE "https://api.heygen.com/v3/lipsyncs/ls_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### Response
```json theme={null}
{
"data": {
"id": "ls_abc123"
}
}
```
## CLI Usage
```bash theme={null}
# Create with precision mode
heygen lipsync create -d '{
"video": {"type": "url", "url": "https://example.com/source.mp4"},
"audio": {"type": "url", "url": "https://example.com/new-audio.mp3"},
"mode": "precision"
}' --wait
# Poll status manually
heygen lipsync get
# List lipsyncs
heygen lipsync list --limit 10
# Update title
heygen lipsync update --title "Updated Title"
# Delete
heygen lipsync delete --force
```
Use `--request-schema` to see all available request fields without needing auth:
```bash theme={null}
heygen lipsync create --request-schema
```
## Polling Pattern
Lipsyncs 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/lipsyncs/ls_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY" | jq -r '.data.status')
echo "Status: $STATUS"
[ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] && break
sleep 10
done
```
Or let the CLI handle polling for you:
```bash theme={null}
heygen lipsync create -d '...' --wait --timeout 30m
```
## Asset Inputs
Both `video` and `audio` fields accept two input formats:
**By URL** — any publicly accessible HTTPS link:
```json theme={null}
{ "type": "url", "url": "https://example.com/file.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" }
```
# Lipsync - Speed
Source: https://developers.heygen.com/lipsync-speed
Run the HeyGen Lipsync API in speed mode for rapid lipsync drafts. Ideal for previewing edits, batch processing, and iterating on content before final render.
* Endpoint: [`POST /v3/lipsyncs`](/reference/create-lipsync)
* Purpose: Dub or replace audio on a video. The job runs asynchronously — poll via [Get Lipsync](/reference/get-lipsync) or use a `callback_url` ([Webhooks](/docs/webhooks)).
* For higher fidelity, see [Precision mode](/lipsync-precision).
### Quick Example
```bash theme={null}
curl -X POST "https://api.heygen.com/v3/lipsyncs" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"video": { "type": "url", "url": "https://example.com/source.mp4" },
"audio": { "type": "url", "url": "https://example.com/new-audio.mp3" },
"mode": "speed"
}'
```
### 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)). |
| `audio` | object | Yes | — | Replacement audio. Same format options as `video`. |
| `mode` | string | No | — | Set to `"speed"` for fast audio-only resync. |
| `title` | string | No | — | Display title for the lipsync in the HeyGen dashboard. |
| `enable_caption` | boolean | No | `false` | Generate captions for the output video. |
| `enable_dynamic_duration` | boolean | No | `true` | Allow output duration to adjust to match the new audio length. |
| `disable_music_track` | boolean | No | `false` | Strip background music from the source video. |
| `enable_speech_enhancement` | boolean | No | `false` | Enhance speech quality in the output. |
| `enable_watermark` | boolean | No | `false` | Add a watermark to the output. |
| `start_time` | number | No | — | Start time in seconds for partial lipsync. |
| `end_time` | number | No | — | End time in seconds for partial lipsync. |
| `keep_the_same_format` | boolean | No | — | Preserve the source video's resolution and bitrate. |
| `fps_mode` | string | No | — | Frame rate mode: `"vfr"`, `"cfr"`, or `"passthrough"`. |
| `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. |
| `folder_id` | string | No | — | Organize the lipsync into a specific project folder. |
### Response
```json theme={null}
{
"data": {
"lipsync_id": "ls_abc123"
}
}
```
| Field | Type | Description |
| ------------ | ------ | ----------------------------------------------------------------------------------------------------- |
| `lipsync_id` | string | Unique identifier. Use with [`GET /v3/lipsyncs/{lipsync_id}`](/reference/get-lipsync) to poll status. |
## Get Lipsync Details
* Endpoint: [`GET /v3/lipsyncs/{lipsync_id}`](/reference/get-lipsync)
* Purpose: Get detailed information about a lipsync including status, download URL, and metadata.
### Quick Example
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/lipsyncs/ls_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### Path Parameters
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | -------------------------- |
| `lipsync_id` | string | Yes | Unique lipsync identifier. |
### Response
```json theme={null}
{
"data": {
"id": "ls_abc123",
"title": "My Lipsync",
"status": "completed",
"duration": 42.5,
"video_url": "https://files.heygen.ai/...",
"callback_id": null,
"created_at": 1717000000,
"failure_message": null
}
}
```
### Response Fields
| Field | Type | Description |
| ----------------- | --------------- | --------------------------------------------------------------------------------------- |
| `id` | string | Unique lipsync identifier. |
| `title` | string or null | Display title. |
| `status` | string | Current status: `"pending"`, `"running"`, `"completed"`, or `"failed"`. |
| `duration` | number or null | Video duration in seconds. Present when completed. |
| `video_url` | string or null | Presigned download URL for the output video. Only present when status is `"completed"`. |
| `callback_id` | string or null | Client-provided callback ID. |
| `created_at` | integer or null | Unix timestamp of creation. |
| `failure_message` | string or null | Error description. Only present when status is `"failed"`. |
## List Lipsyncs
* Endpoint: [`GET /v3/lipsyncs`](/reference/list-lipsyncs)
* Purpose: List lipsyncs with cursor-based pagination.
### Quick Example
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/lipsyncs?limit=10" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### Query Parameters
| Parameter | Type | Required | Default | Description |
| --------- | ------- | -------- | ------- | -------------------------------------- |
| `limit` | integer | No | `10` | Results per page (1–100). |
| `token` | string | No | — | Opaque cursor token for the next page. |
### Response
```json theme={null}
{
"data": [
{
"id": "ls_abc123",
"title": "My Lipsync",
"status": "completed",
"duration": 42.5,
"video_url": "https://files.heygen.ai/...",
"created_at": 1717000000
}
],
"has_more": false,
"next_token": null
}
```
## Update Lipsync
* Endpoint: [`PATCH /v3/lipsyncs/{lipsync_id}`](/reference/update-lipsync)
* Purpose: Update a lipsync's title.
### Quick Example
```bash theme={null}
curl -X PATCH "https://api.heygen.com/v3/lipsyncs/ls_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "title": "Updated Title" }'
```
### Request Body
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | -------------------------- |
| `title` | string | Yes | New title for the lipsync. |
## Delete Lipsync
* Endpoint: [`DELETE /v3/lipsyncs/{lipsync_id}`](/reference/delete-lipsync)
* Purpose: Permanently delete a lipsync.
### Quick Example
```bash theme={null}
curl -X DELETE "https://api.heygen.com/v3/lipsyncs/ls_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
### Response
```json theme={null}
{
"data": {
"id": "ls_abc123"
}
}
```
## CLI Usage
```bash theme={null}
# Create with speed mode
heygen lipsync create -d '{
"video": {"type": "url", "url": "https://example.com/source.mp4"},
"audio": {"type": "url", "url": "https://example.com/new-audio.mp3"},
"mode": "speed"
}' --wait
# Poll status manually
heygen lipsync get
# List lipsyncs
heygen lipsync list --limit 10
# Update title
heygen lipsync update --title "Updated Title"
# Delete
heygen lipsync delete --force
```
Use `--request-schema` to see all available request fields without needing auth:
```bash theme={null}
heygen lipsync create --request-schema
```
## Polling Pattern
Lipsyncs 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/lipsyncs/ls_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY" | jq -r '.data.status')
echo "Status: $STATUS"
[ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] && break
sleep 10
done
```
Or let the CLI handle polling for you:
```bash theme={null}
heygen lipsync create -d '...' --wait --timeout 30m
```
## Asset Inputs
Both `video` and `audio` fields accept two input formats:
**By URL** — any publicly accessible HTTPS link:
```json theme={null}
{ "type": "url", "url": "https://example.com/file.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" }
```
# Claude Code
Source: https://developers.heygen.com/mcp/claude-code
Connect HeyGen to Claude Code via MCP. Generate avatar videos, translate clips, and run lipsync from your terminal coding workflow with natural-language.
Connect HeyGen's Video Agent to Claude Code to generate AI avatar videos directly from your terminal. Once configured, Claude Code can script, render, and deliver videos through natural-language prompts without leaving your development workflow.
## Prerequisites
* Claude Code installed ([installation guide](https://docs.claude.com/en/docs/claude-code/overview))
* Node.js installed (required for MCP server resolution)
* A HeyGen account with Video Agent access
## Adding the MCP Server
Run the following command in your terminal (not inside the Claude Code CLI):
```text theme={null}
claude mcp add --transport http heygen https://mcp.heygen.com/mcp/v1/
```
To make the server available across all projects, add the `-s user` scope flag:
```text theme={null}
claude mcp add --transport http -s user heygen https://mcp.heygen.com/mcp/v1/
```
### Alternative: Direct Config Edit
You can also add the server by editing `~/.claude.json` directly:
```text theme={null}
{
"mcpServers": {
"heygen": {
"type": "http",
"url": "https://mcp.heygen.com/mcp/v1/"
}
}
}
```
Restart Claude Code after editing the config file.
## Authentication
On first use, Claude Code will prompt you to authenticate with HeyGen. Run `/mcp` inside Claude Code and follow the browser-based OAuth flow to authorize access.
## Verifying the Connection
After setup, confirm the server is active:
```text theme={null}
claude mcp list
```
Or from inside Claude Code:
```text theme={null}
/mcp
```
You should see `heygen` listed with a `connected` status.
## Usage
Once connected, prompt Claude Code with a video generation request:
```text theme={null}
Generate a 60-second explainer video about our new API endpoints using HeyGen.
```
Claude Code will call HeyGen's Video Agent to handle scripting, avatar selection, and rendering. Completed videos are accessible from the **Projects** page in your HeyGen dashboard.
### Loading HeyGen Skills (Recommended)
For better prompt structure and higher-quality output, instruct Claude Code to read HeyGen's prompt engineering guidelines before generating:
```text theme={null}
Before writing any video prompts, read the HeyGen skills at:
https://github.com/heygen-com/skills
Follow SKILL.md, references/prompt-optimizer.md, and references/video-agent.md
to structure each prompt with scenes, timing, visual style, and copy rules.
```
## Scoping
| Scope | Flag | Config Location | Availability |
| :-------------- | :-------- | :------------------------------- | :---------------------------- |
| Local (default) | none | `.mcp.json` in project directory | Current project only |
| User | `-s user` | `~/.claude.json` | All projects for current user |
# Claude Web
Source: https://developers.heygen.com/mcp/claude-web
Connect HeyGen to Claude via MCP and generate AI avatar videos directly inside the Claude chat. Covers connector setup, available tools, and example prompts.
## Prerequisites
* An active Claude paid plan (required for custom connectors)
* A HeyGen account (Creator plan or above recommended for full video generation access)
## Setup
### 1. Register the Connector
Navigate to **+** → **Connector** → **Manage Connector** → **+ Add custom connector**.
Set the connector name to `HeyGen` and provide the following remote MCP server URL:
```text theme={null}
https://mcp.heygen.com/mcp/v1/
```
### 2. Authenticate
After saving the connector, click **Connect**. You will be redirected to HeyGen's authorization page. Approve the requested access to complete the OAuth flow.
### 3. Configure Permissions (Optional)
To avoid repeated permission prompts, set the HeyGen connector permissions to **Always Allow**.
## Usage
Open a new Claude chat and provide a video generation prompt. Example:
```text theme={null}
Generate a video using HeyGen MCP about the difference between Skills and MCP.
```
Claude will handle avatar selection, script generation, and video rendering via the HeyGen API. Completed videos are also accessible from the **Projects** page in your HeyGen dashboard.
## Limitations
| Constraint | Detail |
| ---------------- | ----------------------------------------------------------------------------- |
| HeyGen Free Tier | Limited video generation credits. Upgrade to Creator plan for production use. |
| Claude Free Tier | Custom connectors are not available. A paid Claude subscription is required. |
# Cursor
Source: https://developers.heygen.com/mcp/cursor
Add HeyGen to Cursor via the marketplace or a custom MCP server. Generate AI avatar videos from natural-language prompts without leaving your editor.
## Prerequisites
* Cursor installed (latest version recommended)
* A HeyGen account with Video Agent access
## Install from the Cursor Marketplace (Recommended)
HeyGen is listed in the Cursor marketplace, so setup is one click.
Go to [cursor.com/marketplace/heygen](https://cursor.com/marketplace/heygen) and sign in to Cursor if prompted.
Click **Add**. Cursor opens with the HeyGen MCP server pre-filled — confirm to install it.
Open **Cursor Settings → MCP** (or **Tools & Integrations**) and confirm **HeyGen** is listed with its tools enabled.
## Add Manually (Alternative)
To configure it yourself, add HeyGen under **Cursor Settings → MCP → Add new MCP server**, or edit `~/.cursor/mcp.json` directly:
```json theme={null}
{
"mcpServers": {
"heygen": {
"url": "https://mcp.heygen.com/mcp/v1/"
}
}
}
```
If you already have other MCP servers configured, add `heygen` alongside them inside the existing `mcpServers` block.
## Authentication
On first tool use, Cursor opens a browser-based OAuth flow. Sign in to HeyGen and approve access to link the connector to your account — no API key required.
## Usage
In Cursor's chat (Agent mode), prompt a video generation request:
```text theme={null}
Generate a 60-second explainer video about our new API release using HeyGen.
```
Cursor calls HeyGen's tools to handle scripting, avatar selection, and rendering. Completed videos are also accessible from the **Projects** page in your HeyGen dashboard.
## Limitations
| Constraint | Detail |
| ---------------- | ------------------------------------------------------------------------------- |
| HeyGen Free Tier | Limited video generation credits. Upgrade to Creator plan for production use. |
| OAuth required | The connector authenticates via OAuth against your HeyGen account on first use. |
# Gemini CLI
Source: https://developers.heygen.com/mcp/gemini-cli
Wire HeyGen into Gemini CLI via MCP. Generate AI avatar videos from natural-language prompts in your terminal as part of any development or content workflow.
## Prerequisites
* Gemini CLI installed (`npm install -g @google/gemini-cli@latest`)
* A HeyGen account with Video Agent access
## Adding the MCP Server
Open your Gemini CLI settings file and add the HeyGen server under the `mcpServers` key.
**Global (all projects):** `~/.gemini/settings.json`
**Project-scoped:** `.gemini/settings.json` in your project root
```json theme={null}
{
"mcpServers": {
"heygen": {
"httpUrl": "https://mcp.heygen.com/mcp/v1/"
}
}
}
```
If you already have other MCP servers configured, add `heygen` alongside them inside the existing `mcpServers` block.
Restart Gemini CLI after saving the file.
## Verifying the Connection
Launch Gemini CLI and run:
```text theme={null}
/mcp
```
You should see `heygen` listed under connected MCP servers with its available tools displayed.
## Authentication
On first tool invocation, Gemini CLI will prompt you to authorize access to your HeyGen account through a browser-based OAuth flow. Follow the prompt to complete authentication.
## Usage
Once connected, prompt Gemini CLI with a video generation request:
```text theme={null}
Generate a 60-second explainer video about our new API release using HeyGen.
```
Gemini will call HeyGen's Video Agent to handle scripting, avatar selection, and rendering. Completed videos are accessible from the **Projects** page in your HeyGen dashboard.
### Loading HeyGen Skills (Recommended)
For better prompt structure and higher-quality output, instruct Gemini to reference HeyGen's prompt engineering guidelines before generating:
```text theme={null}
Before writing any video prompts, read the HeyGen skills at:
https://github.com/heygen-com/skills
Follow SKILL.md, references/prompt-optimizer.md, and references/video-agent.md
to structure each prompt with scenes, timing, visual style, and copy rules.
```
## Configuration Scoping
| Scope | File Location | Availability |
| ------- | -------------------------------------- | -------------------- |
| Global | `~/.gemini/settings.json` | All projects |
| Project | `.gemini/settings.json` (project root) | Current project only |
# Lovable
Source: https://developers.heygen.com/mcp/lovable
Connect HeyGen to Lovable as a custom MCP chat connector. Generate AI avatar videos from prompts while you build apps in Lovable.
## Prerequisites
* A Lovable account
* A HeyGen account with Video Agent access
## Add the Connector
Lovable supports custom MCP servers as **chat connectors** — per-user connections that give Lovable's chat context while you build.
Go to **Connectors → Chat connectors**. You can also reach this from the **+** menu next to the prompt input, or via the command palette (**Cmd/Ctrl+K**).
Choose **Add custom MCP server**, name it `HeyGen`, and enter the remote MCP server URL:
```text theme={null}
https://mcp.heygen.com/mcp/v1/
```
Save and connect, then complete the one-time OAuth sign-in to HeyGen to approve access — no API key required.
## Usage
In Lovable's chat, prompt a video generation request:
```text theme={null}
Generate a short product walkthrough video with HeyGen for this app.
```
Lovable calls HeyGen's tools to handle scripting, avatar selection, and rendering. Completed videos are accessible from the **Projects** page in your HeyGen dashboard.
Custom MCP servers in Lovable are **per-user** connections — they are personal, not shared with your workspace, and provide context to Lovable's chat during app creation. They are not bundled into your deployed app. See Lovable's [Integrations overview](https://docs.lovable.dev/integrations/introduction) for details.
# Manus
Source: https://developers.heygen.com/mcp/manus
Manus agents can generate fully scripted AI avatar videos through the HeyGen MCP connection. Supports automated schedules and unattended video production runs.
## Prerequisites
* A [Manus](https://manus.im/app) account with access to Manus Computer (Agents)
* A HeyGen account with Video Agent access
## Connecting HeyGen
In Manus, go to **Connect Your Tools**, search for `HeyGen`, and click **Connect**. You will be prompted to authorize access to your HeyGen account.
```text theme={null}
https://manus.im/app
```
Once authorized, HeyGen tools become available to any Manus agent.
## Using HeyGen in Manus Computer
Open **Manus Computer** under the Agents section and select the HeyGen tools for your agent.
```text theme={null}
https://manus.im/app/agents
```
From here, you write a natural-language prompt describing what you want the agent to produce. Manus handles orchestration — it will call HeyGen's Video Agent to script, render, and deliver the output.
**Example prompt:**
```text theme={null}
Every morning at 7 AM Pacific, automatically produce three short (~60-second)
AI-generated videos summarizing the top viral tech stories, then deliver them
in a neat package. Use HeyGen Video Agent.
```
## Improving Output with HeyGen Skills
For higher-quality video prompts, you can instruct the Manus agent to reference HeyGen's open-source prompt engineering guidelines before generating anything. Add the following to your agent instructions:
```text theme={null}
Before writing any prompts, read the HeyGen skills at:
https://github.com/heygen-com/skills
Specifically read SKILL.md, references/prompt-optimizer.md, and
references/video-agent.md. Follow those guidelines to structure
each video prompt — scene types, visual style, timing, copy rules,
media selection, and the optimization checklist.
Each prompt must be:
- Thesis-driven (one argument per video, not a listicle)
- Scene-by-scene with VO, visuals, and timestamps
- ~150 words of voiceover total (~60 seconds)
- Under 10,000 characters
- Any real quotes, numbers, or company names marked CRITICAL
for on-screen text
```
# OpenAI
Source: https://developers.heygen.com/mcp/open-ai
Add HeyGen video generation to OpenAI ChatGPT via MCP. ChatGPT can script, render, and download AI avatar videos using the HeyGen toolset inside any.
If you've been bouncing between tabs trying to create AI-generated videos, there's a simpler way now. HeyGen — one of the more popular AI video platforms — has an app that plugs right into ChatGPT. That means you can script, customize, and generate a video without ever leaving the chat
## Step 1: Open ChatGPT
Head to [chat.openai.com](http://chat.openai.com) and log into your account.
## Step 2: Find the HeyGen App
Look for the [**Apps**](https://chatgpt.com/apps) section in the left sidebar (or in the GPT store area, depending on your interface version). Search for [**HeyGen App**](https://chatgpt.com/apps/heygen/asdk_app_69418aad55e08191aa5e437b649ca2e4). It should come up as an official integration.\\
## Step 3: Connect and Authorize
Click on the HeyGen app, then hit **Connect**. ChatGPT will ask you to authorize a connection between your OpenAI account and HeyGen. This is a standard OAuth flow — you're giving ChatGPT permission to talk to HeyGen's API on your behalf.
If you don't already have a HeyGen account, you'll be prompted to create one during this step. HeyGen does offer a free tier with basic access, though generation limits are tight. If you're planning to use it regularly, HeyGen paid plans unlock longer videos, more avatar options, and higher resolution output.
## Step 4: Generate Your Video
Once the app is connected, you just ask ChatGPT to make a video. Be specific about what you want — the more detail you give, the better the result.
For example, you might say something like:
> "Use HeyGen to create a 60-second product explainer video. Use a female avatar, professional tone, and include a brief intro and call to action at the end."
ChatGPT will handle the back-and-forth with HeyGen's system, and you'll get your video generated directly in the conversation.
# Overview
Source: https://developers.heygen.com/mcp/overview
Connect any MCP-compatible AI agent to HeyGen video generation in a few clicks. No API key, no local server — add the connector, authenticate with OAuth, and start creating.
Add HeyGen as a connector in your AI agent and start generating videos in a few clicks — no API key and nothing to install. Usage draws on your existing HeyGen plan's credits.
## Add HeyGen to Your Agent
In your MCP-compatible agent (Claude, Cursor, Gemini CLI, Manus, and others), open the **Connectors** or **MCP servers** settings and choose **Add custom connector**.
Name the connector `HeyGen` and enter the remote MCP server URL:
```text theme={null}
https://mcp.heygen.com/mcp/v1/
```
Click **Connect** and sign in to HeyGen to approve access. This one-time OAuth flow links the connector to your account — no API key required.
Ask your agent to generate a video — for example, *"Make a 30-second product explainer with HeyGen."* It calls HeyGen's tools directly and bills against your existing plan.
Menu names vary slightly by agent. For click-by-click instructions, see the per-product setup guides below.
## What You Can Do
Once connected, your AI agent has access to the following tools:
### Video Agent
| Tool Name | Description |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create_video_agent` | One-shot video generation from a prompt — the agent handles scripting, avatar selection, scene composition, and rendering. Supports generate (fire-and-forget) and chat (multi-turn) modes. |
| `get_video_agent_session` | Get the current status, progress, video\_id, and recent chat messages for a session. |
| `send_video_agent_message` | Send a follow-up message to an existing chat-mode session. Use to answer agent questions, add context, or request edits to a generated video. |
| `get_video_agent_resource` | Get a single session resource (image, video, draft, avatar, voice, etc.) by its resource\_id. |
| `list_video_agent_session_videos` | List all videos produced within a Video Agent session. |
| `list_video_agent_sessions` | List your Video Agent sessions, newest first, with pagination. |
| `stop_video_agent_session` | Stop an active agent run at its next checkpoint. Partial results are preserved. |
### Videos
| Tool Name | Description |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create_video_from_avatar` | Create a video from a HeyGen avatar (video or photo avatar) with a text script or audio file. |
| `create_video_from_cinematic_avatar` | Create a cinematic video from a prompt plus 1–3 avatar looks and optional reference media. No script or voice — motion is driven by the prompt (Seedance pipeline). |
| `create_video_from_image` | Create a video by animating an arbitrary image with lip-sync to provided audio or generated speech. |
| `list_videos` | List videos in the account with pagination and optional filtering. |
| `get_video` | Get detailed information about a video including status, URLs, and metadata. |
| `delete_video` | Permanently delete a video. This action cannot be undone. |
### Avatars
| Tool Name | Description |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `list_avatar_groups` | List avatar groups (characters). Each group contains one or more looks. Filterable by ownership (public/private). |
| `get_avatar_group` | Get details for a specific avatar group including name, preview URLs, looks count, and training status. |
| `list_avatar_looks` | List avatar looks (outfits, poses, styles). The look id is the avatar\_id to pass when creating a video. |
| `get_avatar_look` | Get details for a specific avatar look including supported engines, preferred orientation, and training status. |
| `update_avatar_look` | Update the display name of an avatar look. Photo avatar and digital twin looks only. |
| `create_digital_twin` | Create a new avatar from video footage. Avatar training is asynchronous. |
| `create_photo_avatar` | Create a new avatar from a photo. Avatar training is asynchronous. |
| `create_prompt_avatar` | Create a new avatar from a text prompt. Avatar training is asynchronous. |
| `create_avatar_consent` | Initiate the consent flow for an avatar group. Returns a URL for the user to complete approval in their browser. |
| `delete_avatar_look` | Permanently delete an avatar look. Photo avatar and digital twin looks only. |
| `delete_avatar_group` | Permanently delete an avatar group and its looks. |
### Voices
| Tool Name | Description |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `create_speech` | Synthesize speech from text using a specified voice. Supports plain text and SSML. Returns a URL to the generated audio file. |
| `list_voices` | List voices with pagination. Filterable by type (public/private), engine, language, and gender. |
| `get_voice` | Get details for a specific voice, including voice-clone status. Use to poll a clone until it's `complete`. |
| `design_voice` | Find voices matching a natural-language description (e.g. "warm, confident female narrator"). Returns up to 3 matches. |
| `clone_voice` | Clone a voice from an audio file. Returns a voice ID you can poll until ready, then use for speech and videos. |
### Audio
| Tool Name | Description |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `search_audio_sounds` | Semantically search the background-music catalog by natural-language description (e.g. "upbeat lofi hip-hop"). Returns tracks ranked by similarity, each with a pre-signed download URL, plus cursor-based pagination. |
### Lip Sync
| Tool Name | Description |
| ---------------- | ------------------------------------------------------------------------------------------- |
| `create_lipsync` | Replace the audio on an existing video and re-animate lip movements to match the new audio. |
| `list_lipsyncs` | List all lipsync jobs in the account with pagination. |
| `get_lipsync` | Get details for a lipsync job including status, video\_url, and caption\_url. |
| `update_lipsync` | Update the display title of a lipsync job. |
| `delete_lipsync` | Permanently delete a lipsync job and its associated files. |
### Video Translation
| Tool Name | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------ |
| `create_video_translation` | Translate a video into one or more target languages with voice cloning and lip-sync. |
| `list_video_translations` | List all video translation jobs in the account with pagination. |
| `get_video_translation` | Get details for a translation job including status, output language, and video\_url. |
| `update_video_translation` | Update the display title of a video translation job. |
| `delete_video_translation` | Permanently delete a video translation and its associated files. |
| `list_video_translation_languages` | List all supported target languages for video translation. |
### Assets
| Tool Name | Description |
| -------------- | ------------------------------------------------------------------------------------- |
| `get_asset` | Get metadata for an uploaded asset — owner, upload time, file type, and a public URL. |
| `delete_asset` | Permanently delete an asset from your workspace. |
### Brand
| Tool Name | Description |
| ----------------------- | ----------------------------------------------------------------------------------- |
| `list_brand_kits` | List the brand kits in your account (logos, colors, fonts). |
| `list_brand_glossaries` | List brand glossaries used to keep terminology consistent across generated content. |
### Account
| Tool Name | Description |
| ------------------ | ----------------------------------------------------------------------------- |
| `get_current_user` | Get the authenticated user's profile, remaining credits, and billing details. |
## Supported Products
HeyGen Remote MCP works with any MCP-compatible agent, including:
* **Claude** (Web, Desktop, and Code)
* **Cursor** (available in the [Cursor marketplace](https://cursor.com/marketplace/heygen))
* **Gemini CLI**
* **Lovable**
* **Manus**
* **Superhuman**
* **OpenAI**
* and more
See the dedicated setup guide for each product for detailed instructions.
## Connect Your Own Agent
You can integrate HeyGen Remote MCP into any custom agent or application that supports the Model Context Protocol. Just point it to the endpoint:
```text theme={null}
https://mcp.heygen.com/mcp/v1/
```
For security, HeyGen Remote MCP uses domain whitelisting. If your agent runs on a domain that isn't already whitelisted, you'll need to request access before it can connect.
**To request domain whitelisting**, submit your domain via the [Integration Intake form](https://form.typeform.com/to/m3EYcOaM).
## Remote MCP
| | Remote MCP |
| ------------------ | ------------------------------------------ |
| **Setup** | Add endpoint URL, authenticate via OAuth |
| **Runs on** | HeyGen's hosted infrastructure |
| **Authentication** | OAuth (no API key needed) |
| **Billing** | Web plan + premium credits |
| **Best for** | Most users — quick setup, works everywhere |
## FAQ
**Do I need an API key?** No. Remote MCP uses OAuth authentication tied to your HeyGen account. No API key required.
**Does this cost extra?** No. Video generation uses the credits included in your existing HeyGen plan.
**Which HeyGen plans support this?** Remote MCP is available on all HeyGen plans.
**Can I use my custom avatars and voices?** Yes. Any avatars and voices available in your HeyGen account are accessible through Remote MCP.
**What's the difference between this and the HeyGen API?** The HeyGen API gives you direct REST endpoints for programmatic control. Remote MCP wraps those capabilities so AI agents can use them conversationally — without you writing integration code.
# Superhuman
Source: https://developers.heygen.com/mcp/superhuman
Generate AI avatar videos from inside Superhuman via MCP. Compose personalized video emails, sales outreach, and follow-ups without leaving the email client.
If you spend most of your day inside the browser — researching, editing, summarising — there's now a way to turn whatever's on your screen into a HeyGen video without context-switching. Superhuman Go (the agent platform from Grammarly) connects directly to HeyGen via MCP + OAuth, so you can prompt it from anywhere on the web.
## Step 1: Open Superhuman Go
Make sure the Grammarly browser extension is installed, then open the **Superhuman Go** side panel. You'll see your default agents in the sidebar (Grammarly, Calendar, Gmail, etc.).
## Step 2: Find the HeyGen agent
Click the **+** (Add agents) button in the sidebar. In the search field, type `heygen` — the **HeyGen** agent (by HeyGen Integrations) will show up.
## Step 3: Connect and authorize
Click on the HeyGen agent to see its skills (Turn this into a video, Check video status, Text to speech, Browse voices, Manage videos) and privacy details. Hit **Sign in** to start the OAuth flow.
Superhuman Go will ask you to authorize access to your HeyGen account. Confirm, and once authentication completes you'll see the **Successfully signed in** confirmation. If you don't already have a HeyGen account, you can create one during this step (free tier available; paid plans unlock longer videos and more avatar options).
## Step 4: HeyGen agent is now active
After authorizing, the HeyGen agent appears in your Superhuman Go sidebar with its own chat surface. You can now ask it directly: **"Ask HeyGen"**.
## Step 5: Generate a video from any page
Browse to any page you want to turn into a video — a doc, an article, your own work. In the HeyGen panel, prompt it naturally. For example, while looking at one of HeyGen's own developer docs:
> *"Create a short video summary of the content on my screen."*
The agent will read your screen context, build an optimized prompt, and call the HeyGen Video Agent. You'll see the `Create_video_agent` step fire, and a few moments later the video appears with preview frames and quick options to regenerate (vertical, subtitled, alternative tone).
The final rendered video plays directly inside the Superhuman panel, with links to **Watch full video** and **Open in HeyGen** for sharing or editing further.
## Beyond a single page — chaining tools
Because Superhuman Go is an agent platform, HeyGen plays well with the other tools you've connected. For example, if you also have **Granola** connected, you can ask the agent to pull your meeting notes and turn them into a HeyGen video in a single prompt:
> *"Can you get my notes from Granola and create a HeyGen video summary of them?"*
The agent will fetch the latest notes from Granola, write a script, and kick off the HeyGen Video Agent — all in one chat turn.
## What's available
The HeyGen agent in Superhuman Go currently supports:
* **HeyGenAssistant** — default chat skill; reads your screen / editor / selected text and creates videos, checks status, generates speech audio
* **QuickVideoFromScreen** — one-shot 60-second landscape video from your current screen
* **CheckVideoStatus** — polls a session and fetches the final video when ready
* **TextToSpeech** — generates speech audio (≤1000 chars, Starfish engine)
* **BrowseVoices** — lists and filters HeyGen voices
* **ManageVideos** — lists, searches, and deletes videos in your HeyGen library
All actions run on your authenticated HeyGen account — videos appear in your HeyGen workspace as if you'd created them through the standard API or UI.
# Models
Source: https://developers.heygen.com/models
Browse every HeyGen AI model behind the API - avatar models, voice models, lipsync engines, and video generation backends.
# HeyGen Avatar Rendering Engines
HeyGen has three generations of rendering engines. New integrations should use **Avatar IV** or **Avatar V** via `POST /v3/videos`.
***
## Avatar III (Legacy)
Only available to existing users through the **v1/v2 legacy endpoints**. Not offered to new users and not accessible via `POST /v3/videos`.
***
## Avatar IV
The **default engine** on the v3 API. Omitting the `engine` field automatically uses Avatar IV.
**Supported avatar types:** `studio_avatar`, `digital_twin`, `photo_avatar`, `image` (arbitrary), `prompt`
**Exclusive features:**
* `motion_prompt` — natural-language string to control body motion and hand gestures (e.g. `"walk towards the camera slowly"`). Photo avatars and arbitrary images only.
* `expressiveness` — controls energy and range of movement: `high`, `medium`, or `low`. Photo avatars and arbitrary images only. Defaults to `low`.
* **Arbitrary image support** — animate any image by setting `type: "image"` without a registered avatar.
**Example:**
```json theme={null}
{
"type": "avatar",
"avatar_id": "YOUR_LOOK_ID",
"script": "Hello from Avatar IV.",
"voice_id": "YOUR_VOICE_ID",
"resolution": "1080p"
}
```
> `engine` is omitted — Avatar IV is selected automatically.
***
## Avatar V
The **latest engine**, offering more natural motion and lip-sync through cross-reference-driven animation. Must be **explicitly opted into** and is only available for avatar looks that support it.
**Supported avatar types:** `studio_avatar`, `digital_twin`, `photo_avatar`, `prompt` (all subject to eligibility) **Not supported:** arbitrary image input (`type: "image"`)
**Supported parameters:**
* `motion_prompt` — natural-language control of body motion and hand gestures.
**Not supported (Avatar IV only):**
* `expressiveness`
### Checking Eligibility
Not every avatar look supports Avatar V. 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 only available if `"avatar_v"` appears in `supported_api_engines`. Requesting it for an ineligible look returns a `400` error.
**Example:**
```json theme={null}
{
"type": "avatar",
"avatar_id": "YOUR_LOOK_ID",
"script": "Hello from Avatar V.",
"voice_id": "YOUR_VOICE_ID",
"resolution": "1080p",
"engine": { "type": "avatar_v" }
}
```
***
## Comparison
| Feature | Avatar III | Avatar IV | Avatar V |
| -------------------------------- | -------------- | --------- | ------------------- |
| API version | v1/v2 (legacy) | v3 | v3 |
| Available to new users | ✗ | ✓ | ✓ |
| Default engine | ✗ | ✓ | ✗ (explicit opt-in) |
| Arbitrary image input | ✗ | ✓ | ✗ |
| `motion_prompt` | ✗ | ✓ | ✓ |
| `expressiveness` | ✗ | ✓ | ✗ |
| Cross-reference animation | ✗ | ✗ | ✓ |
| Better motion & lip-sync quality | ✗ | ✗ | ✓ |
| Eligibility check required | ✗ | ✗ | ✓ |
# More Legacy APIs
Source: https://developers.heygen.com/more-legacy-api
The **v1 and v2 endpoints** will remain fully supported until **October 31, 2026**. Our engineering roadmap and new feature development are focused exclusively on **v3**.
The primary distinction between the legacy endpoints and v3 lies in the [**Studio API**](https://developers.heygen.com/studio-api) and [**Template API**](https://developers.heygen.com/template-api), which are available in v2 but **not yet supported in v3**. Apart from these, the **v3 endpoints cover the full HeyGen API**, providing a unified platform for all new development. We recommend migrating all new and existing integrations to v3. Read more [**here**](https://developers.heygen.com/endpoint-version-comparison).\
\
For questions about migration or enterprise support, please reach out to [HeyGen support.](https://help.heygen.com/en/)
# Motion Graphics from a Prompt
Source: https://developers.heygen.com/motion-graphics
Generate motion graphics and animated text overlays from a natural-language prompt. The HeyGen API outputs broadcast-ready animations, no timeline needed.
## Examples
These were created with Hyperframes + Claude Code in a single session — from idea to MP4 in under 5 minutes each.
HeyGen product promo — animated text, voiceover, motion graphics. Built from a single prompt.
Minerva AI Tutor architecture — animated system diagram with TTS voiceover explaining how each component works.
## The Problem
Motion graphics traditionally require After Effects, Remotion (React), or hiring a designer. AI agents can write code — but most video tools don't speak code. There's no way to go from "make me a product launch video" to a rendered MP4 without a human in the middle.
## How It Works
```
Describe what you want → AI agent writes HTML + GSAP → Preview in browser → Render to MP4
```
Hyperframes turns HTML into video. Your AI coding agent (Claude Code, Cursor, Copilot) writes the HTML composition, and Hyperframes renders it frame-by-frame into a video file.
## Build It
```bash theme={null}
npx hyperframes init my-video
cd my-video
```
This creates a project with an empty composition, installs AI skills, and sets up the preview server. The skills tell your AI agent how to write valid Hyperframes compositions.
Open the project in Claude Code (or your preferred AI agent) and describe the video:
```
Create a 15-second product launch video for "Acme AI" —
dark background, animated headline that types in letter by letter,
stats that count up (10K users, 99.9% uptime, 50ms latency),
and a logo reveal at the end. Vertical 9:16 for social.
```
The agent uses the installed Hyperframes skills to write a valid HTML composition with GSAP animations.
**Write like you're briefing a designer, not writing code.** Describe the vibe, the content, and the pacing. The AI agent handles the implementation — `data-start`, `data-duration`, `class="clip"`, GSAP timeline registration, etc.
```bash theme={null}
npx hyperframes dev
```
Opens a browser preview at `localhost:3002` with hot reload. Edit the composition (or ask your agent to), and changes appear instantly.
Common follow-ups:
* "Make the text bigger"
* "Change the background to a gradient"
* "Speed up the transitions"
* "Add a sound effect when the stats appear"
```bash theme={null}
npx hyperframes render
```
Captures every frame via headless Chrome, encodes with FFmpeg. Output lands in `renders/`.
| Flag | What it does | Default |
| --------------------------------- | -------------- | -------- |
| `--fps 24\|30\|60` | Frame rate | 30 |
| `--quality draft\|standard\|high` | Render quality | standard |
| `--format mp4\|webm` | Output format | mp4 |
Use `--quality draft` while iterating — it's significantly faster. Switch to `standard` or `high` for the final export.
## What Makes a Good Prompt
Based on testing 13+ videos in a single session:
| Approach | Result |
| ----------------------------------------------------- | ---------------------------------------------------------------------------------- |
| "Make a video about X" | Works, but generic. Agent defaults to dark bg + centered text. |
| "Make a video about X, inspired by \[specific style]" | Much better. Give a reference and the agent adapts. |
| "Make a video about X" + 2-3 rounds of feedback | Best results. Start broad, then refine ("make it more playful", "less corporate"). |
**1-3 prompts** gets you a good result if you describe the idea clearly. Complex compositions (multi-scene, data-driven) take 3-6 prompts.
## Beyond Text and Shapes
Hyperframes renders anything a browser can render. This means:
* **SVG animations** — Logo reveals, icon transitions, animated illustrations
* **Canvas/WebGL** — Particle systems, generative art, 3D scenes
* **Data visualizations** — Charts, graphs, dashboards that animate
* **Game-like content** — Simulations, interactive-looking demos
* **Math-driven patterns** — Physics simulations, algorithmic art
If you can build it in a browser, Hyperframes can turn it into a video.
## Add Audio
Hyperframes supports audio tracks natively. You can:
1. **Use HeyGen TTS** to generate voiceover (see [Voices](/docs/voices/speech))
2. **Add music** as an `