Docs / API Platform /Build

Agents API

Generate avatar agents, poll their status, retrieve and update them, then make them speak or inject knowledge into live sessions.

The agent lifecycle

From “I have a face and voice” to “live talking avatar”:

Generate → Poll until ready → Resolve by code → Live session → Speak

This page covers the full REST lifecycle. For the in-process SDK flow, see the Python SDK and agent lifecycle concepts.

Validate your key

POST /v1/validate — verify your API secret before making other calls. Costs no credits.

curl -X POST https://api.bithuman.ai/v1/validate \
  -H "api-secret: $BITHUMAN_API_SECRET"
{ "valid": true }

Generate an agent

POST /v1/agent/generate — create a new avatar agent. Generation is asynchronous and billed per modelexpression-2 costs 2000 credits, the Essence 2 family (essence-2, essence-2-max) 500, the v1 families (essence-1, expression-1) 250, and auto bills the routed model’s rate (machine-readable schedule: GET /v1/pricing). The call returns immediately with an agent_id and processing status.

ParameterTypeRequiredDefaultDescription
promptstringnorandomSystem prompt / personality for the agent.
imagestringnoImage URL or base64 data for appearance. A supplied image is treated as a reference and always regenerated via Seedream 5 edit to standardize it (never used raw); omit it and a portrait is generated from the prompt with Seedream 5 pro.
audiostringnoAudio URL or base64 data for voice cloning.
aspect_ratiostringno16:9Aspect ratio for the generated identity image and driver video — 16:9 landscape (default), 9:16 portrait, 1:1 square. Images are generated at 1080p.
transparencybooleannofalseWhen true, the identity image is generated on a solid green-screen background for chroma-key / transparent embedding — the character itself never uses green.
framingstringnoportraitportrait (default) frames head-and-shoulders; full_body shows the whole figure including the feet (kiosk / standing-avatar layouts).
agent_idstringnoautoCustom agent identifier.
durationnumbernoDeprecated — omit it. The internally generated identity video is standardizing on 10 seconds; the parameter is ignored as that rollout completes.
modelstringnoexpressionAvatar model familyexpression (default) or essence — combined with version to pick the engine: expression+v1 = Expression 1 (the default), essence+v1 = Essence 1, essence+v2 = essence-2 (the combined Essence 2 creation), expression+v2 = expression-2. You may also pass a full engine name directly (essence-1, essence-2, essence-2-max, expression-1, expression-2) or auto (classify-and-route) — those pass through unchanged and version is ignored, so existing integrations keep working. An omitted model defaults to expression at v1 (Expression 1, 250 credits) — a v1 engine at the ungated 250-credit rate; a caller is never silently upgraded onto a v2 engine or a higher price. Invalid values return 400 VALIDATION_ERROR (no credits charged); the retired essence-2-light name returns a targeted hint pointing at essence-2. See models and Essence 2 & Expression 2.
versionstringnov1Engine generation for the chosen model family — v1 (default) selects the first-generation engine (Essence 1 / Expression 1, 250 credits); v2 selects the second-generation engine (Essence 2, 500 credits, or Expression 2, 2000 credits). Ignored when model is a full engine name or auto.

Agent creation is image-only. Provide a portrait image (or let the prompt generate one) — bitHuman generates a 10-second identity video internally (Seedance 1.5 pro, 25 fps), authored to loop seamlessly (its first and last frames match). Video input is not part of the creation contract for any model: a request carrying video is rejected with 400 VIDEO_INPUT_NOT_SUPPORTED before anything is billed (verified against the live API, 2026-08-01).

Model-specific inputs and creation times

The model you pick changes what creation needs and how long it runs. All models share the same pipeline prefix — persona, voice, and image are prepared first (each generated from your prompt when not supplied) — then the model-specific identity step runs:

