# 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"
    )
Combine with [Docs to Video](/cookbook/video-agent/docs-to-video) for a fully automated pipeline: data changes → Hyperframes renders visualization → Video Agent adds avatar narration.

Next Steps

Animated title cards, product launches, and brand content. CI/CD integration for continuous video generation from data.

Design a Custom Voice

Source: https://developers.heygen.com/design-a-voice

Describe the voice you want in plain English — tone, accent, personality — and get a custom voice for any video.

Steps

Use `voice create` with a natural language prompt:
    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

```python theme={null} # From a file with open("README.md") as f: content = f.read()

Or from a URL

import requests
content = requests.get(
    "https://raw.githubusercontent.com/your-org/repo/main/README.md"
).text
```
```python theme={null} import anthropic

client = anthropic.Anthropic()

message = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=[{
"role": "user",
"content": f"""You are a video producer. Convert this documentation
into a HeyGen Video Agent prompt.

Structure as 3–5 scenes with timing. Open with a hook explaining what this
does and why it matters. Walk through key points visually. End with a next
step. Target: 60 seconds. Be specific about visuals.

Documentation:
{content}

Output ONLY the Video Agent prompt."""
}], ) video_prompt = message.content[0].text ```

**The two-stage pattern:** Content → LLM (writes production prompt) → Video Agent (renders). The LLM bridges the gap between "what the docs say" and "what the video should show."
```python theme={null} resp = requests.post( "https://api.heygen.com/v3/video-agents", headers={ "X-Api-Key": HEYGEN_API_KEY, "Content-Type": "application/json", }, json={"prompt": video_prompt}, ) video_id = resp.json()["data"]["video_id"] ```

Optionally attach screenshots or diagrams as file inputs. 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
Store API keys as [GitHub encrypted secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets). Never commit them.

Variations

  • Changelog videos: "Here's what's new in v2.3" — generate for each release
  • API docs: Walk through new endpoints or breaking changes visually
  • Onboarding: Auto-generate "Getting Started" videos from quickstart guides
  • Multi-language: Generate, then translate for international docs

Next Steps

Apply the same pattern to blog posts and articles. Schedule video generation pipelines.

API Key

Source: https://developers.heygen.com/docs/api-key

Getting Your API Key

  1. Go to the HeyGen API dashboard
  2. Click to generate your API key.

Configuring Your API Key

Environment variable (recommended)

```bash bash theme={null} export HEYGEN_API_KEY="your-api-key-here" ```

.env file

If your project uses a .env file (common with Node.js, Python, or frameworks like Next.js):

HEYGEN_API_KEY=your-api-key-here

Claude Code

If you're using Claude Code or any terminal-based workflow, set the key in your shell before starting:

```bash bash theme={null} export HEYGEN_API_KEY="your-api-key-here" claude # or whatever command starts your session ```

Alternatively, add it to your shell profile (~/.bashrc, ~/.zshrc) so it persists across sessions:

```bash bash theme={null} echo 'export HEYGEN_API_KEY="your-api-key-here"' >> ~/.zshrc source ~/.zshrc ```

HeyGen Skills (in Claude)

When using HeyGen through the Skills integration in Claude's computer environment, the API key is read from the environment. Make sure HEYGEN_API_KEY is set before the skill executes any API calls.

Using the Key in Requests

All HeyGen API requests authenticate via the X-Api-Key header. The base URL for all endpoints is https://api.heygen.com.

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/avatars" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  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:

```bash curl theme={null} curl -X GET "https://api.heygen.com/v1/user/me" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  {
    "code": 100,
    "data": {
      "username": "jane_doe",
      "email": "jane@example.com",
      "first_name": "Jane",
      "last_name": "Doe",
      "billing_type": "wallet",
      "wallet": {
        "currency": "usd",
        "remaining_balance": 42.50,
        "auto_reload": { "enabled": false }
      }
    },
    "message": null
  }

A successful response with "code": 100 confirms your key is valid. The billing_type and corresponding billing field (wallet, subscription, or usage_based) show your current balance and billing model.

Security Best Practices

  • Never commit your API key to version control. Add .env to your .gitignore.
  • Never expose the key in client-side / browser code. Always call the API from a backend or server environment.
  • Rotate your key periodically via the API dashboard.
  • Monitor usage in your API dashboard

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

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/avatars/looks?avatar_type=photo_avatar&ownership=public&limit=5" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  {
    "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

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/avatars/looks/look_def456" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  {
    "data": {
      "id": "look_def456",
      "name": "Monica - Business Casual",
      "avatar_type": "photo_avatar",
      "group_id": "group_abc123",
      "gender": "female",
      "preview_image_url": "https://files.heygen.ai/look/preview_def456.jpg",
      "preview_video_url": "https://files.heygen.ai/look/preview_def456.mp4",
      "default_voice_id": "voice_xyz789",
      "tags": ["business", "casual", "female"],
      "supported_api_engines": ["avatar_4_quality", "avatar_4_turbo"],
      "status": "completed"
    }
  }

Filtering by Group

To see all outfits and styles for a specific character, pass its group_id:

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/avatars/looks?group_id=group_abc123" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```

Pagination

