Two doors to HeyGen. This skill documents the dedicated surface under
providers/heygen/video/— member-reachable, credit-metered, with an ownership gate and first-class streaming support./iblai-api-external-service-proxydocuments the generic door (/api/ai-proxy/orgs/{org}/services/heygen/{action}/), which is organization-admin-only and reaches a wider action catalog. Pick the dedicated surface for anything a signed-in user does for themselves; pick the proxy for admin automation over actions this surface does not expose.
Generate and manage HeyGen media through ibl.ai. The Data Manager forwards to HeyGen and returns HeyGen's own response body, so HeyGen's API docs describe the payload shapes; what this skill adds is the ibl.ai envelope around them — auth, credits, ownership, and the parts that are not verbatim.
- Voices — list, clone, synthesize speech.
- Avatars — create photo or digital-twin avatar groups, poll training, list looks, mint a consent URL.
- Videos — generate, poll to completion.
- Assets — upload media for the above to reference.
- Streaming — create an interactive session, then drive a live talking avatar.
The schema is the contract
These endpoints live on the Data Manager service and its live OpenAPI schema is the single source of truth — the URLs and fields below exist for orientation and can drift between releases. Validate before building requests:
- Schema (raw):
https://api.iblai.app/dm/api/docs/schema/ - Swagger UI:
https://api.iblai.app/dm/api/docs/
curl -sS "https://api.iblai.app/dm/api/docs/schema/" -o /tmp/iblai_schema.yaml
grep -nE "providers/heygen" /tmp/iblai_schema.yaml
For the payload shapes inside a response, HeyGen's own documentation is the source of truth — ibl.ai passes them through unchanged.
Auth & conventions
dm_url=https://api.iblai.app/dm— these are Data Manager endpoints reached through the gateway's/dmprefix.base={dm_url}/api/ai-agent/orgs/{org}/users/{username}/providers/heygen/video- Header:
Authorization: Api-Token $IBLAI_API_KEYon every request. - Placeholders:
{org}= your organization key =$IBLAI_ORG.{username}= the user the call acts for. It must be your own username unless you are an organization admin, who may act on any member's path.
- Prefix twin:
{dm_url}/api/ai-mentor/...resolves to the same views. Theai-agentspelling used here is canonical;ai-mentoris marked deprecated in the generated schema but still works. - Upstream versions: voices, avatars, videos and assets target HeyGen v3;
everything under
streaming/targets v1. - Responses are HeyGen's body, verbatim — including HeyGen's
{"data": ...}envelope and its sibling pagination fields (has_more,next_token). Unwrap it yourself. One endpoint is not verbatim:streaming/knowledge-bases/is flattened (see below). - Undeclared fields are forwarded. Anything you send beyond the fields listed here is passed to HeyGen untouched, so newer HeyGen parameters work without waiting for ibl.ai to add them. Validation only covers the fields named here.
- Not connected yet? Run
/iblai-api-loginfirst.
Access
Every member of the organization can use this surface on their own {username} path
out of the box — the default member role carries the whole family. Organization
admins reach any member's path.
The RBAC verbs, for building a custom role, resolve at resource
/platforms/{id}/heygen/:
| Verb | Covers |
|---|---|
Ibl.Mentor/Heygen/list |
voices/, avatar-groups/{id}/looks/, streaming/knowledge-bases/, streaming/avatars/ |
Ibl.Mentor/Heygen/read |
avatar-groups/{id}/, videos/{id}/ |
Ibl.Mentor/Heygen/action |
every POST in this skill |
There is no write or delete verb — creation is covered by action.
Ownership gate. Every billable create records the id it produced against the
user who made it. The routes that take an id back — avatar-groups/{group_id}/…,
videos/{video_id}/, streaming/sessions/{session_id}/… — return 403 when the id
belongs to someone else. Two caveats worth knowing: an admin may act on any id owned
within their own organization but never across organizations, and an id with no
ownership record is allowed through. That last one is deliberate, so ids minted
before the gate existed keep working — but it means an id that did not come from this
surface is not protected by it.
Reads
Voices
- GET
{base}/voices/?[type=public|private][&engine=][&language=][&gender=male|female][&limit=][&token=]— list voices. All params optional and forwarded to HeyGen, which supplies the defaults.limitmust be ≥1.tokenis the cursor from a previous page'snext_token.
Avatars
- GET
{base}/avatar-groups/{group_id}/— avatar group detail. This is the poll target for digital-twin training; the training status lives in the body. - GET
{base}/avatar-groups/{group_id}/looks/— list the looks in a group.group_idcomes from the path and overrides anygroup_idyou send as a query param; every other query param is forwarded to HeyGen untouched.
Videos
- GET
{base}/videos/{video_id}/— video detail, and the poll target for generation. HeyGen's body carriesstatus(pending|processing|completed|failed),durationin seconds,created_at/completed_at, and the output URLs once finished.
Streaming
- GET
{base}/streaming/avatars/— list the avatars available for interactive sessions. Verbatim. - GET
{base}/streaming/knowledge-bases/— list knowledge bases for grounding a session. The one transformed response on this surface: HeyGen returns{"data": {"list": [...]}}and ibl.ai returns{"data": [...]}, preserving every other top-level key. Write your client against the flattened shape.
Writes
Every endpoint in this section is a POST. The five marked billable consume
credits — see Credits in Notes.
Voices
-
POST
{base}/voices/clone/— billable. Confirm with the user first. Clone a voice from a recording.{ "voice_name": "string (required)", "audio": { "…": "object (required) — references an uploaded asset" }, "language": "string", "remove_background_noise": false }HeyGen bills voice-clone training when it completes, which is well after this call returns.
-
POST
{base}/voices/speech/— billable. Synthesize speech from text.{ "voice_id": "string (required)", "text": "string (required)" }
Avatars
-
POST
{base}/avatar-groups/— billable. Confirm with the user first. Create a photo or digital-twin avatar.{ "type": "digital_twin | photo (required)", "name": "string (required)", "file": { "…": "object (required) — references an uploaded asset" } }HeyGen's response nests two ids:
data.avatar_group.idis the group id that everyavatar-groups/{group_id}/…route below takes, anddata.avatar_item.idis a look id. Using the look id where a group id belongs is the most common mistake on this surface. Digital-twin training is charged by HeyGen at completion. -
POST
{base}/avatar-groups/{group_id}/consent/— mint a consent URL for a digital twin. Send an empty body; nothing is read or forwarded. Not billable despite being aPOST.
Videos
-
POST
{base}/videos/— billable. Confirm with the user first. Generate a video.Field Type Required typeavatar|imageyes avatar_idstring when typeisavatarimageobject when typeisimagescriptstring no voice_idstring no audio_urlstring no audio_asset_idstring no titlestring | null no Only
avatarandimageare accepted here; HeyGen's other video types are rejected. Exactly one audio source should be supplied — ibl.ai does not enforce that, HeyGen does.engineandresolution(4k|1080p|720p) are forwarded like any undeclared field and are what determine HeyGen's own per-minute price. Returnsdata.video_id; pollGET {base}/videos/{video_id}/.
Assets
- POST
{base}/assets/— upload a media file for the endpoints above to reference.multipart/form-dataonly, one part namedfile. Default cap 32 MiB, configurable per organization; over-cap is a400, not a413. Not billable. Returns HeyGen'sasset_idandurl.
Streaming
-
POST
{base}/streaming/sessions/— billable. Open an interactive session.{ "avatar_name": "string", "quality": "string", "voice": { "…": "object" }, "language": "string", "knowledge_base_id": "string", "disable_idle_timeout": false, "activity_idle_timeout": 120 }ibl.ai injects
version: "v2",video_encoding: "H264"andsource: "sdk"when you omit them; anything you send wins. Returnssession_id,access_tokenandurl. The media never touches ibl.ai — the browser connects to LiveKit aturland opens HeyGen's chat websocket directly withaccess_token. -
POST
{base}/streaming/sessions/{session_id}/{action}/— drive the session.{action}is one of exactly seven slugs:start,stop,interrupt,keep-alive,start-listening,stop-listening,task. Anything else is a404before HeyGen is called.session_idcomes from the path and overrides any you put in the body. Onlytaskhas a validated body:{ "text": "string (required)", "task_type": "talk | repeat", "task_mode": "sync | async" }None of the seven is billable — deliberately, so a keep-alive can never fail with a payment error mid-session. → Full lifecycle, cadence and each action's effect:
references/streaming.md.
Example
Generate an avatar video and poll it to completion:
dm_url="https://api.iblai.app/dm"
base="$dm_url/api/ai-agent/orgs/$IBLAI_ORG/users/$IBLAI_USERNAME/providers/heygen/video"
auth="Authorization: Api-Token $IBLAI_API_KEY"
# pick a voice
curl -s "$base/voices/?limit=5" -H "$auth" | jq '.data[] | {voice_id, name}'
# generate — returns data.video_id
vid=$(curl -s -X POST "$base/videos/" -H "$auth" -H "Content-Type: application/json" \
-d '{"type":"avatar","avatar_id":"Abigail_expressive_2024112501",
"script":"Welcome to the course.","voice_id":"f38a635bee7a4d1f9b0a654a31d050d2",
"resolution":"1080p","title":"welcome"}' | jq -r '.data.video_id')
# poll until terminal
while :; do
s=$(curl -s "$base/videos/$vid/" -H "$auth" | jq -r '.data.status')
echo "$s"; case "$s" in completed|failed) break ;; esac; sleep 5
done
Open a streaming session:
curl -s -X POST "$base/streaming/sessions/" -H "$auth" -H "Content-Type: application/json" \
-d '{"avatar_name":"Wayne_20240711","quality":"high"}' \
| jq '{session_id: .data.session_id, url: .data.url}'
# then: POST $base/streaming/sessions/<id>/start/, drive with .../task/,
# keep-alive every 60s, and .../stop/ when done.
Notes
- Credits are charged only when ibl.ai supplies the HeyGen account. An
organization that configures its own HeyGen key pays HeyGen directly and is never
charged credits here. When ibl.ai's account serves the call and a price is
configured, the five billable endpoints check the balance before calling HeyGen
and return
402if it is short. The charge is recorded after a successful call, so a failed call is never billed, and a failure to record a charge never turns a successful call into an error. - Reads, controls, consent and asset upload are always free and never return
402. - HeyGen's price is not ibl.ai's price. HeyGen bills video by the second at a
rate set by
engineandresolution; the credit price here is a flat per-call figure the organization configures. They will not agree, by design. - A
403on an id you did not create is the ownership gate — use your own resources. A403on the{username}path segment means you are acting on someone else's path without being an admin. - Upstream auth failures surface as
502, not401. If HeyGen rejects the server's key you get502; a401you receive is always about your ibl.ai token. HeyGen's503is normalised to429. - Poll, do not wait. Video generation, voice-clone training and digital-twin training are all asynchronous with no callback on this surface. Poll the matching detail read; 5 s is a reasonable interval.
streaming/knowledge-bases/is the only reshaped response. Everything else is HeyGen's body byte for byte.- Interactive session time is invisible to this API. Once a session is open the
browser talks to LiveKit directly, so session duration is not reported back through
any endpoint here. Send
stopwhen you are done rather than abandoning the session. - For the generic multi-provider proxy, including HeyGen actions this surface does not
expose (translation, webhooks, talking photos, remaining quota), see
/iblai-api-external-service-proxy.
Reference material
The endpoints above are the primary; these carry the exhaustive material.
references/streaming.md— the interactive session lifecycle end to end: all seven control actions, the LiveKit handoff, keep-alive cadence, idle timeouts and teardown.references/errors.md— every status, the condition behind it, the upstream-to-ibl.ai status translation, and the full402body.