modelIdentity inputIdentity stepTypical creation time
essence-1image (or generated from prompt); an identity video is generated internally if neededBuilds the portable .imx avatar2–5 minutes
expression-1 (default)image (or generated from prompt)None (animates the portrait at runtime)~1–2 minutes
essence-2image (or generated from prompt) — a 10-second identity video is generated from it internally (the video step)Combined: builds the standard Essence 2 identity bundle on a cloud GPU; Max derives from the same identity video25–40 minutes typical; occasionally longer (allowed up to several hours)
essence-2-maxIncluded with every essence-2 creation — its identity derives from the same internally generated identity videoInstant prep of a compact identity bundle (seconds, warm)Available once the combined creation is ready
expression-2image (or generated from prompt)Trains a per-identity model on an H100-class GPUAbout 1–1.5 hours (roughly 60–100 minutes; longer when the adaptive recipe extends to hold quality)
autoimage or prompt (classified automatically)As the routed model — essence-2 or expression-2As the routed model

Set your polling timeout per model — a 5-minute client timeout is fine for essence-1 but will falsely “fail” every expression-2 and essence-2 creation. Full model behavior (serving tiers, idle, pricing) is in each model’s guide.

essence-2 — the combined creation

model: "essence-2" creates both Essence 2 models from one creation: a single 500-credit charge runs the standard Essence 2 training, and Essence 2 Max becomes available at no extra charge from the same internally generated identity video (its identity prepares on demand from that video). Sessions launched as essence-2 serve the standard chain by default; launch with ?model=essence-2-max on the session/embed URL (or the model field on the embed token) when you want the premium model. Once ready, the agent’s supported_models lists both families — essence-2 (the standard Essence 2) and essence-2-max. Both are public names and can be sent straight back as a model value — the internal tier spellings essence-2-light / essence-2-quality are folded before the response is built and never appear in it.

auto — let the platform pick the model

model: "auto" runs an LLM subject classifier over your creation input — the image if you provided one, else the prompt — and routes it:

  • a photorealistic personessence-2 (the combined creation above);
  • a cartoon, animal, creature, or robotexpression-2 (the generative engine, which handles any subject and works best for stylized characters).

auto never rejects on subject — it routes instead — and charges the routed model’s rate (500 credits for essence-2, 2000 for expression-2). It is the default selection in the dashboard’s create flow, but API callers must pass it explicitly: an omitted model defaults to expression-1 (v1, 250 credits) for backward compatibility.

The Essence 2 subject gate (422)

An explicit Essence 2 creation (essence-2, essence-2-max) requires a photorealistic human subject — the Essence identity pipelines train on real human faces. The same classifier runs on your input before anything is billed and before any agent row is created; a non-human or stylized subject is rejected with 422 MODEL_SUBJECT_MISMATCH:

{
  "error": {
    "code": "MODEL_SUBJECT_MISMATCH",
    "message": "essence-2 requires a photorealistic human subject; this image looks like a cartoon — use expression-2",
    "httpStatus": 422
  },
  "status": "error",
  "status_code": 422
}

(With a prompt-only input the message reads “this description sounds like a …”.) Use expression-2 for those subjects, or model: "auto" to route automatically. A classifier outage never blocks creation — the gate fails open.

Note The Python examples below use requests, which is not in the standard library — pip install requests first, or use curl / urllib instead.

import requests

resp = requests.post(
    "https://api.bithuman.ai/v1/agent/generate",
    headers={"Content-Type": "application/json", "api-secret": "YOUR_API_SECRET"},
    json={
        "prompt": "You are a professional video content creator.",
        "image": "https://example.com/avatar.jpg",
        "model": "expression-2",
    },
)
print(resp.json())

Note — image must be publicly fetchable, and this is not checked at submit time. The https://example.com/… URLs above are placeholders. Posting one verbatim returns HTTP 200 with {"success": true, "status": "processing"}, and the job only fails seconds later with Image processing failed: Failed to download after 3 attempts: 404. The credits are charged at submit and automatically refunded on that failure (verified 2026-07-28: -500 then +500 within 4 s), so nothing is lost — but a 200 here is not confirmation that your image was accepted. Poll GET /v1/agent/status/{agent_id} before assuming the creation started.