If has_more is true, pass the next_token value as the token query parameter to fetch the next page.

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/avatars/looks?token=eyJsYXN0X2lkIjoiNDU2In0" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```

Using a Look in Video Creation

Once you have a look id, pass it as avatar_id:

```bash "Video Agent" theme={null} curl -X POST "https://api.heygen.com/v3/video-agents" \ -H "X-Api-Key: $HEYGEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "A product demo for our new app", "avatar_id": "look_def456" }' ```
  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

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/avatars?ownership=public&limit=5" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  {
    "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

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/avatars/group_abc123" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  {
    "data": {
      "id": "group_abc123",
      "name": "Monica",
      "gender": "female",
      "preview_image_url": "https://files.heygen.ai/avatar/preview_abc123.jpg",
      "preview_video_url": "https://files.heygen.ai/avatar/preview_abc123.mp4",
      "looks_count": 3,
      "default_voice_id": "voice_xyz789",
      "consent_status": null,
      "status": "completed",
      "created_at": 1711382400
    }
  }

Pagination

If has_more is true, pass the next_token value as the token query parameter to fetch the next page.

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/avatars?token=eyJsYXN0X2lkIjoiMTIzIn0" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```

Avatars vs. Looks

Avatar groups represent characters (e.g. "Monica"). Each group has one or more looks — different outfits, poses, or styles for that character. When creating a video, you pass a look ID (not a group ID) as the avatar_id. See Avatar Looks 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 requests library:
    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",""
The `url` field must be a publicly accessible video download URL. If you paste the URL into an incognito browser window, the video should play without any login or password.

You can also start from this Google Sheet template 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

  1. Set your API key as an environment variable (recommended):
    export HEYGEN_API_KEY="your-api-key-here"
    
    Alternatively, replace YOUR_API_KEY_HERE in the script directly.
  2. Place your CSV (bulk_translation_input.csv) in the same directory as the script.
  3. Run the script:
    python bulk_translate.py
    
    On macOS or Linux, use python3 if needed:
    python3 bulk_translate.py
    
  4. Review results. The script creates bulk_translation_results.csv with all original columns plus a video_translation_id column.

Checking Translation Status

Use the video_translation_id values from the results CSV to poll each translation's status:

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.

For better security, always store your API key in an environment variable rather than hardcoding it in the script.

Choosing the Right Video API

Source: https://developers.heygen.com/docs/choosing-the-right-video-api

Compare 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):

