Search

Search HiAPI models, tools, and resources.

Back to blog
Tutorial6 min read

HeyGen Avatar V API: From Script to Video

Script and recording payloads, preset values, response shapes, bounded polling and video/SRT downloads with cURL and Python.

HiAPI EditorialHeyGen Avatar VAvatar VideoAPI Tutorial

HeyGen Avatar V generates a lip-synced presenter video from a preset avatar and a script or audio track. On HiAPI it costs $0.15 per second of output, so a 10-second clip costs $1.50.

HeyGen Avatar V is live on HiAPI. Open the model page, pick a presenter and voice, and try a short line before connecting the API. Here is an existing business explainer:

Brandon Office Sitting Front, Dexter - Professional; 16:9, 1280×720, about eight seconds. The request selected 1080p; this is the 720p web version.

Hi, I'm Daniel. Send me your quarterly numbers, and I'll walk your team through what changed and what to do next.

Generate with this script → The script is pre-filled; manually select the avatar and voice above, 1080p, 16:9 and MP4.

Try it in the browser

  1. Open the script link above and keep the short line.
  2. Select the avatar and voice yourself. The link fills only the script.
  3. Choose resolution, aspect ratio and output format.
  4. Generate, preview the voice and lip-sync, and download the clip. Then replace the line with your own message.

V offers 497 avatar presets and 102 voices for explainers, lessons and social clips. V uses preset presenters; choose Avatar IV when your own photo needs to appear on screen.

All resolutions use the same rate, settled to actual output seconds, with failed tasks fully refunded. See the pricing and budget guide for the full calculation.

Prepare your first API request

Create a key in the dashboard. Set model to heygen-avatar-v and always provide input.avatar. Use the exact English request values for avatars and voices from the parameter docs.

Choose exactly one driving input: prompt for a script or audio_url for a recording. Scripts are spoken aloud, so keep camera directions out of prompt. For audio-driven video, provide an accessible HTTP(S) audio URL and omit prompt and voice. Duration follows the spoken script or recording; there is no duration parameter.

What you haveDriving fields
A script to readavatar + prompt; optional voice
A finished recordingavatar + audio_url; omit prompt and voice

Find avatars in Avatar presets and voices in Voice presets. Expand Show all, search, and copy the exact Request value into the payload.

Minimal recording-driven payload: replace this placeholder with your accessible audio URL. Omitted framing, resolution and format use documented defaults. The audio limit is 600 seconds and 32 MiB.

{
  "model": "heygen-avatar-v",
  "input": {
    "avatar": "Brandon Office Sitting Front",
    "audio_url": "https://example.com/your-recording.mp3"
  }
}

Create a task with cURL

Replace the key placeholder. This request uses the business clip's original settings:

export HIAPI_API_KEY="sk-YOUR_KEY"
curl --fail-with-body --connect-timeout 10 --max-time 60 \
  -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "heygen-avatar-v",
    "input": {
      "avatar": "Brandon Office Sitting Front",
      "prompt": "Hi, I\u0027m Daniel. Send me your quarterly numbers, and I\u0027ll walk your team through what changed and what to do next.",
      "voice": "Dexter - Professional",
      "aspect_ratio": "16:9",
      "resolution": "1080p",
      "caption": false,
      "output_format": "mp4"
    }
  }'

Check the HTTP status and JSON code. Save data.taskId after a successful creation, then query that task:

TASK_ID="REPLACE_WITH_data.taskId"
curl --fail-with-body --connect-timeout 10 --max-time 60 \
  "https://api.hiapi.ai/v1/tasks/$TASK_ID" \
  -H "Authorization: Bearer $HIAPI_API_KEY"

queued, handling and archiving are in-progress states. Terminal states are success and fail. On success, find the video item by data.output[].type; if captions are enabled, also find caption. Download each artifact's url before its expireAt.

Response shape examples: the first shows creation; the second shows a successful query with captions enabled. IDs, URLs and expiry timestamps are placeholders; expireAt is a number in Unix seconds. If a POST times out or its response is lost, check task history before submitting again; resume GET with an existing ID.

{
  "code": 200,
  "data": {"taskId": "TASK_ID_FROM_CREATE"}
}
{
  "code": 200,
  "data": {
    "taskId": "TASK_ID_FROM_CREATE",
    "status": "success",
    "output": [
      {"type": "video", "url": "https://example.com/result.mp4", "expireAt": 4070908800},
      {"type": "caption", "url": "https://example.com/result.srt", "expireAt": 4070908800}
    ]
  }
}

Python: submit once, poll, download

Install requests and set HIAPI_API_KEY in your environment. This example checks HTTP and API errors, limits polling to 30 minutes, and prints the task ID so you can resume queries after a timeout.

import os
import time
from pathlib import Path
import requests

API = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"}
payload = {
    "model": "heygen-avatar-v",
    "input": {
        "avatar": "Brandon Office Sitting Front",
        "prompt": "Hi, I'm Daniel. Send me your quarterly numbers, and I'll walk your team through what changed and what to do next.",
        "voice": "Dexter - Professional",
        "aspect_ratio": "16:9",
        "resolution": "1080p",
        "caption": False,
        "output_format": "mp4",
    },
}