{
  "success": true,
  "message": "Agent generation started",
  "agent_id": "A80HVD8577",
  "status": "processing"
}

Note The generation endpoint is POST /v1/agent/generate. (Older docs referenced /v1/agent-generation — that path is incorrect.)

Idempotent retries — the Idempotency-Key header

Creation is billed, so a network timeout on the response should never make you guess whether to retry. Send an Idempotency-Key header (any unique string you choose, e.g. a UUID) with the request:

curl -X POST https://api.bithuman.ai/v1/agent/generate \
  -H "Content-Type: application/json" \
  -H "api-secret: $BITHUMAN_API_SECRET" \
  -H "Idempotency-Key: order-42-avatar-1" \
  -d '{"prompt": "You are a helpful retail assistant.", "model": "essence-2"}'
  • A repeated request with the same key returns the first response verbatim — same agent_id — with an Idempotency-Replayed: true response header, and does not start a second billed generation.
  • When you don’t supply your own agent_id, the agent id is derived from the key, so a retry that lands on a different server replica still converges on the same agent.
  • Use a fresh key per intended creation — reusing a key deliberately gives you the previous creation back.

The header is also honored on POST /v1/video/generate and POST /v1/dynamics/generate.

Poll status

GET /v1/agent/status/{agent_id} — returns the current state of a generation request. Poll every 5 seconds.

StatusDescription
processingInitial state — generation queued.
generatingActive generation in progress (sub-steps running).
completedAn intermediate sub-step finished. Not terminal — it can appear early (even around ~5% progress), so do not stop polling on it.
successA sub-step finished — the voice/portrait step and the identity-video step each write it. Not terminal: it appears mid-run, normally at progress 0.2 and 0.45, before training has even started. Count it as done only when progress is also 1.0 (some historical rows finished on success + 1.0).
readyTerminal success — the model is available for use. Always written together with progress: 1.0 and current_step: "done".
failedFailure — check error_message.

Treat ready and failed as terminal. processing, generating, completed and success are all intermediate, so keep polling. success is a step-level marker, not the end of the run — a loop that stops on it exits at ~20% progress with a null model_url — and completed can appear long before the model is done. The safe terminal test is status == "ready", or status == "success" together with progress == 1.0. Typical wall-clock is two to five minutes for essence-1 — the second-generation models train real per-identity models and take longer (see model-specific inputs and creation times).

While a run is in flight, current_step reports the pipeline stage:

current_stepProgressWhat’s happening
payment~2%Credits reserved (the model’s creation cost — 250, 500, or 2000).
persona5–15%Persona / system prompt prepared.
voice_image~20%Voice and portrait generated (in parallel).
video~45%The 10-second identity video is generated internally (essence-1 and essence-2) — authored to loop seamlessly.
awaiting_face_marking~35%Waiting on manual face marking (rare essence-1 path).
lip_sync70–99%The model-specific identity step — .imx build (essence-1), identity prep (essence-2-max), bundle build (essence-2), or per-identity training (expression-2). The longest step for the v2 models.
done100%Terminal — the agent is ready.
{
  "success": true,
  "data": {
    "agent_id": "A80HVD8577",
    "status": "ready",
    "progress": 1.0,
    "progress_msg": "Complete",
    "current_step": "done",
    "error_message": null,
    "system_prompt": "You are a professional video content creator.",
    "image_url": "https://...",
    "video_url": "https://...",
    "model_url": "https://...",
    "supported_models": ["essence-2-max", "expression-2"],
    "name": "agent name"
  }
}
Progress fieldTypeDescription
progressfloat (0.0–1.0)Generation progress as a fraction. 1.0 is complete.
progress_msgstringHuman-readable progress description.
current_stepstringCurrent generation step (see the table above).
supported_modelsstring[]The model families this agent can be launched as right now, spelled with the public model namesessence-1, expression-1, essence-2, essence-2-max, expression-2 — so every entry can be sent straight back as a model / ?model= value. Trained families (expression-2, essence-2) appear once their per-identity model exists; essence-2-max appears when the agent has a stored identity video (generated internally by Essence creations; its identity prepares on demand from that video); essence-1 appears when its .imx exists. Tier slugs inherit their family, and the combined essence-2 creation shows up as its two tier families (essence-2 and essence-2-max). Also returned on GET /v1/agent/{code}, GET /v1/agents items, and the embed-token response.