```bash bash theme={null} https://app.heygen.com/oauth/authorize?client_id=YOUR_CLIENT_ID&state=RANDOM_STATE&redirect_uri=YOUR_REDIRECT_URI&code_challenge=CODE_CHALLENGE&code_challenge_method=S256&response_type=code ```
  • YOUR_CLIENT_ID: Client ID. (e.g., abc123)
  • RANDOM_STATE: A unique string to maintain state. (e.g., xyz789)
  • YOUR_REDIRECT_URI: Your approved redirect URI, ensure URL-encoded. (e.g., https://example.com/oauth/callback)
  • CODE_CHALLENGE Corresponding code_challenge for a generated code_verifier using the PKCE flow (e.g. E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM). See: RFC 7636 Appendix B.
**Note:**

You can generate a code_verifier and code_challenge using online PKCE tools or standard libraries (e.g., Node.js, Python).

Step 2: Handle the Authorization Callback

After approval, you'll be redirected to your Redirect URI with a code parameter. It'll look like this:

```bash bash theme={null} https://yourapp.com/oauth/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATE ```

Verify that the state matches the one you sent in Step 1, then extract the AUTHORIZATION_CODE.

Step 3: Exchange Authorization Code for Access Token

Exchange the authorization code for an access token using this curl command:

```bash bash theme={null} curl -X POST https://api2.heygen.com/v1/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "code=AUTHORIZATION_CODE" \ -d "client_id=YOUR_CLIENT_ID" \ -d "grant_type=authorization_code" \ -d "redirect_uri=YOUR_REDIRECT_URI" \ -d "code_verifier=YOUR_CODE_VERIFIER" ```
  {
    "token_type": "Bearer",
    "access_token": "YyzWfeiqmklLsvzNsallQgUgfKHaNSnpv60BNgLGsC",
    "expires_in": 864000,
    "refresh_token": "dfU22fh6iD4aCkpy9GI32ulJXVe5eC8u4rt4SXedJHHich6L"
  }

Save the access_token and refresh_token to use in the next steps.

Step 4: Use the Access Token

Now you can make requests to HeyGen's API using the Authorization header with the Bearer scheme. Here's an example listing your avatar groups:

```bash bash theme={null} curl -X GET https://api.heygen.com/v3/avatars \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ```

The HeyGen API endpoints support both authentication methods:

  • OAuth Access Tokens — Use the Authorization header with the Bearer scheme. Format: Authorization: Bearer YOUR_ACCESS_TOKEN
  • API Keys — Use the X-Api-Key header. Format: X-Api-Key: YOUR_API_KEY

Step 5: Refresh the Access Token

Access tokens expire. Keep track of token expiration and refresh as needed. When the access token expires, use the refresh token to obtain a new one:

```bash bash theme={null} curl -X POST https://api2.heygen.com/v1/oauth/refresh_token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=YOUR_CLIENT_ID" \ -d "grant_type=refresh_token" \ -d "refresh_token=REFRESH_TOKEN" ```

Get User's Account Information

After successfully authenticating with HeyGen, you can retrieve account information — including remaining credits and billing details — using the GET /v3/users/me endpoint:

```bash bash theme={null} curl -X GET https://api.heygen.com/v3/users/me \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ```
  {
    "code": 100,
    "data": {
      "username": "jane_doe",
      "email": "jane@example.com",
      "first_name": "Jane",
      "last_name": "Doe",
      "billing_type": "subscription",
      "subscription": {
        "plan": "enterprise",
        "credits": {
          "premium_credits": {
            "remaining": 250,
            "resets_at": "2026-05-01T00:00:00Z"
          },
          "add_on_credits": {
            "remaining": 50,
            "resets_at": null
          }
        }
      }
    },
    "message": null
  }

Replace YOUR_ACCESS_TOKEN with the access token obtained during the OAuth process.

The billing_type field indicates which billing object is populated in the response:

Billing Type Field Description
wallet wallet Prepaid balance in USD (or credits for Enterprise). Includes optional auto-reload settings.
subscription subscription Per-pool credit balances with plan tier and reset dates. Used with OAuth integration apps.
usage_based usage_based Metered billing with current spending and optional spending cap.

Only the field matching billing_type will be populated; the others will be null.

Best Practices and Security Considerations

  • Always use HTTPS for all OAuth-related requests.
  • Store tokens securely and never expose them client-side.
  • Implement token rotation and regularly refresh access tokens. If a request fails, check if the access token has expired.
  • Validate the state parameter to prevent CSRF attacks.
  • Use short-lived access tokens and long-lived refresh tokens.
  • Implement proper error handling for token expiration and other OAuth-related errors.

Create Avatar

Source: https://developers.heygen.com/docs/create-avatar

Create a 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

### Digital Twin (`type: "digital_twin"`)

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.

**Endpoint:** `POST https://api.heygen.com/v3/avatars/{group_id}/consent` Consent is only required for **digital twin** avatars (`type: "video"`). Photo avatars and prompt-to-avatar characters do not require consent.

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").
Check `consent_status` on the avatar group via `GET /v3/avatars/{group_id}` to know when consent is complete.

Avatars vs. Looks

An avatar group is a character identity (e.g. "Sarah"). Each group can have multiple looks — different outfits, poses, or styles. When creating a video, you pass a look ID (not a group ID) as the avatar_id. Use GET /v3/avatars/looks 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

Credit-based pricing is available only for existing Enterprise customers. All current credit-based customers retain their contracted discount rates. HeyGen is introducing a new program for new Enterprise customers, moving them to dollar-based billing. If you are a new Enterprise customer, please visit [Enterprise Billing](/docs/enterprise-pricing-dollar-base) for dollar-based 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 (from POST /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

  1. Monthly Minimum Commitment (MMC): A flat fee charged monthly, regardless of usage.
  2. Included Usage: Each tier includes a pool of usage dollars per month at your contracted rate.
  3. Overage: Usage beyond the included pool is billed at the overage rate per second.

For pricing details and available tiers, contact our sales team.

Dollar Packages

Dollar packages let you purchase a fixed annual pool of dollars under a contract. Your balance is drawn down as you use the API throughout the year.

How It Works

  1. Annual Contract: You agree to a total dollar amount for the contract term (typically 12 months).
  2. Drawdown: Your balance is consumed per second as you use HeyGen's API.
  3. Balance Tracking: Monitor your remaining balance at any time via GET /v3/users/me → wallet.

For packaging options and contract terms, contact our sales team.

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_id is correct and was not mistyped.
  • The video has not been deleted.
  • The video belongs to your account.

avatar_not_found

HTTP status: 404

No avatar was found matching the provided ID. This applies to all avatar types — standard avatars, photo avatars (photars), instant avatars, and avatar kits. Verify that:

  • The avatar_id is correct.
  • The avatar has finished training (if recently created).
  • The avatar belongs to your account or is a public avatar.

voice_not_found

HTTP status: 404

No voice was found matching the provided ID. Verify that the voice_id is correct and that the voice is available in your account. If using a cloned voice, ensure it has finished processing.

template_not_found

HTTP status: 404

No template was found matching the provided ID. Verify that the template_id is correct and that the template is shared with your account or is publicly available.

asset_not_found

HTTP status: 404

No asset was found matching the provided ID. Assets may have been deleted or may not have finished uploading. Verify that the asset_id was returned from a successful POST /v1/asset call and that the asset has not been removed.

webhook_not_found

HTTP status: 404

No webhook endpoint was found matching the provided ID. Verify that the endpoint_id is correct and that the webhook has not been deleted. List your existing webhooks with GET /v3/webhooks/endpoints to find valid endpoint IDs.

resource_not_found

HTTP status: 404

The requested resource was not found. This is a generic not-found error for resources that do not have a more specific error code (e.g., streaming sessions, audio records). Verify that the resource ID is correct and belongs to your account.

invalid_parameter

HTTP status: 400

One or more request parameters are invalid, missing, or in the wrong format. The message field describes which parameter failed validation and why. The param field, when present, identifies the specific field.

Common causes:

  • A required field is missing from the request body.
  • A field value is the wrong type (e.g., string instead of number).
  • A field value is outside the allowed range or not in the set of accepted values.
  • The request body is not valid JSON or is not a JSON object.

conflict

HTTP status: 409

The request conflicts with existing state. For example, attempting to create a webhook endpoint with a URL that is already registered for your account. Use a different value or delete the existing resource first.

resource_not_ready

HTTP status: 409

The requested resource exists but is not yet in a ready state. This can occur when a video translation is still processing or an instant avatar has not finished training. Poll the resource status and retry once it reaches a ready state.

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

`POST /v3/video-agents` with `"mode": "chat"` — Send your initial prompt. The agent begins processing. `GET /v3/video-agents/{session_id}` — Check progress and read agent messages. The session pauses at `reviewing` status. `POST /v3/video-agents/{session_id}` — Send feedback or approve the storyboard. Repeat as needed. Send a message with `auto_proceed: true` or approve the storyboard. The session moves to `generating`, then `completed`.

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.
Set `auto_proceed: true` to skip the review step entirely — the session behaves like the one-shot mode but you still get a `session_id` to track.

Example

```bash curl theme={null} curl -X POST "https://api.heygen.com/v3/video-agents" \ -H "X-Api-Key: $HEYGEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Create a 2-minute onboarding video for new engineering hires. Cover team culture, dev tools, and first-week checklist.", "mode": "chat", "orientation": "landscape" }' ```
  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

POST a text description to `POST /v3/video-agents`. Optionally attach files, pick an avatar, or apply a style. The agent writes a script, selects visuals, and renders the video asynchronously. You receive a `session_id` immediately, and a `video_id` once generation begins. Poll `GET /v3/videos/{video_id}` until `status` is `completed`, then download via `video_url`. Or use a `callback_url` to get notified automatically.

Quick Start

```bash curl theme={null} curl -X POST "https://api.heygen.com/v3/video-agents" \ -H "X-Api-Key: $HEYGEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Create a 30-second product walkthrough for a new project management app" }' ```
  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
  }
}
`video_id` is `null` on creation and is populated once the agent begins rendering. Poll `GET /v3/video-agents/{session_id}` to track progress and retrieve the `video_id`.

