Docs / API Platform /Deliver

Video API

Render a talking-video mp4 over REST — submit a text script or hosted audio, poll the async job, and receive a CDN URL. Per-minute billing, auto-refunded on failure.

Overview

The Video API renders a complete talking-video mp4 of one of your agents speaking — from a text script (the agent’s voice synthesizes it) or from a hosted audio file. It is asynchronous by default: submit a job, then poll for the finished video URL — or pass wait: true for a blocking render that returns the mp4 in the response. On success you get a public CDN URL, the output duration, and the credits charged.

essence-2 / essence-2-max render at 1080p by default — 1080×1920 portrait or 1920×1080 landscape, matching the source orientation and capped at the source’s long side; expression-2 renders at its native 416×720.

Talking videos bill per minute of output, rounded up: essence-2-max is 8 credits/min; expression-1, expression-2, and essence-2 are 4 credits/min; essence-1 is 2 credits/min (essence-2 is the standard distilled render; the former essence-2-light name is retired, and the pre-rename essence-2-quality is still accepted as a deprecated alias for essence-2-max). If a render fails, the charge is automatically refunded.

Limits: up to 120 seconds of output and 5000 characters of text.

Generate a talking video

POST /v1/video/generate — submit a render job. Async by default: returns immediately with a job_id and status: "processing"; poll the GET endpoint for completion. Pass wait: true for blocking mode — the call holds the connection until the render finishes and returns the finished mp4 directly.

ParameterTypeRequiredDescription
modelstringyesEngine: essence-1, expression-1, expression-2, essence-2-max, or essence-2. All five render talking video today.
agent_codestringyesAn agent you own — supplies the avatar identity (and, for text, the default voice).
inputobjectyesThe render source — see below.
input.typestringyestext or audio.
input.textstringfor textScript to speak (≤ 5000 chars).
input.voicestringnoVoice id override for text input. Defaults to the agent’s own voice.
input.audio_urlstringfor audioPublic URL to a WAV or MP3 file.
waitbooleannoBlocking mode. false (default) returns a job_id to poll. true blocks until the render finishes (up to ~90s) and returns the finished video_url — plus duration_seconds and credits_charged — directly in this response; if it exceeds the cap you get the async { job_id } to poll instead. Accepted as a JSON/multipart field or as a ?wait=true query parameter.

Text input

import requests

resp = requests.post(
    "https://api.bithuman.ai/v1/video/generate",
    headers={"Content-Type": "application/json", "api-secret": "YOUR_API_SECRET"},
    json={
        "model": "essence-2",
        "agent_code": "A80HVD8577",
        "input": {"type": "text", "text": "Hello, welcome to bitHuman."},
    },
)
print(resp.json())
{
  "success": true,
  "job_id": "vid_3f9a2c1b8e7d4a6f0b21",
  "status": "processing"
}

Audio input

resp = requests.post(
    "https://api.bithuman.ai/v1/video/generate",
    headers={"Content-Type": "application/json", "api-secret": "YOUR_API_SECRET"},
    json={
        "model": "expression-2",
        "agent_code": "A80HVD8577",
        "input": {"type": "audio", "audio_url": "https://example.com/speech.wav"},
    },
)
print(resp.json())

Blocking mode (wait: true)

Add "wait": true (a JSON/multipart field, or ?wait=true as a query parameter) to hold the connection until the render finishes and get the mp4 back in the same response — no polling. If the render exceeds the ~90-second cap you get the async { job_id } to poll instead.

resp = requests.post(
    "https://api.bithuman.ai/v1/video/generate",
    headers={"Content-Type": "application/json", "api-secret": "YOUR_API_SECRET"},
    json={
        "model": "essence-2-max",
        "agent_code": "A80HVD8577",
        "input": {"type": "text", "text": "Hello, welcome to bitHuman."},
        "wait": True,
    },
)
print(resp.json())
{
  "success": true,
  "job_id": "vid_3f9a2c1b8e7d4a6f0b21",
  "status": "completed",
  "video_url": "https://assets.bithuman.ai/.../vid_3f9a2c1b8e7d4a6f0b21.mp4",
  "duration_seconds": 6.5,
  "credits_charged": 8
}

A 402 (INSUFFICIENT_BALANCE) is returned at submit time if your balance can’t cover the render. An invalid model, a missing/invalid input, or text over the limit returns 400 before any charge. Requesting a model the agent can’t be launched as returns 409 MODEL_NOT_GENERATED — also before any charge: for expression-2 / essence-2 that means the trained per-identity model doesn’t exist yet (agent <code>'s <family> model hasn't been generated yet); essence-2-max is gated on the agent’s source video, which its identity prepares from on demand (agent <code>'s essence-2-quality model requires a source video, which this agent doesn't have — the message keeps the internal essence-2-quality family name until the platform-side flip); essence-1 needs the agent’s .imx model file (present on every completed essence-1 creation), and expression-1 needs an expression-1 agent — or the free post-generation expression-1 model add — with an image. Check the agent’s supported_models on the Agents API, or add the model first.

Get talking-video status

GET /v1/video/{job_id} — poll a render job.

import requests

job_id = "vid_3f9a2c1b8e7d4a6f0b21"
resp = requests.get(
    f"https://api.bithuman.ai/v1/video/{job_id}",
    headers={"api-secret": "YOUR_API_SECRET"},
)
print(resp.json())

While rendering (note: job responses echo the internal family name — an essence-2-max request reads back as essence-2-quality, and essence-2 as essence-2-light, until the platform-side rename flip):

{ "success": true, "job_id": "vid_3f9a2c1b8e7d4a6f0b21", "status": "processing", "model": "essence-2-quality" }

When complete:

{
  "success": true,
  "job_id": "vid_3f9a2c1b8e7d4a6f0b21",
  "status": "completed",
  "model": "essence-2-quality",
  "video_url": "https://assets.bithuman.ai/.../vid_3f9a2c1b8e7d4a6f0b21.mp4",
  "duration_seconds": 6.5,
  "credits_charged": 8
}
FieldTypeDescription
statusstringprocessing, completed, or failed.
modelstringThe engine used.
video_urlstringPublic mp4 URL (present when completed).
duration_secondsnumberOutput duration (present when completed).
credits_chargedintegerCredits charged for this render (present when completed).
errorobjectFailure detail (present when failed); the charge is refunded.

Polling pattern

import time, requests

def wait_for_video(job_id, api_secret, timeout=600):
    while timeout > 0:
        r = requests.get(
            f"https://api.bithuman.ai/v1/video/{job_id}",
            headers={"api-secret": api_secret},
        ).json()
        if r["status"] == "completed":
            return r["video_url"]
        if r["status"] == "failed":
            raise RuntimeError(r.get("error"))
        time.sleep(3)
        timeout -= 3
    raise TimeoutError("render did not finish in time")

See Talking video generation for the concept and Billing & credits for how credits are consumed.