Every entry is a public name and is safe to send straight back: the internal tier spellings essence-2-light / essence-2-quality are folded before the response is built, so they never appear in this array.

Generate and poll

import os, time, requests

BASE = "https://api.bithuman.ai"
SECRET = os.environ["BITHUMAN_API_SECRET"]
headers = {"Content-Type": "application/json", "api-secret": SECRET}

resp = requests.post(f"{BASE}/v1/agent/generate", headers=headers,
                     json={"prompt": "You are a friendly AI assistant."})
agent_id = resp.json()["agent_id"]
print("agent_id:", agent_id)   # save this — you can resume polling any time

while True:
    r = requests.get(f"{BASE}/v1/agent/status/{agent_id}",
                     headers={"api-secret": SECRET}, timeout=30)
    if r.status_code != 200:
        # Transient (429 rate limit, edge blip). Creation keeps running
        # server-side — keep polling rather than aborting the run.
        time.sleep(5)
        continue
    data = r.json()["data"]
    # Only `ready` is terminal. `success` is a STEP-level marker (the
    # voice/image and video steps each write it around 20% and 45%), and
    # `completed` is not terminal either — stopping on them exits mid-run
    # with model_url still null.
    if data["status"] == "ready" or (
        data["status"] == "success" and data.get("progress") == 1.0
    ):
        # model_url is null for models with no downloadable per-identity
        # artifact (expression-1) — that is success, not a failure.
        print("Ready:", agent_id, "| launchable as:", data["supported_models"])
        print("       model_url:", data["model_url"])
        break
    if data["status"] == "failed":
        raise SystemExit(f"Failed: {data['error_message']}")
    print(f"  {data['status']} {data.get('current_step')} {data.get('progress')}")
    time.sleep(5)

Creation failure modes

Two kinds of failure exist — rejected before start (HTTP error, nothing charged) and failed during generation (status: "failed", credits automatically refunded):