Two Modes of Operation

Video Agent supports two workflows depending on how much control you need:

Mode How to use Best for
Generate (mode: "generate") POST /v3/video-agents — default Fire-and-forget. Send a prompt, get a video. The agent auto-proceeds through the storyboard.
Chat (mode: "chat") POST /v3/video-agents with "mode": "chat" Multi-turn interaction. The agent may pause for decisions (e.g. picking a voice), supports revisions and follow-up videos.

Both modes support the same file inputs, avatar/voice overrides, and style options.

{
  "prompt": "Create a product walkthrough for our new app",
  "mode": "chat"
}

Use POST /v3/video-agents/{session_id} to send follow-up messages, answer the agent's questions, or request revisions in a chat session.

Processing Time

Video generation is asynchronous. Processing times depend on video length, complexity, and your plan tier.
Factor Typical Range
Standard plans 5x–10x the final video length (e.g. a 1-min video takes ~5–10 min)
Enterprise plans Faster processing with priority queue access
Multi-scene Each scene adds to total processing time
Peak hours Processing may take longer during high-traffic periods
If a video has been processing for more than 24 hours, something is likely wrong. Contact [HeyGen Support](https://help.heygen.com) with your `video_id`.

Best practices:

  • Use callback_url instead of polling to reduce unnecessary API calls
  • Set reasonable poll intervals (10–30 seconds) if polling
  • Display a progress indicator to end users based on the 5x–10x benchmark

Choosing the Right Video API

Feature Video Agent Direct Video (v3)
Endpoint POST /v3/video-agents POST /v3/videos
Input Natural language prompt Structured JSON
Avatar selection Agent chooses (or you override) You specify
Script writing Agent writes it You write it
Best for Quick prototypes, simple videos Programmatic pipelines
Control level Low (prompt-driven) High (explicit)
Start with Video Agent. If you need precise control over script, avatar, or timing, use `POST /v3/videos` directly.

Key Concepts

Session — Every Video Agent request creates a session (session_id). Sessions track the agent's work: prompt, storyboard, generated assets, and final video. Retrieve session state via GET /v3/video-agents/{session_id}.

Video ID — The video_id is populated once rendering begins. Poll GET /v3/videos/{video_id} for status and the final download URL.

Styles — Curated visual templates that control scene composition, pacing, and aesthetics. Browse them via GET /v3/video-agents/styles and pass a style_id to your request.

File attachments — Images, videos, audio, and PDFs you provide as context. The agent uses these as visual references or content sources. Pass them via the files array as url, asset_id, or base64 inputs.

Incognito mode — Set incognito_mode: true to disable memory injection and extraction for a session.

Error Handling

All Video Agent endpoints return errors in a consistent format:

{
  "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
**OAuth vs API Key:** If you authenticate with an OAuth bearer token, usage is billed against your **web plan**, not the API tier. Check your web plan balance with `GET /v3/users/me → subscription`.

Using an API Key is recommended for automation and integration workflows. API key authentication provides higher concurrency limits and is more flexible and powerful for programmatic use.

Pricing

All rates are billed in USD based on output duration.

Video Generation — Avatar IV

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.

Migrating from v1 or v2? The legacy /v1 and /v2 endpoints will remain fully supported until October 31, 2026, but all new capabilities — including the CLI, MCP, Voice design API, improved error handling, the latest HeyGen models such as lipsync — are available exclusively on v3. We recommend migrating all new and existing integrations to v3. Read more [here](/endpoint-version-comparison). Go to [Settings → API](https://app.heygen.com/home?from=\&nav=API) in the HeyGen dashboard and generate a key. Save it — you can't view it again.
    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:

  1. Acknowledge your request
  2. Generate the video using your HeyGen account
  3. Post the finished video in the thread

Method 2: React with 🎥 emoji

Convert any message into a video by reacting with the camera emoji:

  1. Find a message you want to turn into a video
  2. Click Add reaction (or press R)
  3. Choose the 🎥 :movie_camera: emoji

The bot will use the message text as the video script.

Tip: You can also use a custom :heygen-video: emoji if your workspace has one.

Note: There's a 5-minute cooldown per message to prevent duplicate videos.

Method 3: Curate channel messages

Use the /heygen-curate slash command to find and compile top messages:

/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

  1. Visit your HeyGen account settings at app.heygen.com
  2. Navigate to Connections → Slack
  3. Click Link your Slack account
  4. Sign in to Slack and authorize the connection

Once linked, all videos you create will use your personal HeyGen account.

Check your link status

  1. On the settings menu, Make sure you are on Connections
  2. Check to see if the button is grayed out or says unlink on the Slack card

If the button is grayed out or says unlink, your heygen account and slack accounts are connected.

Unlink your account

To stop using your personal account and switch back to the workspace default:

  1. Go to your HeyGen account settings
  2. Connections → Slack
  3. Click Unlink

Rate limits

To ensure fair usage, the following limits apply:

Action Limit
Video creation 50 per minute, 500 per hour (per workspace)
/heygen-curate command 30 per minute, 300 per hour (per workspace)
Emoji reaction cooldown 1 video per message every 5 minutes

If you hit a rate limit, wait a few minutes and try again. You'll see a message like:

Rate limit reached. Please wait a moment and try again.

Troubleshooting

"Workspace not installed" error

Problem: The bot responds with "Workspace not installed. Please reinstall the HeyGen app."

Solution:

  • The app may have been uninstalled or credentials revoked
  • Reinstall the app following the Installation 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:
    1. Go to your Slack workspace settings
    2. Apps → HeyGen → Configuration
    3. Update the HeyGen credentials
    4. Save changes

FAQ

How much does it cost?

The HeyGen Slack app is free to install. Video generation uses HeyGen credits from your account:

  • Workspace default: Uses the account configured during installation
  • Personal linking: Uses your own HeyGen account and credits

See HeyGen pricing 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:

  1. Created in your HeyGen workspace (visible in your HeyGen dashboard)
  2. Uploaded directly to Slack (stored in your Slack workspace files)
  3. Accessible via the Slack message thread

Can I use this in private channels?

Yes! Invite the HeyGen bot to any channel:

/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:

  1. Go to your Slack workspace settings
  2. Apps → HeyGen
  3. Click Remove App
  4. Confirm removal

Your workspace data will be marked as deactivated but not deleted (for potential reinstallation).

Tips & best practices

Writing great video prompts

Do:

  • Be specific and clear: "Create a welcome video introducing our new design system update"
  • Include context: "Make a tutorial video explaining how to use the new login flow"
  • Keep it concise: Aim for 30-90 seconds of content

Don't:

  • Be too vague: "Make a video"
  • Use very long scripts: Messages over ~500 words may be truncated
  • Include formatting: The bot uses plain text, not markdown

Using /heygen-curate effectively

The curate command works best with:

  • Active channels with regular discussion
  • Time range: Last 24-48 hours typically has the best content
  • Engagement metrics: Reactions and replies indicate valuable messages

Pro tip: Use --notify in channels where you want to create visibility around the curation process.

Managing workspace credits

To avoid surprise credit usage:

  • Set up usage alerts in your HeyGen account
  • Encourage personal account linking for team members who create many videos
  • Monitor usage in your HeyGen analytics dashboard

Support

Need help?

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

```bash curl theme={null} curl "https://api.heygen.com/v3/video-agents/styles?tag=cinematic&limit=5" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  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.

Preview styles before using them. The `preview_video_url` on each style object shows a sample video rendered in that style — use it to pick the right look before generating.

Pagination

The styles endpoint uses cursor-based pagination. When has_more is true, pass the next_token value as the token query parameter in your next request:

# 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 pdf

Example request

```bash curl theme={null} curl -X POST "https://api.heygen.com/v3/assets" \ -H "X-Api-Key: $HEYGEN_API_KEY" \ -F "file=@./product-screenshot.png" ```
  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:

Upload once, reference by ID. Best for files you reuse across multiple videos. Point to a publicly accessible URL. No upload step needed — HeyGen fetches the file directly. Inline the file content as a base64-encoded string. Useful for small files or when you want a self-contained request.

Format comparison

Format Syntax When to use
Asset ID { "type": "asset_id", "asset_id": "asset_..." } Pre-uploaded files, reusable across requests.
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.
Base64 encoding increases payload size by \~33%. For files larger than a few MB, prefer uploading via `POST /v3/assets` or providing a URL.

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:

```bash curl theme={null} # Step 1: Upload the PDF ASSET_ID=$(curl -s -X POST "https://api.heygen.com/v3/assets" \ -H "X-Api-Key: $HEYGEN_API_KEY" \ -F "file=@./quarterly-report.pdf" | jq -r '.data.asset_id')

echo "Uploaded asset: $ASSET_ID"

Step 2: Generate a video using the uploaded PDF

curl -X POST "https://api.heygen.com/v3/video-agents"
-H "X-Api-Key: $HEYGEN_API_KEY"
-H "Content-Type: application/json"
-d "{ "prompt": "Summarize the key findings from this quarterly report in a 60-second video", "files": [{ "type": "asset_id", "asset_id": "$ASSET_ID" }] }"


```python Python theme={null}
import requests

# Step 1: Upload
with open("quarterly-report.pdf", "rb") as f:
    upload_resp = requests.post(
        "https://api.heygen.com/v3/assets",
        headers={"X-Api-Key": HEYGEN_API_KEY},
        files={"file": f},
    )
asset_id = upload_resp.json()["data"]["asset_id"]

# Step 2: Generate
gen_resp = requests.post(
    "https://api.heygen.com/v3/video-agents",
    headers={"X-Api-Key": HEYGEN_API_KEY},
    json={
        "prompt": "Summarize the key findings from this quarterly report in a 60-second video",
        "files": [{"type": "asset_id", "asset_id": asset_id}],
    },
)
session = gen_resp.json()["data"]

Usage Limits

Source: https://developers.heygen.com/docs/usage-limits

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.

This is the **one-shot** workflow — send a prompt, get a video. For multi-turn collaboration with the agent, see [Interactive Sessions](/docs/interactive-sessions).
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:

```json URL theme={null} { "type": "url", "url": "https://example.com/slide-deck.pdf" } ```
  { "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

```bash curl theme={null} curl -X POST "https://api.heygen.com/v3/video-agents" \ -H "X-Api-Key: $HEYGEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Create a 45-second explainer about our Q3 product launch. Use a friendly, upbeat tone. Include the attached slides as visual context.", "orientation": "landscape", "files": [\ { "type": "url", "url": "https://example.com/q3-launch-deck.pdf" }\ ] }' ```
  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}
```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/videos/vid_xyz789" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  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

  1. Be descriptive in your prompt. Include details about tone, target audience, visual style, and pacing — the agent uses all of this to make better decisions.
  2. Attach reference files. Pass slides, images, or documents in the files array to give the agent visual context.
  3. Use orientation when you know the target platform (e.g. "portrait" for mobile/social, "landscape" for presentations).
  4. Apply a style for consistent visual branding across videos. See Styles & References.
  5. Pin a specific avatar or voice with avatar_id and voice_id for 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_num is especially important in Precision mode — accurate speaker separation directly improves the quality of avatar inference per speaker.

Captions

To enable captions, set enable_caption: true in the translation request. Once completed, download them:

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

```bash curl theme={null} curl -X POST "https://api.heygen.com/v3/voices" \ -H "X-Api-Key: $HEYGEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "A warm, confident male voice with a slight British accent. Deep baritone, measured pace, suitable for tech product narration.", "gender": "male" }' ```
  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
  }'
