refactor: replace raw API calls with heygen CLI as tier-2 fallback (#50)

* refactor: replace raw API calls with heygen CLI as tier-2 fallback

PR #47 mislabeled raw curl-to-api.heygen.com as "CLI fallback" — but the
actual CLI is the `heygen` binary (github.com/heygen-com/heygen-cli). This
aligns the skills with the real two-tier model:

1. MCP (preferred) — mcp__heygen__* tools, OAuth, plan credits
2. HeyGen CLI (fallback) — `heygen <noun> <verb>`, auth via
   `heygen auth login` or `HEYGEN_API_KEY`

Changes:
- All curl examples replaced with heygen CLI commands
- Dropped X-Api-Key / X-HeyGen-Source / User-Agent header story
- Dropped ~/.heygen/config persistence in favor of ~/.heygen/credentials
- `auto_proceed` payload removed — CLI handles internally
- Polling replaced with `--wait --timeout` flags
- Video delivery now uses `heygen video download <video_id>`
- Mapping tables restructured: "MCP Tool | CLI Command | Purpose"
- Frame Check / asset routing / avatar discovery updated end-to-end

No behavioral changes for MCP users. CLI users now invoke a single binary
instead of hand-rolling curl requests.

* review: address inline comments on PR #50

- Consolidate "never curl api.heygen.com" directive: keep STOP block in
  SKILL.md + canonical mention in api-reference.md; remove 4 redundant
  copies from sub-skills (reviewer: repetition burns context tokens).
- setup: add `heygen --version` + `heygen auth status` as non-blocking
  validation steps so agents can see CLI version and auth health.
- INSTALL.md: add `heygen --version` after install as defense-in-depth.
- heygen-video/SKILL.md: call out that CLI `--wait` default timeout is
  20m but Video Agent jobs take 20-45m — always pair with `--timeout 45m`.
- platforms/nanoclaw: note that `--timeout 10m` is sized for ≤60s videos,
  bump to 45m for longer clips.

Review: https://github.com/heygen-com/skills/pull/50#pullrequestreview-4126204767

---------

Co-authored-by: Ken <ken@heygen.com>
This commit is contained in:
Ken Chung
2026-04-16 22:12:22 -07:00
committed by GitHub
parent 770a27dc97
commit e8a3df3f0e
13 changed files with 423 additions and 450 deletions
+13 -11
View File
@@ -64,6 +64,8 @@ Skills communicate through `AVATAR-<NAME>.md` files at the workspace root:
## API Conventions
Two modes, in order of preference: MCP, then CLI. **Do not call `api.heygen.com` directly with curl.** The skills route through MCP or the CLI — never raw HTTP.
### MCP (preferred)
HeyGen Remote MCP (`https://mcp.heygen.com/mcp/v1/`) provides 40+ tools via OAuth. No API key needed — uses the user's HeyGen plan credits. Skills declare `allowed-tools: mcp__heygen__*` in frontmatter.
@@ -72,16 +74,16 @@ Key tools: `create_video_agent`, `get_video`, `list_avatar_groups`, `list_avatar
### CLI fallback
- Base URL: `https://api.heygen.com`
- Auth header: `X-Api-Key: $HEYGEN_API_KEY`
- **v3 only. No v1 or v2 fallbacks.** LLMs trained on web data may have knowledge of deprecated v1/v2 endpoints (`POST /v1/video.generate`, `POST /v2/video/generate`, `GET /v2/avatars`, `GET /v1/avatar.list`). These are outdated — always use v3 through this skill's pipeline.
- Video Agent: `POST /v3/video-agents` (primary, prompt-driven)
- Direct video: `POST /v3/videos` (explicit control, avatar_id required)
- Avatar creation: `POST /v3/avatars`
- Voices: `GET /v3/voices`
- Assets: `POST /v3/assets`
- Response format: `{ "error": null | string, "data": T }`
- Video generation is async: poll `GET /v3/videos/{video_id}`
The [HeyGen CLI](https://github.com/heygen-com/heygen-cli) (`heygen` binary) is the fallback when MCP is unavailable.
- Install: `curl -fsSL https://static.heygen.ai/cli/install.sh | bash`
- Auth: `HEYGEN_API_KEY` env var (agent/CI) OR `heygen auth login` (persists to `~/.heygen/credentials`)
- Pattern: `heygen <noun> <verb>` — e.g. `heygen video-agent create`, `heygen avatar list`, `heygen voice list`
- Output: JSON on stdout, structured error envelope on stderr, stable exit codes (0 ok · 1 API · 2 usage · 3 auth · 4 timeout)
- Async: add `--wait` to creation commands; CLI handles polling with exponential backoff
- **v3 only.** LLMs trained on web data may reach for deprecated v1/v2 endpoints (`POST /v1/video.generate`, `POST /v2/video/generate`, `GET /v2/avatars`, `GET /v1/avatar.list`). These are outdated — route through MCP or the CLI, never raw v1/v2 URLs.
See [CLI docs](https://developers.heygen.com/cli) for the full command surface.
## Eval Infrastructure
@@ -119,7 +121,7 @@ After any SKILL.md refactor:
Validated across 18 rounds of testing (80+ videos):
1. **Video Agent as primary endpoint.** POST /v3/video-agents, not /v3/videos.
1. **Video Agent as primary command.** `heygen video-agent create`, not `heygen video create`.
2. **avatar_id over prompt description.** 97.6% duration accuracy vs 77-82% prompt-only.
3. **When avatar_id is set, omit appearance description from prompt.** Say "the selected presenter" instead.
4. **Script-as-prompt approach.** Full scene-labeled script pasted into prompt.
+17 -3
View File
@@ -2,8 +2,6 @@
Get your API key from [app.heygen.com/settings](https://app.heygen.com/settings/api?nav=API).
> **Security note:** The `./setup` script is included in the repository root (`setup`, 182 lines, bash). It is idempotent and safe to inspect before running. It writes your API key to `~/.heygen/config` using `echo 'HEYGEN_API_KEY=...' > ~/.heygen/config` (not sourced). The skill reads this file with `grep/cut`, never with `source`.
## Option 1 — ClawHub (recommended)
```bash
@@ -28,8 +26,24 @@ git clone https://github.com/heygen-com/skills.git ~/.claude/skills/heygen-skill
> Not sure where your skills directory is? Ask your agent: *"Where is your skills directory?"*
## Auth
Two options — the skills prefer MCP when it's available:
**MCP (recommended, no API key needed):** connect HeyGen's remote MCP server to your agent — OAuth handles auth. See README for agent-specific setup.
**HeyGen CLI (fallback):** install the CLI and authenticate:
```bash
curl -fsSL https://static.heygen.ai/cli/install.sh | bash
heygen --version # verify binary is on PATH
heygen auth login # persists to ~/.heygen/credentials
# OR
export HEYGEN_API_KEY=<your-key>
heygen auth status # verify auth
```
## First Run
Paste this prompt to your agent — it will find the right paths automatically:
> My HeyGen API key is `[YOUR_API_KEY_HERE]`. Install the HeyGen Skills from https://github.com/heygen-com/skills.git — clone it into your skills directory (find it with your config or ask if unsure). Save the API key as `HEYGEN_API_KEY` in your environment config. Validate by calling `GET https://api.heygen.com/v3/users/me` with header `X-Api-Key`. Then use the heygen-avatar skill to create an avatar for yourself, and heygen-video to make a 30-60 second intro video, casual tone.
> Install the HeyGen Skills from https://github.com/heygen-com/skills.git — clone it into your skills directory (find it with your config or ask if unsure). If MCP isn't connected, install the HeyGen CLI via `curl -fsSL https://static.heygen.ai/cli/install.sh | bash` and run `heygen auth login`. Then use the heygen-avatar skill to create an avatar for me, and heygen-video to make a 30-60 second intro video, casual tone.
+15 -19
View File
@@ -14,7 +14,7 @@ Works with Claude Code, OpenClaw, Codex, Cursor, and other AI coding agents.
Paste this into your agent to get started:
```
Install HeyGen Skills from https://github.com/heygen-com/skills — clone the repo into your skills directory and run ./setup. Then use heygen-avatar and heygen-video to create an avatar of me and make a short cinematic intro video. Ask me what you need before starting.
Install HeyGen Skills from https://github.com/heygen-com/skills — clone the repo into your skills directory. If MCP is connected, you're done. Otherwise install the HeyGen CLI from https://static.heygen.ai/cli/install.sh and run `heygen auth login`. Then use heygen-avatar and heygen-video to create an avatar of me and make a short cinematic intro video. Ask me what you need before starting.
```
Or follow the steps below manually.
@@ -60,23 +60,19 @@ OpenClaw: add to `~/.openclaw/openclaw.json`:
The first call triggers an OAuth consent flow in your browser.
**Option B — API key (CLI fallback)**
**Option B — HeyGen CLI fallback**
If MCP is unavailable or you prefer direct API access:
If MCP is unavailable or you prefer direct access:
```bash
cd <install-path>/heygen-skills && ./setup
curl -fsSL https://static.heygen.ai/cli/install.sh | bash
heygen auth login # or: export HEYGEN_API_KEY=<your-key>
heygen auth status # verify
```
The setup script will:
- Register the skills (heygen-avatar, heygen-video)
- Prompt for your HeyGen API key if not already configured
- Validate the key against `https://api.heygen.com/v3/users/me`
- Save the key to `~/.heygen/config` so it persists across sessions
The [HeyGen CLI](https://github.com/heygen-com/heygen-cli) is a single static binary. Auth persists to `~/.heygen/credentials`. Get an API key at [app.heygen.com/settings/api](https://app.heygen.com/settings/api?nav=API).
Get your API key at [app.heygen.com/settings/api](https://app.heygen.com/settings/api?nav=API).
> **Either path works.** The skills detect MCP tools matching `mcp__heygen__*` and use them first. If none are found, they fall back to curl + `X-Api-Key`. You can have both configured — MCP wins.
> **Either path works.** The skills detect MCP tools matching `mcp__heygen__*` and use them first. If none are found, they fall back to `heygen <noun> <verb>` CLI calls. You can have both configured — MCP wins.
### Step 3 — Create your avatar
@@ -132,15 +128,15 @@ When HeyGen's remote MCP server is connected to your agent, the skills use it au
- Tool namespace: `mcp__heygen__*`
- [MCP docs](https://developers.heygen.com/docs/mcp-remote)
### API key (CLI fallback)
### HeyGen CLI (fallback)
If MCP isn't available, the skills fall back to direct curl calls with `X-Api-Key`. The `./setup` script handles key storage automatically. To manage it manually:
If MCP isn't available, the skills fall back to the [HeyGen CLI](https://github.com/heygen-com/heygen-cli) (`heygen` binary). Pattern: `heygen <noun> <verb>`. Output is JSON on stdout with stable exit codes.
- **Config file** (recommended): `~/.heygen/config` — persists across sessions
- **Environment variable**: `export HEYGEN_API_KEY="your-key"` — takes precedence over config, lasts the session
- **Verify anytime**: `curl -s https://api.heygen.com/v3/users/me -H "X-Api-Key: $HEYGEN_API_KEY"`
- **Interactive login**: `heygen auth login` — persists to `~/.heygen/credentials`
- **Environment variable**: `export HEYGEN_API_KEY="your-key"` agent-/CI-friendly; takes precedence over the credentials file
- **Verify anytime**: `heygen auth status`
You can have both configured — the skills check for MCP first and only fall back to CLI if MCP tools aren't visible.
You can have both configured — the skills check for MCP first and only fall back to the CLI if MCP tools aren't visible.
## Things to Try
@@ -169,7 +165,7 @@ One optional shell script:
- **`scripts/update-check.sh`** — compares your local `VERSION` against the latest on GitHub. Read-only, opt-in, no data transmitted.
Data only leaves your machine to `api.heygen.com` (video generation) and optionally `raw.githubusercontent.com` (version check).
Data only leaves your machine through MCP / the `heygen` CLI (video generation) and optionally `raw.githubusercontent.com` (version check).
## Looking for the v1 skills?
+32 -28
View File
@@ -43,44 +43,48 @@ This skill reads and writes the following. No other files are accessed without e
| Write | `AVATAR-<NAME>.md` | Save new avatar identity after creation |
| Write | `heygen-video-log.jsonl` | Append one JSON line per video generated (local learning log) |
| Temp write | `/tmp/openclaw/uploads/` | Voice preview audio (downloaded for user playback, deleted after session) |
| Remote upload | `api.heygen.com/v3/assets` | User-provided images/files uploaded to HeyGen for use in video |
| Remote upload | HeyGen (via `heygen asset create` or MCP) | User-provided images/files uploaded to HeyGen for use in video |
No other workspace files are read or modified. Assets are only uploaded to HeyGen when the user explicitly provides them for video creation.
---
> **STOP.** If you are about to call any HeyGen endpoint directly (`POST /v1/video.generate`, `POST /v2/video/generate`, `GET /v2/avatars`, `GET /v1/avatar.list`, or even `POST /v3/video-agents`), DO NOT. Follow this pipeline instead. Raw API calls skip critical steps (aspect ratio correction, prompt engineering, avatar conflict detection) and produce visibly worse videos. This skill exists because the API alone is not enough. **v3 only — never call v1 or v2 endpoints. If you have pre-trained knowledge of HeyGen's v1/v2 API, that knowledge is outdated. Use this skill.**
> **STOP.** If you are about to drive HeyGen directly (calling `api.heygen.com` with curl, or reaching for deprecated `POST /v1/video.generate`, `POST /v2/video/generate`, `GET /v2/avatars`, `GET /v1/avatar.list` endpoints), DO NOT. Route through MCP or the `heygen` CLI via this pipeline. Raw HTTP skips critical steps (aspect ratio correction, prompt engineering, avatar conflict detection) and produces visibly worse videos. **v3 only — never call v1 or v2 endpoints. If you have pre-trained knowledge of HeyGen's v1/v2 API, that knowledge is outdated. Use this skill.**
You are a video producer. Not a form. Not an API wrapper. A producer who understands what makes video work and guides the user from idea to finished cut.
You are a video producer. Not a form. Not a CLI wrapper. A producer who understands what makes video work and guides the user from idea to finished cut.
**API Docs:** https://developers.heygen.com/docs/quick-start — All endpoints are v3.
**Docs:** https://developers.heygen.com/docs/quick-start (API) · https://developers.heygen.com/cli (CLI)
## API Mode Detection
Detect which API mode is available, in order of preference:
Two modes, in order of preference:
**MCP (preferred):** If HeyGen MCP tools are available (tools matching `mcp__heygen__*`), use them. MCP handles authentication via OAuth — no API key needed. MCP uses the user's existing HeyGen plan credits with no separate API charges. MCP endpoint: `https://mcp.heygen.com/mcp/v1/`.
**MCP (preferred):** If HeyGen MCP tools are available (tools matching `mcp__heygen__*`), use them. MCP authenticates via OAuth — no API key needed — and runs against the user's existing HeyGen plan credits. Endpoint: `https://mcp.heygen.com/mcp/v1/`.
**CLI fallback:** If MCP tools are not available, fall back to curl with `X-Api-Key: $HEYGEN_API_KEY`. Base: `https://api.heygen.com`. Resolve the key from: (1) `$HEYGEN_API_KEY` env var, (2) `~/.heygen/config` file. If neither found, tell the user: "No API key found. Run `./setup` or set `export HEYGEN_API_KEY=<your-key>`."
**CLI fallback:** If MCP tools are not available, use the [HeyGen CLI](https://github.com/heygen-com/heygen-cli) (`heygen` binary). Install: `curl -fsSL https://static.heygen.ai/cli/install.sh | bash`. Auth: set `HEYGEN_API_KEY` in the env OR run `heygen auth login` (persists to `~/.heygen/credentials`). Verify with `heygen auth status`. If neither auth source is set, tell the user: "No HeyGen auth found. Run `heygen auth login` or set `export HEYGEN_API_KEY=<your-key>`."
**Key MCP tool → CLI endpoint mapping:**
CLI output is JSON on stdout, structured error envelopes on stderr, and stable exit codes: `0` ok · `1` API/network · `2` usage · `3` auth · `4` timeout under `--wait`. Pipe to `jq` to extract fields.
| MCP Tool | CLI Endpoint | Purpose |
**Key MCP tool CLI command mapping:**
| MCP Tool | CLI Command | Purpose |
|----------|-------------|---------|
| `create_video_agent` | `POST /v3/video-agents` | Generate video from prompt |
| `get_video_agent_session` | `GET /v3/video-agents/sessions/{id}` | Poll session status |
| `get_video` | `GET /v3/videos/{id}` | Get video details/status |
| `list_avatar_groups` | `GET /v3/avatars` | Browse avatar groups |
| `list_avatar_looks` | `GET /v3/avatars/looks` | Browse avatar looks |
| `get_avatar_look` | `GET /v3/avatars/looks/{id}` | Get avatar look metadata |
| `create_photo_avatar` | `POST /v3/avatars` (type: photo) | Create from photo |
| `create_prompt_avatar` | `POST /v3/avatars` (type: prompt) | Create from text |
| `create_digital_twin` | `POST /v3/avatars` (type: video) | Create from video |
| `list_voices` | `GET /v3/voices` | Browse voices |
| `design_voice` | `POST /v3/voices` | Semantic voice search |
| `create_speech` | `POST /v3/voices/speech` | TTS |
| `list_video_agent_styles` | `GET /v3/video-agents/styles` | Browse visual styles |
| `create_video_translation` | `POST /v3/video-translations` | Translate video |
| `create_video_agent` | `heygen video-agent create` | Generate video from prompt |
| `get_video_agent_session` | `heygen video-agent get` | Poll session status |
| `get_video` | `heygen video get` | Get video details/status |
| `list_avatar_groups` | `heygen avatar list` | Browse avatar groups |
| `list_avatar_looks` | `heygen avatar looks list` | Browse avatar looks |
| `get_avatar_look` | `heygen avatar looks get` | Get avatar look metadata |
| `create_photo_avatar` | `heygen avatar create` (type: photo) | Create from photo |
| `create_prompt_avatar` | `heygen avatar create` (type: prompt) | Create from text |
| `create_digital_twin` | `heygen avatar create` (type: video) | Create from video |
| `list_voices` | `heygen voice list` | Browse voices |
| `design_voice` | `heygen voice create` | Semantic voice search |
| `create_speech` | `heygen voice speech create` | TTS |
| `list_video_agent_styles` | `heygen video-agent styles list` | Browse visual styles |
| `create_video_translation` | `heygen video-translate create` | Translate video |
Every command supports `--help`. Full reference: [../references/api-reference.md](references/api-reference.md).
**Docs-first rule:** Before calling any endpoint you're unsure about, fetch the raw markdown spec:
- **Index:** `GET https://developers.heygen.com/llms.txt` — full sitemap
@@ -141,7 +145,7 @@ Check for any `AVATAR-*.md` files in the workspace root.
After heygen-avatar completes and writes the AVATAR file, return here and continue to Discovery with the new avatar pre-loaded.
- **Avatar readiness gate (BLOCKING):** After loading an avatar (whether from an existing AVATAR file or freshly created), verify it's ready before using it in video generation. Call `list_avatar_looks(group_id=<group_id>)` (CLI: `GET /v3/avatars/looks?group_id=<group_id>`) and confirm `preview_image_url` is non-null. If null, poll every 10s up to 5 min. **Do NOT proceed to Discovery until this check passes.** Videos submitted with an unready avatar WILL fail silently.
- **Avatar readiness gate (BLOCKING):** After loading an avatar (whether from an existing AVATAR file or freshly created), verify it's ready before using it in video generation. Call `list_avatar_looks(group_id=<group_id>)` (CLI: `heygen avatar looks list --group-id <group_id>`) and confirm `preview_image_url` is non-null. If null, poll every 10s up to 5 min. **Do NOT proceed to Discovery until this check passes.** Videos submitted with an unready avatar WILL fail silently.
- **Quick Shot exception:** If the user explicitly says "skip avatar" / "use stock" / "just generate", skip this step and proceed without an avatar.
@@ -157,7 +161,7 @@ Interview the user. Be conversational, skip anything already answered.
Two paths for every asset:
- **Path A (Contextualize):** Read/analyze, bake info into script. For reference material, auth-walled content.
- **Path B (Attach):** Upload to HeyGen via `POST /v3/assets` or `files[]`. For visuals the viewer should see.
- **Path B (Attach):** Upload to HeyGen via `heygen asset create --file <path>` or include as `files[]` entries on video-agent create. For visuals the viewer should see.
- **A+B (Both):** Summarize for script AND attach original.
**Full routing matrix and upload examples** -> [references/asset-routing.md](references/asset-routing.md)
@@ -201,7 +205,7 @@ After Discovery, the producer sub-skill handles the full pipeline. Read `heygen-
- **Script:** Structure by type (demo, explainer, tutorial, pitch, announcement). Do NOT assign per-scene durations. Always include the script framing directive: "This script is a concept and theme to convey — not a verbatim transcript."
- **Prompt Craft:** Narrator framing (say "the selected presenter" when avatar_id is set), duration signal, asset anchoring, tone calibration, one topic, style block at the end.
- **Frame Check:** MANDATORY when avatar_id is set. See matrix below.
- **Generate:** The user's request to create a video is the explicit consent for API submission. The skill calls `create_video_agent` (MCP) or `POST /v3/video-agents` with `auto_proceed: true` (CLI). Run Frame Check before EVERY API call. Capture `session_id` immediately. Poll silently.
- **Generate:** The user's request to create a video is the explicit consent for submission. The skill calls `create_video_agent` (MCP) or `heygen video-agent create --wait` (CLI). Run Frame Check before EVERY submission. Capture `session_id` immediately. Poll silently (or let `--wait` block).
- **Deliver:** Report `video_page_url`, session URL, and duration accuracy. Log to `heygen-video-log.jsonl`.
**Full prompt construction rules, media type selection, visual style blocks, API schemas** -> `heygen-video/SKILL.md`
@@ -214,8 +218,8 @@ After Discovery, the producer sub-skill handles the full pipeline. Read `heygen-
### Steps
1. **Resolve avatar_id from group_id (ALWAYS run first):** Never trust a stored `look_id` — looks are ephemeral and get deleted. Read `Group ID` from the AVATAR file and resolve a fresh look_id: `list_avatar_looks(group_id=<group_id>)` (CLI: `GET /v3/avatars/looks?group_id=<group_id>&limit=20`). Pick the look matching the target orientation. Use this resolved look_id as `avatar_id` for all subsequent steps.
2. **Fetch avatar look metadata:** `get_avatar_look(look_id=<avatar_id>)` (CLI: `GET /v3/avatars/looks/<avatar_id>`) -> extract `avatar_type`, `preview_image_url`, `image_width`, `image_height`
1. **Resolve avatar_id from group_id (ALWAYS run first):** Never trust a stored `look_id` — looks are ephemeral and get deleted. Read `Group ID` from the AVATAR file and resolve a fresh look_id: `list_avatar_looks(group_id=<group_id>)` (CLI: `heygen avatar looks list --group-id <group_id> --limit 20`). Pick the look matching the target orientation. Use this resolved look_id as `avatar_id` for all subsequent steps.
2. **Fetch avatar look metadata:** `get_avatar_look(look_id=<avatar_id>)` (CLI: `heygen avatar looks get --look-id <avatar_id>`) -> extract `avatar_type`, `preview_image_url`, `image_width`, `image_height`
3. **Determine orientation:** width > height = landscape, height > width = portrait, width == height = square. Fetch fails = assume portrait.
4. **Determine background:** `photo_avatar` -> Video Agent handles environment. `studio_avatar` -> check if transparent/solid/empty. `video_avatar` -> always has background.
5. **Append the appropriate correction note(s)** to the end of the Video Agent prompt. That's it. No image generation, no new looks.
+16 -17
View File
@@ -33,7 +33,7 @@ Try to read `SOUL.md` from the workspace root.
First, fetch the user's existing HeyGen avatars.
**MCP:** `list_avatar_groups(ownership=private)` — returns the user's private avatar groups.
**CLI:** `curl -s "https://api.heygen.com/v3/avatars" -H "X-Api-Key: $HEYGEN_API_KEY"`
**CLI:** `heygen avatar list --ownership private`
Parse the `data` array.
@@ -47,17 +47,16 @@ Wait for their answer before proceeding.
## API Mode Detection
**MCP (preferred):** If HeyGen MCP tools are available (tools matching `mcp__heygen__*`), use them. MCP handles authentication via OAuth — no API key needed. MCP uses the user's existing HeyGen plan credits.
**MCP (preferred):** If HeyGen MCP tools are available (tools matching `mcp__heygen__*`), use them. MCP authenticates via OAuth — no API key needed — and runs against the user's existing HeyGen plan credits.
**CLI fallback:** If MCP tools are not available, use curl with `X-Api-Key: $HEYGEN_API_KEY`. Resolve the key from: (1) `$HEYGEN_API_KEY` env var, (2) `~/.heygen/config` file. If neither found, tell the user to run `./setup` or `export HEYGEN_API_KEY=<key>`.
**CLI fallback:** If MCP tools are not available, use the [HeyGen CLI](https://github.com/heygen-com/heygen-cli) (`heygen` binary). Auth: set `HEYGEN_API_KEY` in the env OR run `heygen auth login` (persists to `~/.heygen/credentials`). Verify with `heygen auth status`. If neither auth source is set, tell the user to run `heygen auth login` or `export HEYGEN_API_KEY=<key>`.
**CLI headers:** See [../references/api-reference.md](../references/api-reference.md) for required headers (X-Api-Key, User-Agent, X-HeyGen-Source).
**API:** v3 only. Base: `https://api.heygen.com`. Never use v1 or v2 endpoints.
**API:** v3 only. Never call v1 or v2 endpoints.
**Docs-first rule:** Before calling any endpoint you're unsure about:
- **Index:** `GET https://developers.heygen.com/llms.txt` — full sitemap
- **Any page:** Append `.md` to the URL for clean markdown
- Or run `heygen <noun> <verb> --help`
- Read the spec, THEN build your request. Never guess field names.
## Avatar File Convention
@@ -99,7 +98,7 @@ Format:
- Looks: landscape=<look_id>, portrait=<look_id>, square=<look_id>
- Last Synced: <ISO timestamp>
⚠️ look_ids are ephemeral — always resolve fresh from group_id at runtime via GET /v3/avatars/looks?group_id=<id>. Never hardcode look_id as the primary avatar reference.
⚠️ look_ids are ephemeral — always resolve fresh from group_id at runtime via `heygen avatar looks list --group-id <id>` (or MCP `list_avatar_looks`). Never hardcode look_id as the primary avatar reference.
```
**Top sections** (Appearance, Voice) are portable natural language. Any platform can use them.
@@ -135,7 +134,7 @@ Ask if they have a reference photo, explaining that a headshot or clear face pho
This applies to ALL targets (agent, user, named character). For agents, check if a reference photo path already exists in the AVATAR file's Appearance section or in IDENTITY.md before asking.
- **Photo provided** → upload via `POST /v3/assets`, then use Type B (photo) creation in Phase 2
- **Photo provided** → upload via `heygen asset create --file <path>` (or MCP equivalent), then use Type B (photo) creation in Phase 2
- **Skip** → use Type A (prompt) creation in Phase 2
### Phase 1 — Identity Extraction
@@ -163,18 +162,18 @@ Two creation types:
**Type A — From prompt (AI-generated appearance):**
**MCP:** `create_prompt_avatar(name=<name>, prompt=<appearance>, avatar_group_id=<optional>)`
**CLI:** `POST https://api.heygen.com/v3/avatars` with `{"type": "prompt", "name": "...", "prompt": "...", "avatar_group_id": "..."}`
**CLI:** `heygen avatar create -d '{"type":"prompt","name":"...","prompt":"...","avatar_group_id":"..."}'` (accepts inline JSON, a file path, or `-` for stdin)
Prompt limit is 1000 characters. Be descriptive — include style, features, expression, lighting. The API spec says 200 but the actual enforced limit is 1000.
**Type B — From reference image:**
**MCP:** `create_photo_avatar(name=<name>, file=<file_object>, avatar_group_id=<optional>)`
**CLI:** `POST https://api.heygen.com/v3/avatars` with `{"type": "photo", "name": "...", "file": {"type": "url", "url": "..."}, "avatar_group_id": "..."}`
**CLI:** `heygen avatar create -d '{"type":"photo","name":"...","file":{"type":"url","url":"..."},"avatar_group_id":"..."}'`
File options for Type B:
- `{ "type": "url", "url": "https://..." }` — public image URL
- `{ "type": "asset_id", "asset_id": "<id>" }` — from asset upload
- `{ "type": "asset_id", "asset_id": "<id>" }` — from `heygen asset create --file <path>`
- `{ "type": "base64", "media_type": "image/png", "data": "<base64>" }` — inline
**Response:** Returns `avatar_item.id` (look ID) and `avatar_item.group_id` (character identity).
@@ -209,11 +208,11 @@ Find matching voices via semantic search using the Voice section from the AVATAR
**Language matching:** The voice design prompt should specify the target language from `user_language`. Example for Japanese: `"A calm, warm female voice. Professional but approachable. Japanese speaker."` This ensures semantic search returns voices in the correct language.
**MCP:** `design_voice(prompt=<voice description>, seed=0)`
**CLI:** `POST https://api.heygen.com/v3/voices` with `{"prompt": "...", "seed": 0}`
**CLI:** `heygen voice create --prompt "..." --seed 0` (also accepts `--gender`, `--locale`)
Returns 3 voice options per seed. Present all 3 with inline audio previews:
- Download each `preview_audio_url`: `curl -sL "<url>" -o /tmp/voice-design-<n>.mp3`
- Send as audio attachment: `message(action:send, media:"/tmp/voice-design-<n>.mp3", caption:"Option <n>: <voice_name> — <gender>, <language>")` so it plays inline in Telegram/Discord
- Download each `preview_audio_url` to a temp path (any standard download method works — no HeyGen auth needed, these are public S3 URLs)
- Send as audio attachment: `message(action:send, media:"<path>", caption:"Option <n>: <voice_name> — <gender>, <language>")` so it plays inline in Telegram/Discord
- After all previews sent, present selection buttons
**STOP. Wait for the user to pick a voice via buttons or text. Do NOT select a voice yourself or proceed to Phase 4 until the user explicitly chooses.**
@@ -230,7 +229,7 @@ Increment `seed` and call again. Different seeds give completely different voice
Browse HeyGen's existing voice library:
**MCP:** `list_voices(type=private)` then `list_voices(type=public, language=<lang>, gender=<gender>)`
**CLI:** `GET https://api.heygen.com/v3/voices`
**CLI:** `heygen voice list --type private` / `heygen voice list --type public --language <lang> --gender <gender>`
1. Read the Voice section from the AVATAR file
2. Filter by gender and language
@@ -252,7 +251,7 @@ Update the HeyGen section of `AVATAR-<NAME>.md` to match the canonical format:
- Looks: <orientation>=<avatar_item.id> (e.g., landscape=<look_id>, portrait=<look_id>)
- Last Synced: <ISO timestamp>
⚠️ look_ids are ephemeral — always resolve fresh from group_id at runtime via GET /v3/avatars/looks?group_id=<id>. Never hardcode look_id as the primary avatar reference.
⚠️ look_ids are ephemeral — always resolve fresh from group_id at runtime via `heygen avatar looks list --group-id <id>` (or MCP `list_avatar_looks`). Never hardcode look_id as the primary avatar reference.
```
Confirm the avatar is saved and that other skills (like heygen-video) will pick it up automatically. Communicate in `user_language`.
@@ -262,7 +261,7 @@ Confirm the avatar is saved and that other skills (like heygen-video) will pick
If the user wants to see their avatar in action:
**MCP:** `create_video_agent(avatar_id=<avatar_id>, voice_id=<voice_id>, prompt=<greeting>)`
**CLI:** `POST https://api.heygen.com/v3/video-agents` with `{"avatar_id": "...", "voice_id": "...", "prompt": "..."}`
**CLI:** `heygen video-agent create --avatar-id <id> --voice-id <id> --prompt "..." --wait`
Generate a natural greeting in the video language (from `user_language`). Examples: English "Hi, I'm [name]. Nice to meet you!", Japanese "[name]です。はじめまして!", Spanish "Hola, soy [name]. ¡Mucho gusto!", Korean "안녕하세요, [name]입니다. 만나서 반갑습니다!"
+42 -43
View File
@@ -39,21 +39,21 @@ This script is opt-in only. Do not execute it automatically on skill invocation.
# HeyGen Video Producer
You are a video producer. Not a form. Not an API wrapper. A producer who understands what makes video work and guides the user from idea to finished cut.
You are a video producer. Not a form. Not a CLI wrapper. A producer who understands what makes video work and guides the user from idea to finished cut.
**API Docs:** https://developers.heygen.com/docs/quick-start — All endpoints are v3.
**Docs:** https://developers.heygen.com/docs/quick-start (API) · https://developers.heygen.com/cli (CLI)
## API Mode Detection
Detect which API mode is available, in order of preference:
Two modes, in order of preference:
**MCP (preferred):** If HeyGen MCP tools are available (tools matching `mcp__heygen__*`), use them. MCP handles authentication via OAuth — no API key needed. MCP uses the user's existing HeyGen plan credits with no separate API charges.
**MCP (preferred):** If HeyGen MCP tools are available (tools matching `mcp__heygen__*`), use them. MCP authenticates via OAuth — no API key needed — and runs against the user's existing HeyGen plan credits.
**CLI fallback:** If MCP tools are not available, use curl with `X-Api-Key: $HEYGEN_API_KEY`. Resolve the key from: (1) `$HEYGEN_API_KEY` env var, (2) `~/.heygen/config` file. If neither found, tell the user to run `./setup` or `export HEYGEN_API_KEY=<key>`.
**CLI fallback:** If MCP tools are not available, use the [HeyGen CLI](https://github.com/heygen-com/heygen-cli) (`heygen` binary). Auth: set `HEYGEN_API_KEY` in the env OR run `heygen auth login` (persists to `~/.heygen/credentials`). Verify with `heygen auth status`. If neither auth source is set, tell the user to run `heygen auth login` or `export HEYGEN_API_KEY=<key>`.
**CLI headers:** See [../references/api-reference.md](../references/api-reference.md) for required headers (X-Api-Key, User-Agent, X-HeyGen-Source).
CLI output: JSON on stdout, structured error envelope on stderr, stable exit codes (0 ok · 1 API · 2 usage · 3 auth · 4 timeout). Pipe to `jq` to extract fields. Add `--wait` on creation commands to block on completion instead of hand-rolling a poll loop.
**Throughout this skill:** Each API operation shows the MCP tool name first. If MCP is unavailable, use the curl equivalent from [../references/api-reference.md](../references/api-reference.md).
**Throughout this skill:** Each operation shows the MCP tool name first. If MCP is unavailable, use the `heygen` command shown alongside it. Full command reference: [../references/api-reference.md](../references/api-reference.md).
---
@@ -88,7 +88,7 @@ Interview the user. Be conversational, skip anything already answered.
Two paths for every asset:
- **Path A (Contextualize):** Read/analyze, bake info into script. For reference material, auth-walled content.
- **Path B (Attach):** Upload to HeyGen via `POST /v3/assets` or `files[]`. For visuals the viewer should see.
- **Path B (Attach):** Upload to HeyGen via `heygen asset create --file <path>` (or include as `files[]` entries on video-agent create). For visuals the viewer should see.
- **A+B (Both):** Summarize for script AND attach original.
📖 **Full routing matrix and upload examples → [../references/asset-routing.md](../references/asset-routing.md)**
@@ -106,9 +106,9 @@ Two approaches — use one or combine both:
**1. API Styles (`style_id`)** — Curated visual templates. One parameter replaces all visual direction.
**MCP:** `list_video_agent_styles(tag=<tag>, limit=20)` — filter by tag, returns style_id, name, thumbnail_url, preview_video_url, tags, aspect_ratio.
**CLI:** `curl -s "https://api.heygen.com/v3/video-agents/styles?tag=cinematic&limit=10" -H "X-Api-Key: $HEYGEN_API_KEY"`
**CLI:** `heygen video-agent styles list --tag cinematic --limit 10`
Tags: `cinematic`, `retro-tech`, `iconic-artist`, `pop-culture`, `handmade`, `print`. Pass `style_id` to `create_video_agent`.
Tags: `cinematic`, `retro-tech`, `iconic-artist`, `pop-culture`, `handmade`, `print`. Pass `style_id` / `--style-id` to the video-agent create call.
**Show users thumbnails + preview videos before choosing.** Browse by tag, show 3-5 options with previews, let user pick. If a style has a fixed `aspect_ratio`, match orientation to it.
@@ -402,7 +402,7 @@ YouTube/web/LinkedIn → `"landscape"` | TikTok/Reels/Shorts → `"portrait"` |
**Never trust a stored `look_id` — looks are ephemeral and get deleted.** Always resolve fresh from the `group_id`:
**MCP:** `list_avatar_looks(group_id=<group_id>)` — returns all looks for the group.
**CLI:** `curl -s "https://api.heygen.com/v3/avatars/looks?group_id=<group_id>&limit=20" -H "X-Api-Key: $HEYGEN_API_KEY"`
**CLI:** `heygen avatar looks list --group-id <group_id> --limit 20`
From the response, pick the look matching the target orientation. Use the first match. If no looks exist in the group, tell the user.
@@ -410,7 +410,7 @@ From the response, pick the look matching the target orientation. Use the first
### Steps
1. **Fetch avatar look metadata:** `get_avatar_look(look_id=<avatar_id>)` (CLI: `GET /v3/avatars/looks/<avatar_id>`) → extract `avatar_type`, `preview_image_url`, `image_width`, `image_height`
1. **Fetch avatar look metadata:** `get_avatar_look(look_id=<avatar_id>)` (CLI: `heygen avatar looks get --look-id <avatar_id>`) → extract `avatar_type`, `preview_image_url`, `image_width`, `image_height`
2. **Determine orientation:** width > height = landscape, height > width = portrait, width == height = square. Fetch fails = assume portrait.
3. **Determine background:** `photo_avatar` → Video Agent handles environment. `studio_avatar` → check if transparent/solid/empty. `video_avatar` → always has background.
4. **Append the appropriate correction note(s)** to the end of the Video Agent prompt. That's it. No image generation, no new looks.
@@ -462,31 +462,29 @@ BACKGROUND NOTE: The selected avatar has no background or a transparent backdrop
- **Full Producer**: User approved script. Proceed.
- **Quick Shot**: Generate immediately.
### API Call
### Submit
📖 **Full request/response schemas, interactive sessions, webhooks → [../references/api-reference.md](../references/api-reference.md)**
📖 **Full command reference, interactive sessions, webhooks → [../references/api-reference.md](../references/api-reference.md)**
**Step 1: Run Frame Check (if `avatar_id` set) — MAIN SESSION ONLY**
Before calling the API, run the Frame Check steps above. Build the corrected prompt with any FRAMING NOTE or BACKGROUND NOTE appended.
Before submitting, run the Frame Check steps above. Build the corrected prompt with any FRAMING NOTE or BACKGROUND NOTE appended.
**Step 2: Build the complete payload object in main session**
Before spawning any subagent, assemble the full request payload:
```json
{
"prompt": "<corrected prompt — Frame Check notes already embedded>",
"avatar_id": "<look_id resolved from group_id>",
"voice_id": "<confirmed voice_id>",
"style_id": "<optional>",
"orientation": "landscape",
"auto_proceed": true,
"files": []
}
```
This payload is the handoff to any subagent. The subagent receives a finished payload — it does NOT modify the prompt, does NOT re-run Frame Check, does NOT look up avatar IDs.
**Step 2: Build the complete payload in main session**
Before spawning any subagent, assemble the full set of arguments:
| Flag | Value |
|---|---|
| `--prompt` | corrected prompt — Frame Check notes already embedded |
| `--avatar-id` | look_id resolved from group_id |
| `--voice-id` | confirmed voice_id |
| `--style-id` | optional |
| `--orientation` | `landscape` or `portrait` |
This payload is the handoff to any subagent. The subagent receives a finished set of arguments — it does NOT modify the prompt, does NOT re-run Frame Check, does NOT look up avatar IDs.
**Step 3: Subagent spawn pattern (for batch or non-blocking generation)**
When generating multiple videos or wanting non-blocking polling, spawn one subagent per video with the finished payload.
When generating multiple videos or wanting non-blocking polling, spawn one subagent per video with the finished args.
Subagents are for **submit + poll + deliver only**. All creative decisions, Frame Check, and prompt construction happen in the main session before the spawn.
> ⛔ **BATCH RULE:** When generating N videos in parallel, spawn subagents in batches of **23 max**. Submitting too many simultaneously causes queue congestion — all get stuck in `thinking` for 15+ min. Submit batch 1, wait for completions, then submit batch 2.
@@ -495,26 +493,27 @@ Subagents are for **submit + poll + deliver only**. All creative decisions, Fram
**MCP:** `create_video_agent(prompt=<prompt>, avatar_id=<look_id>, voice_id=<voice_id>, style_id=<optional>, orientation=<orientation>)`
**CLI:** `POST /v3/video-agents` with `auto_proceed: true` (skips HeyGen's internal review checkpoint — no approval UI exists in the API flow).
**CLI:** `heygen video-agent create` — add `--wait --timeout 45m` to block on completion, or omit `--wait` and poll manually. **Always pair `--wait` with `--timeout 45m`** — the CLI default is 20m, but Video Agent jobs routinely take 20-45m, so the default will time out mid-generation.
> **`auto_proceed: true` is intentional — do not remove.** The HeyGen Video Agent API pauses at an interactive review checkpoint by default; without this flag, videos never complete. This is a known API behavior, not a security bypass — generation still requires explicit user request and a valid API key. No content is generated without user-initiated invocation of this skill.
```bash
curl -sX POST "https://api.heygen.com/v3/video-agents" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "prompt": "...", "avatar_id": "...", "voice_id": "...", "orientation": "landscape", "auto_proceed": true }'
heygen video-agent create \
--prompt "..." \
--avatar-id "..." \
--voice-id "..." \
--orientation landscape \
--wait --timeout 45m
```
Response: `{ "data": { "video_id": "...", "session_id": "..." } }`
The CLI returns JSON on stdout: `{"data": {"video_id": "...", "session_id": "..."}}` after submission. With `--wait`, it blocks until the video completes and emits the final status object. Without `--wait`, submit returns immediately — poll with `heygen video-agent get --session-id <id>`.
**⚠️ Always capture `session_id` immediately.** Session URL: `https://app.heygen.com/video-agent/{session_id}`. Cannot be recovered later.
### Polling
**MCP:** `get_video_agent_session(session_id=<session_id>)` — returns status, progress, video_id.
**CLI:** `GET /v3/videos/<video_id>` with polling.
**CLI:** `heygen video-agent get --session-id <session_id>` (or `heygen video get <video-id>` once you have the `video_id`).
Total wall time per video: **2045 minutes**. First check at **5 min**, then every **60s** up to 45 min.
Total wall time per video: **2045 minutes**. If you passed `--wait`, the CLI handles polling with exponential backoff. If polling manually: first check at **5 min**, then every **60s** up to 45 min.
Status flow: `thinking``generating``completed` | `failed`
@@ -522,12 +521,12 @@ Stuck in `thinking` >15 min with no progress → flag to user.
### Delivery
1. Get the `video_url` (S3 mp4) from the completed status response.
2. Download the MP4 locally: `curl -sL "<video_url>" -o /tmp/heygen-<video_id>.mp4`
3. Send inline via message tool: `message(action:send, media:"/tmp/heygen-<video_id>.mp4", caption:"Your video is ready! 🎬\n📊 Duration: [actual]s vs [target]s ([percentage]%)")`. This makes the video playable inline in Telegram/Discord instead of an external link.
1. Get the `video_url` (S3 mp4) from the completed status response, or use `heygen video get <video_id> | jq -r '.data.video_page_url'` for the shareable link.
2. Download the MP4 locally: `heygen video download <video_id>` (writes the file and emits `{"asset", "message", "path"}` on stdout — chain on `.path`).
3. Send inline via message tool: `message(action:send, media:"<downloaded-path>", caption:"Your video is ready! 🎬\n📊 Duration: [actual]s vs [target]s ([percentage]%)")`. This makes the video playable inline in Telegram/Discord instead of an external link.
4. Also share the HeyGen dashboard link for editing: `https://app.heygen.com/videos/<video_id>`
Always report duration accuracy. Clean up /tmp files after sending.
Always report duration accuracy. Clean up downloaded files after sending.
---
+26 -39
View File
@@ -11,26 +11,25 @@ NOT for: image generation, audio-only TTS, video translation, or cinematic b-rol
## Required Environment
- `HEYGEN_API_KEY` — Get from https://app.heygen.com/settings?nav=API
- `heygen` CLI — install: `curl -fsSL https://static.heygen.ai/cli/install.sh | bash`. Verify: `heygen auth status`.
## Steps
### Step 1: Discover Available Avatars
```bash
curl -s -X GET "https://api.heygen.com/v3/avatars" \
-H "X-Api-Key: $HEYGEN_API_KEY" | jq '.data.avatar_list[:5] | .[] | {avatar_id: .avatar_group_id, avatar_name}'
heygen avatar list --ownership public --limit 5 | jq '.data[] | {group_id, avatar_name}'
```
Pick an avatar_id. If the user has a specific avatar, use that ID.
Pick an `avatar_id`. If the user has a specific avatar, use that ID. To see looks for a group: `heygen avatar looks list --group-id <group_id>`.
### Step 2: Find a Voice
```bash
curl -s -X GET "https://api.heygen.com/v3/voices" \
-H "X-Api-Key: $HEYGEN_API_KEY" | jq '.data.voices[:10] | .[] | {voice_id, display_name, language}'
heygen voice list --limit 10 | jq '.data.voices[] | {voice_id, display_name, language}'
```
Pick a voice_id matching the desired language and tone.
Pick a `voice_id` matching the desired language and tone.
### Step 3: Write the Script
@@ -43,36 +42,22 @@ Write a spoken-word script for the avatar. Rules:
### Step 4: Generate the Video
```bash
curl -s -X POST "https://api.heygen.com/v3/video-agents" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"video_inputs": [{
"character": {
"type": "avatar",
"avatar_id": "AVATAR_ID_HERE",
"avatar_style": "normal"
},
"voice": {
"type": "text",
"input_text": "YOUR SCRIPT HERE",
"voice_id": "VOICE_ID_HERE"
}
}],
"dimension": {
"width": 1920,
"height": 1080
}
}'
heygen video-agent create \
--prompt "YOUR SCRIPT HERE" \
--avatar-id "AVATAR_ID_HERE" \
--voice-id "VOICE_ID_HERE" \
--orientation landscape \
--wait --timeout 10m
```
Save the `session_id` from the response.
With `--wait`, the CLI blocks until the video completes and emits the final status object. Without `--wait`, submission returns immediately — save the `session_id` from stdout for manual polling.
### Step 5: Poll for Completion
`--timeout 10m` is sized for short videos (≤60s). **Bump to `--timeout 45m` for videos >60s** or when targeting 2+ minute output — Video Agent generation routinely takes 20-45m for longer clips.
### Step 5: Poll for Completion (only without `--wait`)
```bash
curl -s -X GET "https://api.heygen.com/v3/video-agents/sessions/SESSION_ID" \
-H "X-Api-Key: $HEYGEN_API_KEY" | jq '{status: .data.status, video_url: .data.video_url}'
heygen video-agent get --session-id SESSION_ID | jq '{status: .data.status, video_url: .data.video_url}'
```
Poll every 15 seconds. Status progression: `pending``processing``completed`.
@@ -84,25 +69,27 @@ When status is `completed`, the `video_url` field contains the download URL.
Download the video and present it to the user:
```bash
curl -sL -o output.mp4 "VIDEO_URL_HERE"
heygen video download <video_id>
```
Writes the MP4 to disk and emits `{"asset", "message", "path"}` on stdout — chain on `.path`.
## Verification
After generating a video, confirm:
1. Response contains `session_id` (generation accepted)
2. Polling returns `status: "completed"` within 5 minutes
3. `video_url` is a valid HTTPS URL
1. CLI exits `0` and stdout contains `session_id` (generation accepted)
2. Polling (or `--wait`) returns `status: "completed"` within 5 minutes
3. `video_url` / `video_page_url` is a valid HTTPS URL
4. Downloaded file is a playable MP4
## Troubleshooting
| Error | Fix |
| Symptom | Fix |
|-------|-----|
| 401 Unauthorized | Check HEYGEN_API_KEY is set and valid |
| 400 Bad Request | Verify avatar_id and voice_id exist (re-run Steps 1-2) |
| Exit code `3` / auth error on stderr | Check `heygen auth status`; run `heygen auth login` or set `HEYGEN_API_KEY` |
| Exit code `2` / usage error | Run `heygen video-agent create --help` — verify flag names and required args |
| Status stuck on "processing" | Wait up to 5 minutes. Videos over 60s take longer. |
| Empty video_url | Video may have failed. Check `error` field in poll response. |
| Empty `video_url` | Video may have failed. Check `error` field in poll response. |
## Limits
+186 -178
View File
@@ -1,70 +1,87 @@
# API Reference — HeyGen v3
# Command Reference — HeyGen v3
## API Modes
## Modes
**MCP (preferred):** HeyGen Remote MCP at `https://mcp.heygen.com/mcp/v1/` — OAuth-based, no API key needed. Tools prefixed `mcp__heygen__*`. Uses the user's existing HeyGen plan credits.
**CLI fallback:** All endpoints use base URL `https://api.heygen.com` with header `X-Api-Key: $HEYGEN_API_KEY`.
**CLI fallback:** The [HeyGen CLI](https://github.com/heygen-com/heygen-cli) — a single `heygen` binary. Pattern: `heygen <noun> <verb>`. Every command supports `--help`. Never call `api.heygen.com` with curl — use MCP or the CLI.
## Required CLI Headers
## CLI setup
Every CLI request — no exceptions:
```
X-Api-Key: $HEYGEN_API_KEY
User-Agent: HeyGen-Skills/2.0.3 (OpenClaw; heygen-skills) # x-release-please-version
X-HeyGen-Source: openclaw-skill
Install:
```bash
curl -fsSL https://static.heygen.ai/cli/install.sh | bash
```
## Live Documentation
Auth (pick one):
```bash
export HEYGEN_API_KEY=your-key-here # agent/CI, ephemeral
echo "$KEY" | heygen auth login # agent, persisted to ~/.heygen/credentials
heygen auth login # humans, interactive
heygen auth status # verify
```
Canonical docs with exact schemas, curl examples, and field descriptions:
- **Full index:** https://developers.heygen.com/llms.txt
- **Any page as markdown:** append `.md` to URL (e.g. `https://developers.heygen.com/docs/video-agent.md`)
## CLI behavior
| Aspect | Behavior |
|---|---|
| **stdout** | Always JSON. `heygen video download` writes binary to disk; stdout emits `{"asset", "message", "path"}`. |
| **stderr** | Structured envelope: `{"error": {"code", "message", "hint"}}`. Stable `code` values for branching. |
| **Exit codes** | `0` ok · `1` API/network · `2` usage · `3` auth · `4` timeout under `--wait` (stdout contains partial resource for resume). |
| **Request bodies** | Flags for simple inputs; `-d` for nested JSON (inline, file path, or `-` for stdin). Flags override matching fields. |
| **Async jobs** | `--wait` blocks with exponential backoff; `--timeout` sets max (default 20m). 429s and 5xx retry automatically. |
Extract fields with `jq`:
```bash
heygen video get <video_id> | jq -r '.data.video_page_url'
```
## Live documentation
Canonical docs with exact schemas, examples, and field descriptions:
- **API index:** https://developers.heygen.com/llms.txt
- **Any API page as markdown:** append `.md` to URL (e.g. `https://developers.heygen.com/docs/video-agent.md`)
- **API reference pages:** `https://developers.heygen.com/reference/<slug>.md`
- **CLI docs:** https://developers.heygen.com/cli
- **MCP docs:** https://developers.heygen.com/mcp.md
When in doubt about a field name or type, fetch the `.md` page before guessing.
When in doubt about a field name or type, fetch the `.md` page (or run `heygen <noun> <verb> --help`) before guessing.
## Endpoints
## Command surface
| Action | MCP Tool | CLI Method | CLI Endpoint |
|--------|----------|------------|--------------|
| **Video Agent (primary)** | `create_video_agent` | POST | `/v3/video-agents` |
| **Poll Session** | `get_video_agent_session` | GET | `/v3/video-agents/sessions/{session_id}` |
| **Send Session Message** | `send_video_agent_message` | POST | `/v3/video-agents/sessions/{session_id}/messages` |
| **Stop Session** | `stop_video_agent_session` | POST | `/v3/video-agents/sessions/{session_id}/stop` |
| **Session Resources** | `get_video_agent_resource` | — | — |
| **Session Videos** | `list_video_agent_session_videos` | — | — |
| **Avatar Video (direct)** | `create_video_from_avatar` | POST | `/v3/videos` |
| **Image Video (direct)** | `create_video_from_image` | POST | `/v3/videos` |
| **Poll Video Status** | `get_video` | GET | `/v3/videos/{video_id}` |
| **List Videos** | `list_videos` | GET | `/v3/videos` |
| **Delete Video** | `delete_video` | DELETE | `/v3/videos/{video_id}` |
| **List Avatar Groups** | `list_avatar_groups` | GET | `/v3/avatars` |
| **Get Avatar Group** | `get_avatar_group` | GET | `/v3/avatars/{group_id}` |
| **List Avatar Looks** | `list_avatar_looks` | GET | `/v3/avatars/looks` |
| **Get Avatar Look** | `get_avatar_look` | GET | `/v3/avatars/looks/{look_id}` |
| **Update Avatar Look** | `update_avatar_look` | — | — |
| **Create Photo Avatar** | `create_photo_avatar` | POST | `/v3/avatars` |
| **Create Prompt Avatar** | `create_prompt_avatar` | POST | `/v3/avatars` |
| **Create Digital Twin** | `create_digital_twin` | POST | `/v3/avatars` |
| **Avatar Consent** | `create_avatar_consent` | — | — |
| **List Voices** | `list_voices` | GET | `/v3/voices` |
| **Design Voice** | `design_voice` | POST | `/v3/voices` |
| **TTS** | `create_speech` | POST | `/v3/voices/speech` |
| **Create Lip Sync** | `create_lipsync` | POST | `/v3/overdubs` |
| **List Lip Syncs** | `list_lipsyncs` | GET | `/v3/overdubs` |
| **Get Lip Sync** | `get_lipsync` | GET | `/v3/overdubs/{id}` |
| **Translate Video** | `create_video_translation` | POST | `/v3/video-translations` |
| **Get Translation** | `get_video_translation` | GET | `/v3/video-translations/{id}` |
| **Translation Languages** | `list_video_translation_languages` | GET | `/v3/video-translations/languages` |
| **Account Info** | `get_current_user` | — | — |
| **List Styles** | `list_video_agent_styles` | GET | `/v3/video-agents/styles` |
| **Upload Asset** | — | POST | `/v3/assets` |
| **Webhooks** | — | POST/GET/DELETE | `/v3/webhooks` |
| Action | MCP Tool | CLI Command |
|--------|----------|-------------|
| **Video Agent (primary)** | `create_video_agent` | `heygen video-agent create` |
| **Poll Session** | `get_video_agent_session` | `heygen video-agent get --session-id <id>` |
| **Send Session Message** | `send_video_agent_message` | `heygen video-agent send --session-id <id> --message <m>` |
| **Stop Session** | `stop_video_agent_session` | `heygen video-agent stop --session-id <id>` |
| **Session Resources** | `get_video_agent_resource` | `heygen video-agent resources get --session-id <id> --resource-id <id>` |
| **Session Videos** | `list_video_agent_session_videos` | `heygen video-agent videos list --session-id <id>` |
| **Direct Video** | `create_video_from_avatar` / `create_video_from_image` | `heygen video create` |
| **Poll Video Status** | `get_video` | `heygen video get <video_id>` |
| **List Videos** | `list_videos` | `heygen video list` |
| **Delete Video** | `delete_video` | `heygen video delete <video_id>` |
| **Download Video** | — | `heygen video download <video_id>` |
| **List Avatar Groups** | `list_avatar_groups` | `heygen avatar list` |
| **Get Avatar Group** | `get_avatar_group` | `heygen avatar get --group-id <id>` |
| **List Avatar Looks** | `list_avatar_looks` | `heygen avatar looks list` |
| **Get Avatar Look** | `get_avatar_look` | `heygen avatar looks get --look-id <id>` |
| **Update Avatar Look** | `update_avatar_look` | `heygen avatar looks update --look-id <id> --name <n>` |
| **Create Avatar** | `create_photo_avatar` / `create_prompt_avatar` / `create_digital_twin` | `heygen avatar create -d <json>` |
| **Avatar Consent** | `create_avatar_consent` | `heygen avatar consent create --group-id <id>` |
| **List Voices** | `list_voices` | `heygen voice list` |
| **Design Voice** | `design_voice` | `heygen voice create --prompt <p>` |
| **TTS** | `create_speech` | `heygen voice speech create --text <t> --voice-id <id>` |
| **Lipsync** | `create_lipsync` / `list_lipsyncs` / `get_lipsync` | `heygen lipsync create` / `list` / `get --lipsync-id <id>` |
| **Video Translate** | `create_video_translation` | `heygen video-translate create` |
| **Get Translation** | `get_video_translation` | `heygen video-translate get --video-translation-id <id>` |
| **Translation Languages** | `list_video_translation_languages` | `heygen video-translate languages list` |
| **Account Info** | `get_current_user` | `heygen user` |
| **List Styles** | `list_video_agent_styles` | `heygen video-agent styles list` |
| **Upload Asset** | — | `heygen asset create --file <path>` |
| **Webhooks** | — | `heygen webhook endpoints create/list/update/delete` · `heygen webhook events list` |
## Video Agent — One-Shot
## Video Agent — one-shot
**MCP:**
```
@@ -79,106 +96,89 @@ create_video_agent(
**CLI:**
```bash
curl -s -X POST "https://api.heygen.com/v3/video-agents" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "<constructed prompt>",
"avatar_id": "<optional, from discovery>",
"voice_id": "<optional, from discovery>",
"style_id": "<optional, from styles>",
"orientation": "landscape",
"files": [
{"type": "asset_id", "asset_id": "<uploaded_id>"}
],
"callback_url": "<optional webhook URL>",
"callback_id": "<optional custom ID for webhook payload>",
"incognito_mode": false
}'
heygen video-agent create \
--prompt "<constructed prompt>" \
--avatar-id "<optional, from discovery>" \
--voice-id "<optional, from discovery>" \
--style-id "<optional, from styles>" \
--orientation landscape \
--wait --timeout 45m
```
| Parameter | Type | Required | Description |
|-----------|------|:--------:|-------------|
| `prompt` | string | ✓ | Constructed prompt with script, visual directions, style |
| `avatar_id` | string | | Look ID from avatar discovery |
| `voice_id` | string | | Voice ID from voice listing |
| `style_id` | string | | Style ID from styles listing |
| `orientation` | string | | `"landscape"` or `"portrait"` (default: landscape) |
| `files` | array | | File objects: `{type, url/asset_id/data}` |
| `callback_url` | string | | Webhook URL for completion notification |
| `callback_id` | string | | Custom ID included in webhook payload |
| `incognito_mode` | boolean | | Disables cross-session memory injection/extraction. Use for evals. |
Attach files or override advanced fields (e.g. `callback_url`, `callback_id`, `incognito_mode`) via `-d '{...}'`. Flags override matching fields in `-d`.
Response: `{"data": {"video_id": "abc123", "session_id": "sess_xyz789"}}`
| Flag | Description |
|------|-------------|
| `--prompt` | Constructed prompt with script, visual directions, style (required) |
| `--avatar-id` | Look ID from avatar discovery |
| `--voice-id` | Voice ID from voice listing |
| `--style-id` | Style ID from `video-agent styles list` |
| `--orientation` | `landscape` or `portrait` (default: landscape) |
| `--mode` | Generation mode override |
| `--callback-url` | Webhook URL for completion notification |
| `--callback-id` | Custom ID included in webhook payload |
| `--incognito-mode` | Disables cross-session memory. Use for evals. |
| `--wait` | Block until the video completes (handles polling with exponential backoff) |
| `--timeout` | Max wall time when using `--wait` (default 20m) |
**⚠️ Always capture `session_id`.** Cannot be recovered later via GET.
Returns (submission): `{"data": {"video_id": "abc123", "session_id": "sess_xyz789"}}`. With `--wait`, the CLI blocks and returns the final status object.
## Interactive Session Mode (⚠️ EXPERIMENTAL)
**⚠️ Always capture `session_id`.** Cannot be recovered later.
## Interactive session mode (⚠️ EXPERIMENTAL)
> Sessions have known reliability issues: frequently stuck at `processing`, `reviewing` may never be reached, follow-up messages fail with timing errors, stop command may not trigger generation. Use one-shot for production.
**Create session:**
```bash
curl -s -X POST "https://api.heygen.com/v3/video-agents/sessions" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "<initial prompt>",
"avatar_id": "<optional>",
"voice_id": "<optional>",
"style_id": "<optional>",
"orientation": "landscape",
"auto_proceed": false
}'
# Create session (no --wait — interactive)
heygen video-agent create --prompt "<initial>" --avatar-id <id> --voice-id <id>
# Poll
heygen video-agent get --session-id <session_id>
# Send follow-up
heygen video-agent send --session-id <session_id> --message "Make the intro more energetic"
# Stop (finalize)
heygen video-agent stop --session-id <session_id>
```
**Poll:** `GET /v3/video-agents/sessions/<session_id>`
Status flow: `processing``reviewing``generating``completed` | `failed`
**Send follow-up:** `POST /v3/video-agents/sessions/<session_id>/messages`
```json
{"message": "Make the intro more energetic", "auto_proceed": false}
```
**Stop (finalize):** `POST /v3/video-agents/sessions/<session_id>/stop`
## Avatar Video (Direct Control Path)
## Direct Video (talking-head path)
For talking-head videos without Video Agent scene planning:
```bash
curl -s -X POST "https://api.heygen.com/v3/videos" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"avatar_id": "<look_id>",
"script": "<narrator script>",
"voice_id": "<voice_id>",
"title": "My Video",
"resolution": "1080p",
"aspect_ratio": "16:9"
}'
heygen video create -d '{
"avatar_id": "<look_id>",
"script": "<narrator script>",
"voice_id": "<voice_id>",
"title": "My Video",
"resolution": "1080p",
"aspect_ratio": "16:9"
}'
```
| Parameter | Type | Notes |
|-----------|------|-------|
| `avatar_id` | string | Mutually exclusive with `image_url` / `image_asset_id` |
| `image_url` | string | Direct photo URL — no avatar creation needed |
| `image_asset_id` | string | Uploaded photo asset ID |
| `script` | string | Plain text narrator script |
| `voice_id` | string | Required |
| `audio_url` / `audio_asset_id` | string | Pre-recorded audio instead of TTS |
| `resolution` | string | `"1080p"` or `"720p"` |
| `aspect_ratio` | string | `"16:9"` or `"9:16"` |
| `motion_prompt` | string | Motion/expression direction (photo avatars) |
| `expressiveness` | string | `"high"`, `"medium"`, or `"low"` (photo avatars) |
| `remove_background` | boolean | Remove avatar background |
| `background` | object | `{type:"color", value:"#1E40AF"}` or `{type:"image", url:"..."}` |
| `voice_settings` | object | `{speed: 1.0, pitch: 0, locale: "en-US"}` — set locale to match video language (e.g., `ja-JP`, `es-ES`, `ko-KR`) |
| Field | Notes |
|-------|-------|
| `avatar_id` | Mutually exclusive with `image_url` / `image_asset_id` |
| `image_url` | Direct photo URL — no avatar creation needed |
| `image_asset_id` | Uploaded photo asset ID (from `heygen asset create`) |
| `script` | Plain text narrator script |
| `voice_id` | Required |
| `audio_url` / `audio_asset_id` | Pre-recorded audio instead of TTS |
| `resolution` | `"1080p"` or `"720p"` |
| `aspect_ratio` | `"16:9"` or `"9:16"` |
| `motion_prompt` | Motion/expression direction (photo avatars) |
| `expressiveness` | `"high"`, `"medium"`, or `"low"` (photo avatars) |
| `remove_background` | Remove avatar background |
| `background` | `{type:"color", value:"#1E40AF"}` or `{type:"image", url:"..."}` |
| `voice_settings` | `{speed: 1.0, pitch: 0, locale: "en-US"}` — set locale to match video language (`ja-JP`, `es-ES`, `ko-KR`) |
**When to use which:**
| | Video Agent (`/v3/video-agents`) | Avatar Video (`/v3/videos`) |
| | Video Agent (`video-agent create`) | Direct Video (`video create`) |
|---|---|---|
| **Input** | Full prompt with creative direction | Script + avatar/image + voice |
| **Scenes** | Auto scene planning, B-roll, transitions | Single continuous take |
@@ -188,52 +188,56 @@ curl -s -X POST "https://api.heygen.com/v3/videos" \
## Polling
```bash
curl -s "https://api.heygen.com/v3/videos/<video_id>" \
-H "X-Api-Key: $HEYGEN_API_KEY"
heygen video get <video_id>
```
Status: `pending``processing``completed` | `failed`
Completion includes `video_url`, `thumbnail_url`, `duration`.
Completion response includes `video_url`, `video_page_url`, `thumbnail_url`, `duration`.
**Cadence:**
**Cadence (manual polling):**
1. First check at **2 minutes**
2. Every **30 seconds** for next 3 minutes
3. Every **60 seconds** up to 30 minutes
4. Stuck `pending` >10 min → flag to user
5. After 30 min → stop, give dashboard fallback
Prefer `--wait` on creation — the CLI handles this cadence automatically.
## Webhooks
### Inline Callback (Simplest)
### Inline callback (simplest)
Add `callback_url` + `callback_id` directly to your `POST /v3/video-agents` or `POST /v3/video-agents/sessions` request:
```json
{"prompt": "...", "callback_url": "https://your-server.com/hook", "callback_id": "my-ref-123"}
Add `--callback-url` + `--callback-id` directly to `heygen video-agent create`:
```bash
heygen video-agent create --prompt "..." \
--callback-url "https://your-server.com/hook" \
--callback-id "my-ref-123"
```
HeyGen POSTs to your URL when the video completes or fails. Payload includes `video_id`, `video_url`, `callback_id`.
### Registered Webhooks (Persistent)
### Registered webhooks (persistent)
```bash
# Register endpoint for specific event types
curl -X POST "https://api.heygen.com/v3/webhooks" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/webhook", "events": ["video_agent.success", "video_agent.fail"]}'
heygen webhook endpoints create \
--url "https://example.com/webhook" \
--events video_agent.success --events video_agent.fail
# List registered webhooks
curl -s "https://api.heygen.com/v3/webhooks" -H "X-Api-Key: $HEYGEN_API_KEY"
# List registered endpoints
heygen webhook endpoints list
# Rotate signing secret
heygen webhook endpoints rotate-secret --endpoint-id <id>
# Delete
curl -X DELETE "https://api.heygen.com/v3/webhooks/<id>" -H "X-Api-Key: $HEYGEN_API_KEY"
heygen webhook endpoints delete --endpoint-id <id>
# Browse delivered events (filter by type or entity)
curl -s "https://api.heygen.com/v3/webhooks/events?event_type=video_agent.success&limit=10" \
-H "X-Api-Key: $HEYGEN_API_KEY"
heygen webhook events list --event-type video_agent.success --limit 10
```
### Event Types
### Event types
| Event | Description |
|-------|-------------|
@@ -248,46 +252,51 @@ curl -s "https://api.heygen.com/v3/webhooks/events?event_type=video_agent.succes
Each event payload: `{event_id, event_type, event_data: {video_id, video_url, callback_id}, created_at}`
## Video Management
Browse all available event types: `heygen webhook event-types list`.
## Video management
```bash
# List all videos
curl -s "https://api.heygen.com/v3/videos" -H "X-Api-Key: $HEYGEN_API_KEY"
# Delete a video
curl -X DELETE "https://api.heygen.com/v3/videos/<video_id>" -H "X-Api-Key: $HEYGEN_API_KEY"
heygen video list
heygen video list --folder-id <id> --limit 50
heygen video delete <video_id>
heygen video download <video_id> # writes mp4 to disk, emits {"path"} on stdout
```
## Error Handling
## Error handling
| Error | Action |
CLI errors come back as structured envelopes on stderr:
```json
{"error": {"code": "not_found", "message": "Video not found", "hint": "Check ID with: heygen video list"}}
```
| Exit code / error code | Action |
|-------|--------|
| 401 Unauthorized | API key invalid. Check HEYGEN_API_KEY. |
| 402 Payment Required | Insufficient credits. Tell user. |
| 429 Rate Limited | Wait 60s, retry once. |
| 500+ Server Error | Retry once after 30s. |
| 200 but no video_id | Retry once. If still failing, tell user to check dashboard. |
| Exit `3` / `auth_*` | API key invalid. Check `heygen auth status`, re-run `heygen auth login`, or set `HEYGEN_API_KEY`. |
| `402` / `payment_required` | Insufficient credits. Tell the user. |
| Exit `1` / `rate_limited` | CLI retries 429s automatically; if it still errors, wait 60s and re-run. |
| Exit `1` / 5xx | CLI retries; if it still errors, retry once after 30s. |
| Exit `4` / timeout under `--wait` | Submission succeeded — stdout contains partial resource with `session_id` / `video_id`. Resume via `heygen video-agent get --session-id <id>`. |
| Exit `2` / usage | Invalid flags or arguments. Run `heygen <command> --help`. |
**Asset upload failure:** Log which asset failed, proceed without it. Inform user.
## Voice Design
Find matching voices from HeyGen's library using natural language. Uses semantic search (Pinecone) over all existing voices. No new voices are generated and no quota is consumed. Returned `voice_id`s work immediately in any video endpoint.
Find matching voices from HeyGen's library using natural language. Uses semantic search (Pinecone) over all existing voices. No new voices are generated and no quota is consumed. Returned `voice_id`s work immediately in any video command.
```bash
curl -s -X POST "https://api.heygen.com/v3/voices" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A calm, warm female voice with a slight Korean accent. Professional but approachable.",
"seed": 0
}'
heygen voice create \
--prompt "A calm, warm female voice with a slight Korean accent. Professional but approachable." \
--seed 0
```
| Parameter | Type | Required | Description |
|-----------|------|:--------:|-------------|
| `prompt` | string | ✓ | Natural language description of the desired voice |
| `seed` | integer | | Start at 0. Controls which set of voices you get. Deterministic: same prompt + seed = same results. Increment for more options. |
| Flag | Description |
|------|-------------|
| `--prompt` | Natural language description of the desired voice (required) |
| `--seed` | Start at 0. Controls which set of voices you get. Deterministic: same prompt + seed = same results. Increment for more options. |
| `--gender` | Optional filter |
| `--locale` | Optional filter |
**Response:**
```json
@@ -312,12 +321,11 @@ curl -s -X POST "https://api.heygen.com/v3/voices" \
Returns 3 voices per seed. Deterministic: same prompt + seed always returns the same set. If none match, increment seed and try again. Each voice has a `preview_audio_url` (MP3) for user auditioning.
**Key:** This is semantic search, not generation. Free to call. No voice slots consumed.
**Key:** Semantic search, not generation. Free to call. No voice slots consumed.
**Language matching:** Include the target language in the voice design prompt (e.g., "Japanese speaker", "Korean native"). For voice browsing, use the `language` query parameter:
**Language matching:** Include the target language in the voice design prompt (e.g., "Japanese speaker", "Korean native"). For voice browsing, use the `--language` flag:
```bash
curl -s "https://api.heygen.com/v3/voices?type=public&language=ja&limit=20" \
-H "X-Api-Key: $HEYGEN_API_KEY"
heygen voice list --type public --language ja --limit 20
```
## Pricing
+5 -6
View File
@@ -51,12 +51,11 @@ When the user provides files, URLs, or references, route each asset to the right
### Path B (Attach)
Upload to HeyGen:
```bash
curl -X POST "https://api.heygen.com/v3/assets" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-F "file=@/path/to/file.png"
```
Max 32MB per file. Returns `asset_id`.
**MCP:** upload via the asset tool (depends on environment).
**CLI:** `heygen asset create --file /path/to/file.png`
Max 32MB per file. Returns JSON with the new `asset_id`.
Or pass inline in `files[]`:
```json
+47 -48
View File
@@ -9,8 +9,7 @@
**MCP:** `list_avatar_looks(ownership=private)` — filter client-side by name match.
**CLI:**
```bash
curl -s "https://api.heygen.com/v3/avatars/looks?ownership=private&limit=50" \
-H "X-Api-Key: $HEYGEN_API_KEY"
heygen avatar looks list --ownership private --limit 50
```
Avoids the 2-call group→looks pattern.
@@ -22,14 +21,11 @@ Avoids the 2-call group→looks pattern.
**CLI:**
```bash
curl -s "https://api.heygen.com/v3/avatars?ownership=private&limit=50" \
-H "X-Api-Key: $HEYGEN_API_KEY"
curl -s "https://api.heygen.com/v3/avatars/looks?group_id=<group_id>&limit=50" \
-H "X-Api-Key: $HEYGEN_API_KEY"
heygen avatar list --ownership private --limit 50
heygen avatar looks list --group-id <group_id> --limit 50
```
Each look has an `id` — this is the `avatar_id` you pass to the API.
Each look has an `id` — this is the `avatar_id` you pass downstream.
Avatar types: `studio_avatar`, `video_avatar`, `photo_avatar`. Photo avatars support `motion_prompt` and `expressiveness`.
@@ -40,7 +36,7 @@ Avatar types: `studio_avatar`, `video_avatar`, `photo_avatar`. Photo avatars sup
Check `heygen-video-log.jsonl` for last used avatar_id. If found:
**MCP:** `get_avatar_look(look_id=<look_id>)`
**CLI:** `curl -s "https://api.heygen.com/v3/avatars/looks/<look_id>" -H "X-Api-Key: $HEYGEN_API_KEY"`
**CLI:** `heygen avatar looks get --look-id <look_id>`
Show preview image: "Last time you used [Avatar Name]. Use her again?"
@@ -55,8 +51,7 @@ If presenter wanted, present private avatars first. For public/stock avatars, br
**MCP:** `list_avatar_groups(ownership=public)`
**CLI:**
```bash
curl -s "https://api.heygen.com/v3/avatars?ownership=public&limit=20" \
-H "X-Api-Key: $HEYGEN_API_KEY"
heygen avatar list --ownership public --limit 20
```
Show group names + one representative image. Let the user pick a person.
@@ -64,11 +59,10 @@ Show group names + one representative image. Let the user pick a person.
**MCP:** `list_avatar_looks(group_id=<group_id>)`
**CLI:**
```bash
curl -s "https://api.heygen.com/v3/avatars/looks?group_id=<group_id>&limit=10" \
-H "X-Api-Key: $HEYGEN_API_KEY"
heygen avatar looks list --group-id <group_id> --limit 10
```
**Why group-first:** The flat `/v3/avatars/looks?ownership=public` endpoint returns 50+ results for only 3 unique people per page. Group-level browsing (2 calls) gives much better discovery UX.
**Why group-first:** The flat `heygen avatar looks list --ownership public` call returns 50+ results for only 3 unique people per page. Group-level browsing (2 calls) gives much better discovery UX.
### A4: Voice direction
@@ -76,7 +70,7 @@ After avatar is settled, confirm voice preferences (accent, delivery style, lang
**ALWAYS show a playable voice preview.** Each voice response includes `preview_audio_url` — share it.
**Handling missing/broken previews:** Some voices return bare `s3://` paths or `null`. When this happens: note "(no preview available)" and offer to generate a short TTS sample via `create_speech` (MCP) or `POST /v3/voices/speech` (CLI).
**Handling missing/broken previews:** Some voices return bare `s3://` paths or `null`. When this happens: note "(no preview available)" and offer to generate a short TTS sample via `create_speech` (MCP) or `heygen voice speech create --text "<sample>" --voice-id <id> --input-type plain_text --language en --locale en-US` (CLI).
---
@@ -96,10 +90,12 @@ Three creation types:
**MCP:** `create_photo_avatar(name=<name>, file=<file_object>, avatar_group_id=<optional>)`
**CLI:**
```bash
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 Avatar", "file": {"type": "url", "url": "https://example.com/headshot.jpg"}, "avatar_group_id": "<optional>"}'
heygen avatar create -d '{
"type": "photo",
"name": "My Avatar",
"file": {"type": "url", "url": "https://example.com/headshot.jpg"},
"avatar_group_id": "<optional>"
}'
```
Photo requirements: JPEG or PNG, min 512x512, clear front-facing face, good lighting.
@@ -108,10 +104,12 @@ Photo requirements: JPEG or PNG, min 512x512, clear front-facing face, good ligh
**MCP:** `create_prompt_avatar(name=<name>, prompt=<appearance>, avatar_group_id=<optional>)`
**CLI:**
```bash
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": "Tech Presenter", "prompt": "Young professional woman, modern workspace, confident smile", "avatar_group_id": "<optional>"}'
heygen avatar create -d '{
"type": "prompt",
"name": "Tech Presenter",
"prompt": "Young professional woman, modern workspace, confident smile",
"avatar_group_id": "<optional>"
}'
```
Prompt max: 1000 characters. Optional: up to 3 `reference_images`.
@@ -120,15 +118,17 @@ Prompt max: 1000 characters. Optional: up to 3 `reference_images`.
**MCP:** `create_digital_twin(name=<name>, file=<file_object>, avatar_group_id=<optional>)`
**CLI:**
```bash
curl -X POST "https://api.heygen.com/v3/avatars" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "video", "name": "My Video Avatar", "file": {"type": "asset_id", "asset_id": "<uploaded_asset_id>"}, "avatar_group_id": "<optional>"}'
heygen avatar create -d '{
"type": "video",
"name": "My Video Avatar",
"file": {"type": "asset_id", "asset_id": "<uploaded_asset_id>"},
"avatar_group_id": "<optional>"
}'
```
All three return `avatar_item` with `id` (look_id) and `group_id` — use `id` as `avatar_id` for videos.
Files: `{"type": "url", "url": "..."}`, `{"type": "asset_id", "asset_id": "..."}`, or `{"type": "base64", "data": "...", "content_type": "..."}`.
Files: `{"type": "url", "url": "..."}`, `{"type": "asset_id", "asset_id": "..."}` (from `heygen asset create --file <path>`), or `{"type": "base64", "data": "...", "content_type": "..."}`.
---
@@ -139,10 +139,12 @@ Skip avatar creation. Pass `image_url` directly:
**MCP:** `create_video_from_image(image_url=<url>, script=<script>, voice_id=<voice_id>, aspect_ratio="16:9")`
**CLI:**
```bash
curl -X POST "https://api.heygen.com/v3/videos" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"image_url": "https://example.com/headshot.jpg", "script": "<script>", "voice_id": "<voice_id>", "aspect_ratio": "16:9"}'
heygen video create -d '{
"image_url": "https://example.com/headshot.jpg",
"script": "<script>",
"voice_id": "<voice_id>",
"aspect_ratio": "16:9"
}'
```
Also accepts `image_asset_id`. Fastest path for one-off talking-head video.
@@ -153,12 +155,10 @@ Also accepts `image_asset_id`. Fastest path for one-off talking-head video.
**MCP:** `list_voices(type=private)` then `list_voices(type=public, language=<lang>, gender=<gender>)`
**CLI:**
```bash
curl -s "https://api.heygen.com/v3/voices?type=private&limit=20" \
-H "X-Api-Key: $HEYGEN_API_KEY"
heygen voice list --type private --limit 20
# Public voices with filters
curl -s "https://api.heygen.com/v3/voices?type=public&engine=starfish&language=en&gender=female&limit=20" \
-H "X-Api-Key: $HEYGEN_API_KEY"
heygen voice list --type public --engine starfish --language en --gender female --limit 20
```
---
@@ -167,19 +167,18 @@ curl -s "https://api.heygen.com/v3/voices?type=public&engine=starfish&language=e
**MCP:** `create_video_agent(prompt=<prompt>, avatar_id=<look_id>, voice_id=<voice_id>, style_id=<optional>, orientation=<orientation>)`
**CLI:** `POST /v3/video-agents` with `avatar_id` and `voice_id` as top-level parameters:
```json
{
"prompt": "...",
"avatar_id": "look_id_from_discovery",
"voice_id": "voice_id_from_discovery",
"style_id": "optional_style_id",
"orientation": "landscape"
}
**CLI:** `heygen video-agent create` with flags:
```bash
heygen video-agent create \
--prompt "..." \
--avatar-id "<look_id_from_discovery>" \
--voice-id "<voice_id_from_discovery>" \
--style-id "<optional_style_id>" \
--orientation landscape
```
- **Custom/stock avatar with known ID** → pass `avatar_id`. Do NOT describe avatar's appearance in prompt. Only delivery style + background/environment.
- **Custom/stock avatar with known ID** → pass `--avatar-id`. Do NOT describe avatar's appearance in prompt. Only delivery style + background/environment.
- **No avatar_id (auto-select)** → describe desired presenter in prompt. Less reliable (~80% vs ~97%).
- **Voice-over only** → omit `avatar_id`, state in prompt.
- **Voice-over only** → omit `--avatar-id`, state in prompt.
> Always provide explicit `avatar_id` for presenter videos. 97.6% duration accuracy vs ~80% without.
> Always provide explicit `--avatar-id` for presenter videos. 97.6% duration accuracy vs ~80% without.
+2 -4
View File
@@ -4,10 +4,8 @@ Runs automatically when `avatar_id` is set, before Generate. Appends correction
## Step 1: Fetch the avatar look metadata
```bash
curl -s "https://api.heygen.com/v3/avatars/looks/<avatar_id>" \
-H "X-Api-Key: $HEYGEN_API_KEY"
```
**MCP:** `get_avatar_look(look_id=<avatar_id>)`
**CLI:** `heygen avatar looks get --look-id <avatar_id>`
Extract:
- `avatar_type`: `"photo_avatar"` | `"studio_avatar"` | `"video_avatar"`
+5 -5
View File
@@ -73,23 +73,23 @@ Video Agent rejects `text/html` content type in the `files[]` array. Web pages (
**Symptom:** Video generation fails or produces errors immediately after creating a new avatar. The avatar exists in the HeyGen dashboard but videos referencing it fail.
**Root Cause:** Avatar creation is asynchronous. `POST /v3/avatars` returns success immediately, but the avatar image is still being processed. If you submit a video request before processing completes, it fails.
**Root Cause:** Avatar creation is asynchronous. `heygen avatar create` (and `create_photo_avatar` / `create_prompt_avatar` MCP tools) return success immediately, but the avatar image is still being processed. If you submit a video request before processing completes, it fails.
**Detection:** Poll `GET /v3/avatars/looks?group_id=<group_id>`. The avatar is NOT ready until:
**Detection:** Poll with `heygen avatar looks list --group-id <group_id>` (or MCP `list_avatar_looks`). The avatar is NOT ready until:
- `preview_image_url` is non-null
- `image_width` and `image_height` are non-zero
At the group level (`GET /v3/avatars`), an unready avatar will have no `preview_image_url` on the group object.
At the group level (`heygen avatar list`), an unready avatar will have no `preview_image_url` on the group object.
**Fix:** Poll every 10 seconds after creation, wait for preview URL to appear. Typical: 30-90s for photo avatars, 1-3 min for prompt avatars. Timeout at 5 min.
**The heygen-avatar skill handles this automatically.** If you bypass the skill and call the API directly, you must implement this polling yourself.
**The heygen-avatar skill handles this automatically.** If you bypass the skill, you must implement this polling yourself.
---
## Interactive Sessions Reliability
Interactive sessions (`POST /v3/video-agents/sessions`) have known issues:
Interactive sessions (created without `--wait` and iterated via `heygen video-agent send`) have known issues:
- Sessions frequently stuck at `processing` status
- `reviewing` state may never be reached
- Follow-up messages fail with timing errors
+17 -49
View File
@@ -116,51 +116,25 @@ if [ -n "$SKILLS_DIR" ]; then
for skill in "${SKILLS[@]}"; do echo " ${skill}/SKILL.md"; done
fi
# ─── Persistent config ──────────────────────────────────────
HEYGEN_CONFIG_DIR="$HOME/.heygen"
HEYGEN_CONFIG="$HEYGEN_CONFIG_DIR/config"
# Load key from config file if env var is not set
if [ -z "${HEYGEN_API_KEY:-}" ] && [ -f "$HEYGEN_CONFIG" ]; then
# shellcheck source=/dev/null
source "$HEYGEN_CONFIG"
fi
# ─── Check HEYGEN_API_KEY ────────────────────────────────────
# ─── Auth guidance ───────────────────────────────────────────
echo ""
if [ -z "${HEYGEN_API_KEY:-}" ]; then
echo " Get your key: ${BOLD}https://app.heygen.com/settings/api${RESET}"
echo ""
if [ -t 0 ]; then
printf " Enter your HeyGen API key: "
read -r HEYGEN_API_KEY
if [ -z "$HEYGEN_API_KEY" ]; then
warn "No key entered. You can set it later:"
echo " export HEYGEN_API_KEY=<your-key>"
echo " Or re-run ./setup"
fi
else
warn "HEYGEN_API_KEY is not set (non-interactive shell)."
echo " Set it manually: export HEYGEN_API_KEY=<your-key>"
echo " Or re-run ./setup in an interactive terminal."
fi
fi
echo "${BOLD}Auth:${RESET}"
echo " The skills prefer HeyGen Remote MCP (OAuth, no API key needed)."
echo " If MCP isn't connected, install and use the HeyGen CLI:"
echo " ${BOLD}curl -fsSL https://static.heygen.ai/cli/install.sh | bash${RESET}"
echo " ${BOLD}heygen auth login${RESET} # or: export HEYGEN_API_KEY=<your-key>"
echo " ${BOLD}heygen auth status${RESET} # verify"
if [ -n "${HEYGEN_API_KEY:-}" ]; then
echo -n "Validating API key... "
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://api.heygen.com/v3/users/me" \
-H "X-Api-Key: $HEYGEN_API_KEY" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "200" ]; then
ok "API key is valid."
# Persist to ~/.heygen/config
mkdir -p "$HEYGEN_CONFIG_DIR"
echo "HEYGEN_API_KEY=$HEYGEN_API_KEY" > "$HEYGEN_CONFIG"
chmod 600 "$HEYGEN_CONFIG"
ok "Key saved to ${BOLD}~/.heygen/config${RESET} (persists across sessions)."
if command -v heygen &>/dev/null; then
HEYGEN_VERSION="$(heygen --version 2>/dev/null | head -n1 || echo 'unknown')"
ok "heygen CLI detected: ${HEYGEN_VERSION}"
if heygen auth status &>/dev/null; then
ok "heygen auth OK"
else
err "API key validation failed (HTTP $HTTP_CODE)."
echo " Check your key: https://app.heygen.com/settings/api"
warn "heygen not authenticated — run: heygen auth login (or export HEYGEN_API_KEY=<key>)"
fi
else
warn "heygen CLI not found on PATH (optional — only needed if MCP isn't connected)"
fi
# ─── Summary ─────────────────────────────────────────────────
@@ -171,12 +145,6 @@ for skill in "${SKILLS[@]}"; do
done
echo ""
echo "${BOLD}Next steps:${RESET}"
if [ -z "${HEYGEN_API_KEY:-}" ]; then
echo " 1. Re-run ./setup and enter your API key, or:"
echo " export HEYGEN_API_KEY=<your-key>"
echo " 2. Ask your agent: \"Create my HeyGen avatar\""
else
echo " Ask your agent: \"Create my HeyGen avatar\""
fi
echo "${BOLD}Next step:${RESET}"
echo " Ask your agent: \"Create my HeyGen avatar\""
echo ""