FailureSurfaceNotes
Invalid model value400 VALIDATION_ERRORInvalid model '<x>'; must be one of: auto, essence, essence-1, essence-2, essence-2-max, expression, expression-1, expression-2Rejected before dispatch; no credits charged. Retired names get a targeted hint instead of the bare list — e.g. essence-2-light“‘essence-2-light’ was consolidated into ‘essence-2’ (2026-07-05)…”.
Malformed body400 VALIDATION_ERRORRequest body must be valid JSON / …a JSON objectRejected before dispatch.
video in the request body400 VIDEO_INPUT_NOT_SUPPORTEDAgent creation is image-only. Provide a portrait image; bitHuman generates a 10-second idle/driver video internally so it loops seamlessly (first frame == last frame). …Rejected before dispatch — nothing charged (verified live 2026-08-01). Send image instead — the identity video is always generated internally, for every model.
Too many Essence 2 creations in flightstatus: "failed" with a capacity error_message (queue position + an honest ETA derived from the measured drain rate)essence-2 creations are admission-controlled: at most 2 in-flight creations per account, and a deep platform queue can also defer admission. Rejection happens before billing — nothing is charged, no refund needed. Wait for an in-flight creation to finish, then retry.
A second-generation family paused for your account (rare)503 MODEL_NOT_YET_AVAILABLE<model> isn't available for generation yet. Specify 'essence-1' or 'expression-1' to generate now.Essence 2 / Expression 2 are GA (since July 10, 2026) — creation is open for all accounts, so this isn’t returned in normal operation. It remains the safety response if a v2 family is ever re-paused; nothing is charged and the v1 families always work.
Non-human subject on an explicit Essence 2 creation422 MODEL_SUBJECT_MISMATCHessence-2 requires a photorealistic human subject; this image looks like a <verdict> — use expression-2Rejected before billing and before any agent row exists — see the subject gate. auto routes instead of rejecting.
Not enough credits402 INSUFFICIENT_BALANCE (also surfaces as status: "failed" with a payment error_message if the reserve fails mid-pipeline)Creation costs the model’s rate — 250 (v1), 500 (Essence 2), or 2000 (expression-2).
A pipeline step failsstatus: "failed" + error_message naming the step (voice, image, video, or the model step)Terminal for that agent_id; the creation credits are refunded automatically. Create again after fixing the input.
essence-2-max on an agent without a stored identity video409 MODEL_NOT_GENERATED at launchMax prepares its identity from the agent’s internally generated identity video — create with (or add) the combined essence-2, which generates it. See Essence 2 Max.
v2 creation “stuck” at lip_syncNot a failureThat’s the training/prep step — the longest part for expression-2 / essence-2. Keep polling; see creation times.

More session-time issues (connect latency, tier pinning, idle behavior): Session behavior & troubleshooting.

Get an agent

GET /v1/agent/{code} — retrieve full details for an agent by its code.

import requests

code = "A80HVD8577"
data = requests.get(
    f"https://api.bithuman.ai/v1/agent/{code}",
    headers={"api-secret": "YOUR_API_SECRET"},
).json()
agent = data["data"]
print(agent["name"], agent["status"])
{
  "success": true,
  "data": {
    "agent_id": "A80HVD8577",
    "code": "A80HVD8577",
    "status": "ready",
    "model": "essence-2",
    "supported_models": ["essence-2-max", "essence-2"],
    "name": "My Agent",
    "system_prompt": "You are a friendly AI assistant",
    "voice_id": "aBc123…",
    "image_url": "https://assets.bithuman.ai/A80HVD8577/image_20260115_103000_000001.jpg",
    "video_url": "https://assets.bithuman.ai/A80HVD8577/video_20260115_103200_000002.mp4",
    "model_url": "https://assets.bithuman.ai/A80HVD8577/A80HVD8577.lebundle.imx"
  }
}

The response carries the agent’s full record (abridged above; verified against the live API 2026-08-01) — the persona (system_prompt, name, description, language, gender), the voice (voice_id), the media (image_url, video_url — the internally generated 10-second identity video — and model_url), the creation state (status, progress, current_step, error_message), and the launch surface (model, supported_models — every entry a public model name you can send straight back as a model / ?model= value).

List your agents

GET /v1/agents — list the agents owned by your API secret, newest first. Paginated with limit (default 20, max 100) and offset; filter by generation state with status.

import requests

resp = requests.get(
    "https://api.bithuman.ai/v1/agents",
    headers={"api-secret": "YOUR_API_SECRET"},
    params={"limit": 20, "offset": 0, "status": "ready"},
).json()

for a in resp["data"]:
    print(a["code"], a["status"])
print(resp["pagination"])   # {limit, offset, total, has_more}

Page through with offset until pagination.has_more is false.

Delete an agent

DELETE /v1/agent/{code} — permanently delete an agent you own. Stored assets are cleaned up best-effort; usage history is retained for billing. Deleting a missing or non-owned agent returns 404.

import requests

requests.delete(
    "https://api.bithuman.ai/v1/agent/A80HVD8577",
    headers={"api-secret": "YOUR_API_SECRET"},
).json()
# {"success": true, "agent_code": "A80HVD8577", "deleted": true}

Update an agent’s prompt