**Prompting tips:**
  • Specify gender and age ("young woman in her 20s", "mature male voice")
    • Describe the accent ("American Midwest", "slight French accent")
    • Set the tone ("warm and friendly", "authoritative", "playful")
    • Mention pacing ("measured and calm", "energetic and fast")
    • Reference a use case ("suitable for corporate training", "good for storytelling")

Voices

Source: https://developers.heygen.com/docs/voices/overview

Browse 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.

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/voices?language=English&gender=female" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  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".
Each voice includes a `preview_audio_url` — play these to audition voices before using one in your video.

Step 2: Design a Custom Voice (Optional)

If none of the pre-built voices fit, use POST /v3/voices 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.
**Prompting tips for voice design:**
  • Specify gender and approximate age ("young woman in her 20s", "mature male voice")
    • Describe the accent ("American Midwest", "slight French accent")
    • Set the tone ("warm and friendly", "authoritative", "playful")
    • Mention pacing ("measured and calm", "energetic and fast")
    • Reference a use case ("suitable for corporate training", "good for storytelling")

Step 3: Use a Voice in Video Creation

Once you have a voice_id, pass it when creating a video.

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

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/voices?language=English&gender=female&limit=5" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  {
    "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.

Starfish only works with **Starfish-compatible voices**. Not all HeyGen voices support this engine. Use `GET /v3/voices?engine=starfish` to get a list of compatible voices before calling this endpoint.