def api_request(method, url, **kwargs):
    advice = (
        "Submission state may be unknown; check task history before another POST."
        if method == "POST"
        else f"Continue GET with the saved task ID: {url.rsplit('/', 1)[-1]}."
    )
    try:
        response = requests.request(
            method, url, headers=HEADERS, timeout=(10, 30), **kwargs
        )
    except requests.RequestException:
        raise RuntimeError(f"Network error. {advice}") from None
    if not response.ok:
        raise RuntimeError(f"HTTP {response.status_code}. {advice}")
    try:
        body = response.json()
    except ValueError:
        raise RuntimeError(f"Response is not JSON. {advice}") from None
    if not isinstance(body, dict):
        raise RuntimeError(f"Unexpected response shape. {advice}")
    if body.get("code") != 200 or not isinstance(body.get("data"), dict):
        raise RuntimeError(f"API error: code={body.get('code')}. {advice}")
    return body["data"]

# Submit once. No automatic POST retries.
created = api_request("POST", API, json=payload)
task_id = created.get("taskId")
if not isinstance(task_id, str) or not task_id:
    raise RuntimeError("Missing taskId; check task history before resubmitting.")
print("taskId:", task_id)  # Keep this ID to resume GET queries.
deadline = time.monotonic() + 1800
while time.monotonic() < deadline:
    data = api_request("GET", f"{API}/{task_id}")
    status = data.get("status")
    if status == "fail":
        raise RuntimeError(f"Task failed: {task_id}; inspect its task record.")
    if status == "success":
        break
    if status not in ("queued", "handling", "archiving"):
        raise RuntimeError(f"Unexpected task status: {status}")
    time.sleep(8)
else:
    raise TimeoutError(f"Polling timed out; continue GET for taskId={task_id}.")

raw_outputs = data.get("output")
if not isinstance(raw_outputs, list):
    raise RuntimeError(f"Missing output list; inspect taskId={task_id}.")
outputs = {
    item["type"]: item
    for item in raw_outputs
    if isinstance(item, dict) and item.get("type") in ("video", "caption")
}
if "video" not in outputs:
    raise RuntimeError("Successful task has no video artifact.")
for kind, artifact in outputs.items():
    if not isinstance(artifact.get("url"), str) or not artifact["url"]:
        raise RuntimeError(f"Missing {kind} URL; inspect taskId={task_id}.")
    suffix = payload["input"]["output_format"] if kind == "video" else "srt"
    target = Path(f"avatar-v.{suffix}")
    # Artifact downloads do not need the API Authorization header.
    with requests.get(artifact["url"], stream=True, timeout=(10, 120)) as response:
        response.raise_for_status()
        with target.open("wb") as file:
            for chunk in response.iter_content(chunk_size=1024 * 1024):
                if chunk:
                    file.write(chunk)
    print("saved:", target)

Parameters to set explicitly

InputRule or default
avatarRequired; exact English preset value
prompt / audio_urlChoose one; script up to 5,000 characters, audio up to 600 seconds and 32 MiB
voiceOptional for scripts; defaults to Warm Pro Narrator
resolutionDefault 720p; also 1080p and lowercase 4k
aspect_ratioDefault 16:9; also 9:16, 4:5, 5:4, 1:1, auto
fitDefault cover; also contain
captionDefault false; true returns a separate SRT file
output_formatDefault mp4; also webm
remove_backgroundDefault false; for transparency, combine true with webm and check a short output from your chosen avatar
backgroundObject: type is color or image; value is a hex color or HTTP(S) image URL

Load the SRT into your player or editor to overlay subtitles. This vertical social example requested caption=true and returned a separate SRT. The video is 1080×1920, 6.52 seconds, requested at 1080p and 9:16. Import the separate SRT into your editor.

New drop just landed: our travel mug keeps coffee hot for twelve hours. Swipe up and grab yours today!

Generate with this script → The script is pre-filled; manually select Riley Casual Front, Charming Charles - Friendly, 9:16, 1080p and MP4, then enable captions.

Four production checks

  1. Validate the avatar, driving input, preset values and length limits.
  2. Save the task ID; resume GET after a query error or polling timeout.
  3. Find the video artifact, plus caption when enabled, by type.
  4. Download and inspect actual dimensions, audio and the SRT before delivery.

Common problems

  • Input validation: check the required avatar, exactly one driving input, exact preset values and script/audio limits. Use 4k and remove duration.
  • POST timeout: check task history first. If you have a taskId, continue GET queries; each POST creates a new task.
  • Terminal fail: inspect the task record's error and correct the relevant input before submitting again.
  • Expiring outputs: download promptly after success. Locate captions by type=caption.

For a server integration, add "callback": {"url": "https://yourapp.com/webhook", "when": "final"} beside input. It receives the terminal task object; keep GET available as a fallback query.

Read the per-second pricing and budgets, full API parameter reference.

Generate with this script → The script is pre-filled; select the business avatar and voice, 1080p, 16:9 and MP4. Start with one sentence, then connect your full script.

Generate it with HiAPI

Choose a model, enter your prompt, and see the result.

HiAPI Blog

Related articles

HiAPI

Generate it with HiAPI

Start generating