POST /v1/agent/{code} — update the system prompt of an existing agent without regenerating it. The agent must already exist. For a new face or voice, generate a new agent.

import requests

code = "A80HVD8577"
resp = requests.post(
    f"https://api.bithuman.ai/v1/agent/{code}",
    headers={"Content-Type": "application/json", "api-secret": "YOUR_API_SECRET"},
    json={"system_prompt": "You are a professional sales assistant."},
)
print(resp.json())
{ "agent_code": "A80HVD8577", "updated": true }

Add a model to an existing agent

POST /v1/agent/{code}/models — add an avatar model to an agent you already created, without re-creating it or regenerating its persona/voice/image. The agent must be in the ready state (else 409 AGENT_NOT_READY).

Body: {"model": "<name>"} — one of essence-1, essence-2, expression-1, expression-2 (anything else returns 400 VALIDATION_ERROR listing the options; the Essence 2 tiers are not individually addable — essence-2 is the one combined add).

modelWhat happensPrerequisitesCreditsTime
expression-1Instant enablement — the shared v1 engine drives the agent’s existing image + voice at runtime; nothing is trainedstored image and voice (else 422)0immediate (this response)
expression-2Trains the per-identity Expression 2 model from the stored imagestored image (else 422)2000about 1–1.5 h
essence-2The combined add: trains the standard Essence 2 from the agent’s stored identity video (generated internally at creation); Max lights up from the same video at no extra chargestored identity video (else 422 MODEL_PREREQUISITE_MISSING) + photorealistic-human subject on the stored image (else 422 MODEL_SUBJECT_MISMATCH)50045 min–3 h
essence-1Builds the v1 .imx — reuses the stored identity video, or generates one internally from the stored imagestored identity video or image (else 422)250~10–20 min
import requests

resp = requests.post(
    "https://api.bithuman.ai/v1/agent/A66GYD8664/models",
    headers={"Content-Type": "application/json", "api-secret": "YOUR_API_SECRET"},
    json={"model": "expression-2"},
)
print(resp.json())

An async add (everything except expression-1) responds immediately:

{
  "success": true,
  "agent_id": "A66GYD8664",
  "model": "expression-2",
  "status": "processing",
  "credits": 2000,
  "supported_models": ["essence-1", "essence-2-max"],
  "message": "expression-2 model add started (typically 10-45 minutes). 2000 credits are charged (refunded automatically if the add fails). Poll GET /v1/agent/status/A66GYD8664 until supported_models includes expression-2."
}

The minute estimate embedded in the response message is advisory — the table above has the typical times. Poll GET /v1/agent/status/{code} until supported_models contains the new family (essence-2 adds both tiers, essence-2 and essence-2-max). The agent keeps serving as-is while the add runs — status stays ready for the v2 adds. An instant add (expression-1, or a model the agent already has) returns status: "ready" with credits: 0 in the same response — re-POSTing the same model never double-charges, and a failed add refunds automatically.

Failure shapes: 400 VALIDATION_ERROR · 404 NOT_FOUND (unknown or not-owned agent) · 409 AGENT_NOT_READY · 422 MODEL_PREREQUISITE_MISSING / 422 MODEL_SUBJECT_MISMATCH · 503 MODEL_NOT_YET_AVAILABLE (only if a v2 family is paused — not returned in normal operation since the July 10, 2026 GA; nothing charged).

Download an agent’s model

GET /v1/agent/{code}/model/download — download the generated model artifact for an agent you own. The family defaults to the agent’s own model; override with ?model=<family> (public names and runtime tier slugs fold onto their family — the essence-2-{gpu,ane,cpu} force slugs and the retired essence-2-light fold onto essence-2). essence-2-quality is no longer accepted and returns a 400; send essence-2-max. What you get per family:

FamilyArtifactNotes
essence-1<code>.imxThe portable IMX container — runs locally in the CLI and the Python SDK.
essence-2<code>.lebundle.imxThe standard Essence 2 artifact — unified IMX container. ~85–105 MB for an agent created on the current renderer (measured across the live fleet, 2026-07-28). Agents created before the 2026-07-27 renderer change carry a larger bundle — up to ~550 MB — until they are retrained; the artifact shrank roughly . Size is per identity: read Content-Length rather than assuming a fixed figure. Licensed weights — a local runtime must complete the license activation flow; today the model serves via bitHuman cloud.
essence-2-max<code>.pklThe Essence 2 Max artifact — IMX container; renders on bitHuman’s GPU cloud (not a local-playback artifact).
expression-2<code>.imxThe portable IMX container (~20–90 MB per identity; legacy .avatar zip) — runs locally on macOS (Apple Silicon) and Linux, or in the browser via ?render=local; also served on bitHuman’s cloud.
expression-1Not downloadable: no per-identity artifact exists (the shared v1 engine renders server-side from the agent’s image) → 400 MODEL_NOT_DOWNLOADABLE.

The default response is a 302 redirect to the artifact (public URL for essence-1, 1-hour signed URL for the private families), so a plain curl works:

curl -LOJ -H "api-secret: $BITHUMAN_API_SECRET" \
  "https://api.bithuman.ai/v1/agent/A17ZTB0222/model/download?model=expression-2"
# → A17ZTB0222.imx

Pass ?redirect=false to get the URL as JSON instead (for UIs that want to fetch or label first):

{
  "success": true,
  "data": {
    "code": "A17ZTB0222",
    "model": "expression-2",
    "filename": "A17ZTB0222.imx",
    "url": "https://…signed…",
    "expires_in": 3600
  }
}

(expires_in is null for the public essence-1 URL.)

Errors (full reference):

StatusCodeWhen
400VALIDATION_ERRORUnknown model value — the message lists the downloadable families.
400MODEL_NOT_DOWNLOADABLEThe family has no per-identity artifact (expression-1). Not retryable.
404NOT_FOUNDAgent unknown or not owned by this account.
404MODEL_ARTIFACT_NOT_READYThe family is supported but its artifact hasn’t been published to the download store yet — the message says when to retry. Poll on this code.
409MODEL_NOT_GENERATEDThe requested family isn’t in the agent’s supported_models (same gate as embed/session launch).
429RATE_LIMITEDRead-bucket rate limit.

Tip The bitHuman CLI wraps this endpoint: bithuman pull A17ZTB0222 downloads the artifact, recognizes its model family, and prints what to do next — an essence-1 .imx runs locally with bithuman run.

Download an agent’s self-hosted avatar

GET /v1/agent/{agent_code}/self-hosted-avatar — download the prepared avatar bundle (<code>.pkl) that the self-hosted Essence 2 Max container serves. This is the endpoint a live-licensed container calls automatically the first time you render one of your agents by code; call it yourself for air-gapped installs and copy the file into the container’s avatars volume.

Auth is your api-secret — as Authorization: Bearer (what the container forwards) or the classic api-secret header. The response is the raw bundle bytes (no redirect), so a plain curl works:

curl -H "Authorization: Bearer $BITHUMAN_API_SECRET" \
  "https://api.bithuman.ai/v1/agent/A06ZSE8608/self-hosted-avatar" \
  -o A06ZSE8608.pkl

A 404 means the agent doesn’t exist, isn’t yours, or has no self-hosted bundle (it must be a completed essence-2 creation); a 401 means the credential is bad. The same artifact is also available through model download with ?model=essence-2-max (302-redirect form).

Make an agent speak

POST /v1/agent/{agent_code}/speak — trigger the agent to speak a message to users in an active session.

Requires a LIVE session. /speak speaks into a conversation that is already open — it cannot start one. With nobody connected there is no room to deliver to and the call returns 404 NOT_FOUND ("No active rooms found for agent <code>"). A successful call echoes delivered_to_rooms; if that is 0, or rooms_skipped_no_worker is non-zero, the message reached no one.