Quick Example

```bash curl theme={null} curl -X POST "https://api.heygen.com/v3/voices/speech" \ -H "X-Api-Key: $HEYGEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Hello from HeyGen!", "voice_id": "1bd001e7e50f421d891986aad5c8bbd2" }' ```
  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:

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/webhooks/event-types" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  {
    "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.

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/webhooks/events?event_type=avatar_video.success&limit=5" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  {
    "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:

```json "Subscribe to video events only" theme={null} { "url": "https://yourapp.com/webhooks/heygen", "events": [\ "avatar_video.success",\ "avatar_video.fail",\ "video_agent.success",\ "video_agent.fail"\ ] } ```
  {
    "url": "https://yourapp.com/webhooks/heygen"
  }

Omitting the events field (or setting it to null) subscribes the endpoint to all event types.

To change subscriptions later, use PATCH /v3/webhooks/endpoints/{endpoint_id} with an updated events array.

Handling Events in Your Application

When an event is delivered, HeyGen sends a POST request to your endpoint URL with the event payload as JSON. A typical handler:

  1. Verify the signature using your endpoint's signing secret.
  2. Parse event_type to determine what happened.
  3. Process event_data — for success events this typically includes the video_id and video_url; for failures it includes error details.
  4. 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.

```bash curl theme={null} curl -X POST "https://api.heygen.com/v3/webhooks/endpoints" \ -H "X-Api-Key: $HEYGEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.com/webhooks/heygen", "events": ["avatar_video.success", "avatar_video.fail"] }' ```
  {
    "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

```bash curl theme={null} curl -X GET "https://api.heygen.com/v3/webhooks/endpoints?limit=10" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  {
    "data": [\
      {\
        "endpoint_id": "ep_abc123",\
        "url": "https://yourapp.com/webhooks/heygen",\
        "events": ["avatar_video.success", "avatar_video.fail"],\
        "status": "enabled",\
        "created_at": "2026-03-25T12:00:00Z",\
        "secret": null\
      }\
    ],
    "has_more": false,
    "next_token": null
  }
Parameter Type Required Default Description
limit integer No 10 Results per page (1–100).
token string No — Opaque cursor for the next page.
The `secret` field is only returned when creating an endpoint or rotating the secret. It will be `null` in list responses.

Update an Endpoint

Change the URL and/or subscribed event types. The events array is fully replaced — include all event types you want to keep.

```bash curl theme={null} curl -X PATCH "https://api.heygen.com/v3/webhooks/endpoints/ep_abc123" \ -H "X-Api-Key: $HEYGEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.com/webhooks/heygen-v2", "events": ["avatar_video.success", "avatar_video.fail", "video_agent.success"] }' ```
  {
    "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.

```bash curl theme={null} curl -X DELETE "https://api.heygen.com/v3/webhooks/endpoints/ep_abc123" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  {
    "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.

```bash curl theme={null} curl -X POST "https://api.heygen.com/v3/webhooks/endpoints/ep_abc123/rotate-secret" \ -H "X-Api-Key: $HEYGEN_API_KEY" ```
  {
    "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:

```json "Video Agent with callback" theme={null} { "prompt": "A product demo for our new app", "callback_url": "https://yourapp.com/callbacks/heygen", "callback_id": "my-custom-id-123" } ```

The callback_id is echoed back in the webhook payload so you can correlate the notification with your request.

E-commerce Product Videos

Source: https://developers.heygen.com/e-commerce-product-videos

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

```python theme={null} # From your catalog API, database, or CSV products = [\ {\ "name": "CloudWalk Pro Running Shoes",\ "category": "Footwear",\ "price": "$129",\ "description": "Lightweight performance running shoe with responsive foam midsole and breathable knit upper.",\ "features": [\ "ResponsiveFoam midsole — 30% more energy return",\ "Breathable knit upper — keeps feet cool on long runs",\ "Carbon fiber plate — propels you forward",\ "Only 7.2 oz — one of the lightest in its class",\ ],\ "images": [\ "https://cdn.store.com/products/cloudwalk-hero.jpg",\ "https://cdn.store.com/products/cloudwalk-side.jpg",\ "https://cdn.store.com/products/cloudwalk-sole.jpg",\ ],\ },\ # ... hundreds more\ ] ``` Consider using different video styles for different product categories. For example, fashion might benefit from energy and aspiration, while electronics might call for clarity and specs.
    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

Same catalog-to-video pattern, applied to properties. Distribute product videos across social platforms.

Endpoint Version Comparison

Source: https://developers.heygen.com/endpoint-version-comparison

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/generate and POST /v2/video/generate with POST /v3/videos
  • Replace GET /v1/video_status.get with GET /v3/videos/{id}
  • Replace avatar and voice listing endpoints with v3 equivalents
  • Replace POST /v1/video/upload with POST /v3/assets
  • Replace GET /v1/user/me with GET /v3/users/me
  • Migrate video translation calls to /v3/video-translations
  • Update webhook handling from callback_url to managed /v3/webhooks/endpoints
  • Update pagination logic to cursor-based where applicable
  • Flag: If using Studio API (multi-scene) or Template API — these remain v2-only for now; monitor the v3 changelog 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"]
  }
}
`--wait` only supports single-language translations. For batch (multiple `output_languages`), poll each ID individually with `heygen video-translate get`.

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"
  }
}
Store the `secret` securely — it's used to verify webhook signatures and won't be shown again. Use `heygen webhook endpoints rotate-secret ` to generate a new one.

