# Assets Source: https://developers.heygen.com/assets
Upload files for use across the HeyGen API.
Upload images, videos, audio, or PDFs to get an `asset_id` you can reference in other endpoints — like `POST /v3/video-agents`, `POST /v3/videos`, or `POST /v3/avatars`.
## Upload an Asset
```bash theme={null}
curl -X POST https://api.heygen.com/v3/assets
-H "x-api-key: YOUR_API_KEY"
-F "file=@./my-photo.png"
```
```json Response theme={null} { "data": { "asset_id": "ast_abc123", "url": "https://files.heygen.com/asset/ast\_abc123.png", "mime_type": "image/png", "size_bytes": 204800 } } ```
## Supported File Types
| Category | Formats | | -------- | --------- | | Image | PNG, JPEG | | Video | MP4, WebM | | Audio | MP3, WAV | | Document | PDF |
Max file size: **32 MB**. MIME type is auto-detected from file bytes.
## Using Assets
Once uploaded, reference the `asset_id` anywhere the API accepts asset inputs:
```json theme={null} // In POST /v3/video-agents (file attachments) { "prompt": "Explain this diagram", "files": [{ "type": "asset_id", "asset_id": "ast_abc123" }] } ```
```json theme={null} // In POST /v3/avatars (photo avatar) { "type": "photo", "name": "My Avatar", "file": { "type": "asset_id", "asset_id": "ast_abc123" } } ```
Anywhere that accepts an asset also accepts a direct URL (`{"type": "url", "url": "https://..."}`) or base64 (`{"type": "base64", "media_type": "image/png", "data": "..."}`). Use `asset_id` when you need to reuse the same file across multiple requests.
# Automated Broadcast Source: https://developers.heygen.com/automated-broadcast
Build a pipeline that generates and distributes video content on a schedule — daily news, weekly updates, recurring series.
## The Problem
Publishing regular video content — daily news roundups, weekly company updates, recurring educational series — is unsustainable without a production team. But consistency is what builds an audience.
## How It Works
``` Schedule triggers → Aggregate content → LLM writes script → Video Agent renders → Auto-distribute ```
A fully automated pipeline that runs on a schedule, collects fresh content from your sources, generates a video, and delivers it to your audience — no human in the loop.
## Build It
Pull content from whatever sources feed your broadcast.
```python theme={null} import requests from datetime import datetime
def aggregate_content(): stories = []
RSS feeds
import feedparser feed = feedparser.parse("https://news.ycombinator.com/rss") for entry in feed.entries[:5]: stories.append({ "title": entry.title, "summary": entry.get("summary", ""), "source": "Hacker News", "url": entry.link, })
APIs (example: your internal metrics)
metrics = requests.get("https://api.yourapp.com/weekly-stats").json() stories.append({ "title": f"This week: {metrics['new_users']} new users, {metrics['revenue']} revenue", "summary": f"Growth of {metrics['growth_pct']}% week over week", "source": "Internal", })
return stories
stories = aggregate_content() ```
```python theme={null} import anthropic
client = anthropic.Anthropic()
story_text = "\n".join( f"- {s['title']} ({s['source']}): {s['summary']}" for s in stories )
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1500,
messages=[{
"role": "user",
"content": f"""Create a HeyGen Video Agent prompt for a 60-second
news/update video.
Date: {datetime.now().strftime('%B %d, %Y')}
Stories to cover:
{story_text}
Structure:
- Intro (5s): "Here's your [daily/weekly] update for [date]"
- Stories (45s): Cover the top 3 stories with text overlays for key stats
- Sign-off (10s): "That's your update. See you [tomorrow/next week]."
Tone: Authoritative but approachable. Clean, news-desk style background.
Keep pacing brisk — one story every 15 seconds."""
}],
)
video_prompt = message.content[0].text
```
```python theme={null} resp = requests.post( "https://api.heygen.com/v3/video-agents", headers={ "X-Api-Key": HEYGEN_API_KEY, "Content-Type": "application/json", }, json={"prompt": video_prompt}, ) video_id = resp.json()["data"]["video_id"]
Poll until complete
import time while True: status = requests.get( f"https://api.heygen.com/v3/videos/{video\_id}", headers={"X-Api-Key": HEYGEN_API_KEY}, ).json()["data"]
if status["status"] == "completed": video_url = status["video_url"] break elif status["status"] == "failed": raise Exception(f"Video failed: {status.get('failure_message')}")
time.sleep(15) ```
Deliver the video to your audience wherever they are.
```python theme={null}
Telegram
import telegram bot = telegram.Bot(token=TELEGRAM_TOKEN) bot.send_video(chat_id=CHANNEL_ID, video=video_url, caption="Daily Update")
Slack
requests.post(SLACK_WEBHOOK, json={ "text": f"Daily update is ready: {video_url}", })
Email (via your ESP)
send_email( to=subscriber_list, subject=f"Your Daily Update — {datetime.now().strftime('%B %d')}", html=f' ', ) ```
Run the pipeline on a schedule using cron, GitHub Actions, or a cloud function.
```yaml theme={null}
.github/workflows/daily-broadcast.yml
name: Daily Video Broadcast on: schedule: - cron: '0 17 * * 1-5' # 5 PM UTC, weekdays jobs: broadcast: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: pip install -r requirements.txt - run: python broadcast.py env: HEYGEN_API_KEY: ${{ secrets.HEYGEN_API_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }} ```
## Real-World Example
STUDIO 47, a German broadcaster, reported these results after adopting HeyGen for automated video production (via [HeyGen customer stories](https://www.heygen.com/customer-stories/studio-47)):
\* Significantly faster content creation \* 24/7 production capability \* Substantial cost reduction vs traditional production \* Expanded into multilingual content that wasn't feasible before
## Resilient Delivery
Build fallbacks for when things go wrong:
```python theme={null} def deliver(video_url, caption): try:
Try primary: send video by URL
bot.send_video(chat_id=CHANNEL_ID, video=video_url, caption=caption) except Exception: try:
Fallback: download and upload as file
video_data = requests.get(video_url).content bot.send_video(chat_id=CHANNEL_ID, video=video_data, caption=caption) except Exception:
Last resort: send text with link
bot.send_message(chat_id=CHANNEL_ID, text=f"{caption}\n\n{video_url}") ```
## Broadcast Types
| Type | Schedule | Content source | Duration | | --------------------- | -------------- | -------------------------------- | -------- | | **Daily news** | Every morning | RSS, APIs, web scrape | 45–60s | | **Weekly roundup** | Monday morning | Internal metrics + industry news | 90s | | **Product changelog** | Each release | Git commits, release notes | 30–45s | | **Company all-hands** | Weekly/monthly | Meeting notes, updates | 60–90s | | **Social digest** | Daily | Trending topics in your niche | 30s |
## Variations
\* **Multi-language:** Generate once, [translate](/cookbook/video-agent/multilingual-content) for regional audiences \* **Different avatars per topic:** Use different presenters for different content categories \* **Audience segmentation:** Generate different versions for different subscriber segments
\\\*
## Next Steps
Repurpose existing content instead of aggregating new content.
Trigger video generation from code changes instead of a schedule.
# Automated Video Pipeline Source: https://developers.heygen.com/automated-pipeline
Generate videos programmatically from data — in CI/CD, on a schedule, or triggered by events.
## The Problem
You need to generate the same type of video repeatedly with different data — weekly reports, personalized onboarding videos, per-customer dashboards, changelog announcements. Doing this manually doesn't scale.
## How It Works
``` Data event → Template composition + injected data → Hyperframes render → Distribute ```
Hyperframes compositions are just HTML files. You can template them, inject data, and render programmatically — no browser, no human, no AI agent in the loop.
## Build It
Build one great composition with your AI agent, then extract the variable parts:
```html theme={null}
{{ACTIVE_USERS}}active users this week
{{REVENUE}}revenue
```
```python theme={null} import subprocess import shutil from pathlib import Path
def generate_report_video(data: dict, output_path: str): """Generate a weekly report video from data."""
Copy template
work_dir = Path(f"/tmp/report-{data['week']}") shutil.copytree("templates/weekly-report", work_dir, dirs_exist_ok=True)
Inject data into template
html = (work_dir / "index.html").read_text() html = html.replace("{{ACTIVE_USERS}}", f"{data['active_users']:,}") html = html.replace("{{REVENUE}}", f"${data['revenue']:,.0f}") html = html.replace("{{GROWTH}}", f"{data['growth_pct']:.1f}%") (work_dir / "index.html").write_text(html)
Render
subprocess.run([
"npx", "hyperframes", "render",
"--output", output_path,
"--quality", "standard",
"--fps", "30",
], cwd=str(work_dir), check=True)
Cleanup
shutil.rmtree(work_dir) return output_path ```
**GitHub Actions:**
```yaml theme={null}
.github/workflows/weekly-report.yml
name: Weekly Report Video on: schedule: - cron: '0 9 * * 1' # Every Monday at 9am
jobs: generate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' - run: sudo apt-get install -y ffmpeg - run: python scripts/generate_report.py - uses: actions/upload-artifact@v4 with: name: weekly-report path: renders/*.mp4 ```
**Webhook-triggered:**
```python theme={null} from flask import Flask, request
app = Flask(__name__)
@app.route("/webhook/new-signup", methods=["POST"]) def on_new_signup(): user = request.json generate_welcome_video( name=user["name"], company=user["company"], output=f"renders/welcome-{user['id']}.mp4" )
Upload to CDN, send via email, etc.
return {"status": "ok"} ```
## Pipeline Patterns
| Trigger | Data source | Output | Example | | ----------------- | ---------------------- | --------------------------- | ------------------------- | | **Cron schedule** | Database query | Weekly/monthly report video | Monday metrics recap | | **Webhook** | Event payload | Per-user personalized video | Welcome onboarding | | **Git push** | Changelog / commit log | Release announcement | "What's new in v2.4" | | **API call** | Request parameters | On-demand custom video | Customer dashboard export |
## Combine with Video Agent
For the best of both worlds — motion graphics + avatar narration:
```python theme={null} def generate_narrated_report(data):
Step 1: Render the motion graphics with Hyperframes
graphics_path = generate_report_video(data, "renders/graphics.mp4")
Step 2: Generate avatar narration with Video Agent
narration = requests.post( "https://api.heygen.com/v3/video-agents", headers={"X-Api-Key": HEYGEN_API_KEY}, json={ "prompt": f"""Narrate this weekly report: {data['active_users']:,} active users (up {data['growth_pct']:.0f}%), ${data['revenue']:,.0f} revenue. Keep it under 15 seconds, upbeat and concise.""", }, ).json()
Step 3: Composite in Hyperframes (avatar PiP over graphics)
... or use ffmpeg to overlay
```
Start simple — get one template working end-to-end, then add automation. A working pipeline that generates one video type reliably is more valuable than a complex system that handles everything.
\\\*
## Next Steps
Build the animated visualizations that feed into your pipeline.
Similar automation pattern using Video Agent for avatar-based content.
# Changelog Source: https://developers.heygen.com/changelog
Product updates and announcements for the HeyGen API and developer platform.
**Programmatic voice cloning is now available**
You can now create and manage voice clones directly from the API, no dashboard step required.
\* `POST /v3/voices/clone` initiates a clone from a reference audio sample. \* `GET /v3/voices/{voice_id}` returns clone status and details so you can poll until processing completes. \* Use the resulting `voice_id` anywhere a voice is accepted (`POST /v3/videos`, `POST /v3/voices/speech`, etc.).
See the [voices overview](/docs/voices/overview) for details.
**Render captions directly into your videos**
`POST /v2/videos` and `POST /v3/videos` now accept a `caption.style` field. Set it to burn captions into the rendered video instead of (or in addition to) consuming the sidecar subtitle file at `subtitle_url`.
Useful for social platforms where viewers watch with sound off and you want captions baked into the asset.
**Apply your own watermark to generated videos**
`POST /v3/videos` accepts a new optional `watermark` property on both `CreateVideoFromAvatar` and `CreateVideoFromImage` requests. Pass a PNG or JPEG image to overlay it onto the rendered output — handy for branding, attribution, or moderation marks.
Available as a premium option for select Enterprise customers — [contact support](https://help.heygen.com) to request access.
**`watermark` now uses the `WatermarkInput` schema**
The inline schema previously used for the `watermark` field on video generation requests has been replaced with the dedicated `WatermarkInput` type. The shape of the property has changed — update existing payloads to match the new schema before upgrading.
**Empty `title` queries are no longer accepted**
`GET /v2/videos` and `GET /v3/videos` now require at least one character for the `title` query parameter (`minLength` increased from 0 to 1). Omit the parameter entirely if you don't want to filter by title — sending an empty string will return a validation error.
**Better error feedback when the voice clone limit is reached**
`POST /v3/voices/clone` now returns the `resource_limit_reached` error code (HTTP 400) when your account has hit its voice clone quota, instead of a generic validation error. The response message tells you to delete unused clones or contact support to raise the limit. See the [error codes reference](/docs/error-codes#resource-limit-reached) for handling guidance.
**Avatar IV is now the default engine on the Studio API**
Starting April 20, 2026, the `POST /v2/video/generate` endpoint defaults to Avatar IV for self-serve users. Enterprise accounts are not affected.
\* If you need to continue using Avatar III, set `use_avatar_iv_model: false` on each `character` in your request. \* Setting `use_avatar_iv_model: true` explicitly opts in. If the avatar doesn't support Avatar IV, the API returns a `400 invalid_parameter` error. \* See the [Studio API](/studio-api#avatar-engine-default-change) page for details and example requests.
**Expanded tool coverage for the HeyGen MCP server**
The HeyGen Remote MCP server now includes tools for managing avatars, videos, lipsync, and video translation — making more of the API accessible to AI agents like Claude, Cursor, Gemini CLI, and Manus.
\* Added avatar management tools: `create_digital_twin`, `create_photo_avatar`, `create_prompt_avatar`, `create_avatar_consent`, `list_avatar_looks`, `get_avatar_look`, `update_avatar_look`, and more. \* Added full video CRUD: `create_video_from_avatar`, `create_video_from_image`, `list_videos`, `get_video`, `delete_video`. \* Added lipsync and video translation management tools. \* Added `design_voice` for finding voices from a natural-language description. \* See the [MCP overview](/mcp/overview) for the full tool list.
**CLI command surface synced with v3 API**
The HeyGen CLI now covers all v3 endpoints, including Video Agent, Lipsync, Video Translation (with Proofreads), Webhooks, and Assets.
\* Added `--wait` flag for blocking until async operations complete, with configurable `--timeout`. \* Added `--request-schema` and `--response-schema` flags to inspect API schemas without authentication. \* Added `--force` flag for non-interactive destructive operations in CI. \* See the [CLI commands](/commands) and [features](/features) pages for usage details.
**v1 and v2 endpoints sunset on October 31, 2026**
A formal deprecation timeline is now in place for the v1 and v2 API. Both versions remain fully operational through October 31, 2026, after which they will be retired.
\* Studio API (multi-scene) and Template API will continue to be supported on v2 until a v3 equivalent is available. \* See the [endpoint version comparison](/endpoint-version-comparison) for a full migration checklist and feature comparison.
**More granular error responses across the API**
New error codes provide clearer feedback when requests fail, making it easier to handle edge cases in your integration.
\* `ai_vendor_access_restricted` — workspace AI vendor policy blocks the request. \* `unlimited_mode_disabled` — avatar doesn't support unlimited mode. \* `voice_unavailable` — cloned voice failed processing or expired. \* `ephemeral_upload_disabled` — eager upload temporarily disabled for the account. \* `gateway_timeout` — external resource could not be fetched in time. \* See the full [error codes reference](/docs/error-codes) for details and troubleshooting.
**New requirement for Starfish engine compatibility**
Text-to-speech generation endpoints now require the use of voices that support the Starfish engine.
\* Updated `POST /v1/audio/text_to_speech` and `POST /v3/voices/speech` documentation. \* Developers should filter for compatible voices by passing `engine=starfish` when calling the voice listing endpoints.
**Standardized Asset ID descriptions**
Documentation across multiple endpoints has been clarified to consistently refer to asset IDs originating from the HeyGen asset upload endpoint. No functional changes were made to the API behavior.
\* Applies to request bodies for: \* `POST /v3/avatars` \* `POST /v3/lipsyncs` \* `POST /v3/video-agents` and `POST /v3/video-agents/{session_id}` \* `POST /v3/video-translations`, `POST /v3/video-translations/proofreads`, and `PUT /v3/video-translations/proofreads/{proofread_id}/srt` \* `POST /v3/videos`
**New endpoint for listing Video Agents**
We have introduced a new endpoint to allow developers to retrieve a list of all existing video agents associated with their account.
\* Added `GET /v3/video-agents`: Use this endpoint to fetch your video agents, enabling easier integration and management of your agent instances.
**Updated error codes for Avatar endpoints**
We have updated the error response codes for avatar-related endpoints to provide more specific feedback when a group cannot be located.
\* `GET /v3/avatars/{group_id}`: The 404 response error code has been updated from `not_found` to `avatar_group_not_found`. \* `POST /v3/avatars/{group_id}/consent`: The 404 response error code has been updated from `not_found` to `avatar_group_not_found`.
**Advanced voice customization and output formatting**
We have introduced new parameters to provide finer control over generated audio and video output quality.
\* Added `volume` and `engine_settings` to `voice_settings` for `POST /v2/videos` and `POST /v3/videos`. These settings apply when using text-to-speech (`script` + `voice_id`). \* Added `output_format` to `POST /v3/videos` for both `CreateVideoFromAvatar` and `CreateVideoFromImage` request schemas.
**Improved error handling for webhook management**
We have updated our webhook endpoints to provide more consistent and descriptive error responses.
\* Added a `409` conflict response to `POST /v3/webhooks/endpoints` to better handle registration errors. \* Standardized error codes for `404` responses across `DELETE`, `PATCH`, and `POST /v3/webhooks/endpoints/{endpoint_id}/rotate-secret` by updating the error code to `webhook_not_found`.
**Support for custom output formats in video generation**
You can now specify a preferred output format when creating videos. The API response now includes the `output_format` field to confirm the format used for your generated video.
\* Added optional `output_format` request property to `POST /v2/videos`. \* Added `output_format` to the response body of `POST /v2/videos` (200 OK). \* Added `output_format` to the response body of `POST /v3/videos` (200 OK).
**Comprehensive API Documentation Updates**
We have updated the endpoint descriptions across our entire V3 API to provide clearer guidance, better parameter context, and more precise functionality definitions. While the underlying API logic remains consistent, the improved documentation clarifies how to integrate with our latest engine versions and features.
\* **Video Generation**: `POST /v3/videos` now officially documents support for the Avatar IV engine and upcoming Avatar V. \* **Avatars**: Clarified workflows for `POST /v3/avatars` (asynchronous training) and added guidance on the mandatory consent flow for private avatars via `POST /v3/avatars/{group_id}/consent`. \* **Video Agent**: Streamlined descriptions for session-based interactions, clearly distinguishing between `generate` (one-shot) and `chat` (multi-turn) modes. \* **Lipsync & Translation**: Updated documentation for `POST /v3/lipsyncs` and `POST /v3/video-translations` to emphasize the `speed` vs. `precision` mode selection for output quality. \* **Webhooks**: Clarified that `PATCH /v3/webhooks/endpoints/{endpoint_id}` performs a full replacement of the event types array. \* **Assets**: Updated supported MIME types for `POST /v3/assets` to include refined file type lists.
**Added caption\_url to Lipsync and Video Translation responses**
You can now retrieve the `caption_url` for generated lipsyncs and video translations, providing direct access to the generated caption files.
\* `GET /v3/lipsyncs` and `GET /v3/lipsyncs/{lipsync_id}` \* `PATCH /v3/lipsyncs/{lipsync_id}` \* `GET /v3/video-translations` and `GET /v3/video-translations/{video_translation_id}` \* `PATCH /v3/video-translations/{video_translation_id}`
**Updated documentation for avatar consent**
Clarified the implementation details for the avatar consent flow to ensure a smoother user experience.
\* `POST /v3/avatars/{group_id}/consent`: Updated documentation to clarify that the returned URL must be presented directly to the user in a browser to complete the consent process.
**Support for avatar-default voices**
You can now generate videos using an avatar's default voice without explicitly specifying a `voice_id`. When creating a video, if `voice_id` is omitted while `avatar_id` is present, the system will automatically use the avatar's default voice.
\* Updated `POST /v3/videos`: The `voice_id` requirement has been relaxed for both `CreateVideoFromAvatar` and `CreateVideoFromImage` schemas, allowing the system to fall back to the avatar's default voice.
**Enhanced capabilities for Video Agent interactions**
We have updated the description and scope of the `POST /v3/video-agents/{session_id}` endpoint to better reflect its versatility in managing agent-led workflows.
\* Updated the endpoint description to clarify support for answering agent-posed questions and requesting specific edits or revisions. \* The request body schema has been updated to better align with these extended conversational and editing capabilities.
**New 'thinking' status for Video Agents**
We have introduced a new `thinking` state to the Video Agent response object to provide better visibility into agent processing workflows.
\* Updated `POST /v3/video-agents` \* The `status` field in the response now includes the `thinking` enum value. \* Integration note: Ensure your client-side parsers are prepared to handle this new status value in the response body.
**Updated Video Agent session retrieval and new video listing**
We have refactored how resource data is handled in Video Agent sessions to improve performance. Additionally, we have introduced a new endpoint to fetch videos associated with a session.
\* **Breaking Change:** The `resources` property has been removed from the response body of `GET /v3/video-agents/{session_id}`. \* **Migration:** To access resource details previously found in the session object, please use the new `GET /v3/video-agents/{session_id}/resources/{resource_id}` endpoint. \* **New Endpoint:** Added `GET /v3/video-agents/{session_id}/videos` to retrieve a list of videos generated within a specific agent session.
**Breaking change: Restructured Video Agent session management**
We have updated the Video Agent API to simplify session handling. Please note that the previous `/v3/video-agents/sessions` path structure is deprecated and removed.
\* **Removed endpoints:** `POST /v3/video-agents/sessions`, `GET /v3/video-agents/sessions/{session_id}`, `POST /v3/video-agents/sessions/{session_id}/messages`, `GET /v3/video-agents/sessions/{session_id}/resources`, and `POST /v3/video-agents/sessions/{session_id}/stop` have been removed. \* **Migration:** Replace existing calls with the new flattened endpoints under `/v3/video-agents/{session_id}`. \* **New endpoints added:** \* `GET /v3/video-agents/{session_id}` \* `POST /v3/video-agents/{session_id}` \* `GET /v3/video-agents/{session_id}/resources/{resource_id}` \* `POST /v3/video-agents/{session_id}/stop`
**New configuration options for Video Agent sessions**
The `POST /v3/video-agents` endpoint now supports advanced control over session flow.
\* Added `mode`: Supports `generate` (default, one-shot) and `chat` (multi-turn, allows revisions and follow-ups). \* Added `auto_proceed`: Enables automated progression through storyboards. \* Added `skip_agentic_stop`: Provides granular control over agent stopping behavior.
**API Operation ID update**
The operation ID for `GET /v3/users/me` has been updated from `getUserMeV3` to `getCurrentUserV3` to maintain consistency across our SDKs.
**Added support for custom voice creation**
We have introduced a new endpoint to allow developers to programmatically create and add new voices to their HeyGen account.
\* Added `POST /v3/voices` to the API.
**Refactored POST /v3/videos request body**
We have updated the `POST /v3/videos` endpoint to use a discriminated union for improved type safety and flexibility. This change replaces the legacy flat request structure with dedicated schemas for creating videos from avatars versus images.
\* **Breaking Change:** The request body structure has been completely overhauled. You must now specify a type discriminator: use `CreateVideoFromAvatar` for digital twins/avatars or `CreateVideoFromImage` for custom image animation. \* **Migration:** All properties previously passed at the top level of the request (e.g., `avatar_id`, `image_url`, `voice_id`, `script`) must now be nested within the appropriate schema based on the video source. \* The operation ID for this endpoint has been updated from `createAvatarVideoV3` to `createVideo`.
**Enhanced error messaging across all endpoints**
We have updated the error response schemas and examples across the entire API suite. Developers can now expect more consistent and detailed error responses for common issues, including:
\* Improved `400 Bad Request` messages with clearer parameter validation feedback. \* Standardized `401 Unauthorized` responses when API keys are missing or expired. \* Consistent `429 Rate Limited` responses that align with standard retry headers. \* Better descriptive error messages for resource-specific failures (e.g., `404 Not Found` for specific IDs).
These updates ensure that your integrations can better handle exceptions and debugging.
**HeyGen for Developers — New v3 API Surface**
We've launched a new set of v3 endpoints across the HeyGen API, bringing a consistent interface, cursor-based pagination, and a unified asset input model to all major resources.
What's new:
\* All v3 endpoints share a standard error format, cursor-based pagination (`has_more` / `next_token`), and consistent authentication via `X-Api-Key` or OAuth bearer token. \* Asset inputs now use a type-discriminated union — pass files as `{ "type": "url", "url": "..." }`, `{ "type": "asset_id", "asset_id": "..." }`, or `{ "type": "base64", "media_type": "...", "data": "..." }` across all endpoints. \* New and updated endpoints include: Video Agent (`POST /v3/video-agents`), Videos (`POST /v3/videos`), Voices (`GET /v3/voices`, `POST /v3/voices/speech`), Video Translations (`POST /v3/video-translations`), Overdub (`POST /v3/overdubs`), Avatars (`POST /v3/avatars`), Assets (`POST /v3/assets`), Webhooks (`/v3/webhooks/*`), and User (`GET /v3/users/me`).
The v1/v2 endpoints continue to work, but we recommend migrating to v3 for all new integrations.
# Overview Source: https://developers.heygen.com/cli
Get from zero to a generated video in minutes — right from your terminal.
The HeyGen CLI gives developers and AI agents command-line access to HeyGen's video platform. It wraps the v3 API, outputs structured JSON by default, and works out of the box in scripts, CI pipelines, and agent workflows.
## 1. Install the CLI
```bash theme={null} curl -fsSL https://static.heygen.ai/cli/install.sh | bash ```
This installs the latest stable release into `~/.local/bin`.
Verify the installation:
```bash theme={null} heygen --version ```
The CLI ships as a single binary with no runtime prerequisites. macOS (Apple Silicon and Intel) and Linux (x64 and arm64) are supported. Windows support is coming soon — WSL is recommended in the meantime.
## 2. Authenticate
Log in with your API key from [API dashboard](https://app.heygen.com/settings/api?nav=API):
```bash theme={null} heygen auth login ```
Paste your API key when prompted. The key is stored locally at `~/.heygen/credentials`.
For CI/Docker/agent environments, set the environment variable instead — it takes precedence over stored credentials:
```bash theme={null} export HEYGEN_API_KEY=your-api-key ```
Verify your credentials:
```bash theme={null} heygen auth status ```
## 3. Create a Video
Send a prompt to the Video Agent and let it handle avatar, voice, and layout:
```bash theme={null} heygen video-agent create --prompt "A presenter explaining our product launch in 30 seconds" ```
```json Output theme={null} { "data": { "session_id": "sess_abc123", "status": "generating", "video_id": "vid_xyz789", "created_at": 1711288320 } } ```
The CLI returns immediately with structured JSON. Your video is generating in the background.
For full control over every parameter, use `video create` with a JSON body:
```bash theme={null} heygen video create -d '{ "type": "avatar", "avatar_id": "avt_angela_01", "script": "Welcome to our Q4 earnings call.", "voice_id": "1bd001e7e50f421d891986aad5e3e5d2" }' ```
Use `--request-schema` on any command to discover the expected JSON fields — no auth required:
```bash theme={null} heygen video create --request-schema heygen video-agent create --request-schema ```
## 4. Check Status
Poll for the result using the `video_id` returned from step 3:
```bash theme={null} heygen video get vid_xyz789 ```
```json Output theme={null} { "data": { "id": "vid_xyz789", "title": "Product launch explainer", "status": "completed", "video_url": "https://files.heygen.com/video/vid\_xyz789.mp4", "thumbnail_url": "https://files.heygen.com/thumb/vid\_xyz789.jpg", "duration": 32.5, "created_at": 1711288320, "completed_at": 1711288452 } } ```
Status moves through `pending` → `processing` → `completed` or `failed`. If the video fails, the response includes `failure_code` and `failure_message` fields.
**Tip:** Add `--wait` to the create command to block until the video is ready instead of polling manually. The default timeout is 20 minutes — override with `--timeout 30m`. On timeout, the CLI exits with code `4` and prints the last known resource state along with a hint to resume polling manually.
## 5. Download the Video
Once complete, download to a local file:
```bash theme={null} heygen video download vid_xyz789 --output-path ./launch-video.mp4 ```
```json Output theme={null} { "asset": "video", "message": "Downloaded video to ./launch-video.mp4", "path": "./launch-video.mp4" } ```
If the video was created with captions enabled, you can download the captioned version:
```bash theme={null} heygen video download vid_xyz789 --asset captioned --output-path ./launch-captioned.mp4 ```
# Commands Source: https://developers.heygen.com/commands
Complete command reference for the HeyGen CLI, organized by resource.
All commands follow the pattern `heygen `. The command surface is auto-generated from HeyGen's OpenAPI specification — when new v3 endpoints ship, the CLI picks them up automatically.
Run `heygen --help` for detailed usage and examples on any command. Use `--request-schema` or `--response-schema` on any command to see the full JSON schema for its request or response — no auth required.
## Video Agent
Create videos from text prompts using AI. The agent picks avatar, voice, and layout automatically.
| Command | API Endpoint | Description | | ------------------------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------- | | `heygen video-agent create` | `POST /v3/video-agents` | Create a video from a prompt | | `heygen video-agent get ` | `GET /v3/video-agents/{session_id}` | Get session status and messages | | `heygen video-agent send ` | `POST /v3/video-agents/{session_id}` | Send a follow-up message or request revision | | `heygen video-agent stop ` | `POST /v3/video-agents/{session_id}/stop` | Stop an in-progress session | | `heygen video-agent resources get ` | `GET /v3/video-agents/{session_id}/resources/{resource_id}` | Get a session resource | | `heygen video-agent styles list` | `GET /v3/video-agents/styles` | List available video styles | | `heygen video-agent videos list ` | `GET /v3/video-agents/{session_id}/videos` | List videos produced by a session |
### Flags for `video-agent create`
| Flag | Description | | ----------------------- | -------------------------------------------------------------------- | | `--prompt ` | The message/prompt for video generation (required) | | `--mode ` | `generate` (default, one-shot) or `chat` (multi-turn with revisions) | | `--avatar-id ` | Specific avatar ID to use | | `--voice-id ` | Specific voice ID to use for narration | | `--style-id ` | Style ID from `video-agent styles list` | | `--orientation ` | `landscape` or `portrait` (auto-detected if omitted) | | `--incognito-mode` | Disable memory injection and extraction for this session | | `--callback-url ` | Webhook URL for completion/failure notifications | | `--callback-id ` | ID echoed back in the webhook payload |
## Videos
Create, list, retrieve, and delete avatar videos with full parameter control.
| Command | API Endpoint | Description | | ---------------------------------- | ------------------------------ | --------------------------------------- | | `heygen video create` | `POST /v3/videos` | Create a video with explicit parameters | | `heygen video list` | `GET /v3/videos` | List your videos | | `heygen video get ` | `GET /v3/videos/{video_id}` | Get video details and status | | `heygen video delete ` | `DELETE /v3/videos/{video_id}` | Delete a video | | `heygen video download ` | Client-side | Download a video file to disk |
### Flags for `video create`
`video create` uses a discriminated union request body — the `type` field determines which fields are valid. Pass the full body with `-d`:
```bash theme={null} # Avatar-based video heygen video create -d '{ "type": "avatar", "avatar_id": "avt_angela_01", "script": "Hello world", "voice_id": "1bd001e7e50f421d891986aad5e3e5d2" }'
# Image-based video heygen video create -d '{ "type": "image", "image": {"type": "url", "url": "https://example.com/photo.jpg"}, "script": "Hello", "voice_id": "1bd001e7e50f421d891986aad5e3e5d2" }' ```
Run `heygen video create --request-schema` to see all available fields.
### Flags for `video list`
| Flag | Description | | ------------------ | --------------------------------------------------------- | | `--limit ` | Maximum items per page (1–100, default 10) | | `--token ` | Pagination cursor from a previous response's `next_token` | | `--folder-id ` | Filter videos by folder ID | | `--title ` | Filter videos by title substring |
### Flags for `video download`
| Flag | Description | | ---------------------- | -------------------------------------------- | | `--output-path ` | Output file path (default: `{video-id}.mp4`) | | `--asset ` | `video` (default) or `captioned` |
## Avatars
Browse and manage avatars and their looks (outfits/styles).
| Command | API Endpoint | Description | | ----------------------------------------- | ------------------------------------- | ------------------------ | | `heygen avatar create` | `POST /v3/avatars` | Create an avatar | | `heygen avatar list` | `GET /v3/avatars` | List avatar groups | | `heygen avatar get ` | `GET /v3/avatars/{group_id}` | Get avatar group details | | `heygen avatar looks list` | `GET /v3/avatars/looks` | List avatar looks | | `heygen avatar looks get ` | `GET /v3/avatars/looks/{look_id}` | Get avatar look details | | `heygen avatar looks update ` | `PATCH /v3/avatars/looks/{look_id}` | Rename an avatar look | | `heygen avatar consent create ` | `POST /v3/avatars/{group_id}/consent` | Initiate a consent flow |
### Filter flags for `avatar list`
| Flag | Description | | --------------------- | ----------------------------------------- | | `--ownership ` | `public` or `private` | | `--limit ` | Maximum items per page (1–50, default 20) | | `--token ` | Pagination cursor |
### Filter flags for `avatar looks list`
| Flag | Description | | ---------------------- | -------------------------------------------------- | | `--group-id ` | Filter by avatar group | | `--avatar-type ` | `studio_avatar`, `digital_twin`, or `photo_avatar` | | `--ownership ` | `public` or `private` | | `--limit ` | Maximum items per page (1–50, default 20) | | `--token ` | Pagination cursor |
The `id` field on a look is what you pass as `avatar_id` to `video create`. The look's `avatar_type` field determines which engines and request parameters are compatible.
## Voices
Browse voices and generate speech audio.
| Command | API Endpoint | Description | | ---------------------------- | ------------------------ | -------------------------------------------------- | | `heygen voice list` | `GET /v3/voices` | List voices | | `heygen voice create` | `POST /v3/voices` | Design a voice from a natural language description | | `heygen voice speech create` | `POST /v3/voices/speech` | Generate speech audio from text |
### Filter flags for `voice list`
| Flag | Description | | ------------------- | ---------------------------------------- | | `--type ` | `public` (default) or `private` | | `--engine ` | Filter by voice engine (e.g. `starfish`) | | `--language ` | Filter by language name (e.g. `English`) | | `--gender ` | `male` or `female` | | `--limit ` | Results per page (1–100, default 20) | | `--token ` | Pagination cursor |
### Flags for `voice create`
| Flag | Description | | ------------------ | --------------------------------------------------------------- | | `--prompt ` | Natural language description of the desired voice (required) | | `--gender ` | `male` or `female` | | `--locale ` | BCP-47 locale tag (e.g. `en-US`) | | `--seed ` | Increment to get a different batch of voice results (default 0) |
### Flags for `voice speech create`
| Flag | Description |
| --------------------- | -------------------------------------------------- |
| `--text ` | Text to synthesize (required) |
| `--voice-id ` | Voice ID to use (required) |
| `--speed ` | Playback speed multiplier, `0.5`–`2.0` (default 1) |
| `--language ```` | 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:
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
Turn blog posts, articles, and newsletters into video — reach audiences that don't read.
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.
# 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
Turn datasets, metrics, and algorithms into animated videos — charts that move, dashboards that update, patterns that evolve.
## Examples
</div>
9 sorting algorithms on 100 bars — bubble sort through merge sort. Each comparison plays a pitched tone. 76 seconds with synthesized audio.
</Tab>
<Tab title="4000 Weeks">
<div>
<iframe />
</div>
A 75-year life as 3,900 weekly squares. They fill in with an accelerating heartbeat. The empty ones are what's left.
</Tab>
<Tab title="Flappy Bird">
<div>
<iframe />
</div>
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.
</Tab>
</Tabs>
## 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
<Steps>
<Step title="Prepare your data">
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
```
</Step>
<Step title="Describe the visualization to your AI agent">
```
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.
</Step>
<Step title="Add generated audio (optional)">
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("<h", int(max(-1, min(1, s)) * 32767)))
```
Then reference it in the composition:
```html theme={null}
<audio id="data-sfx" data-start="0" data-track-index="5"
data-volume="0.8" src="data-sound.wav"></audio>
```
</Step>
<Step title="Preview and render">
```bash theme={null}
npx hyperframes dev # preview at localhost:3002
npx hyperframes render # export to MP4
```
</Step>
</Steps>
## 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"
)
Next Steps
Design a Custom Voice
Source: https://developers.heygen.com/design-a-voice
Describe the voice you want in plain English — tone, accent, personality — and get a custom voice for any video.
Steps
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.
</Step>
<Step title="Get different options with --seed">
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
```
</Step>
<Step title="Use the voice in a video">
Take the `voice_id` and pass it to any video creation command.
<CodeGroup>
```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"
}'
```
</CodeGroup>
</Step>
</Steps>
## 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
Auto-generate video walkthroughs when your documentation changes — in CI/CD or on demand.
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
Or from a URL
import requests
content = requests.get(
"https://raw.githubusercontent.com/your-org/repo/main/README.md"
).text
```
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
```
Optionally attach screenshots or diagrams as file inputs. Then poll for completion.
CI/CD Integration
Trigger video generation automatically when documentation changes:
# .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
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 for international docs
Next Steps
API Key
Source: https://developers.heygen.com/docs/api-key
Getting Your API Key
- Go to the HeyGen API dashboard
- Click to generate your API key.
Configuring Your API Key
Environment variable (recommended)
.env file
If your project uses a .env file (common with Node.js, Python, or frameworks like Next.js):
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:
Alternatively, add it to your shell profile (~/.bashrc, ~/.zshrc) so it persists across sessions:
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.
const response = await fetch("https://api.heygen.com/v3/avatars", {
headers: { "X-Api-Key": process.env.HEYGEN_API_KEY },
});
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:
{
"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
.envto 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
Avatar Looks
Source: https://developers.heygen.com/docs/avatar-looks
List avatar looks (outfits/styles) with cursor-based pagination and filtering. Each look has a unique ID that you pass as avatar_id to POST /v3/videos or POST /v3/video-agents.
Quick Example
{
"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 or POST /v3/video-agents. |
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. |
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
{
"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:
Pagination
If has_more is true, pass the next_token value as the token query parameter to fetch the next page.
Using a Look in Video Creation
Once you have a look id, pass it as avatar_id:
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
List avatar groups (characters) with cursor-based pagination. Each group contains one or more looks (outfits/styles) — use the Avatar Looks endpoint to browse them.
Quick Example
{
"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} for details, or use to filter looks. |
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. |
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
{
"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.
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 for how to browse and select looks.
Bulk Video Translation
Source: https://developers.heygen.com/docs/bulk-video-translation
When you need to translate many videos at once (e.g., localizing a content library), you can script against the API using a CSV as input. This guide walks through a Python script that reads a CSV of videos, submits translation requests to the v3 API, and writes the results to a new CSV.
Prerequisites
- Python 3.6+
- The
requestslibrary:pip install requests - A HeyGen API key from the Subscriptions & API section of your dashboard.
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 to discover valid codes). |
url |
Yes | Public video download URL. |
folder_id |
No | Folder ID to organize translations. |
Example:
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",""
You can also start from this Google Sheet template and export it as CSV.
The Script
Save the following as bulk_translate.py:
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
- Set your API key as an environment variable (recommended):
Alternatively, replaceexport HEYGEN_API_KEY="your-api-key-here"YOUR_API_KEY_HEREin the script directly. - Place your CSV (
bulk_translation_input.csv) in the same directory as the script. - Run the script:
On macOS or Linux, usepython bulk_translate.pypython3if needed:python3 bulk_translate.py - Review results. The script creates
bulk_translation_results.csvwith all original columns plus avideo_translation_idcolumn.
Checking Translation Status
Use the video_translation_id values from the results CSV to poll each translation's status:
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations/<video_translation_id>' \
--header 'accept: application/json' \
--header 'x-api-key: <your-api-key>'
When the status is completed, the response includes a presigned video_url to download the translated video.
Choosing the Right Video API
Source: https://developers.heygen.com/docs/choosing-the-right-video-api
Compare Video Agent and direct video creation to pick the right approach for your use case.
HeyGen offers two ways to create videos programmatically. The right choice depends on how much control you need.
| Video Agent | Direct Video | |
|---|---|---|
| Endpoint | POST /v3/video-agents |
POST /v3/videos |
| Input | Natural language prompt | Structured JSON |
| Script writing | Agent writes it | You write it |
| Avatar selection | Agent picks (or you override) | You specify |
| Voice selection | Agent picks (or you override) | You specify |
| Interactive iteration | ✅ Via chat mode | ❌ |
| Webhook support | ✅ callback_url |
✅ callback_url |
| Control level | Low (prompt-driven) | High (explicit) |
Video Agent — best for speed
Send a text prompt, get a video. The agent handles scripting, avatar selection, and scene composition automatically.
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.
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.",
"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.
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.
You can also combine both — 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
Securely integrate your application with HeyGen using OAuth 2.0
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)
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):
- 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_challengefor a generatedcode_verifierusing the PKCE flow (e.g.E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM). See: RFC 7636 Appendix B.
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:
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:
{
"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:
The HeyGen API endpoints support both authentication methods:
- OAuth Access Tokens — Use the
Authorizationheader with the Bearer scheme. Format:Authorization: Bearer YOUR_ACCESS_TOKEN - API Keys — Use the
X-Api-Keyheader. 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:
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:
{
"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
stateparameter 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 new avatar from your own footage, images, or a text description. Returns an avatar_item (the look) and an avatar_group (the character identity). The look ID is what you pass as avatar_id when creating videos.
Creation Methods
Create an avatar from video footage. The speaker in the video becomes a reusable digital twin.
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": "..." }`, 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. |
</Tab>
<Tab title="Photo Avatar">
### 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. |
</Tab>
<Tab title="Prompt-to-Avatar">
### 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.
<Info>
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.
</Info>
```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. Be specific for best results. |
| `reference_images` | array | No | Optional reference images to guide generation. Array of objects: `[{ "type": "url", "url": "..." }]`. |
| `avatar_group_id` | string | No | Attach to an existing character identity. Omit to create a new one. |
#### With Reference Images
You can provide reference images to guide the avatar's appearance — useful for matching a brand style or specific aesthetic:
```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",
"prompt": "Professional woman in her 40s, warm smile, wearing a navy blazer, modern office background with plants",
"reference_images": [\
{ "type": "url", "url": "https://example.com/style-reference.png" },\
{ "type": "url", "url": "https://example.com/setting-reference.jpg" }\
]
}'
```
<Tip>
**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
</Tip>
</Tab>
</Tabs>
## 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. |
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. |
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.
Request
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
{
"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"). |
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 to browse looks, or pass avatar_group_id when creating a new avatar to add a look to an existing character.
Enterprise Pricing
Source: https://developers.heygen.com/docs/enterprise-pricing
All rates are billed in credits based on output duration.
Video Generation — Avatar IV
| 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 Type | Rate |
|---|---|
| Photo Avatar | 0.0033 credits / sec |
| Digital Twin | 0.0033 credits / sec |
| Studio Avatar | 0.0033 credits / sec |
Video Agent
| Feature | Rate |
|---|---|
| Prompt to Video | 0.0667 credits / sec |
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(fromPOST /v3/assets), an HTTPS URL, or base64-encoded content.
Asset Upload (POST /v3/assets)
- Maximum file size: 32 MB.
- 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
HeyGen offers two Enterprise billing models depending on your contract. Your account team will help you choose the right fit.
| 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
- Monthly Minimum Commitment (MMC): A flat fee charged monthly, regardless of usage.
- Included Usage: Each tier includes a pool of usage dollars per month at your contracted rate.
- Overage: Usage beyond the included pool is billed at the overage rate per second.
For pricing details and available tiers, contact our sales team.
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
- Annual Contract: You agree to a total dollar amount for the contract term (typically 12 months).
- Drawdown: Your balance is consumed per second as you use HeyGen's API.
- 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.
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
{
"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.
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 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.
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 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_idis 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_idis 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.
content_policy_violation
HTTP status: 400
The request references a resource that has been rejected for violating HeyGen's content policy. This typically applies to instant avatars that did not pass the moderation review. Create a new resource that complies with our usage 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). Delete unused resources to free up capacity, or contact HeyGen support 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.
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 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.
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 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.
Interactive Sessions
Source: https://developers.heygen.com/docs/interactive-sessions
Review storyboards, send follow-up messages, and iterate with the Video Agent before generating.
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.
Session lifecycle
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}. |
failed |
Something went wrong. Check messages for error details. |
Create a session
POST https://api.heygen.com/v3/video-agents
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. |
auto_proceed |
boolean | No | If true, skip interactive review and go straight to video generation. Default: false. |
callback_url |
string | No | Webhook URL for completion/failure notifications. |
callback_id |
string | No | Caller-defined ID echoed back in the webhook payload. |
Example
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
{
"data": {
"session_id": "sess_abc123",
"status": "thinking",
"video_id": null,
"created_at": 1711382400
}
}
Poll session status
GET https://api.heygen.com/v3/video-agents/{session_id}
Returns the current session status, progress percentage, chat messages, and the video_id once generation starts.
Response
{
"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}. |
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}. |
Send a follow-up message
POST https://api.heygen.com/v3/video-agents/{session_id}
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
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
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
{
"data": {
"session_id": "sess_abc123",
"run_id": "run_def456",
"title": "Engineering Onboarding Video"
}
}
After sending a message, poll GET /v3/video-agents/{session_id} to see the agent's response and updated status.
Get a session resource
GET https://api.heygen.com/v3/video-agents/{session_id}/resources/{resource_id}
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.
Example
curl "https://api.heygen.com/v3/video-agents/sess_abc123/resources/res_storyboard_001" \
-H "X-Api-Key: $HEYGEN_API_KEY"
Response
{
"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
POST https://api.heygen.com/v3/video-agents/{session_id}/stop
Stop an in-progress agent run. The agent halts at the next checkpoint, and partial results are preserved.
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 '{}'
{
"data": {
"session_id": "sess_abc123"
}
}
Overview
Source: https://developers.heygen.com/docs/overview
Generate videos with a single prompt using the Video Agent API. No web app required.
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
Quick Start
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"])
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);
{
"data": {
"session_id": "sess_abc123",
"status": "generating",
"video_id": null,
"created_at": 1711382400
}
}
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.
{
"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
| 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 |
Best practices:
- Use
callback_urlinstead 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) |
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:
{
"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
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:
GET /v3/users/me → wallet
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
| 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 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 |
Video Agent
| Feature | Rate |
|---|---|
| Prompt to Video | $0.0333 / sec |
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
Get from zero to a generated video in minutes.
export HEYGEN_API_KEY="your-api-key-here"
```
</Step>
<Step title="Create a video">
Send a prompt to the Video Agent and let it handle the rest:
<Tabs>
<Tab title="curl">
```bash Request 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"}'
```
</Tab>
<Tab title="Python">
```python Request 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"},
)
data = resp.json()["data"]
print(data["video_id"])
```
</Tab>
<Tab title="Node.js">
```javascript Request 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 { data } = await resp.json();
console.log(data.video_id);
```
</Tab>
</Tabs>
```json Response theme={null}
{
"data": {
"session_id": "sess_abc123",
"status": "generating",
"video_id": "vid_xyz789",
"created_at": 1711382400
}
}
```
</Step>
<Step title="Poll for the result">
Video generation is async. Use the `video_id` to check status:
<Tabs>
<Tab title="curl">
```bash Request theme={null}
curl -X GET "https://api.heygen.com/v3/videos/vid_xyz789" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
</Tab>
<Tab title="Python">
```python Request theme={null}
import time
video_id = "vid_xyz789"
while True:
resp = requests.get(
f"https://api.heygen.com/v3/videos/{video_id}",
headers={"X-Api-Key": HEYGEN_API_KEY},
)
video = resp.json()["data"]
if video["status"] in ("completed", "failed"):
break
time.sleep(10)
print(video["video_url"])
```
</Tab>
<Tab title="Node.js">
```javascript Request theme={null}
const poll = async (videoId) => {
while (true) {
const resp = await fetch(
`https://api.heygen.com/v3/videos/${videoId}`,
{ headers: { "X-Api-Key": process.env.HEYGEN_API_KEY } }
);
const { data } = await resp.json();
if (data.status === "completed" || data.status === "failed") return data;
await new Promise((r) => setTimeout(r, 10000));
}
};
const video = await poll("vid_xyz789");
console.log(video.video_url);
```
</Tab>
</Tabs>
```json Response (completed) theme={null}
{
"data": {
"id": "vid_xyz789",
"status": "completed",
"video_url": "https://files.heygen.com/video/vid_xyz789.mp4",
"thumbnail_url": "https://files.heygen.com/thumb/vid_xyz789.jpg",
"duration": 32.5
}
}
```
Status moves through `pending` → `processing` → `completed` | `failed`. Once completed, download from `video_url`.
<Tip>
Skip polling by passing a `callback_url` in your creation request to get a webhook notification instead.
</Tip>
</Step>
</Steps>
## Resources
<CardGroup>
<Card title="Video Agent" icon="wand-magic-sparkles" href="/docs/video-agent">
Generate videos from a text prompt — the agent handles avatar, script, and production.
</Card>
<Card title="Video Translation" icon="language" href="/docs/video-translate">
Translate videos into 30+ languages with natural voice cloning and lip-sync.
</Card>
<Card title="Webhooks" icon="bell" href="/docs/webhooks">
Get notified when videos, translations, and avatars finish processing.
</Card>
<Card title="API Limits and Costs" icon="dollar-sign" href="/docs/pricing">
Rate limits, usage, and pricing per operation.
</Card>
</CardGroup>
## Tools
<CardGroup>
<Card title="CLI" icon="terminal" href="/cli">
Script video creation and translation from your terminal.
</Card>
<Card title="MCP Server" icon="plug" href="/mcp/overview">
Connect HeyGen to AI agents and copilots via Model Context Protocol.
</Card>
<Card title="Authentication" icon="key" href="/docs/api-key">
API key setup, OAuth tokens, and request signing.
</Card>
</CardGroup>
# Slack Integration
Source: https://developers.heygen.com/docs/slack
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:
- Acknowledge your request
- Generate the video using your HeyGen account
- Post the finished video in the thread
Method 2: React with 🎥 emoji
Convert any message into a video by reacting with the camera emoji:
- Find a message you want to turn into a video
- Click Add reaction (or press
R) - 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:
/heygen-curate [#channel] [--notify] [--days N]
Examples:
/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
- Visit your HeyGen account settings at app.heygen.com
- Navigate to Connections → Slack
- Click Link your Slack account
- Sign in to Slack and authorize the connection
Once linked, all videos you create will use your personal HeyGen account.
Check your link status
- On the settings menu, Make sure you are on Connections
- 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:
- Go to your HeyGen account settings
- Connections → Slack
- 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:
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 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)
- 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:
- Go to your Slack workspace settings
- Apps → HeyGen → Configuration
- Update the HeyGen credentials
- 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 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)
- 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:
- Created in your HeyGen workspace (visible in your HeyGen dashboard)
- Uploaded directly to Slack (stored in your Slack workspace files)
- Accessible via the Slack message thread
Can I use this in private channels?
Yes! Invite the HeyGen bot to any channel:
/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 for details
How do I uninstall?
To remove the HeyGen app:
- Go to your Slack workspace settings
- Apps → HeyGen
- Click Remove App
- 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
Support
Need help?
- Documentation: docs.heygen.com/slack
- Email: support@heygen.com
- Community: Join our community for tips and discussions
Styles & References
Source: https://developers.heygen.com/docs/styles-and-references
Browse and apply curated visual styles to Video Agent videos.
Styles are curated visual templates that control how the 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
GET https://api.heygen.com/v3/video-agents/styles
Returns a paginated list of styles. Each style 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
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
{
"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 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:
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.
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:
# 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. |
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:
{
"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, video, audio, and PDFs to use as file inputs in Video Agent and other endpoints.
The Assets API lets you upload files to HeyGen and receive an asset_id you can reference in other endpoints — including Video Agent, avatar creation, and video translation.
POST https://api.heygen.com/v3/assets
Upload a file using multipart/form-data. The MIME type is auto-detected from file bytes.
Constraints
| Constraint | Value |
|---|---|
| Max file size | 32 MB |
| Supported images | png, jpeg |
| Supported video | mp4, webm |
| Supported audio | mp3, wav |
| Other |
Example request
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"])
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
{
"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. |
Use assets in Video Agent
Once uploaded, reference the asset_id in the files array when creating a video:
{
"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:
Format comparison
| Format | Syntax | When to use |
|---|---|---|
| Asset ID | { "type": "asset_id", "asset_id": "asset_..." } |
Pre-uploaded files, reusable across requests. |
| URL | { "type": "url", "url": "https://..." } |
Files already hosted publicly. Simplest option. |
| Base64 | { "type": "base64", "media_type": "image/png", "data": "iVBOR..." } |
Small files, self-contained requests, no hosting needed. |
Where assets can be used
The asset_id format is accepted anywhere the API takes file inputs:
| Endpoint | Use case |
|---|---|
POST /v3/video-agents |
Attach reference files (images, slides, video clips, audio). |
POST /v3/video-agents/{session_id} |
Send additional files in follow-up messages. |
POST /v3/avatars |
Provide a photo or video for avatar creation. |
POST /v3/video-translations |
Provide source video or custom audio. |
Example: Upload then generate
A complete workflow — upload a PDF, then use it to generate a video:
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
Concurrency Limits
| Plan | Max Concurrent Video Jobs |
|---|---|
| Pay-As-You-Go | 10 |
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:
Avatar Input
- Script text: Maximum 5,000 characters.
- Audio input: Maximum 10 minutes (600 seconds).
Video Agent Input
Asset Upload (POST /v3/assets)
- Maximum file size: 32 MB.
- 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.
- 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 |
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
Prompt to Video
Source: https://developers.heygen.com/docs/video-agent
Create videos from a text prompt with full control over avatar, voice, style, and file inputs.
POST https://api.heygen.com/v3/video-agents
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. Applies a curated visual template. See Styles & 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 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:
{ "type": "asset_id", "asset_id": "asset_abc123" }
{ "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 to get an asset_id — see Upload Assets.
Example request
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"]
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
{
"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}. null until rendering begins — poll GET /v3/video-agents/{session_id} 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 https://api.heygen.com/v3/video-agents/{session_id}
GET https://api.heygen.com/v3/videos/{video_id}
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)
{
"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:
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:
GET https://api.heygen.com/v3/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. |
curl "https://api.heygen.com/v3/videos?limit=5" \
-H "X-Api-Key: $HEYGEN_API_KEY"
Delete a video
Permanently remove a video:
DELETE https://api.heygen.com/v3/videos/{video_id}
curl -X DELETE "https://api.heygen.com/v3/videos/vid_xyz789" \
-H "X-Api-Key: $HEYGEN_API_KEY"
{
"data": {
"id": "vid_xyz789",
"deleted": true
}
}
Tips for better results
- 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.
- Attach reference files. Pass slides, images, or documents in the
filesarray to give the agent visual context. - Use
orientationwhen you know the target platform (e.g."portrait"for mobile/social,"landscape"for presentations). - Apply a style for consistent visual branding across videos. See Styles & References.
- Pin a specific avatar or voice with
avatar_idandvoice_idfor brand consistency, or omit them to let the agent choose.
Video Translation - Speed
Source: https://developers.heygen.com/docs/video-translate
Mode: "speed" (default) Best for: fast turnaround, batch jobs, and workflows where time matters more than perfect lip-sync.
Quick Start
1. List Supported Languages
Before translating, fetch the available target language codes:
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations/languages' \
--header 'accept: application/json' \
--header 'x-api-key: <your-api-key>'
2. Submit a Translation (Single Language)
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations' \
--header 'accept: application/json' \
--header 'x-api-key: <your-api-key>' \
--header 'Content-Type: application/json' \
--data '{
"video": {
"type": "url",
"url": "<video_url>"
},
"output_languages": ["Spanish"],
"mode": "speed",
"title": "My Translated Video"
}'
Batch (Multiple Languages)
Translate into several languages in one request:
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations' \
--header 'accept: application/json' \
--header 'x-api-key: <your-api-key>' \
--header 'Content-Type: application/json' \
--data '{
"video": {
"type": "url",
"url": "<video_url>"
},
"output_languages": ["English", "Spanish", "French"],
"mode": "speed",
"title": "Global Campaign"
}'
Response returns one ID per language:
{
"data": {
"video_translation_ids": [\
"tr_abc123-en",\
"tr_abc123-es",\
"tr_abc123-fr"\
]
}
}
3. Poll for Status
| 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": "<asset_id>" } |
The URL must be publicly accessible (test by opening in an incognito browser).
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 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:
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations/<video_translation_id>/caption?format=srt' \
--header 'accept: application/json' \
--header 'x-api-key: <your-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.
Step 1 — Create Proofread Session
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations/proofreads' \
--header 'x-api-key: <your-api-key>' \
--header 'Content-Type: application/json' \
--data '{
"video": { "type": "url", "url": "<video_url>" },
"output_languages": ["Spanish"],
"title": "Review Before Publishing",
"mode": "speed"
}'
Returns proofread_ids — one per language.
Step 2 — Poll Until completed
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations/proofreads/<proofread_id>' \
--header 'x-api-key: <your-api-key>'
Step 3 — Download & Edit the SRT
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations/proofreads/<proofread_id>/srt' \
--header 'x-api-key: <your-api-key>'
Edit the returned srt_url file locally, then upload the revised version:
curl --request PUT \
--url 'https://api.heygen.com/v3/video-translations/proofreads/<proofread_id>/srt' \
--header 'x-api-key: <your-api-key>' \
--header 'Content-Type: application/json' \
--data '{ "srt": { "type": "url", "url": "<your_edited_srt_url>" } }'
Step 4 — Generate Final Video
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations/proofreads/<proofread_id>/generate' \
--header 'x-api-key: <your-api-key>' \
--header 'Content-Type: application/json' \
--data '{ "captions": true }'
Returns a video_translation_id to poll via GET /v3/video-translations/<id>.
Other Operations
List All Translations
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations?limit=10' \
--header 'x-api-key: <your-api-key>'
Uses has_more + next_token for pagination.
Delete a Translation
curl --request DELETE \
--url 'https://api.heygen.com/v3/video-translations/<video_translation_id>' \
--header 'x-api-key: <your-api-key>'
When to Use Speed vs. Precision
| Speed | 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 |
Video Translation - Precision
Source: https://developers.heygen.com/docs/video-translation-precision
Mode: "precision" Best for: high-quality final delivery, talking-head videos, and content where accurate lip-sync is critical.
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
Before translating, fetch available target language codes:
2. Submit a Translation (Single Language)
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations' \
--header 'accept: application/json' \
--header 'x-api-key: <your-api-key>' \
--header 'Content-Type: application/json' \
--data '{
"video": {
"type": "url",
"url": "<video_url>"
},
"output_languages": ["Spanish"],
"mode": "precision",
"title": "High Quality Translation"
}'
Batch (Multiple Languages)
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations' \
--header 'accept: application/json' \
--header 'x-api-key: <your-api-key>' \
--header 'Content-Type: application/json' \
--data '{
"video": {
"type": "url",
"url": "<video_url>"
},
"output_languages": ["English", "Spanish", "French"],
"mode": "precision",
"title": "Global Campaign — High Quality"
}'
Response returns one ID per language:
{
"data": {
"video_translation_ids": [\
"tr_abc123-en",\
"tr_abc123-es",\
"tr_abc123-fr"\
]
}
}
3. Poll for Status
| 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
The URL must be publicly accessible (test by opening in an incognito browser).
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 URL notified on completion or failure |
callback_id |
— | Your own ID, echoed back in the webhook payload |
Tip: Setting
speaker_numis 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:
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.
Step 1 — Create Proofread Session
curl --request POST \
--url 'https://api.heygen.com/v3/video-translations/proofreads' \
--header 'x-api-key: <your-api-key>' \
--header 'Content-Type: application/json' \
--data '{
"video": { "type": "url", "url": "<video_url>" },
"output_languages": ["Spanish"],
"title": "Review Before Publishing",
"mode": "precision"
}'
Returns proofread_ids — one per language.
Step 2 — Poll Until completed
Step 3 — Download & Edit the SRT
Edit the returned srt_url file locally, then upload the revised version:
Step 4 — Generate Final Video
Returns a video_translation_id to poll via GET /v3/video-translations/<id>.
Other Operations
List All Translations
curl --request GET \
--url 'https://api.heygen.com/v3/video-translations?limit=10' \
--header 'x-api-key: <your-api-key>'
Uses has_more + next_token for pagination.
Delete a Translation
When to Use Speed vs. Precision
Design a Voice
Source: https://developers.heygen.com/docs/voices/design-voices
Can't find a pre-built voice that fits? Describe the voice you want and HeyGen returns up to 3 matching options. Pick the one that fits best and use its voice_id directly in video creation or text-to-speech.
Quick Example
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']}")
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}`));
{
"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. |
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:
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
}'
- 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")
- Describe the accent (
Voices
Source: https://developers.heygen.com/docs/voices/overview
Browse available voices, design custom voices with AI, and use them in your videos.
HeyGen provides 300+ pre-built voices across dozens of languages, plus the ability to generate custom AI voices from a text description. This guide walks through the full workflow: browse → design → use.
Step 1: Browse Available Voices
Use GET /v3/voices to list available voices with cursor-based pagination. Filter by language, gender, type, or engine.
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']})")
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})`)
);
{
"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 video creation endpoints. |
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". |
Step 2: Design a Custom Voice (Optional)
If none of the pre-built voices fit, use POST /v3/voices to generate up to 3 AI voice options from a text description. The endpoint returns a ranked list — pick the one that fits best.
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']}")
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. |
- 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")
- Describe the accent (
Step 3: Use a Voice in Video Creation
Once you have a voice_id, pass it when creating a video.
With Video Agent
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:
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, you can fine-tune playback via voice_settings:
{
"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
Search and list available voices with filtering and cursor-based pagination. Use a voice_id from this endpoint when creating speech or videos.
Quick Example
{
"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 or video creation endpoints. |
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
curl -X GET "https://api.heygen.com/v3/voices?language=Spanish" \
-H "X-Api-Key: $HEYGEN_API_KEY"
By Gender
curl -X GET "https://api.heygen.com/v3/voices?gender=male" \
-H "X-Api-Key: $HEYGEN_API_KEY"
Your Private (Cloned) Voices
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, filter by the starfish engine:
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.
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
If you want to generate audio from text without creating a video, HeyGen offers a dedicated TTS engine called Starfish. Pass a script and a compatible voice ID — get back an audio file URL.
Quick Example
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"])
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);
{
"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:
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 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 support_pause on the voice object from GET /v3/voices to confirm the voice supports SSML break tags.
curl -X POST "https://api.heygen.com/v3/voices/speech" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "<speak>Welcome to HeyGen. <break time=\"500ms\"/> Let us get started.</speak>",
"voice_id": "1bd001e7e50f421d891986aad5c8bbd2",
"input_type": "ssml"
}'
Webhook Events
Source: https://developers.heygen.com/docs/webhook-events
Understand the event types HeyGen can deliver and browse your event history.
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:
{
"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. |
List Delivered Events
Browse events that have been delivered to your endpoints. Filter by event type or entity ID.
{
"data": [\
{\
"event_id": "evt_abc123",\
"event_type": "avatar_video.success",\
"event_data": {\
"video_id": "vid_xyz789",\
"video_url": "https://files.heygen.com/video/vid_xyz789.mp4",\
"callback_id": "my-custom-id-123"\
},\
"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:
{
"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:
- Verify the signature using your endpoint's signing secret.
- Parse
event_typeto determine what happened. - Process
event_data— for success events this typically includes thevideo_idandvideo_url; for failures it includes error details. - Return a 2xx response promptly to acknowledge receipt.
Webhooks
Source: https://developers.heygen.com/docs/webhooks
Register and manage webhook endpoints to receive real-time notifications from HeyGen.
Instead of polling GET /v3/videos/{video_id} for status, you can register a webhook endpoint to receive a POST notification when a video completes, fails, or other events occur.
- Base path:
https://api.heygen.com/v3/webhooks/endpoints
Authentication
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.
{
"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 for the full list. |
entity_id |
string | No | Scope this endpoint to a specific resource (e.g. a personalized video project). |
List Endpoints
{
"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. |
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.
{
"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.
{
"data": {}
}
Rotate Signing Secret
Generate a new signing secret for an endpoint. The old secret is immediately invalidated. Store the new secret securely — it will not be shown again.
{
"data": {
"endpoint_id": "ep_abc123",
"secret": "whsec_n3w5ecr3t..."
}
}
Verifying Payloads
When HeyGen delivers an event, use the secret from endpoint creation (or rotation) to verify the request is authentic. Compare the signature in the incoming request headers against an HMAC-SHA256 digest of the raw payload body using your secret.
Using Callbacks Instead
If you don't need a persistent webhook endpoint, you can pass a callback_url directly when creating a video. This sends a one-off notification for that specific video without registering an endpoint:
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
Generate product videos from catalog data at scale — because product pages with video consistently outperform those without.
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
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.
"""
```
</Step>
<Step title="Batch generate with rate limiting">
```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.
</Step>
<Step title="Embed on product pages">
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"],
)
```
</Step>
</Steps>
## 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:
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
- 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
Endpoint Version Comparison
Source: https://developers.heygen.com/endpoint-version-comparison
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 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/generateandPOST /v2/video/generatewithPOST /v3/videos - Replace
GET /v1/video_status.getwithGET /v3/videos/{id} - Replace avatar and voice listing endpoints with v3 equivalents
- Replace
POST /v1/video/uploadwithPOST /v3/assets - Replace
GET /v1/user/mewithGET /v3/users/me - Migrate video translation calls to
/v3/video-translations - Update webhook handling from
callback_urlto 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 for replacements
- Test all migrated endpoints in staging before October 31, 2026
Examples
Source: https://developers.heygen.com/examples
Practical recipes for videos, TTS, avatars, translation, webhooks, and scripting.
Create a Video with the Agent
Let the AI pick the avatar, voice, and layout from a text prompt:
heygen video-agent create --prompt "A presenter explaining our product launch in 30 seconds"
{
"data": {
"session_id": "sess_abc123",
"status": "generating",
"video_id": "vid_xyz789",
"created_at": 1711288320
}
}
Block until the video is ready:
heygen video-agent create --prompt "A presenter explaining our product launch in 30 seconds" --wait
Browse available styles first, then apply one:
heygen video-agent styles list
heygen video-agent create --prompt "Product launch" --style-id <style-id> --wait
Create a Video with Full Control
Skip the agent and specify every detail yourself using -d:
heygen video create -d '{
"type": "avatar",
"avatar_id": "avt_angela_01",
"script": "Welcome to our Q4 earnings call.",
"voice_id": "1bd001e7e50f421d891986aad5e3e5d2",
"aspect_ratio": "16:9",
"resolution": "1080p"
}' --wait
{
"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:
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:
heygen video create --request-schema
Download a Video
Download the video file once it's complete:
heygen video download vid_qr8821 --output-path ./my-video.mp4
{
"asset": "video",
"message": "Downloaded video to ./my-video.mp4",
"path": "./my-video.mp4"
}
Download the captioned version (requires enable_caption at creation time):
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:
heygen voice speech create \
--text "Hello world, welcome to HeyGen." \
--voice-id 1bd001e7e50f421d891986aad5e3e5d2
{
"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:
heygen voice create --prompt "warm, confident female narrator"
{
"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:
heygen voice create --prompt "warm, confident female narrator" --seed 1
List and Filter Voices
Browse voices available for TTS:
heygen voice list --language English --gender female --limit 5
{
"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:
heygen avatar looks list --group-id avt_angela_01 --limit 5
{
"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:
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:
heygen asset create --file ./my-photo.jpg
{
"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:
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:
heygen video-translate create \
--output-languages es \
--mode precision \
--wait
For complex options (custom audio, SRT files), use -d:
cat request.json | heygen video-translate create -d - --wait
Without --wait:
{
"data": {
"video_translation_ids": ["trl_55f"]
}
}
Manage existing translations:
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:
heygen webhook endpoints create \
--url "https://example.com/webhook" \
--events "avatar_video.success,avatar_video.fail"
{
"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"
}
}
List all available event types:
heygen webhook event-types list
Update or delete an endpoint:
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
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
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
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
cat request.json | heygen video create -d - --wait
Page through all your videos
# 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)
heygen avatar list --human
Check your remaining credits
heygen user me get | jq '.data.wallet'
Features
Source: https://developers.heygen.com/features
Common flags, error handling, pagination, async polling, and CLI behaviors.
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 <duration> |
Max wait time when using --wait (e.g. 10m, 1h) |
20m |
--limit <n> |
Maximum items per page (1–100) | Endpoint-specific |
--token <cursor> |
Pagination cursor from a previous response's next_token |
None |
--force |
Skip confirmation prompts for destructive operations | Off (prompt) |
-d, --data <json|path|-> |
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:
heygen video-agent create --prompt "Welcome to our Q4 earnings call." --wait
Without --wait, you get the initial response:
{
"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:
{
"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:
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:
{"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 createheygen video-agent createheygen video-translate createheygen 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:
# 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:
heygen video create -d base.json --wait
Use --request-schema to discover the expected JSON shape for any command — no auth required:
heygen video create --request-schema
heygen lipsync create --request-schema
Pagination
List commands return paginated results. Each response includes has_more and next_token:
{
"data": [...],
"has_more": true,
"next_token": "eyJsYXN0X2lkIjoiYXZ0X21hcmN1c18wMiJ9"
}
Manual pagination
Use --token to fetch the next page:
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 -:
# Pipe a script file into video create
cat script.json | heygen video create -d -
# Here-doc
heygen video create -d - <<EOF
{
"type": "avatar",
"avatar_id": "avt_angela_01",
"script": "Welcome to our quarterly update.",
"voice_id": "1bd001e7e50f421d891986aad5e3e5d2"
}
EOF
Destructive Operations and --force
Commands that delete resources (video delete, webhook endpoints delete, video-translate delete, lipsync delete) prompt for confirmation interactively:
⚠ Delete video vid_xyz789? This cannot be undone. [y/N]
Use --force to skip the prompt — useful in scripts and CI:
heygen video delete vid_xyz789 --force
Error Handling
All errors use a consistent JSON envelope on stderr:
{
"error": {
"code": "not_found",
"message": "Video not found",
"hint": "Check ID with: heygen video list",
"request_id": "req_abc123"
}
}
code— machine-readable error typemessage— human-readable descriptionhint— suggested action to resolve the errorrequest_id— included when the error comes from the API (from theX-Request-IDheader). Omitted for local errors (bad flags, missing credentials, network failures).
Exit Codes
| Code | Meaning | When |
|---|---|---|
0 |
Success | Operation completed |
1 |
General error | API 4xx/5xx, network failure, terminal job failure |
2 |
Usage error | Bad flags, missing required args |
3 |
Auth error | 401/403, missing or invalid API key |
4 |
Timeout | Resource was created but polling timed out before completion |
Exit code 4 is distinct from 1 so agents can tell "the job exists but we don't know the final state" apart from a hard failure. Stdout will contain the last known resource state when exit 4 occurs.
Rate Limiting
429 responses are retried automatically with exponential backoff, respecting the Retry-After header. The error only surfaces if retries are exhausted. The default retry count is 2; override with the HEYGEN_MAX_RETRIES environment variable.
Configuration
Persistent settings are managed with heygen config:
heygen config set output human # default to pretty output
heygen config set analytics false # disable anonymous usage analytics
heygen config get output # read a single value
heygen config list # show all config values and their sources
Config values are stored locally at ~/.heygen/config.toml.
Config keys
Environment variable overrides
| Variable | Description |
|---|---|
HEYGEN_API_KEY |
API key (takes precedence over stored credentials) |
HEYGEN_OUTPUT |
Output format: json or human |
HEYGEN_NO_ANALYTICS |
Set to any non-empty value to disable analytics |
HEYGEN_MAX_RETRIES |
Max retry count for transient failures (default: 2) |
HEYGEN_API_BASE |
Override the API base URL (internal/test use) |
Self-Update
The CLI can update itself:
heygen update # install the latest version
heygen update --version v0.1.0 # install a specific version
The version flag requires the v prefix. Dev builds track dev prereleases; stable builds track stable releases only.
Analytics
The CLI collects anonymous usage analytics to inform product decisions. This includes command usage, error rates, CLI version, and platform. No API keys, scripts, prompts, or personally identifiable information are ever tracked.
Disable analytics at any time:
heygen config set analytics false
Or via environment variable:
export HEYGEN_NO_ANALYTICS=1
Analytics calls are non-blocking and never slow down the CLI.
Digital Twin
Source: https://developers.heygen.com/generate-avatar-video
A Digital Twin is a lifelike avatar trained from real video footage of a person. Once created, you can make it speak any script in any supported voice — no camera or studio required.
Prerequisites
Step 1 — Find your Digital Twin
List your private Digital Twin looks to get the avatar_id:
curl -X GET "https://api.heygen.com/v3/avatars/looks?avatar_type=digital_twin&ownership=private" \
-H "x-api-key: YOUR_API_KEY"
From the response, copy the id field of the look you want. This is your avatar_id.
Step 2 — Create the video
Send a POST request to /v3/videos with type: "avatar", your Digital Twin ID, a script, and a voice:
curl -X POST "https://api.heygen.com/v3/videos" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "avatar",
"avatar_id": "YOUR_DIGITAL_TWIN_LOOK_ID",
"script": "Hello! I am your Digital Twin. This video was generated entirely through the HeyGen API.",
"voice_id": "YOUR_VOICE_ID",
"title": "My First Digital Twin Video",
"resolution": "1080p",
"aspect_ratio": "16:9"
}'
Step 3 — Poll for completion
Video generation is asynchronous. Poll GET /v3/videos/{video_id} until status is completed:
curl -X GET "https://api.heygen.com/v3/videos/YOUR_VIDEO_ID" \
-H "x-api-key: YOUR_API_KEY"
Status values
| Status | Meaning |
|---|---|
pending |
Queued for processing |
processing |
Video is being generated |
completed |
Ready — video_url is available |
failed |
Something went wrong |
Once completed, the response includes a video_url with a presigned download link.
Full example
import requests
import time
API_KEY = "YOUR_API_KEY"
BASE = "https://api.heygen.com"
HEADERS = {"x-api-key": API_KEY, "Content-Type": "application/json"}
# 1. Create the video
resp = requests.post(f"{BASE}/v3/videos", headers=HEADERS, json={
"type": "avatar",
"avatar_id": "YOUR_DIGITAL_TWIN_LOOK_ID",
"script": "Welcome to our product demo. Let me walk you through the new features.",
"voice_id": "YOUR_VOICE_ID",
"resolution": "1080p",
"aspect_ratio": "16:9"
})
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 | Description |
|---|---|---|
title |
string | Display name in the HeyGen dashboard |
resolution |
string | 4k, 1080p, or 720p |
aspect_ratio |
string | 16:9 or 9:16 |
remove_background |
boolean | Removes the avatar background (twin must be trained with matting enabled) |
background |
object | Set a solid color or image background |
voice_settings |
object | Adjust speed (0.5–1.5), pitch (-50 to +50), and locale |
callback_url |
string | Webhook URL — receive a POST when the video is ready |
Using webhooks instead of polling
Instead of polling, pass a callback_url when creating the video. HeyGen will send a POST request to that URL when the video completes or fails.
{
"type": "avatar",
"avatar_id": "YOUR_DIGITAL_TWIN_LOOK_ID",
"script": "This video uses a webhook callback.",
"voice_id": "YOUR_VOICE_ID",
"callback_url": "https://your-server.com/webhooks/heygen"
}
Photo Avatar
Source: https://developers.heygen.com/image-to-video
A Photo Avatar is created from a single still image of a person. HeyGen animates the face, syncs lip movements to your script, and produces a realistic talking-head video — all from one photo.
Prerequisites
Step 1 — Create a Photo Avatar
Upload your photo and create a Photo Avatar with POST /v3/avatars:
# Upload the image
curl -X POST "https://api.heygen.com/v3/assets" \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@portrait.jpg"
# Create the avatar
curl -X POST "https://api.heygen.com/v3/avatars" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "photo",
"name": "Sarah — Marketing",
"file": {
"type": "asset_id",
"asset_id": "RETURNED_ASSET_ID"
}
}'
```
</Tab>
</Tabs>
The response includes an `avatar_item` with an `id` — this is your `avatar_id` for video creation.
## Step 2 — Generate the video
Use `POST /v3/videos` with `type: "avatar"` and the Photo 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": "avatar",
"avatar_id": "YOUR_PHOTO_AVATAR_ID",
"script": "Hi there! This video was created from a single photo using the HeyGen API.",
"voice_id": "YOUR_VOICE_ID",
"title": "Photo Avatar Demo",
"resolution": "1080p",
"aspect_ratio": "16:9"
}'
Step 3 — Poll for completion
Video generation is asynchronous. Poll until status reaches completed:
curl -X GET "https://api.heygen.com/v3/videos/YOUR_VIDEO_ID" \
-H "x-api-key: YOUR_API_KEY"
Full example
import requests
import time
API_KEY = "YOUR_API_KEY"
BASE = "https://api.heygen.com"
HEADERS = {"x-api-key": API_KEY, "Content-Type": "application/json"}
# 1. Create a Photo Avatar from a URL
avatar_resp = requests.post(f"{BASE}/v3/avatars", headers=HEADERS, json={
"type": "photo",
"name": "Demo Avatar",
"file": {
"type": "url",
"url": "https://example.com/portrait.jpg"
}
})
avatar_id = avatar_resp.json()["data"]["avatar_item"]["id"]
print(f"Avatar created: {avatar_id}")
# 2. Generate a video
video_resp = requests.post(f"{BASE}/v3/videos", headers=HEADERS, json={
"type": "avatar",
"avatar_id": avatar_id,
"script": "Welcome! This is a Photo Avatar created from a single image.",
"voice_id": "YOUR_VOICE_ID",
"resolution": "1080p",
"aspect_ratio": "16:9"
})
video_id = video_resp.json()["data"]["video_id"]
print(f"Video created: {video_id}")
# 3. 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)
Photo Avatar–specific parameters
These parameters are only available when using a Photo Avatar (avatar_type: photo_avatar):
| Parameter | Type | Description |
|---|---|---|
motion_prompt |
string | Natural-language prompt to control body motion (e.g. "nodding gently") |
expressiveness |
string | high, medium, or low (default: low) |
Example with motion and expressiveness
{
"type": "avatar",
"avatar_id": "YOUR_PHOTO_AVATAR_ID",
"script": "Let me show you our quarterly results.",
"voice_id": "YOUR_VOICE_ID",
"motion_prompt": "gesturing with hands while presenting",
"expressiveness": "high"
}
Optional parameters
| Parameter | Type | Description |
|---|---|---|
title |
string | Display name in the HeyGen dashboard |
resolution |
string | 4k, 1080p, or 720p |
aspect_ratio |
string | 16:9 or 9:16 |
remove_background |
boolean | Remove the avatar background |
background |
object | Set a solid color (type: "color") or image background |
voice_settings |
object | Adjust speed, pitch, and locale |
callback_url |
string | Webhook URL for completion notification |
Using a preset Photo Avatar
You can skip avatar creation and use a public preset avatar instead:
curl -X GET "https://api.heygen.com/v3/avatars/looks?avatar_type=photo_avatar&ownership=public" \
-H "x-api-key: YOUR_API_KEY"
Pick any id from the response and use it directly as your avatar_id in POST /v3/videos.
Image to Video
Source: https://developers.heygen.com/image-to-video-1
HeyGen can animate a person in any image directly into a lip-synced talking video. Unlike Photo Avatars, this approach requires no avatar creation step — just pass an image to the video endpoint and go. This is ideal for one-off videos, rapid prototyping, or when you don't need a reusable avatar.
Prerequisites
Step 1 — Generate the video
Use POST /v3/videos with type: "image" and an image object instead of avatar_id:
# 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"
}'
```
</Tab>
</Tabs>
<Note>
`type: "image"` and `type: "avatar"` are mutually exclusive — use exactly one.
</Note>
## 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"
Full example
import requests
import time
API_KEY = "YOUR_API_KEY"
BASE = "https://api.heygen.com"
HEADERS = {"x-api-key": API_KEY, "Content-Type": "application/json"}
# 1. 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": "16:9"
})
video_id = resp.json()["data"]["video_id"]
print(f"Video created: {video_id}")
## 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"
}
Optional parameters
| Parameter | Type | Description |
|---|---|---|
title |
string | Display name in the HeyGen dashboard |
resolution |
string | 4k, 1080p, or 720p |
aspect_ratio |
string | 16:9 or 9:16 |
remove_background |
boolean | 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 |
Interactive avatar next js demo
Source: https://developers.heygen.com/interactive-avatar-next-js-demo
Lipsync - Precision
Source: https://developers.heygen.com/lipsync-precision
Replace or dub audio on an existing video with high-accuracy avatar-inference lip-sync.
- Endpoint:
POST https://api.heygen.com/v3/lipsyncs - Purpose: Dub or replace audio on a video with high-accuracy lip-sync. The job runs asynchronously — poll status via the Get Lipsync Details endpoint.
Quick Example
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). |
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 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
{
"data": {
"lipsync_id": "ls_abc123"
}
}
| Field | Type | Description |
|---|---|---|
lipsync_id |
string | Unique identifier. Use with GET /v3/lipsyncs/{lipsync_id} to poll status. |
Get Lipsync Details
- Endpoint:
GET https://api.heygen.com/v3/lipsyncs/{lipsync_id} - Purpose: Get detailed information about a lipsync including status, download URL, and metadata.
Quick Example
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
{
"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 https://api.heygen.com/v3/lipsyncs - Purpose: List lipsyncs with cursor-based pagination.
Quick Example
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
{
"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 https://api.heygen.com/v3/lipsyncs/{lipsync_id} - Purpose: Update a lipsync's title.
Quick Example
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 https://api.heygen.com/v3/lipsyncs/{lipsync_id} - Purpose: Permanently delete a lipsync.
Quick Example
curl -X DELETE "https://api.heygen.com/v3/lipsyncs/ls_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY"
Response
{
"data": {
"id": "ls_abc123"
}
}
CLI Usage
# 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 <lipsync-id>
# List lipsyncs
heygen lipsync list --limit 10
# Update title
heygen lipsync update <lipsync-id> --title "Updated Title"
# Delete
heygen lipsync delete <lipsync-id> --force
Use --request-schema to see all available request fields without needing auth:
heygen lipsync create --request-schema
Polling Pattern
Lipsyncs are processed asynchronously. Poll until status reaches "completed" or "failed".
Status transitions: pending → running → completed | failed
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:
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:
{ "type": "url", "url": "https://example.com/file.mp4" }
By asset ID — reference a file previously uploaded via POST /v3/assets:
{ "type": "asset_id", "asset_id": "asset_xyz789" }
Lipsync - Speed
Source: https://developers.heygen.com/lipsync-speed
Replace or dub audio on an existing video with fast audio-only lip-sync.
- Endpoint:
POST https://api.heygen.com/v3/lipsyncs - Purpose: Dub or replace audio on a video. The job runs asynchronously — poll status via the Get Lipsync Details endpoint.
Quick Example
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
Response
{
"data": {
"lipsync_id": "ls_abc123"
}
}
Get Lipsync Details
Quick Example
curl -X GET "https://api.heygen.com/v3/lipsyncs/ls_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY"
Path Parameters
Response
Response Fields
List Lipsyncs
- Endpoint:
GET https://api.heygen.com/v3/lipsyncs - Purpose: List lipsyncs with cursor-based pagination.
Quick Example
curl -X GET "https://api.heygen.com/v3/lipsyncs?limit=10" \
-H "X-Api-Key: $HEYGEN_API_KEY"
Query Parameters
Response
Update Lipsync
- Endpoint:
PATCH https://api.heygen.com/v3/lipsyncs/{lipsync_id} - Purpose: Update a lipsync's title.
Quick Example
Request Body
Delete Lipsync
- Endpoint:
DELETE https://api.heygen.com/v3/lipsyncs/{lipsync_id} - Purpose: Permanently delete a lipsync.
Quick Example
curl -X DELETE "https://api.heygen.com/v3/lipsyncs/ls_abc123" \
-H "X-Api-Key: $HEYGEN_API_KEY"
Response
{
"data": {
"id": "ls_abc123"
}
}
CLI Usage
# 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 <lipsync-id>
# List lipsyncs
heygen lipsync list --limit 10
# Update title
heygen lipsync update <lipsync-id> --title "Updated Title"
# Delete
heygen lipsync delete <lipsync-id> --force
Use --request-schema to see all available request fields without needing auth:
heygen lipsync create --request-schema
Polling Pattern
Lipsyncs are processed asynchronously. Poll until status reaches "completed" or "failed".
Status transitions: pending → running → completed | failed
Or let the CLI handle polling for you:
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:
{ "type": "url", "url": "https://example.com/file.mp4" }
By asset ID — reference a file previously uploaded via POST /v3/assets:
{ "type": "asset_id", "asset_id": "asset_xyz789" }
Overview
Source: https://developers.heygen.com/mcp
Connect HeyGen video generation to any MCP-compatible AI agent — no API keys, no local server, no separate credits.
HeyGen Remote MCP lets AI agents like Manus, Claude, Gemini CLI, and Cursor create HeyGen videos on your behalf using your existing HeyGen account. It uses the Model Context Protocol (MCP) over a hosted endpoint, so there's nothing to install or run locally.
You authenticate once with OAuth, and your agent gets access to HeyGen's video tools — using the credits from your current plan.
Endpoint:
https://mcp.heygen.com/mcp/v1/
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. |
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_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. |
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. |
design_voice |
Find voices matching a natural-language description (e.g. "warm, confident female narrator"). Returns up to 3 matches. |
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. |
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)
- Gemini CLI
- Cursor
- 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:
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 here: [link]
How It Works
- Connect — Add the HeyGen remote MCP endpoint to your agent
- Authenticate — Sign in with your HeyGen account via OAuth (one-time)
- Use — Your agent calls HeyGen tools directly in conversation or code
All video generation uses your existing HeyGen plan and credits. There are no separate API charges or additional billing.
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.
Claude Web
Source: https://developers.heygen.com/mcp/claude
Generate AI avatar videos directly within Claude using HeyGen's remote MCP server
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:
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:
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. |
Claude Code
Source: https://developers.heygen.com/mcp/claude-code
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)
- 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):
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:
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:
{
"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:
claude mcp list
Or from inside Claude Code:
/mcp
You should see heygen listed with a connected status.
Usage
Once connected, prompt Claude Code with a video generation request:
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:
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
Generate AI avatar videos directly within Claude using HeyGen's remote MCP server
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:
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:
Generate a video using HeyGen MCP about the difference between Skills and MCP.
Limitations
Gemini CLI
Source: https://developers.heygen.com/mcp/gemini-cli
Connect HeyGen's Video Agent to Gemini CLI to generate AI avatar videos directly from your terminal. Once configured, Gemini can script, render, and deliver videos through natural-language prompts as part of your development 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
{
"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:
/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:
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:
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 |
Manus
Source: https://developers.heygen.com/mcp/manus
HeyGen's Video Agent is available as a native tool connection in Manus. Once connected, Manus agents can generate fully scripted and rendered AI avatar videos — including on automated schedules — without any manual editing or production workflow.
Prerequisites
- A Manus 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.
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.
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:
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:
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
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 and log into your account.
Step 2: Find the HeyGen App
Look for the Apps section in the left sidebar (or in the GPT store area, depending on your interface version). Search for HeyGen App. 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 HeyGen video generation to any MCP-compatible AI agent — no API keys, no local server, no separate credits.
You authenticate once with OAuth, and your agent gets access to HeyGen's video tools — using the credits from your current plan.
Endpoint:
https://mcp.heygen.com/mcp/v1/
What You Can Do
Once connected, your AI agent has access to the following tools:
Video Agent
Videos
Avatars
Voices
Lip Sync
Video Translation
Account
Supported Products
HeyGen Remote MCP works with any MCP-compatible agent, including:
- Claude (Web, Desktop, and Code)
- Gemini CLI
- Cursor
- 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:
https://mcp.heygen.com/mcp/v1/
To request domain whitelisting, submit your domain here: [link]
How It Works
All video generation uses your existing HeyGen plan and credits. There are no separate API charges or additional billing.
Remote MCP
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.
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 and 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.
For questions about migration or enterprise support, please reach out to HeyGen support.
Motion Graphics from a Prompt
Source: https://developers.heygen.com/motion-graphics
Generate animated title cards, product launches, and visual content — no After Effects, no React, just HTML.
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
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.
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.
<Tip>
**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.
</Tip>
</Step>
<Step title="Preview and iterate">
```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"
</Step>
<Step title="Render to MP4">
```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 |
<Tip>
Use `--quality draft` while iterating — it's significantly faster. Switch to `standard` or `high` for the final export.
</Tip>
</Step>
</Steps>
## 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 `<audio>` element with `data-start` and `data-volume`
3. **Synthesize sounds** programmatically (sorting visualizers, game SFX)
```html theme={null}
<audio id="voiceover" data-start="0" data-track-index="5"
data-volume="0.9" src="voiceover.wav"></audio>
<audio id="music" data-start="0" data-track-index="6"
data-volume="0.3" src="background.mp3"></audio>
Next Steps
Multilingual Content
Source: https://developers.heygen.com/multilingual-content
Create one video, translate it into 10+ languages with lip-sync — reach a global audience.
See It in Action
One video, generated in English, then translated into multiple languages with lip-sync — stitched together:
The Problem
Professional dubbing costs thousands of dollars per language. Most companies either skip localization entirely or settle for subtitles — missing the large audience that prefers content in their native language.
How It Works
Create source video (Video Agent) → Translate (Video Translation API) → Distribute per region
Generate your video once in one language. Then translate it into as many languages as you need — with lip-sync so the avatar's mouth matches the translated audio.
Build It
import requests
resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={
"X-Api-Key": HEYGEN_API_KEY,
"Content-Type": "application/json",
},
json={
"prompt": "Create a 60-second product overview of TaskFlow, a project management app..."
},
)
source_video_id = resp.json()["data"]["video_id"]
# ... poll until completed, get video_url
```
</Step>
<Step title="Translate into multiple languages">
Use the Video Translation API to translate into multiple languages in a single request.
```python theme={null}
target_languages = ["es", "fr", "de", "ja", "zh", "pt-BR", "ko", "ar", "hi"]
resp = requests.post(
"https://api.heygen.com/v2/video_translate",
headers={
"X-Api-Key": HEYGEN_API_KEY,
"Content-Type": "application/json",
},
json={
"video_url": source_video_url,
"output_languages": target_languages,
# "translate_mode": "precision" # For premium lip-sync quality
},
)
translation_id = resp.json()["data"]["video_translate_id"]
```
See [Video Translation docs](/docs/video-translate) for all supported languages and options.
</Step>
<Step title="Choose your quality mode">
Two modes with different cost/quality tradeoffs:
| Mode | Cost | Lip-sync quality | Best for |
| ------------------- | ------------- | ---------------- | -------------------------------- |
| **Speed** (default) | \$0.05/second | Good | Most content, fast turnaround |
| **Precision** | \$0.10/second | Premium | Face-heavy videos, brand content |
For a 60-second video translated into 9 languages:
* Speed mode: 60s × 9 × $0.05 = **$27\*\*
* Precision mode: 60s × 9 × $0.10 = **$54\*\*
See [Pricing](/docs/pricing) for current rates.
</Step>
<Step title="Generate captions">
Get subtitle files for each translated version.
```python theme={null}
# After translation is complete
resp = requests.get(
f"https://api.heygen.com/v2/video_translate/{translation_id}/caption",
headers={"X-Api-Key": HEYGEN_API_KEY},
params={"format": "srt"}, # or "vtt"
)
captions = resp.json()
```
<Info>
Caption URLs expire after 7 days but are regenerated each time you request them.
</Info>
</Step>
<Step title="Distribute per region">
Now you have the source video + 9 translated versions + captions for each. Distribute based on your audience's region.
```python theme={null}
# Example: organize outputs by language
for lang in target_languages:
print(f"{lang}: video_url=..., caption_url=...")
# Upload to regional CDN, post to regional social accounts, etc.
```
</Step>
</Steps>
## Real-World Results
These results are reported by companies via [HeyGen's customer stories](https://www.heygen.com/customer-stories):
| Company | What they did | Reported result |
| -------------- | ----------------------------------------------- | ------------------------------------------- |
| **Trivago** | Localized TV ads across 30 markets | Halved post-production time |
| **Würth** | Translated employee training | Reported 80% reduction in translation costs |
| **McDonald's** | "Grandma McFlurry" campaign translated globally | Used HeyGen for multi-language distribution |
## Workflow: Translate Everything You Generate
Once you have a translation pipeline, apply it to every video you create:
Social Media Pipeline → Generate 5 English videos → Translate each to 9 languages = 45 videos Training Pipeline → Generate 10 module videos → Translate to 7 languages = 70 training videos Product Demo → Generate 1 demo → Translate to 5 key markets = 5 localized demos
The marginal cost per additional language is minimal compared to the reach it unlocks.
## Variations
* **Audio-only translation:** Keep the original video visuals, just change the voiceover — useful when lip-sync isn't critical
* **Subtitle-only:** Generate captions without re-rendering the video — cheapest option for supplementary languages
* **Regional adaptation:** Use different Video Agent prompts per region (not just translation but cultural adaptation) for high-priority markets
***
## Next Steps
<CardGroup>
<Card title="Training & Onboarding" icon="book-open" href="/cookbook/video-agent/training-and-onboarding">
Generate the training content that feeds into this pipeline.
</Card>
<Card title="Social Media Pipeline" icon="share-nodes" href="/cookbook/video-agent/social-media-pipeline">
Multiply your social content across languages.
</Card>
</CardGroup>
# Output Modes
Source: https://developers.heygen.com/output-modes
How the CLI formats output for agents, scripts, and humans.
The CLI is **agent-first**: the default output is structured JSON — no spinners, no color codes, no decorations. Just parseable data on stdout. Progress, warnings, and errors go to stderr so piping always works cleanly.
Humans can opt into a prettier experience with `--human`.
## Default: JSON (Agent-Friendly)
Every command outputs clean JSON by default. The response follows the HeyGen API envelope shape — a top-level `data` field wraps the payload:
```bash theme={null}
heygen avatar list
{
"data": [\
{\
"id": "avt_angela_01",\
"name": "Angela",\
"gender": "female",\
"looks_count": 3,\
"preview_image_url": "https://files.heygen.com/avatar/avt_angela_01.jpg",\
"default_voice_id": "1bd001e7e50f421d891986aad5e3e5d2",\
"created_at": 1709856000\
}\
],
"has_more": true,
"next_token": "eyJsYXN0X2lkIjoiYXZ0X21hcmN1c18wMiJ9"
}
--human: Pretty Output for Humans
Add --human when you're working interactively. You get tables, colorized status values, and human-readable timestamps:
heygen video list --human
ID Title Status Created
4621f8ba1a8f4811b32f669c37be53a2 HeyGen in 20 Seconds completed 2026-03-28 15:48
75c58ba041394ddcb3737d7eff9d514b Video Agent Weekly Recap completed 2026-03-25 22:18
Showing 4 of 5 columns. Remove --human for full JSON output.
For get commands (single resource), --human renders a key-value layout:
heygen video get abc123 --human
Id: abc123
Status: completed
Title: Demo
When --wait is used in human mode, the CLI shows a live spinner on stderr while polling:
heygen video-agent create --prompt "Product demo" --wait --human
· Processing... (42s)
On a non-TTY stderr (e.g. in CI with --human), the spinner falls back to plain-text status lines:
Polling: status=processing (elapsed 10s)
Polling: status=processing (elapsed 22s)
Errors
Errors always go to stderr as a structured JSON envelope, regardless of output mode:
{
"error": {
"code": "not_found",
"message": "Video abc123 not found",
"hint": "Check ID with: heygen video list",
"request_id": "req_abc123"
}
}
In --human mode, errors render as readable text instead:
Error: Video abc123 not found
Hint: Check ID with: heygen video list
The request_id field is included when the error comes from the API (from the X-Request-ID response header). It is omitted for local errors such as bad flags or missing credentials.
Exit Codes
| Code | Meaning |
|---|---|
0 |
Success |
1 |
General error (API error, network failure) |
2 |
Usage error (invalid flags, missing arguments) |
3 |
Authentication error (missing or invalid API key) |
4 |
Timeout (resource created but polling timed out) |
Exit code 4 is distinct from 1 so agents and scripts can tell "the resource was created but we don't know the final status" apart from a hard failure. Stdout will contain the last known resource state when exit 4 occurs.
Configuring a Default Output Mode
Set a persistent default so you don't need --human on every command:
heygen config set output human
Valid values are json (default) and human. The priority order is:
--human flag → HEYGEN_OUTPUT env var → config set output → default (json)
# Always human by default, but get JSON for this one invocation
heygen config set output human
heygen video get vid_xyz789 --output json
Stdout vs Stderr
The CLI strictly separates data from everything else:
- stdout — JSON payload only. This is what gets piped to
jq, captured in shell variables, or consumed by agents. - stderr — progress indicators, warnings, and error messages.
This means piping works cleanly with no extra flags:
# Extract all video IDs from your account
heygen video list | jq -r '.data[].id'
# Create a video and immediately open the URL in your browser
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
Overview
Source: https://developers.heygen.com/overview
The API docs show you how to call the endpoints. The cookbook shows you what to build.
CLI Recipes
Get up and running fast with these step-by-step CLI workflows.
Video Agent Workflows
Hyperframes Workflows
Foundations
Personalized Greetings & Recognition
Source: https://developers.heygen.com/personalized-greetings-and-recognition
Generate personalized birthday, welcome, and recognition videos at scale — make every person feel valued.
The Problem
A personalized video message makes someone's day. But recording individual videos for every employee birthday, customer milestone, or team celebration doesn't scale past a handful of people.
How It Works
Recipient data (name, occasion, details) → Personalized prompt → Video Agent renders → Deliver
Define a template that feels personal, fill in recipient-specific details, and generate a unique video for each person.
Build It
The presenter should be warm and genuine: "Happy birthday, {name}! From everyone at {company}, we hope your day is amazing. {personal_note} Here's to a great year ahead!"
Tone: Celebratory, warm, genuine — like a friend, not a corporate card. Background: Festive but tasteful. Include subtle confetti or balloons. """, }, "work_anniversary": { "prompt": """Create a 25-second work anniversary video for {name}.
"Congratulations {name} on {years} years at {company}! {achievement_note} Thank you for everything you bring to the team. Here's to many more!"
Tone: Appreciative and sincere. Professional but warm. """, }, "welcome": { "prompt": """Create a 20-second welcome video for {name} joining {team}.
"Welcome to {company}, {name}! We're so excited to have you on the {team} team. {welcome_note} Can't wait to work with you!"
Tone: Enthusiastic, welcoming, energetic. """, }, "customer_milestone": { "prompt": """Create a 20-second milestone video for {name} at {company}.
"Hey {name}, we just wanted to say thank you. {milestone_detail} We really appreciate your trust in us. Here's to what's next!"
Tone: Grateful, personal, not salesy. """, }, } ```
recipients = [
{
"name": "Sarah Chen",
"occasion": "birthday",
"company": "Acme Corp",
"personal_note": "We heard you're celebrating with a trip to Japan — enjoy every moment!",
},
{
"name": "Marcus Johnson",
"occasion": "work_anniversary",
"company": "Acme Corp",
"years": "5",
"achievement_note": "From leading the product launch to mentoring three new engineers — your impact has been incredible.",
},
{
"name": "Priya Patel",
"occasion": "welcome",
"company": "Acme Corp",
"team": "Engineering",
"welcome_note": "The team has been looking forward to having a Kubernetes expert on board.",
},
]
jobs = [] for r in recipients: template = TEMPLATES[r["occasion"]] prompt = template["prompt"].format(**r)
resp = requests.post( "https://api.heygen.com/v3/video-agents", headers={ "X-Api-Key": HEYGEN_API_KEY, "Content-Type": "application/json", }, json={"prompt": prompt}, ) jobs.append({ "recipient": r, "video_id": resp.json()["data"]["video_id"], }) time.sleep(5) ```
Then poll for completion and deliver via email, Slack, or your HR platform.
What Makes It Feel Personal
The difference between a greeting that lands and one that feels generic:
| Generic (don't do this) | Personal (do this) |
|---|---|
| "Happy birthday from the team!" | "Happy birthday, Sarah! We heard you're heading to Japan — enjoy!" |
| "Congrats on your anniversary" | "5 years, Marcus — from leading the product launch to mentoring three engineers" |
| "Welcome aboard" | "Welcome Priya! The team's been looking forward to having a K8s expert" |
The specific detail is what makes it feel like someone actually thought about this person.
Automation Ideas
| Trigger | Source | Delivery |
|---|---|---|
| Employee birthday | HRIS/HR platform | Email + Slack channel |
| Work anniversary | HRIS with start dates | Email + manager notification |
| New hire start date | Onboarding system | Email on day 1 |
| Customer renewal | CRM | Email from account manager |
| Deal closed | CRM | Slack celebration + email to rep |
| Usage milestone | Product analytics | In-app or email |
Variations
- Manager from-the-desk: Use a specific avatar that represents the CEO or team lead for extra impact
- Team compilations: Generate individual short clips from each team member, then concatenate into a group video
- Holiday greetings: Batch-generate for all clients or employees with holiday-themed prompts
- Multi-language: Use Video Translation for global teams
Next Steps
Personalized Sales Outreach
Source: https://developers.heygen.com/personalized-sales-outreach
Generate personalized video messages for prospects at scale — from CRM data to inbox.
The Problem
Generic sales emails have notoriously low response rates. Personalized video consistently outperforms text — but recording a custom video for each prospect doesn't scale past a handful per day.
How It Works
CRM/prospect data → Personalized prompt per contact → Batch generate → Deliver via email/LinkedIn
You define a prompt template with variables (name, company, pain point). For each prospect, the template fills in their details and Video Agent generates a personalized video.
Build It
prospects = [\
{\
"name": "Sarah Chen",\
"company": "Acme Corp",\
"role": "VP of Marketing",\
"pain_point": "spending 40+ hours/month on video content creation",\
"value_prop": "cut video production time by 90% with AI-generated content",\
},\
{\
"name": "Marcus Johnson",\
"company": "TechStart Inc",\
"role": "Head of Sales",\
"pain_point": "low response rates on cold outreach",\
"value_prop": "personalized video messages that get 10x more replies",\
},\
# ...\
]
```
</Step>
<Step title="Build a prompt template">
The template should produce a video that feels personally recorded — not like a mail merge.
```python theme={null}
def build_outreach_prompt(prospect):
return f"""Create a 30-second personalized sales video.
The presenter should speak directly to the viewer as if recording
a quick personal message. Warm, genuine, not salesy.
Script direction:
- Open: "Hi {prospect['name']}, I was looking at what {prospect['company']}
is doing and had a quick thought for you."
- Problem: Briefly mention that many {prospect['role']}s are
{prospect['pain_point']}.
- Solution: Share that there's a way to {prospect['value_prop']}.
- CTA: "Would love to show you how this works — mind if I send
over a 5-minute demo? Just reply to this email."
Tone: Conversational and genuine. This should feel like a real person
who took 30 seconds to record a message, not a polished ad.
Keep it under 35 seconds. Landscape orientation.
"""
```
<Tip>
**The key to personalized video:** The prompt should include specific details about the prospect (company name, role, pain point) but the delivery should feel natural and unrehearsed. Avoid making it sound like a template — that defeats the purpose.
</Tip>
</Step>
<Step title="Generate videos in batch">
Submit all videos with rate limit spacing.
```python theme={null}
import requests
import time
HEYGEN_API_KEY = "your-api-key"
jobs = []
for prospect in prospects:
prompt = build_outreach_prompt(prospect)
resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={
"X-Api-Key": HEYGEN_API_KEY,
"Content-Type": "application/json",
},
json={"prompt": prompt},
)
data = resp.json()["data"]
jobs.append({
"prospect": prospect,
"video_id": data["video_id"],
})
print(f"Submitted for {prospect['name']}: {data['video_id']}")
time.sleep(5)
```
</Step>
<Step title="Collect results and deliver">
Once all videos are rendered, pair each video URL with the prospect for delivery.
```python theme={null}
import time
for job in jobs:
while True:
resp = requests.get(
f"https://api.heygen.com/v3/videos/{job['video_id']}",
headers={"X-Api-Key": HEYGEN_API_KEY},
).json()["data"]
if resp["status"] == "completed":
job["video_url"] = resp["video_url"]
job["thumbnail_url"] = resp["thumbnail_url"]
print(f"Ready for {job['prospect']['name']}: {resp['video_url']}")
break
elif resp["status"] == "failed":
job["video_url"] = None
print(f"Failed for {job['prospect']['name']}")
break
time.sleep(10)
# Now you have a list of prospects with their personalized video URLs
# Feed this into your email/LinkedIn outreach tool
```
</Step>
</Steps>
## Delivery Strategies
| Channel | How to embed | Tips |
| ------------------ | ------------------------------------------------------------------- | -------------------------------------------------------------- |
| **Email** | Use the `thumbnail_url` as a clickable image linking to `video_url` | GIF thumbnails get higher click rates than static images |
| **LinkedIn** | Upload the video directly or share the link in a message | Video messages tend to get significantly higher response rates |
| **Landing page** | Embed the video on a personalized page per prospect | Combine with personalized page content for full experience |
| **Sales platform** | Most platforms (Outreach, Salesloft, HubSpot) support video embeds | Check your platform's video integration docs |
## Brand Consistency
When generating videos for many prospects, you want every video to feel on-brand:
* **Same avatar:** Pass a specific `avatar_id` to ensure the same "salesperson" appears in every video. See [Avatars](/docs/avatars) to browse options.
* **Same voice:** Pass a specific `voice_id` for consistent voice. See [Voices](/docs/voices).
* **Same style instructions:** Include brand colors, background, and tone in every prompt.
```python theme={null}
def build_outreach_prompt(prospect):
return f"""Create a 30-second personalized sales video.
Avatar: Use avatar look_id "josh_lite3_20230714".
Background: Clean, modern office with warm lighting.
Brand: Professional but approachable. No flashy graphics.
...rest of the prompt...
"""
Variations
- Follow-up sequences: Generate a series of videos per prospect — intro, value prop, case study, final nudge
- Event-triggered: Generate a welcome video when a prospect signs up for a trial or downloads a resource
- Account-based marketing: Generate company-specific videos that reference the prospect's recent news or achievements
Next Steps
Photo to Video
Source: https://developers.heygen.com/photo-to-video
Turn a single headshot into a talking avatar video — upload, create, voice, render. All from the CLI.
Prerequisites
- HeyGen CLI installed and authenticated (
heygen auth login) - A headshot photo (PNG or JPEG, max 32 MB). Front-facing, good lighting, and a neutral expression works best.
Steps
heygen asset create --file ./headshot.jpg
```
```json theme={null}
{
"data": {
"asset_id": "ast_abc123"
}
}
```
</Step>
<Step title="Create a Photo Avatar">
Pass the asset to `avatar create`. This trains a Photo Avatar from your image:
```bash theme={null}
heygen avatar create -d '{
"files": [{"type": "asset_id", "asset_id": "ast_abc123"}]
}'
```
```json theme={null}
{
"data": {
"avatar_id": "avt_xyz789",
"avatar_group_id": "grp_def456",
"status": "processing"
}
}
```
Avatar training takes a few minutes. Poll the status:
```bash theme={null}
heygen avatar looks get avt_xyz789
```
Wait until `status` is `completed` before proceeding.
<Note>
Use `--request-schema` on any command to discover all available fields: `heygen avatar create --request-schema`
</Note>
</Step>
<Step title="Pick a voice">
Browse available voices:
```bash theme={null}
heygen voice list --language English --gender female --limit 5
```
```json theme={null}
{
"data": [\
{\
"voice_id": "1bd001e7e50f421d891986aad5e3e5d2",\
"name": "Sara",\
"gender": "female",\
"language": "English"\
}\
]
}
```
Copy the `voice_id` you want. If none of the stock voices fit, see the **Design a Custom Voice** recipe.
</Step>
<Step title="Generate the video">
```bash theme={null}
heygen video create -d '{
"type": "avatar",
"avatar_id": "avt_xyz789",
"script": "Hi there! I was created from a single photo using the HeyGen CLI.",
"voice_id": "1bd001e7e50f421d891986aad5e3e5d2",
"aspect_ratio": "16:9"
}'
```
Add `--wait` to block until the video is ready, or poll manually with `heygen video get <video_id>`.
</Step>
<Step title="Download">
```bash theme={null}
heygen video download vid_qrs321 --output-path ./my-avatar-video.mp4
```
</Step>
</Steps>
## Full shell script
Chain everything together in one script:
```bash theme={null}
#!/bin/bash
set -e
# 1. Upload
ASSET_ID=$(heygen asset create --file ./headshot.jpg | jq -r '.data.asset_id')
echo "Uploaded: $ASSET_ID"
# 2. Create avatar
AVATAR_ID=$(heygen avatar create -d "{\"files\": [{\"type\": \"asset_id\", \"asset_id\": \"$ASSET_ID\"}]}" \
| jq -r '.data.avatar_id')
echo "Avatar: $AVATAR_ID (training...)"
# 3. Wait for avatar training
while true; do
STATUS=$(heygen avatar looks get "$AVATAR_ID" | jq -r '.data.status')
[ "$STATUS" = "completed" ] && break
[ "$STATUS" = "failed" ] && echo "Avatar training failed" && exit 1
sleep 10
done
echo "Avatar ready"
# 4. Create video and wait
VIDEO=$(heygen video create --wait -d "{
\"type\": \"avatar\",
\"avatar_id\": \"$AVATAR_ID\",
\"script\": \"Hello! This video was generated from a single photo.\",
\"voice_id\": \"1bd001e7e50f421d891986aad5e3e5d2\"
}")
VIDEO_ID=$(echo "$VIDEO" | jq -r '.data.id')
echo "Video ready: $VIDEO_ID"
# 5. Download
heygen video download "$VIDEO_ID" --output-path ./result.mp4
echo "Done: ./result.mp4"
Optional parameters
| Parameter | Description |
|---|---|
aspect_ratio |
16:9 (landscape) or 9:16 (portrait) |
background |
Set a solid color or image background |
callback_url |
Webhook URL — skip polling and get notified when the video is ready |
Product Demo Videos
Source: https://developers.heygen.com/product-demo-videos
Generate product demos from screenshots and specs — and regenerate when your product changes.
The Problem
Product demos require screen recording, narration, and editing. They go stale with every UI update. Most teams have a backlog of features that should have demo videos but don't, because production can't keep up.
How It Works
Screenshots + feature specs → Video Agent prompt → Narrated walkthrough → Update by regenerating
Attach your product screenshots as file inputs. Video Agent creates a narrated walkthrough with an avatar presenter. When your UI changes, take new screenshots and re-run.
Build It
features = [\
{\
"name": "Dashboard Overview",\
"screenshot": "https://your-cdn.com/screenshots/dashboard.png",\
"description": "Main dashboard showing key metrics, recent activity, and quick actions",\
},\
{\
"name": "Kanban Board",\
"screenshot": "https://your-cdn.com/screenshots/kanban.png",\
"description": "Drag-and-drop task management with customizable columns and filters",\
},\
{\
"name": "Analytics",\
"screenshot": "https://your-cdn.com/screenshots/analytics.png",\
"description": "Team performance metrics with trend charts and export options",\
},\
]
```
</Step>
<Step title="Build a feature-by-feature prompt">
Structure the prompt around your features, not around a generic "product overview." Each feature gets its own segment.
```python theme={null}
def build_demo_prompt(product_name, features, duration="60 seconds", audience="product managers"):
feature_list = "\n".join(
f"- {f['name']}: {f['description']}"
for f in features
)
return f"""Create a {duration} product demo video for {product_name}.
Target audience: {audience}
Walk through these features using the attached screenshots as visual reference:
{feature_list}
Structure:
- Hook (5s): "{product_name} helps you [key value prop] — let me show you how."
- Feature walkthrough (80% of duration): Cover each feature with the presenter
pointing out the key elements visible in the screenshots. Explain the benefit,
not just what it does.
- CTA (5s): "Start your free trial at [url]"
Tone: Knowledgeable but approachable — like a product manager giving a live demo
to a colleague. Not a sales pitch.
IMPORTANT: Reference the attached screenshots as visual context. The viewer
should see the product interface while the presenter explains each feature.
"""
prompt = build_demo_prompt("TaskFlow", features)
```
</Step>
<Step title="Submit with screenshots as file inputs">
Attach your screenshots so Video Agent can use them as visual context.
```python theme={null}
import requests
files = [{"type": "url", "url": f["screenshot"]} for f in features]
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_id = resp.json()["data"]["video_id"]
```
See [Video Agent → File Input Formats](/docs/video-agent#file-input-formats) for all supported file types and upload methods.
</Step>
<Step title="Poll and download">
Wait for rendering, then download. See [Video Agent docs](/docs/video-agent) for the polling pattern.
</Step>
</Steps>
## Demo Styles
Generate different versions for different audiences:
| Style | Duration | Audience | Prompt focus |
| ----------------------- | -------- | ----------------------- | ------------------------------------------------- |
| **Quick overview** | 30s | Social media, ads | One key value prop, fast-paced, visual-heavy |
| **Feature walkthrough** | 60–90s | Prospects, landing page | Feature-by-feature with benefits |
| **Deep dive** | 2–3min | Evaluators, tech buyers | Detailed functionality, integrations, edge cases |
| **What's new** | 30–45s | Existing users | Just the new/changed features from latest release |
## Staying Current
The key advantage: **when your product changes, re-run the pipeline with new screenshots.**
```python theme={null}
# Automate: take screenshots programmatically with Playwright
from playwright.sync_api import sync_playwright
def capture_screenshots(urls, output_dir="screenshots"):
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1280, "height": 720})
for name, url in urls.items():
page.goto(url)
page.screenshot(path=f"{output_dir}/{name}.png")
browser.close()
# Run this before each demo generation to always use current UI
capture_screenshots({
"dashboard": "https://app.taskflow.com/dashboard",
"kanban": "https://app.taskflow.com/board",
"analytics": "https://app.taskflow.com/analytics",
})
Variations
- Comparison videos: "TaskFlow vs. Competitor" — show side-by-side screenshots
- Customer-specific demos: Customize the prompt with the prospect's industry and pain points for personalized demos at scale (see Personalized Outreach)
- Interactive follow-up: After the pre-recorded demo, offer a Live Avatar interactive demo for Q&A
Next Steps
Real Estate Listing Videos
Source: https://developers.heygen.com/real-estate-listing-videos
Turn property photos and listing data into narrated tour videos — for a fraction of traditional production costs.
The Problem
Listings with video consistently get more inquiries than those without. But professional property tour videos cost thousands of dollars each — making them viable only for luxury properties. Most agents have great photos but no video.
How It Works
Property photos + listing data → Video Agent prompt → Narrated property tour → Post to listing sites
Attach your property photos as file inputs and describe the property in your prompt. Video Agent creates a narrated walkthrough with an avatar presenting the property highlights.
Build It
return f"""Create a 45-second property tour video for a real estate listing.
Property: {listing['address']} Price: {listing['price']} | {listing['bedrooms']} bed / {listing['bathrooms']} bath | {listing['sqft']} sq ft
Tour structure using the attached property photos: - Opening (5s): Presenter stands in front of the home. "Welcome to {listing['address']} — let me show you why this {listing['bedrooms']}-bedroom home is something special." - Kitchen & living (15s): Walk through the main living areas, highlighting: {highlights[0] if len(listing['highlights']) > 0 else ''} - Primary suite (10s): Showcase the bedroom and bathroom - Outdoor space (10s): Show the backyard and patio - Closing (5s): "{listing['price']}. Schedule your private showing today."
Tone: Warm, professional, inviting — like a top-producing agent who genuinely loves this home. Not salesy. Neighborhood note: {listing['neighborhood']} """
prompt = build_listing_prompt(listing) ```
files = [{"type": "url", "url": url} for url in listing["photos"]]
Then poll for completion — see Video Agent docs.
import time
listings = load_from_mls() # Your listing data source
for listing in listings:
prompt = build_listing_prompt(listing)
files = [{"type": "url", "url": url} for url in listing["photos"]]
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},
)
listing["video_id"] = resp.json()["data"]["video_id"]
time.sleep(5) # Rate limit spacing
```
</Step>
</Steps>
## Video Styles by Property Type
| Property type | Duration | Style | Focus |
| -------------------- | -------- | ------------------------- | -------------------------------------- |
| **Starter home** | 30s | Friendly, energetic | Value, neighborhood, schools |
| **Luxury** | 60–90s | Elegant, cinematic | Design details, materials, views |
| **Investment** | 30s | Numbers-driven | ROI, rental income, location |
| **Commercial** | 45s | Professional | Square footage, traffic, zoning |
| **New construction** | 45s | Exciting, forward-looking | Customization options, builder quality |
## Cost Comparison
| Approach | Cost per video | Time | Scalability |
| --------------------------- | --------------- | ----------- | ----------- |
| Professional videographer | \$1,000+ | Days–weeks | Low |
| DIY with phone + editing | \$0 (your time) | 2–4 hours | Very low |
| **Video Agent from photos** | **\~\$2–5** | **Minutes** | **High** |
## Variations
* **Neighborhood spotlight:** Generate a separate video about the area — schools, dining, parks, commute times
* **Open house invite:** Short 15-second teaser: "Open house this Saturday at \[address]. Here's a sneak peek."
* **Multi-language:** Translate for international buyers using [Video Translation](/cookbook/video-agent/multilingual-content)
* **Agent branding:** Use the same avatar and style across all listings for consistent personal brand
***
## Next Steps
<CardGroup>
<Card title="Product Demos" icon="desktop" href="/cookbook/video-agent/product-demos">
Same screenshot-to-video pattern, applied to software.
</Card>
<Card title="E-commerce Product Videos" icon="cart-shopping" href="/cookbook/video-agent/ecommerce-product-videos">
Same catalog-to-video pattern, applied to products.
</Card>
</CardGroup>
# Clone a Voice
Source: https://developers.heygen.com/reference/clone-a-voice
/openapi/external-api.json post /v3/voices/clone
Creates a voice clone from an audio file. Returns a voice_clone_id that can be polled via GET /v3/voices/{voice_clone_id} until the status is 'complete'. The resulting voice can be used with POST /v3/voices/speech and POST /v3/videos.
# Create Avatar
Source: https://developers.heygen.com/reference/create-avatar
/openapi/external-api.json post /v3/avatars
Creates a new avatar from an image, video footage, or a text prompt. Supports photo, digital_twin, and prompt types. Avatar training is asynchronous.
# Create Avatar Consent
Source: https://developers.heygen.com/reference/create-avatar-consent
/openapi/external-api.json post /v3/avatars/{group_id}/consent
Initiates the consent flow for an avatar group and returns a URL for the user to complete approval in their browser. Required before a private avatar can be used for video generation.
# Create Lipsync
Source: https://developers.heygen.com/reference/create-lipsync
/openapi/external-api.json post /v3/lipsyncs
Replaces the audio on an existing video and re-animates the speaker's lip movements to match the new audio. Use mode: 'speed' for fast output or 'precision' for high-quality lip-sync.
# Create Proofread Session
Source: https://developers.heygen.com/reference/create-proofread-session
/openapi/external-api.json post /v3/video-translations/proofreads
Creates a proofread session that extracts editable subtitles from a video before final rendering.
# Create Video
Source: https://developers.heygen.com/reference/create-video
/openapi/external-api.json post /v3/videos
Creates a video from a HeyGen avatar or an arbitrary image. Supports scripts or pre-recorded audio for lip-sync. Supports the Avatar IV engine and the upcoming Avatar V, while Avatar III video generation requires the legacy API (v1 or v2).
# Create Video Agent Session
Source: https://developers.heygen.com/reference/create-video-agent-session
/openapi/external-api.json post /v3/video-agents
One-shot video generation from a prompt — agent handles scripting, avatar selection, scene composition, and rendering. Supports generate (fire-and-forget) and chat (multi-turn) modes.
# Create Video Translation
Source: https://developers.heygen.com/reference/create-video-translation
/openapi/external-api.json post /v3/video-translations
Translates a video into one or more target languages with voice cloning and lip-sync. Returns one video_translation_id per language. Use mode: 'speed' (default) for fast turnaround or 'precision' for higher lip-sync quality.
# Create Webhook Endpoint
Source: https://developers.heygen.com/reference/create-webhook-endpoint
/openapi/external-api.json post /v3/webhooks/endpoints
Registers an HTTPS URL to receive webhook event notifications. Returns the endpoint details and a signing secret. The signing secret is only shown at creation and rotation — store it securely.
# Delete Lipsync
Source: https://developers.heygen.com/reference/delete-lipsync
/openapi/external-api.json delete /v3/lipsyncs/{lipsync_id}
Permanently deletes a lipsync job and its associated files. This action cannot be undone.
# Delete Video
Source: https://developers.heygen.com/reference/delete-video
/openapi/external-api.json delete /v3/videos/{video_id}
Permanently deletes a video and its associated files. This action cannot be undone.
# Delete Video Translation
Source: https://developers.heygen.com/reference/delete-video-translation
/openapi/external-api.json delete /v3/video-translations/{video_translation_id}
Permanently deletes a video translation and its associated files. This action cannot be undone.
# Delete Webhook Endpoint
Source: https://developers.heygen.com/reference/delete-webhook-endpoint
/openapi/external-api.json delete /v3/webhooks/endpoints/{endpoint_id}
Permanently removes a webhook endpoint. Events will no longer be delivered to this URL. This action cannot be undone.
# Design a Voice
Source: https://developers.heygen.com/reference/design-a-voice
/openapi/external-api.json post /v3/voices
Returns up to 3 voices matching a natural language description (e.g. 'warm, confident female narrator'). Use the seed parameter to get different batches of results.
# Download Proofread SRT
Source: https://developers.heygen.com/reference/download-proofread-srt
/openapi/external-api.json get /v3/video-translations/proofreads/{proofread_id}/srt
Returns presigned download URLs for the edited and original SRT files of a completed proofread session.
# Generate Speech
Source: https://developers.heygen.com/reference/generate-speech
/openapi/external-api.json post /v3/voices/speech
Synthesize speech audio from text using a specified voice. The voice must support the starfish engine — use GET /v3/voices?engine=starfish to find compatible voices. Supports plain text and SSML. Speed range: 0.5–2.0x. Returns a URL to the generated audio file along with duration and optional word-level timestamps.
# Generate Video from Proofread
Source: https://developers.heygen.com/reference/generate-video-from-proofread
/openapi/external-api.json post /v3/video-translations/proofreads/{proofread_id}/generate
Starts final video generation using the approved subtitles from a proofread session.
# Get Avatar Group
Source: https://developers.heygen.com/reference/get-avatar-group
/openapi/external-api.json get /v3/avatars/{group_id}
Returns details for a specific avatar group including name, gender, preview URLs, looks count, and training status.
# Get Avatar Look
Source: https://developers.heygen.com/reference/get-avatar-look
/openapi/external-api.json get /v3/avatars/looks/{look_id}
Returns details for a specific avatar look including supported engines, preferred orientation, preview URLs, and training status.
# Get Current User
Source: https://developers.heygen.com/reference/get-current-user
/openapi/external-api.json get /v3/users/me
Returns the authenticated user's profile, remaining credits or balance, and billing details.
# Get Lipsync
Source: https://developers.heygen.com/reference/get-lipsync
/openapi/external-api.json get /v3/lipsyncs/{lipsync_id}
Returns details for a lipsync job including status, video_url, caption_url, and failure info if applicable.
# Get Proofread Session
Source: https://developers.heygen.com/reference/get-proofread-session
/openapi/external-api.json get /v3/video-translations/proofreads/{proofread_id}
Returns the status and details of a proofread session.
# Get Session Resource
Source: https://developers.heygen.com/reference/get-session-resource
/openapi/external-api.json get /v3/video-agents/{session_id}/resources/{resource_id}
Returns a single session resource (image, video, draft, avatar, voice, etc.) by its resource_id.
# Get Video
Source: https://developers.heygen.com/reference/get-video
/openapi/external-api.json get /v3/videos/{video_id}
Returns details for a video including status, video_url, thumbnail_url, duration, and failure info if applicable.
# Get Video Agent Session
Source: https://developers.heygen.com/reference/get-video-agent-session
/openapi/external-api.json get /v3/video-agents/{session_id}
Returns the current status, progress, video_id, and recent chat messages for a session.
# Get Video Translation
Source: https://developers.heygen.com/reference/get-video-translation
/openapi/external-api.json get /v3/video-translations/{video_translation_id}
Returns details for a translation job including status, output language, video_url, and failure info if applicable.
# Get Voice
Source: https://developers.heygen.com/reference/get-voice
/openapi/external-api.json get /v3/voices/{voice_id}
Returns details for a specific voice, including clone workflow status when available. Use this to poll a voice clone until its status is 'complete'.
# List Avatar Groups
Source: https://developers.heygen.com/reference/list-avatar-groups
/openapi/external-api.json get /v3/avatars
Returns a paginated list of avatar groups (characters). Each group contains one or more looks. Filterable by ownership.
# List Avatar Looks
Source: https://developers.heygen.com/reference/list-avatar-looks
/openapi/external-api.json get /v3/avatars/looks
Returns a paginated list of avatar looks (outfits, poses, styles). Filterable by group_id, avatar_type, and ownership. The look id is the avatar_id to pass when creating a video.
# List Lipsyncs
Source: https://developers.heygen.com/reference/list-lipsyncs
/openapi/external-api.json get /v3/lipsyncs
Returns a paginated list of all lipsync jobs in the account.
# List Session Videos
Source: https://developers.heygen.com/reference/list-session-videos
/openapi/external-api.json get /v3/video-agents/{session_id}/videos
Returns all videos produced within a Video Agent session, sorted newest-first.
# List Supported Translation Languages
Source: https://developers.heygen.com/reference/list-supported-translation-languages
/openapi/external-api.json get /v3/video-translations/languages
Returns all supported target language names for video translation.
# List Video Agent Sessions
Source: https://developers.heygen.com/reference/list-video-agent-sessions
/openapi/external-api.json get /v3/video-agents
Returns a paginated list of video agent sessions for the authenticated user, sorted newest-first.
# List Video Agent Styles
Source: https://developers.heygen.com/reference/list-video-agent-styles
/openapi/external-api.json get /v3/video-agents/styles
Returns curated visual style templates available for Video Agent sessions. Each style controls scene composition, pacing, and aesthetics. Supports tag filtering (e.g. 'cinematic', 'retro-tech').
# List Video Translations
Source: https://developers.heygen.com/reference/list-video-translations
/openapi/external-api.json get /v3/video-translations
Returns a paginated list of all video translation jobs in the account.
# List Videos
Source: https://developers.heygen.com/reference/list-videos
/openapi/external-api.json get /v3/videos
Returns a paginated list of all videos in the account. Filterable by folder_id or title substring.
# List Voices
Source: https://developers.heygen.com/reference/list-voices
/openapi/external-api.json get /v3/voices
Returns a paginated list of voices, filterable by type, engine, language, and gender. Use engine=starfish for voices compatible with the TTS endpoint.
# List Webhook Endpoints
Source: https://developers.heygen.com/reference/list-webhook-endpoints
/openapi/external-api.json get /v3/webhooks/endpoints
Returns a paginated list of registered webhook endpoints.
# List Webhook Event Types
Source: https://developers.heygen.com/reference/list-webhook-event-types
/openapi/external-api.json get /v3/webhooks/event-types
Returns all available webhook event types with human-readable descriptions.
# List Webhook Events
Source: https://developers.heygen.com/reference/list-webhook-events
/openapi/external-api.json get /v3/webhooks/events
Returns a paginated history of delivered webhook events. Filterable by event_type or entity_id.
# Rotate Webhook Signing Secret
Source: https://developers.heygen.com/reference/rotate-webhook-signing-secret
/openapi/external-api.json post /v3/webhooks/endpoints/{endpoint_id}/rotate-secret
Generates a new signing secret for a webhook endpoint and immediately invalidates the old one. Store the new secret securely — it will not be shown again.
# Send Message or Request Revision
Source: https://developers.heygen.com/reference/send-message-or-request-revision
/openapi/external-api.json post /v3/video-agents/{session_id}
Sends a follow-up message to an existing session. Use to answer agent questions, add context, or request edits to a generated video. Only valid for sessions created in chat mode.
# Stop Video Agent Session
Source: https://developers.heygen.com/reference/stop-video-agent-session
/openapi/external-api.json post /v3/video-agents/{session_id}/stop
Halts an active agent run at its next checkpoint. Partial results are preserved.
# Update Avatar Look
Source: https://developers.heygen.com/reference/update-avatar-look
/openapi/external-api.json patch /v3/avatars/looks/{look_id}
Updates the display name of an avatar look. Only supported for photo avatar and digital twin look types.
# Update Lipsync
Source: https://developers.heygen.com/reference/update-lipsync
/openapi/external-api.json patch /v3/lipsyncs/{lipsync_id}
Updates the display title of a lipsync job.
# Update Video Translation
Source: https://developers.heygen.com/reference/update-video-translation
/openapi/external-api.json patch /v3/video-translations/{video_translation_id}
Updates the display title of a video translation job.
# Update Webhook Endpoint
Source: https://developers.heygen.com/reference/update-webhook-endpoint
/openapi/external-api.json patch /v3/webhooks/endpoints/{endpoint_id}
Updates the URL and/or subscribed event types for a webhook endpoint. The events array is fully replaced — include all types you want to keep.
# Upload Asset
Source: https://developers.heygen.com/reference/upload-asset
/openapi/external-api.json post /v3/assets
Uploads a file (image, video, audio, or PDF) and returns an asset_id for use in other endpoints. Max 32 MB. Supported types: png, jpeg, mp4, webm, mp3, wav, pdf.
# Upload Proofread SRT
Source: https://developers.heygen.com/reference/upload-proofread-srt
/openapi/external-api.json put /v3/video-translations/proofreads/{proofread_id}/srt
Replaces the proofread subtitles with an edited SRT file.
# Showcase
Source: https://developers.heygen.com/showcase
Real projects built with the HeyGen API. Get inspired, then build your own.
These projects demonstrate what's possible when you combine Video Agent with AI coding agents, browser extensions, CI/CD pipelines, and more.
***
## README-to-Video
**Auto-generate video walkthroughs from GitHub README changes.**
A GitHub Action watches for README changes, uses Claude to write a scene-by-scene production prompt, and sends it to Video Agent. The rendered video is automatically embedded back in the README.
* **HeyGen features:** Video Agent API
* **Stack:** TypeScript, GitHub Actions, Claude
* **Key insight:** The quality gap between good and mediocre videos comes down to the prompt. This project uses an LLM to write production-quality briefs with specific visual directions — not just narration scripts.
* **Cost:** \~\$0.05–0.15 per video
<Accordion title="How the prompt pipeline works">
Instead of passing the README text directly to Video Agent, the system uses a two-stage prompt:
1. **Meta-prompt** — Claude receives the README content + instructions on how to write a great Video Agent prompt (scene structure, visual style, B-roll descriptions, pacing)
2. **Video prompt** — Claude outputs a detailed production brief that Video Agent can execute
This "prompt-that-writes-a-prompt" pattern is reusable for any content-to-video workflow.
</Accordion>
***
## Viral Video Pipeline
**Research trending topics, then batch-generate short-form videos.**
Researches trending self-improvement topics via web search, then generates 6 TikTok/Reels/Shorts-ready videos in one run. Fully automated — no camera, no mic, no editing.
* **HeyGen features:** Video Agent API (batch generation, portrait mode)
* **Stack:** Claude Code + HeyGen Skills
* **Key insight:** Rate limit handling is critical for batch generation. This pipeline fires videos sequentially with 5–10 second gaps and tracks all video IDs for async polling.
* **Output:** 6 videos (25–40s each) + batch report with performance predictions
* **Cost:** \~\$6 in HeyGen credits for the full batch
***
## Site2Video — Chrome Extension
**One-click: turn any website into a professional, brand-consistent video.**
A Chrome extension captures a full-page screenshot, analyzes the site's visual DNA (colors, typography, layout), generates a style-aware Video Agent prompt, and renders a branded video.
* **HeyGen features:** Video Agent API, Asset Upload, 1,200+ avatars
* **Stack:** Vite + React (extension), Next.js (backend), Gemini/Claude (LLM)
* **Key insight:** Every prompt is generated from scratch using LLM analysis — no static templates. The system extracts visual style from the page itself and translates it into Video Agent prompt instructions.
* **Video modes:** Founder Pitch (60s), Product Walkthrough (90s), Teardown (75s), Investor Summary (45s), Social Ad (30s)
* **Visual styles:** 14 curated styles + Auto mode that extracts style from the website
***
## AI News Broadcast
**Automated daily AI briefings: scrape → script → render → distribute.**
A pipeline that gathers AI papers from arXiv and Hacker News, builds a script with an LLM, generates a video via Video Agent, and posts it to Telegram.
* **HeyGen features:** Video Agent API
* **Stack:** Bun + TypeScript
* **Key insight:** The modular architecture (research → script → video → deliver) makes each stage independently testable and swappable. You could replace the Telegram delivery with email, Slack, or YouTube upload.
***
## AI Mafia — Live Avatar Game
**Social deduction game with AI-powered Live Avatar NPCs.**
A Mafia/Werewolf game where 3 AI players argue, accuse, bluff, and vote in real-time using HeyGen Live Avatars. Each NPC has a distinct personality and is powered by Claude for decision-making.
* **HeyGen features:** Live Avatar SDK (real-time streaming)
* **Stack:** Next.js, React, HeyGen Live Avatar SDK, Claude
* **Key insight:** Live Avatars enable real-time interactive experiences — not just pre-rendered videos. The NPCs read game state, develop strategies, and respond with natural speech and expressions.
* **Characters:** Maria (expressive), Chen (calm analyst), Alex (emotional reactor)
<Info>
This project uses **Live Avatars** (real-time streaming), not Video Agent. It showcases a different interaction model — live, conversational AI rather than pre-rendered video content.
</Info>
***
## Build Your Own
These projects share common patterns you can reuse:
1. **Content → LLM → Video Agent prompt** — The meta-prompt pattern works for any content type
2. **Batch generation with rate limit handling** — Sequential queuing with status tracking
3. **Style extraction → prompt instructions** — Translate visual context into Video Agent language
4. **Modular pipelines** — Separate research, scripting, rendering, and delivery stages
Start with a [workflow](/cookbook/video-agent/social-media-pipeline), learn the [prompt techniques](/cookbook/patterns/prompt-engineering), and build from there.
# Social Media Content Pipeline
Source: https://developers.heygen.com/social-media-content-pipeline
Batch-generate short-form videos for TikTok, Reels, and Shorts — no camera, no mic, no editing.
## Examples
These were generated with Video Agent from a text prompt — no camera, no editing, no mic.
<Tabs>
<Tab title="Freelancing story">
<div>
<iframe title="HeyGen video player" />
</div>
</Tab>
<Tab title="AI editing story">
<div>
<iframe title="HeyGen video player" />
</div>
</Tab>
</Tabs>
## The Problem
Creating consistent social media video content requires a camera, microphone, editing skills, and hours of time per video. Most teams can't keep up with the pace platforms demand — daily or weekly posts across TikTok, Reels, and Shorts.
## How It Works
Choose topics → Write prompts → Batch generate videos → Post
You define the topics and tone. Video Agent handles avatar selection, scripting, visuals, and rendering. Generate a week's worth of content in one run.
## Build It
<Steps>
<Step title="Choose your topics">
You can source topics manually, from trending data, or from audience research. Here's an example batch of 5 topics for a SaaS marketing account:
```python theme={null}
topics = [\
"Why your onboarding flow is losing users in the first 60 seconds",\
"The 3 metrics every SaaS founder checks before coffee",\
"Stop building features nobody asked for — do this instead",\
"Your pricing page is broken and here's the proof",\
"The hiring mistake that kills startups faster than bad code",\
]
```
<Tip>
**Use an LLM for topic research.** Ask Claude or ChatGPT: "Give me 10 trending topics in \[your niche] that would perform well as 30-second TikToks." You can also use web search APIs to find what's trending right now.
</Tip>
</Step>
<Step title="Write prompts optimized for short-form">
Short-form video has a specific structure: **hook → content → CTA**, all in under 60 seconds. Your prompts should reflect this.
```python theme={null}
def build_prompt(topic):
return f"""Create a 30-second vertical video (portrait orientation) for TikTok/Reels.
Topic: {topic}
Structure:
- Hook (0-5s): Open with a bold, scroll-stopping statement or question.
- Content (5-25s): Deliver 2-3 punchy insights. Fast pacing, one idea
every 5-7 seconds. Use text overlays for key points.
- CTA (25-30s): End with a clear call-to-action.
Tone: Casual and energetic, like talking to a friend who's also in the industry.
Orientation: portrait.
"""
prompts = [build_prompt(t) for t in topics]
```
See [Prompt Engineering](/cookbook/patterns/prompt-engineering) for more techniques — especially the scene-by-scene structure for longer videos.
</Step>
<Step title="Batch generate with rate limit handling">
Submit all videos to Video Agent. Space them out to respect [rate limits](/docs/pricing).
```python theme={null}
import requests
import time
HEYGEN_API_KEY = "your-api-key"
video_ids = []
for i, prompt in enumerate(prompts):
resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={
"X-Api-Key": HEYGEN_API_KEY,
"Content-Type": "application/json",
},
json={
"prompt": prompt,
"orientation": "portrait",
},
)
data = resp.json()["data"]
video_ids.append(data["video_id"])
print(f"[{i+1}/{len(prompts)}] Submitted: {data['video_id']}")
# Wait between submissions to avoid rate limits
if i < len(prompts) - 1:
time.sleep(5)
print(f"\nAll {len(video_ids)} videos submitted.")
```
</Step>
<Step title="Poll for completion">
Wait for all videos to finish rendering.
```python theme={null}
import time
def poll_videos(video_ids):
results = {}
pending = set(video_ids)
while pending:
for vid in list(pending):
resp = requests.get(
f"https://api.heygen.com/v3/videos/{vid}",
headers={"X-Api-Key": HEYGEN_API_KEY},
)
data = resp.json()["data"]
if data["status"] == "completed":
results[vid] = data["video_url"]
pending.discard(vid)
print(f"Completed: {vid}")
elif data["status"] == "failed":
results[vid] = None
pending.discard(vid)
print(f"Failed: {vid} — {data.get('failure_message')}")
if pending:
print(f"Waiting... {len(pending)} still rendering")
time.sleep(15)
return results
results = poll_videos(video_ids)
```
<Tip>
For production pipelines, use [webhooks](/docs/webhooks) instead of polling. Pass a `callback_url` in each creation request and handle notifications as they arrive.
</Tip>
</Step>
<Step title="Download and distribute">
Download all completed videos, then post to your platforms.
```python theme={null}
import os
os.makedirs("output", exist_ok=True)
for i, (vid, url) in enumerate(results.items()):
if url:
video_data = requests.get(url).content
filename = f"output/video_{i+1}.mp4"
with open(filename, "wb") as f:
f.write(video_data)
print(f"Saved: {filename}")
```
</Step>
</Steps>
## Platform Optimization
Different platforms have different sweet spots:
| Platform | Ideal Duration | Orientation | Tips |
| --------------- | -------------- | ---------------- | ----------------------------------------- |
| TikTok | 15–60s | Portrait (9:16) | Strong hook in first 2s, fast pacing |
| Instagram Reels | 15–60s | Portrait (9:16) | Clean visuals, text overlays |
| YouTube Shorts | 15–60s | Portrait (9:16) | Slightly more polished, educational angle |
| LinkedIn | 30–90s | Landscape (16:9) | Professional tone, industry insights |
Pass `"orientation": "portrait"` or `"orientation": "landscape"` in your API call. See [Video Agent docs](/docs/video-agent) for all parameters.
## Variations
* **Themed series:** Generate 5 videos on the same topic from different angles (beginner, advanced, myth-busting, case study, hot take)
* **Multi-language:** Generate in English, then use [Video Translation](/docs/video-translate) to create versions in other languages
* **A/B testing:** Generate 2 versions of the same topic with different hooks, measure which performs better
***
## Next Steps
<CardGroup>
<Card title="Prompt Engineering" icon="wand-magic-sparkles" href="/cookbook/patterns/prompt-engineering">
Write prompts that produce scroll-stopping content.
</Card>
<Card title="Content Repurposing" icon="recycle" href="/cookbook/video-agent/content-repurposing">
Already have blog content? Turn it into video.
</Card>
</CardGroup>
# Studio API
Source: https://developers.heygen.com/studio-api
Generate videos using the AI Studio backend with support for avatars, voices, and dynamic backgrounds.
## Overview
`POST https://api.heygen.com/v2/video/generate`
Generates videos using the AI Studio backend with support for avatars, voices, and dynamic backgrounds. You can create videos using either your photo avatar or digital twin. This endpoint supports Avatar III and Avatar IV.
Each video is composed of one or more **scenes** (up to 50), where each scene defines its own avatar, voice, background, and on-screen text.
## Request Body
### Top-Level Parameters
| Parameter | Type | Required | Description |
| ------------------ | ------- | -------- | -------------------------------------------------------------------------------------------------- |
| `video_inputs` | array | Yes | Array of scene objects (1–50). Each scene defines an avatar, voice, background, and optional text. |
| `caption` | boolean | No | Enable captions in the video. Only supported for text-based voice input. Default: `false`. |
| `title` | string | No | Title of the video. |
| `callback_id` | string | No | Custom ID for callback/webhook tracking. |
| `dimension` | object | No | Custom output dimensions. Defaults to `1920×1080`. |
| `dimension.width` | integer | No | Width of the output video. Default: `1920`. |
| `dimension.height` | integer | No | Height of the output video. Default: `1080`. |
| `folder_id` | string | No | Folder ID where the video is stored. |
| `callback_url` | string | No | URL to notify when video rendering is complete. |
### Scene Object (`video_inputs[]`)
Each item in the `video_inputs` array represents a scene and can contain the following:
#### `character`
Defines the avatar or talking photo for the scene.
| Parameter | Type | Required | Description |
| ------------------------- | ------- | -------- | ---------------------------------------------------------------------------------------------- |
| `type` | string | Yes | `avatar` or `talking_photo`. |
| `avatar_id` | string | Yes\* | Unique avatar identifier. *Required when `type` is `avatar`.* |
| `talking_photo_id` | string | Yes\* | Unique talking photo identifier. *Required when `type` is `talking_photo`.* |
| `avatar_style` | string | No | `normal`, `closeUp`, or `circle`. Applies only to `avatar` type. Default: `normal`. |
| `talking_photo_style` | string | No | `circle`. Applies only to `talking_photo` type. |
| `talking_style` | string | No | `stable` or `expressive`. Applies only to `talking_photo` type. Default: `stable`. |
| `expression` | string | No | `default` or `happy`. Applies only to `talking_photo` type. |
| `scale` | float | No | Avatar size. Range: `0.0`–`5.0`. Default: `1`. |
| `offset` | object | No | Position adjustment: `{ "x": 0.0, "y": 0.0 }`. |
| `use_avatar_iv_model` | boolean | No | Whether to use Avatar IV. |
| `prompt` | string | No | Avatar IV motion prompt. Applies to `talking_photo` type when `use_avatar_iv_model` is `true`. |
| `keep_original_prompt` | boolean | No | Preserve motion prompt as-is (skip enhancement). Applies when `use_avatar_iv_model` is `true`. |
| `matting` | boolean | No | Remove photo background. |
| `super_resolution` | boolean | No | Enhance image quality. Applies only to `talking_photo` type. |
| `circle_background_color` | string | No | Hex color for circle style background (e.g., `#FFFFFF`). |
#### `voice`
Defines what the avatar says in this scene.
| Parameter | Type | Required | Description |
| --------------------- | ------- | -------- | --------------------------------------------------------------------------------------------- |
| `type` | string | Yes | `text`, `audio`, or `silence`. |
| `voice_id` | string | Yes\* | Voice identifier. *Required for `text` type.* |
| `input_text` | string | Yes\* | Text the avatar will speak. *Required for `text` type.* |
| `speed` | float | No | Voice speed. Range: `0.5`–`1.5`. Default: `1`. Applies to `text` type. |
| `pitch` | integer | No | Voice pitch. Range: `-50`–`50`. Default: `0`. Applies to `text` type. |
| `emotion` | string | No | `Excited`, `Friendly`, `Serious`, `Soothing`, or `Broadcaster`. Applies to `text` type. |
| `locale` | string | No | Voice accent/locale (e.g., `en-US`, `pt-BR`). Applies to `text` type. |
| `audio_url` | string | Yes\* | URL of uploaded audio. *Required for `audio` type (provide either this or `audio_asset_id`).* |
| `audio_asset_id` | string | Yes\* | Asset ID of uploaded audio. *Required for `audio` type (provide either this or `audio_url`).* |
| `duration` | string | No | Silence duration in seconds. Range: `1.0`–`100.0`. Default: `1`. Applies to `silence` type. |
| `elevenlabs_settings` | object | No | Advanced ElevenLabs voice settings (see below). Applies to `text` type. |
**ElevenLabs Settings:**
| Parameter | Type | Description |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `model` | string | ElevenLabs model: `eleven_monolingual_v1`, `eleven_multilingual_v1`, `eleven_multilingual_v2`, `eleven_turbo_v2`, `eleven_turbo_v2_5`, or `eleven_v3`. |
| `similarity_boost` | float | Similarity to original voice. Range: `0.0`–`1.0`. |
| `stability` | float | Voice consistency. Range: `0.0`–`1.0`. For `eleven_v3`, default is `1.0` and allowed values are `0`, `0.5`, `1.0`. |
| `style` | float | Style intensity. Range: `0.0`–`1.0`. |
#### `background`
Defines the scene background.
| Parameter | Type | Required | Description |
| ---------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Yes | `color`, `image`, or `video`. |
| `value` | string | Yes\* | Hex color code (e.g., `#FFFFFF`). *Required for `color` type.* |
| `url` | string | Yes\* | URL of uploaded image/video. *Required for `image`/`video` type (provide either this or the corresponding asset ID).* |
| `image_asset_id` | string | Yes\* | Asset ID for image background. *Provide either this or `url`.* |
| `video_asset_id` | string | Yes\* | Asset ID for video background. *Provide either this or `url`.* |
| `play_style` | string | No | Playback mode: `freeze`, `loop`, or `fit_to_scene`. Applies to `video` type. |
| `fit` | string | No | How background fits the screen: `crop`, `cover`, `contain`, or `none`. Default: `cover`. |
#### `text`
Optional on-screen text overlay.
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | ------------------------------------ |
| `type` | string | Yes | Must be `text`. |
| `text` | string | Yes | Text content to display. |
| `font_family` | string | No | Font family (e.g., `Arial`). |
| `font_size` | float | No | Font size in points. |
| `font_weight` | string | No | `bold`. |
| `color` | string | No | Text color in hex (e.g., `#FFFFFF`). |
| `position` | object | No | Position: `{ "x": 0.0, "y": 0.0 }`. |
| `text_align` | string | No | `left`, `center`, or `right`. |
| `line_height` | float | Yes | Line height / spacing between lines. |
| `width` | number | No | Text container width. |
## Example Request
```json theme={null}
{
"title": "My Legacy Video",
"caption": false,
"dimension": {
"width": 1920,
"height": 1080
},
"video_inputs": [\
{\
"character": {\
"type": "avatar",\
"avatar_id": "YOUR_AVATAR_ID",\
"avatar_style": "normal"\
},\
"voice": {\
"type": "text",\
"voice_id": "YOUR_VOICE_ID",\
"input_text": "Welcome to the first scene of this video.",\
"speed": 1.0\
},\
"background": {\
"type": "color",\
"value": "#1a1a2e"\
}\
},\
{\
"character": {\
"type": "avatar",\
"avatar_id": "YOUR_AVATAR_ID",\
"avatar_style": "closeUp"\
},\
"voice": {\
"type": "text",\
"voice_id": "YOUR_VOICE_ID",\
"input_text": "And here is the second scene with a different style."\
},\
"background": {\
"type": "color",\
"value": "#16213e"\
}\
}\
]
}
Response
200 — Success
{
"error": null,
"data": {
"video_id": "af273759c9xa47369e05418c69drq174"
}
}
| Field | Type | Description |
|---|---|---|
error |
string | null | Error message if the request fails; null on success. |
data.video_id |
string | Unique identifier of the generated video. |
Full API Reference
For complete details, see the Create Avatar Video (V2) endpoint documentation.
Template API
Source: https://developers.heygen.com/template-api
Generate a video based on a specified template, including scene IDs and dynamic variable replacements.
Overview
POST https://api.heygen.com/v2/template/{template_id}/generate
Generates a video based on the specified template, including scene IDs to define the sequence of scenes and variable values for replacement.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id |
string | Yes | Unique identifier of the template. |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
variables |
string (JSON) | Yes | Dynamic variables used within the template. |
caption |
boolean | No | Enable captions in the video. Default: false. |
title |
string | No | Title of the video. |
dimension |
object | No | Custom output dimensions. Must match the template's aspect ratio. |
dimension.width |
integer | No | Width of the output video. Default: 1280. |
dimension.height |
integer | No | Height of the output video. Default: 720. |
include_gif |
boolean | No | Include a GIF preview URL in the webhook response. Default: false. |
enable_sharing |
boolean | No | Make the video publicly shareable immediately after creation. |
folder_id |
string | No | Folder ID where the video is stored. |
brand_voice_id |
string | No | Brand Glossary ID for applying predefined translation and pronunciation rules (translation exclusions, enforced terms, vocabulary mappings, tone preferences). |
callback_url |
string | No | URL to notify when video rendering is complete. If both a webhook and callback_url are configured, events are sent to both. |
keep_text_vertically_centered |
boolean | No | When true, replaced text elements are vertically centered based on their actual rendered height. |
Example Request
POST /v2/template/YOUR_TEMPLATE_ID/generate
{
"title": "My Template Video",
"caption": false,
"dimension": {
"width": 1280,
"height": 720
},
"variables": {
"script": {
"name": "script",
"type": "text",
"properties": {
"content": "Hello, welcome to our product demo."
}
},
"headline": {
"name": "headline",
"type": "text",
"properties": {
"content": "Product Overview"
}
}
}
}
Response
200 — Success
{
"error": null,
"data": {
"video_id": "763fca2469b98a65b351eqr8c449f4e8"
}
}
Full API Reference
For complete details, see the Generate Video from Template (V2) endpoint documentation.
Training & Onboarding Videos
Source: https://developers.heygen.com/training-and-onboarding-videos
Convert training docs, policies, and SOPs into engaging video — and keep them in sync when materials change.
The Problem
Corporate training content is expensive to produce, goes stale fast, and doesn't scale across languages. A single compliance training video can cost $5,000–$15,000 to produce professionally — and needs to be re-recorded every time a policy changes.
How It Works
Training docs/policies → LLM structures into video modules → Video Agent renders → Translate for global teams
Generate training videos from your existing materials. When the source document updates, regenerate the video. Need it in 10 languages? Use Video Translation.
Build It
modules = [\
{\
"title": "Data Privacy Basics",\
"source": "policies/data-privacy.pdf",\
"duration": "3 minutes",\
"style": "professional, clear, reassuring",\
},\
{\
"title": "Handling Customer Data",\
"source": "policies/data-handling.md",\
"duration": "4 minutes",\
"style": "professional, specific, example-driven",\
},\
{\
"title": "Reporting a Breach",\
"source": "policies/breach-response.md",\
"duration": "2 minutes",\
"style": "urgent but calm, step-by-step",\
},\
]
```
</Step>
<Step title="Generate video prompts with an LLM">
Use an LLM to convert each training document into a structured video prompt. The LLM acts as an instructional designer.
```python theme={null}
import anthropic
client = anthropic.Anthropic()
def generate_training_prompt(module, content):
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1500,
messages=[{\
"role": "user",\
"content": f"""You are an instructional designer creating a training\
video from a policy document. Convert this into a HeyGen Video Agent prompt.\
\
Structure the video as:\
1. Introduction (10s) — What this training covers and why it matters\
2. Key concepts (60-70% of duration) — Break into 2-4 clear sections\
with specific examples and scenarios employees will recognize\
3. Do's and Don'ts (15s) — Quick visual checklist\
4. Summary + quiz teaser (10s) — Recap key points, prompt to take the quiz\
\
Requirements:\
- Tone: {module['style']}\
- Duration: {module['duration']}\
- Use text overlays for key terms and definitions\
- Include scenario-based examples ("Imagine you receive an email from...")\
- Make it engaging — this isn't a lecture, it's a conversation\
\
Training document:\
{content}\
\
Output ONLY the Video Agent prompt."""\
}],
)
return message.content[0].text
# Generate prompt for each module
for module in modules:
with open(module["source"]) as f:
content = f.read()
module["video_prompt"] = generate_training_prompt(module, content)
```
</Step>
<Step title="Generate videos">
Submit each module to Video Agent. Space them out for rate limits.
```python theme={null}
import requests
import time
HEYGEN_API_KEY = "your-api-key"
for module in modules:
resp = requests.post(
"https://api.heygen.com/v3/video-agents",
headers={
"X-Api-Key": HEYGEN_API_KEY,
"Content-Type": "application/json",
},
json={
"prompt": module["video_prompt"],
# Optional: attach policy document for visual context
# "files": [{"type": "url", "url": "https://..."}]
},
)
module["video_id"] = resp.json()["data"]["video_id"]
print(f"Submitted: {module['title']} → {module['video_id']}")
time.sleep(5)
```
Then poll for completion — see [Video Agent docs](/docs/video-agent) for the polling pattern.
</Step>
<Step title="Translate for global teams">
Once your English videos are ready, translate them for every region in one batch.
```python theme={null}
languages = ["es", "fr", "de", "ja", "zh", "pt", "ko"]
for module in modules:
resp = requests.post(
"https://api.heygen.com/v2/video_translate",
headers={
"X-Api-Key": HEYGEN_API_KEY,
"Content-Type": "application/json",
},
json={
"video_url": module["video_url"],
"output_languages": languages,
},
)
module["translations"] = resp.json()
print(f"Translating {module['title']} into {len(languages)} languages")
```
See [Video Translation docs](/docs/video-translate) for speed vs precision mode and all supported languages.
</Step>
</Steps>
## Training Categories
This workflow applies to any training content:
| Category | Source material | Key considerations |
| --------------------------------- | ------------------------------------- | ---------------------------------------------------- |
| **Compliance** (HIPAA, GDPR, SOX) | Regulatory docs, policies | Must be accurate, auditable, up-to-date |
| **Onboarding** | Employee handbook, culture docs | Warm and welcoming tone, company-specific |
| **Software training** | Help docs, screenshots | Attach screenshots as file inputs for visual context |
| **Safety** | Safety procedures, SOPs | Clear, step-by-step, scenario-based |
| **Sales enablement** | Product knowledge, objection handling | Conversational, example-heavy |
## Keeping Videos in Sync
The biggest advantage of generated training videos: **when the policy changes, regenerate the video.**
Policy doc updated → Detect change (git diff, CMS webhook, manual trigger) → Re-run the same pipeline → New video replaces the old one → Re-translate if needed
No re-recording, no scheduling a film crew, no editing. Just re-run the pipeline.
## Variations
* **Interactive follow-up:** After the pre-rendered training video, launch a [Live Avatar AI Tutor](/cookbook/live-avatar/ai-tutor) for Q\&A and knowledge checks
* **Manager versions:** Generate a shorter executive summary version alongside the full training
* **Assessment-ready:** End each video with key questions that feed into your LMS quiz system
***
## Next Steps
<CardGroup>
<Card title="Multilingual Content" icon="globe" href="/cookbook/video-agent/multilingual-content">
Deep dive into translating videos across languages.
</Card>
<Card title="AI Tutor" icon="graduation-cap" href="/cookbook/live-avatar/ai-tutor">
Add interactive Q\&A after training with a Live Avatar tutor.
</Card>
</CardGroup>
# Get Current User
Source: https://developers.heygen.com/user-profile
* Endpoint: `GET https://api.heygen.com/v3/users/me`
* Purpose: Returns the authenticated user's profile, remaining credits or balance, and billing details. Use this to check your account status or remaining quota before making API calls.
### Authentication
### Quick Example
```bash theme={null}
curl -X GET "https://api.heygen.com/v3/users/me" \
-H "X-Api-Key: $HEYGEN_API_KEY"
Response
{
"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
}
},
"subscription": null,
"usage_based": null
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
username |
string | Account username. |
email |
string or null | Account email. |
first_name |
string or null | First name. |
last_name |
string or null | Last name. |
billing_type |
string or null | Which billing object is populated: "wallet", "subscription", or "usage_based". |
Billing Types
The billing_type field tells you which of the three billing objects to read. Only one is populated at a time.
Wallet (billing_type: "wallet") — Prepaid balance, typically for API key auth.
| Field | Type | Description |
|---|---|---|
wallet.currency |
string | "usd" or "credits" |
wallet.remaining_balance |
number or null | Current balance. |
wallet.auto_reload.enabled |
boolean | Whether auto-reload is on. |
wallet.auto_reload.threshold_usd |
number or null | Balance threshold that triggers reload. |
wallet.auto_reload.amount_usd |
number or null | Amount added on reload. |
Subscription (billing_type: "subscription") — Per-pool credit balances, typically for OAuth apps.
| Field | Type | Description |
|---|---|---|
subscription.plan |
string | Plan tier: "free", "starter", "creator", "pro", "team", "enterprise", or "business_plus". |
subscription.credits.premium_credits.remaining |
integer or null | Remaining premium credits. |
subscription.credits.premium_credits.resets_at |
string or null | When the credit pool resets. |
subscription.credits.add_on_credits.remaining |
integer or null | Remaining add-on credits. |
Usage-based (billing_type: "usage_based") — Metered billing with optional spending cap.
| Field | Type | Description |
|---|---|---|
usage_based.spending_current_usd |
number or null | Current spend this period. |
usage_based.spending_cap_usd |
number or null | Spending cap (if set). |
Video Agent Styles
Source: https://developers.heygen.com/video-agent-with-styles
Pick a visual style — cinematic, handmade, retro — write a prompt, and let the Video Agent handle the rest.
Steps
heygen video-agent styles list
```
```json theme={null}
{
"data": [\
{\
"style_id": "349d91e1ad2444eabab2672a9057f298",\
"name": "Thriller",\
"aspect_ratio": "16:9",\
"tags": ["cinematic"]\
},\
{\
"style_id": "be9f5b18fb294c99a0e34c15707145fc",\
"name": "Lego",\
"aspect_ratio": "16:9",\
"tags": ["handmade"]\
},\
{\
"style_id": "13898c3b01ec4dafae5fc17753c7dd7a",\
"name": "iOS",\
"aspect_ratio": "9:16",\
"tags": ["retro-tech"]\
}\
]
}
```
Each style has an `aspect_ratio` — some are landscape (`16:9`), others portrait (`9:16`). Use `--human` for a readable table view.
<Note>
Each style includes a `preview_video_url` and `thumbnail_url` — open them to preview the visual treatment before choosing.
</Note>
</Step>
<Step title="Generate a styled video">
Pass the `style_id` along with your prompt:
```bash theme={null}
heygen video-agent create \
--prompt "A 30-second explainer about how AI is transforming video production" \
--style-id "349d91e1ad2444eabab2672a9057f298"
```
```json theme={null}
{
"data": {
"session_id": "sess_abc123",
"status": "generating",
"video_id": "vid_xyz789",
"created_at": 1711288320
}
}
```
The agent picks the avatar, voice, and layout. The style controls the visual treatment.
To override the agent's choices, pass additional flags:
```bash theme={null}
heygen video-agent create \
--prompt "A product launch announcement" \
--style-id "349d91e1ad2444eabab2672a9057f298" \
--avatar-id "avt_angela_01" \
--voice-id "1bd001e7e50f421d891986aad5e3e5d2" \
--orientation landscape
```
</Step>
<Step title="Wait and download">
```bash theme={null}
# Block until ready
heygen video-agent create \
--prompt "A quick intro to our company" \
--style-id "be9f5b18fb294c99a0e34c15707145fc" \
--wait
# Or poll manually
heygen video get vid_xyz789
# Download
heygen video download vid_xyz789 --output-path ./styled-video.mp4
```
</Step>
</Steps>
## Batch: same content, multiple styles
Generate the same prompt across different visual styles:
```bash theme={null}
#!/bin/bash
set -e
PROMPT="A 30-second pitch for an AI-powered design tool"
STYLES=(
"349d91e1ad2444eabab2672a9057f298:Thriller"
"be9f5b18fb294c99a0e34c15707145fc:Lego"
"279082e3beda4ac5a4e9a4f2a36c7d74:Silent-Film"
)
for entry in "${STYLES[@]}"; do
STYLE_ID="${entry%%:*}"
STYLE_NAME="${entry##*:}"
echo "Generating $STYLE_NAME..."
VIDEO_ID=$(heygen video-agent create \
--prompt "$PROMPT" \
--style-id "$STYLE_ID" \
| jq -r '.data.video_id')
echo " Video ID: $VIDEO_ID (generating...)"
done
echo "All videos submitted. Poll with: heygen video get <video-id>"
Interactive sessions for iteration
Review and refine before generating:
# Start a session
SESSION=$(heygen video-agent sessions create \
--prompt "A product demo video in Lego style" \
--style-id "be9f5b18fb294c99a0e34c15707145fc" \
| jq -r '.data.session_id')
# Check the storyboard
heygen video-agent sessions get "$SESSION"
# Send feedback
heygen video-agent sessions messages create "$SESSION" \
-d '{"message": "Make the intro more energetic and add a CTA at the end"}'
# Stop if you want to start over
heygen video-agent sessions stop "$SESSION"
Available flags
| Flag | Description |
|---|---|
--prompt |
Text prompt describing the video (required) |
--style-id |
Visual style from styles list |
--avatar-id |
Override the agent's avatar choice |
--voice-id |
Override the agent's voice choice |
--orientation |
landscape or portrait |
--incognito-mode |
Disable memory for this session |
--callback-url |
Webhook URL for completion notifications |
--wait |
Block until ready (default timeout: 20 min) |
--timeout |
Override wait timeout (e.g. --timeout 30m) |
Writing Effective Video Prompts
Source: https://developers.heygen.com/writing-effective-video-prompts
What actually works when prompting Video Agent — based on real experiments, not theory.
Video Agent is prompt-driven. But "more detail" doesn't always mean "better video." We ran 14 experiments with different prompting strategies to find out what actually produces the best results. Here's what we learned.
See the Difference
Same topic, different prompts. Watch both — the difference is the entire argument of this page.
Make a video about remote work benefits.
```
<iframe title="HeyGen video player" />
</Tab>
<Tab title="Crafted prompt">
Prompt:
```text theme={null}
Two years ago, I could only hire people within 30 miles of our
office. Today, my team spans 4 countries and 3 time zones. We
found engineers we never would have found locally. Our office
costs dropped to nearly zero. And here's the surprising part —
people actually stayed longer. Remote isn't the future. It's
already the default.
Tone: Like a founder on a podcast — reflective, honest, sharing
a personal experience. Not a pitch, not a lecture. Just someone
who tried something and it worked.
Background: Casual home office or coffee shop. Warm, natural.
30 seconds. Landscape.
```
<iframe title="HeyGen video player" />
</Tab>
</Tabs>
Both are about remote work benefits. The second used a natural story script with a tone description — no timestamps, no scene structure, no prescribed overlays. Just a great script and a feeling.
## The #1 Rule: Write a Great Script
The single biggest factor in video quality is the script — the actual words the presenter will say. Everything else (visuals, overlays, pacing) is secondary. Video Agent makes good production decisions on its own. Your job is to give it great words to work with.
<Tabs>
<Tab title="Weak script">
```text theme={null}
Here are three science-backed ways to sleep better tonight.
First: cut screens 30 minutes before bed — blue light
suppresses melatonin. Second: cool your room to 65 degrees.
Third: wake up at the same time every day.
```
Informational, clinical, reads like a textbook. The video will be competent but forgettable.
</Tab>
<Tab title="Strong script">
```text theme={null}
Six months ago I was averaging 5 hours of broken sleep. I
tried everything — supplements, meditation apps, white noise
machines. Nothing worked. Then I did three stupidly simple
things: I put my phone charger in the kitchen. I turned the
thermostat down to 65. And I set one alarm — same time, every
single day. No more negotiating with the snooze button. Within
two weeks I was sleeping 7 hours straight. No supplements. No
apps. Just discipline and a cold room.
```
Personal, narrative, has an arc. The viewer is hooked because someone is telling a real story — not listing facts.
</Tab>
</Tabs>
In our experiments, the personal story consistently produced better videos than the informational version — better B-roll choices, better pacing, more engaging delivery.
## What Makes a Script Work
**Stories beat lists.** First-person narratives ("I tried X, then Y happened") give Video Agent richer material to work with than bullet points. The agent generates better visuals when the script has emotional texture.
**Bold beats safe.** Provocative framing ("Stop trying to sleep 8 hours. Seriously.") produced more engaging videos than neutral framing. The agent matched the script's energy with bolder visual choices.
**Flow beats structure.** Scripts that read naturally — like someone talking to a friend — deliver better than scripts chopped into rigid segments. If it sounds awkward to read aloud, it'll sound awkward in the video.
**Questions don't work well.** Scripts built around questions ("Do you check your phone before bed? What temperature is your bedroom?") felt unnatural with a single speaker. Save the Socratic method for [Live Avatar](/cookbook/live-avatar/ai-tutor) conversations.
## Add Tone, Not Timestamps
After writing your script, the most useful thing you can add is a **tone description** — how the video should *feel*, not how it should be structured.
<Tabs>
<Tab title="Tone description (do this)">
```text theme={null}
[your script here]
Tone: Like a founder on a podcast — reflective, honest, no
corporate speak. The presenter should feel like they're sharing
a personal experience, not reading a script.
Background: Casual home office or coffee shop. Warm, natural.
Duration: 30 seconds.
```
Guides the delivery and mood without constraining the production.
</Tab>
<Tab title="Timestamp structure (avoid this)">
```text theme={null}
Scene 1 (0-5s): Hook — "..."
Scene 2 (5-12s): Tip 1 — "..."
Scene 3 (12-20s): Tip 2 — "..."
Scene 4 (20-27s): Tip 3 — "..."
Scene 5 (27-30s): Close — "..."
```
Gives you precise control but makes the delivery feel robotic. The agent follows the timing exactly, and the result sounds choppy.
</Tab>
</Tabs>
In our tests, adding tone improved delivery quality. Adding timestamps and scene structure gave more control but hurt the natural flow of speech.
## Let Video Agent Handle Production
Video Agent makes surprisingly good decisions about:
* **B-roll selection** — relevant, well-timed visuals
* **Text overlays** — clean typography, good placement
* **Color palette** — matches the mood of the script
* **Music** — appropriate energy and tone
* **Pacing** — natural rhythm based on the script
You don't always need to specify these. In our experiments (tested on a health/wellness topic), the minimal prompt ("Make a 30-second video about 3 tips for better sleep") produced a video with solid B-roll, thoughtful overlays, and a calming color palette — all chosen by the agent. Results may vary by topic and content type.
**Only override production decisions when you have a specific need.** For example:
* `Orientation: portrait` — when targeting TikTok/Reels
* `Duration: 30 seconds` — when you have a length constraint
* Keep the presenter on screen (see below for translation-ready videos)
## Reference Files for Context
When your video is about something visual — a product, a document, a website — attach files so the agent has context to work with.
```json theme={null}
{
"prompt": "Create a product walkthrough based on the attached screenshots...",
"files": [\
{ "type": "url", "url": "https://example.com/screenshot.png" }\
]
}
This works well for product demos, content summaries, and brand-consistent videos. See Video Agent docs for supported file types.
Translation-Ready Videos
If you plan to translate your video into other languages using Video Translation, the presenter's face needs to be visible throughout for lip-sync to work. Add this to your prompt:
This is a direct-to-camera message. Think of it like a FaceTime
call — one person, one camera, sincere eye contact throughout.
The presenter should be visible and speaking for the entire video.
Prompt Templates
These templates use the patterns that worked best in our experiments: natural scripts, tone descriptions, and minimal production direction.
Tone: Honest, slightly amazed it worked. Like a podcast story. Not polished — real. Duration: 30 seconds.
</Accordion>
<Accordion title="Bold Take (30s)">
```text theme={null}
[Open with a contrarian or surprising statement. Challenge a\
common assumption. Then deliver 2-3 rapid points that support\
your take. Close with a memorable line.]
Tone: Confident, slightly provocative. Not angry — just done
with bad advice. Like a friend who's tired of watching you
struggle.
Duration: 30 seconds.
Tone: Deadpan, honest, slightly amused. The humor is in the delivery, not the words. Orientation: portrait.
</Accordion>
<Accordion title="Translation-Ready Message (30-45s)">
```text theme={null}
[Write a warm, universal message. Avoid idioms, slang, or\
culturally specific references — this will be translated into\
multiple languages. Keep sentences short and clear.]
This is a direct-to-camera message — one person, one camera,
sincere eye contact throughout. Like a FaceTime call from a
friend.
Tone: Warm, sincere, inclusive.
Duration: 35 seconds. Landscape.
Common Mistakes
Next Steps