ParameterTypeRequiredDescription
messagestringyesText the agent will speak.
room_idstringnoTarget a specific room. If omitted, delivers to all active rooms.
curl -X POST https://api.bithuman.ai/v1/agent/A12345678/speak \
  -H "Content-Type: application/json" \
  -H "api-secret: $BITHUMAN_API_SECRET" \
  -d '{
    "message": "We have a 20% discount available today.",
    "room_id": "customer_session_1"
  }'
{
  "agent_code": "A12345678",
  "context_type": "speak",
  "delivered_to_rooms": 1
}

Inject knowledge

POST /v1/agent/{agent_code}/add-context — add background knowledge the agent uses to inform future responses. Set type to speak to trigger speech instead.

ParameterTypeRequiredDefaultDescription
contextstringyesKnowledge to inject (or message to speak).
typestringnoadd_contextadd_context injects knowledge silently; speak triggers a verbal response.
room_idstringnoTarget a specific room. If omitted, delivers to all active rooms.
import requests

requests.post(
    "https://api.bithuman.ai/v1/agent/A12345678/add-context",
    headers={"Content-Type": "application/json", "api-secret": "YOUR_API_SECRET"},
    json={
        "context": "Customer has VIP status. Preferred name: Alex. Account since 2021.",
        "type": "add_context",
        "room_id": "vip_session_42",
    },
)

Note /speak and /add-context target agents created on the bitHuman platform that have an active session — not local SDK agents. Without a live room you’ll get 404 NOT_FOUND. Start a session via the embed flow or a LiveKit worker first.

Error codes

HTTPCodeWhen
401UNAUTHORIZEDInvalid or missing api-secret.
402INSUFFICIENT_BALANCENot enough credits (generation costs 250 for the v1 models, 500 for Essence 2, 2000 for expression-2).
404NOT_FOUNDNo agent with the given code (message: "Agent not found for code: <code>").
404NOT_FOUNDAgent has no active session to /speak or /add-context (message: "No active rooms found for agent <code>").
400VALIDATION_ERRORInvalid request body (e.g. bad type value, or an invalid / retired model name — the error message lists the accepted values).
400VIDEO_INPUT_NOT_SUPPORTEDAgent creation with a video input. Creation is image-only — provide a portrait image; the 10-second identity video is generated internally so it loops seamlessly (first frame == last frame). Rejected before anything is billed — never send video.
503MODEL_NOT_YET_AVAILABLEA second-generation family paused for your account. Essence 2 / Expression 2 are GA (since July 10, 2026) and open for all accounts, so creation and model add don’t return this in normal operation — it’s the safety response if a v2 family is ever re-paused. Nothing charged; the v1 families always work.
409MODEL_NOT_GENERATEDA launch surface (embed-token model, talking video, model download) requested a family the agent can’t be launched as — it’s missing from supported_models. Trained families: "agent <code>'s <model> model hasn't been generated yet"; essence-2-max is gated on the stored identity video it prepares from (generated internally by Essence creations; the message names the public family essence-2-max). Add the model or create the agent with it.
409AGENT_NOT_READYPOST /v1/agent/{code}/models on an agent that is still generating or failed — models can only be added to a ready agent.
422MODEL_SUBJECT_MISMATCHAn explicit Essence 2 creation or add whose input isn’t a photorealistic human subject — see the subject gate. Nothing is billed.
422MODEL_PREREQUISITE_MISSINGModel add on an agent missing a stored asset the model needs (a stored identity video for essence-2 — generated internally by Essence creations, never uploaded; image for expression-2; image + voice for expression-1).
400MODEL_NOT_DOWNLOADABLEModel download for a family with no per-identity artifact (expression-1).
404MODEL_ARTIFACT_NOT_READYModel download for a supported family whose artifact hasn’t been published yet — retryable; the message says when.

See the full error reference and the interactive API reference.