List all available event types:

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 create
  • heygen video-agent create
  • heygen video-translate create
  • heygen lipsync create

Complex Request Bodies (-d / --data)

Endpoints with nested inputs — discriminated unions, arrays of objects, nested configs — use -d for raw JSON instead of individual flags:

# 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 type
  • message — human-readable description
  • hint — suggested action to resolve the error
  • request_id — included when the error comes from the API (from the X-Request-ID header). 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

A Digital Twin `avatar_id` (type: `digital_twin`). Use `GET /v3/avatars/looks?avatar_type=digital_twin` to find yours. A `voice_id` for the voice you want. Use `GET /v3/voices` to browse available voices.

Step 1 — Find your Digital Twin

List your private Digital Twin looks to get the avatar_id:

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

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

Step 2 — Create the video

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

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

Step 3 — Poll for completion

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

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

Status values

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

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

Full example

import requests
import time

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

# 1. Create the video
resp = requests.post(f"{BASE}/v3/videos", headers=HEADERS, json={
    "type": "avatar",
    "avatar_id": "YOUR_DIGITAL_TWIN_LOOK_ID",
    "script": "Welcome to our product demo. Let me walk you through the new features.",
    "voice_id": "YOUR_VOICE_ID",
    "resolution": "1080p",
    "aspect_ratio": "16:9"
})
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"
}
Register a webhook endpoint via `POST /v3/webhooks/endpoints` and subscribe to `avatar_video.success` and `avatar_video.fail` events for production use.

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

A portrait photo (PNG or JPEG) — clear, front-facing, good lighting A `voice_id` for the voice you want. Use `GET /v3/voices` to browse options.

Step 1 — Create a Photo Avatar

Upload your photo and create a Photo Avatar with POST /v3/avatars:

```bash theme={null} 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": "url", "url": "https://example.com/portrait.jpg" } }' ``` First upload the image via `POST /v3/assets`, then reference the returned `asset_id`:
    # 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
Photo quality matters. Use a well-lit, front-facing portrait with a neutral expression for the best results. Avoid sunglasses, hats covering the forehead, or extreme angles.

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

An image of a person (PNG or JPEG) — accessible via a public URL or uploaded as an asset A `voice_id` for the voice you want. Use `GET /v3/voices` to browse options.

Step 1 — Generate the video

Use POST /v3/videos with type: "image" and an image object instead of avatar_id:

```bash theme={null} curl -X POST "https://api.heygen.com/v3/videos" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "image", "image": { "type": "url", "url": "https://example.com/person.jpg" }, "script": "Hello! This video was generated directly from a photo, with no avatar setup needed.", "voice_id": "YOUR_VOICE_ID", "title": "Image to Video Demo", "resolution": "1080p", "aspect_ratio": "16:9" }' ``` First upload via `POST /v3/assets`, then reference the returned `asset_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"
}
`script` and `audio_url`/`audio_asset_id` are mutually exclusive. If you provide a `script`, you must also provide a `voice_id`.

Optional parameters

Parameter Type Description
title string Display name in the HeyGen dashboard
resolution string 4k, 1080p, 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
If you plan to generate multiple videos with the same person, create a Photo Avatar once and reuse its `avatar_id`. This saves processing time and unlocks motion and expressiveness controls.

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

  1. Connect — Add the HeyGen remote MCP endpoint to your agent
  2. Authenticate — Sign in with your HeyGen account via OAuth (one-time)
  3. 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/
H Cvxlz DWIA Awx O6 H Cvy Fxba UAA0C Gl H Cvy UKI Wo A Ale2x

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.

H Cvz Fgb Xmaa7t Vo H Cvz S Bd Xk A Ek SZL

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.

H Cvws P0W8A Eb Nk G H Cvw2e2xk A Ae8o K

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/
H Cvxlz DWIA Awx O6 H Cvy Fxba UAA0C Gl H Cvy UKI Wo A Ale2x

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.

H Cvz Fgb Xmaa7t Vo H Cvz S Bd Xk A Ek SZL

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.
H Cvws P0W8A Eb Nk G H Cvw2e2xk A Ae8o K

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.

Hero5f Ka IA Amxxy Hero9km X0A Ayi PG HERO 6NWAAABPX HERPC Rz Wc A Ae Ifz

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
HER Qw G6WEAA Fqh H

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
HER Sdc Bw A Aj9f8

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.

Screenshot 2026 04 02 At 8 52 33 AM

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.\

Screenshot 2026 04 02 At 8 53 39 AM Screenshot 2026 04 02 At 8 54 15 AM

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.

Screenshot 2026 04 02 At 8 54 50 AM Screenshot 2026 04 02 At 8 55 35 AM

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."

Screenshot 2026 04 02 At 8 57 01 AM

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

Fully maintained, but no longer receiving new feature investment.\ \ *Deprecation Timeline — Oct 31, 2026* The actively developed API. All new endpoints, models, and capabilities — including Avatar IV and Video Agent — are v3-first.

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.