Agents
Create an avatar agent from a portrait, then manage it and make it speak.
An agent is an avatar (face, voice and persona) identified by a short code such as A23WJF0199. Create one, poll until it is ready, then use it everywhere: the web embed, the SDKs, talking video and live sessions. Creation and model adds cost credits per model (pricing); everything else on this page is free.
The sample avatars (such as A23WJF0199, wise-pup) only download; every other call on this page needs an agent you own, and returns 404 for a sample.
To add knowledge to a live session, see Knowledge; every operation is also in the API reference index.
Generate an agent
POST /v1/agent/generate generateAgent
Starts an asynchronous creation and returns an agent_id at once. Credits are reserved at submit and refunded automatically if creation fails. Pick the model from the subject: a real person → essence-2; a cartoon, animal, robot or any other character → expression-2 (Choosing a model).
Request
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | always send it | essence-2 (a real person), expression-2 (any character) or auto (picks from the image). essence-1 and expression-1 are the first generation. Always send it: it becomes required on 2026-12-26 (see Notes). |
image | string | no | Portrait URL (publicly fetchable) or base64. Used as a reference; a portrait is generated from prompt when omitted |
prompt | string | no | System prompt and personality |
audio | string | no | Voice sample URL or base64, for voice cloning |
aspect_ratio | string | no | 16:9 (default), 9:16 or 1:1 |
framing | string | no | portrait (default) or full_body |
transparency | boolean | no | true generates on a green-screen background for chroma key |
agent_id | string | no | Leave it out; a fresh code is generated. If it names an agent you already own, that agent is regenerated in place (it returns to processing); another account’s code returns 404 |
A model outside your plan gets 403 PLAN_REQUIRED.
Headers: api-secret, and optionally Idempotency-Key: a repeated request with the same key returns the first response and starts no second creation.
Notes
- Omitted,
modelstill creates anexpression-1agent and warns (MODEL_DEFAULT_DEPRECATED), so a character is refused with422. From 2026-12-26modelis required, and the bare namesessenceandexpression(and theversionfield) are refused with a400naming the replacement. - Creation takes minutes for
essence-2,essence-1andexpression-1, and longer forexpression-2. Set your polling timeout per model. - Every model except
expression-2needs a clear, real human face. Foressence-2,essence-1orexpression-1, a cartoon, stylized character, animal, robot or creature in the photo or prompt, or a photo with no face found, returns422 MODEL_SUBJECT_MISMATCHbefore anything is charged; the message tells you to use Expression 2. Anessence-2creation or add also checks the photo’s face before charging: a face too small in frame, or several similar-sized faces, returns422 IMAGE_FACE_UNSUITABLE— upload a closer photo of one person.autoroutes people toessence-2and everything else toexpression-2. - A
200does not mean the image was fetched. An unreachableimagefails the creation a few seconds later (refunded); poll status to confirm. Creation is image-only: avideofield returns400 VIDEO_INPUT_NOT_SUPPORTED. - At most two
essence-2creations run at once per account; a third fails at once with a capacity message and no charge.
Example
curl
curl -X POST https://api.bithuman.ai/v1/agent/generate \
-H "Content-Type: application/json" \
-H "api-secret: $BITHUMAN_API_SECRET" \
-H "Idempotency-Key: museum-guide-1" \
-d '{"model": "expression-2", "prompt": "You are a cheerful museum guide.", "image": "https://example.com/portrait.jpg"}'
Python
import os, requests
resp = requests.post(
"https://api.bithuman.ai/v1/agent/generate",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
json={"model": "expression-2", "prompt": "You are a cheerful museum guide.", "image": "https://example.com/portrait.jpg"},
)
print(resp.json())
Node
const resp = await fetch("https://api.bithuman.ai/v1/agent/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
"api-secret": process.env.BITHUMAN_API_SECRET,
"Idempotency-Key": "museum-guide-1",
},
body: JSON.stringify({
model: "expression-2",
prompt: "You are a cheerful museum guide.",
image: "https://example.com/portrait.jpg",
}),
});
console.log(await resp.json());
Response
{"success": true, "message": "Agent generation started", "agent_id": "A80HVD8577", "status": "processing"}Poll status
GET /v1/agent/status/{agent_id} getAgentStatus
Reports a creation’s progress. Poll every 5 seconds until status is ready or failed. On the way status takes other values, such as generating and success, which are not final. A boolean final field, true only for ready and failed, is coming; until it appears in responses, stop on ready or failed. A missing or mistyped agent_id returns 404 NOT_FOUND. The response is the agent’s full record, the same as Get an agent; the fields below are the ones to poll.
current_step | Progress | Stage |
|---|---|---|
payment | about 2% | credits reserved |
persona | 5–15% | persona prepared |
voice_image | about 20% | voice and portrait |
video | about 45% | identity video (Essence models) |
lip_sync | 70–99% | the model step; the longest for second-generation models |
done | 100% | ready |
Notes
supported_modelslists the models the agent can be launched as, spelled asmodelvalues you can send back.model_statusgives each requested model’s state (pending,ready,failed). Models never requested are absent.- For second-generation models
model_urlis a storage reference, not a link: fetch the file with Download an agent’s model. - The downloadable model file is published shortly after
ready; until then the download returns404 MODEL_ARTIFACT_NOT_READY. Retry.
curl
curl https://api.bithuman.ai/v1/agent/status/A80HVD8577 -H "api-secret: $BITHUMAN_API_SECRET"
Python
import os, requests
resp = requests.get(
"https://api.bithuman.ai/v1/agent/status/A80HVD8577",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
)
print(resp.json())
Node
const resp = await fetch("https://api.bithuman.ai/v1/agent/status/A80HVD8577", {
headers: { "api-secret": process.env.BITHUMAN_API_SECRET },
});
console.log(await resp.json());
{"success": true, "data": {"agent_id": "A80HVD8577", "status": "ready", "progress": 1.0, "current_step": "done", "error_message": null, "supported_models": ["expression-2"], "model_status": {"expression-2": {"state": "ready", "reason": null}}, "name": "Museum Guide"}}import os, time, requests
def wait_until_ready(agent_id, timeout_s=3 * 3600):
deadline = time.time() + timeout_s
while time.time() < deadline:
r = requests.get(f"https://api.bithuman.ai/v1/agent/status/{agent_id}",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]}, timeout=30)
if r.ok:
data = r.json()["data"]
if data["status"] in ("ready", "failed"):
if data["status"] == "failed":
raise RuntimeError(data["error_message"])
return data
time.sleep(5) # creation continues server-side through transient errors
raise TimeoutError(agent_id)Get an agent
GET /v1/agent/{code} getAgent
Returns the agent’s full record: persona (system_prompt, name, language), voice_id, media URLs, creation state, model and supported_models.
curl
curl https://api.bithuman.ai/v1/agent/A80HVD8577 -H "api-secret: $BITHUMAN_API_SECRET"
Python
import os, requests
resp = requests.get(
"https://api.bithuman.ai/v1/agent/A80HVD8577",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
)
print(resp.json())
Node
const resp = await fetch("https://api.bithuman.ai/v1/agent/A80HVD8577", {
headers: { "api-secret": process.env.BITHUMAN_API_SECRET },
});
console.log(await resp.json());
{"success": true, "data": {"code": "A80HVD8577", "status": "ready", "model": "expression-2", "supported_models": ["expression-2"], "name": "Museum Guide", "system_prompt": "You are a cheerful museum guide.", "language": "en", "voice_id": "bf0a246a-8642-498a-9950-80c35e9276b5", "image_url": "https://…/image.jpg"}}List your agents
GET /v1/agents listAgents
Lists your agents, newest first. Query: limit (default 20, max 100), offset, status (for example ready). Items are summaries (code, name, model, status, supported_models, created_at and a few more); read the full record with Get an agent. Deleted agents appear with status: "deleted". Older agents can also show completed, success or unknown; read supported_models to see what they can launch as.
curl
curl "https://api.bithuman.ai/v1/agents?status=ready&limit=20" -H "api-secret: $BITHUMAN_API_SECRET"
Python
import os, requests
resp = requests.get(
"https://api.bithuman.ai/v1/agents?status=ready&limit=20",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
)
print(resp.json())
Node
const resp = await fetch("https://api.bithuman.ai/v1/agents?status=ready&limit=20", {
headers: { "api-secret": process.env.BITHUMAN_API_SECRET },
});
console.log(await resp.json());
{"success": true, "data": [{"code": "A80HVD8577", "name": "Museum Guide", "model": "expression-2", "status": "ready"}], "pagination": {"limit": 20, "offset": 0, "total": 1, "has_more": false}}Update an agent
POST /v1/agent/{code} updateAgent
POST /v1/agent/{code} (not PATCH, which returns 404) changes the system_prompt, the voice-provider selection (providers, see Voice providers), or both. Send at least one, or the call returns 400 MISSING_PARAM. The name is generated and cannot be set. The voice cannot be set here: change it in the Console (Voices).
curl
curl -X POST https://api.bithuman.ai/v1/agent/A80HVD8577 \
-H "Content-Type: application/json" -H "api-secret: $BITHUMAN_API_SECRET" \
-d '{"system_prompt": "You are a concise sales assistant."}'
Python
import os, requests
resp = requests.post(
"https://api.bithuman.ai/v1/agent/A80HVD8577",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
json={"system_prompt": "You are a concise sales assistant."},
)
print(resp.json())
Node
const resp = await fetch("https://api.bithuman.ai/v1/agent/A80HVD8577", {
method: "POST",
headers: {
"Content-Type": "application/json",
"api-secret": process.env.BITHUMAN_API_SECRET,
},
body: JSON.stringify({ system_prompt: "You are a concise sales assistant." }),
});
console.log(await resp.json());
{"agent_code": "A80HVD8577", "updated": true}Delete an agent
DELETE /v1/agent/{code} deleteAgent
Deletes an agent you own. Usage history is kept. An unknown or unowned code returns 404.
curl
curl -X DELETE https://api.bithuman.ai/v1/agent/A80HVD8577 -H "api-secret: $BITHUMAN_API_SECRET"
Python
import os, requests
resp = requests.delete(
"https://api.bithuman.ai/v1/agent/A80HVD8577",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
)
print(resp.json())
Node
const resp = await fetch("https://api.bithuman.ai/v1/agent/A80HVD8577", {
method: "DELETE",
headers: { "api-secret": process.env.BITHUMAN_API_SECRET },
});
console.log(await resp.json());
{"success": true, "agent_code": "A80HVD8577", "deleted": true}Add a model to an existing agent
POST /v1/agent/{code}/models addAgentModel
Adds a model to a ready agent without re-creating it; the body is {"model": "<model>"}. Re-adding a model the agent has costs nothing, and a failed add is refunded.
model | Needs | Time | Credits |
|---|---|---|---|
expression-1 | a stored image and voice, of a real person | immediate | free |
expression-2 | a stored image | takes longer to build | 2000 |
essence-2 | a stored identity video and a photoreal person | about 2–2.5 h (an add still trains; a new essence-2 agent is ready in minutes) | 500 |
essence-1 | a stored image or identity video, of a real person | 10–20 min | 250 |
Poll status until model_status["essence-2"].state is ready or failed. The top-level status stays ready during an add. A failed add is refunded, and its reason says why. Errors: 409 AGENT_NOT_READY, 422 MODEL_PREREQUISITE_MISSING, 422 MODEL_SUBJECT_MISMATCH (an essence-2, essence-1 or expression-1 add for an agent that isn’t a real person; nothing is charged, see Choosing a model).
curl
curl -X POST https://api.bithuman.ai/v1/agent/A80HVD8577/models \
-H "Content-Type: application/json" -H "api-secret: $BITHUMAN_API_SECRET" \
-d '{"model": "essence-2"}'
Python
import os, requests
resp = requests.post(
"https://api.bithuman.ai/v1/agent/A80HVD8577/models",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
json={"model": "essence-2"},
)
print(resp.json())
Node
const resp = await fetch("https://api.bithuman.ai/v1/agent/A80HVD8577/models", {
method: "POST",
headers: {
"Content-Type": "application/json",
"api-secret": process.env.BITHUMAN_API_SECRET,
},
body: JSON.stringify({ model: "essence-2" }),
});
console.log(await resp.json());
{"success": true, "agent_id": "A80HVD8577", "model": "essence-2", "status": "processing", "supported_models": ["expression-2"]}Download an agent’s model
GET /v1/agent/{code}/model/download downloadAgentModel
Redirects (302) to the agent’s model file, a .imx container, for the SDKs and CLI. Pass ?model= to choose a model when the agent has several; the default is the model it was created with. Sample avatars download with no credential; your own agents need the api-secret header.
Name the output file yourself (-o). Add ?redirect=false to get the URL as JSON:
| Status | Code | Meaning |
|---|---|---|
400 | MODEL_ | the model has no file (expression-1) |
403 | PLAN_REQUIRED | the model is not in your plan; the message names the plan |
404 | MODEL_ | published shortly after ready; retry |
409 | MODEL_ | the agent does not have that model; add it |
curl
curl -fL -o A80HVD8577.imx -H "api-secret: $BITHUMAN_API_SECRET" \
"https://api.bithuman.ai/v1/agent/A80HVD8577/model/download?model=expression-2"
Python
import os, requests
resp = requests.get(
"https://api.bithuman.ai/v1/agent/A80HVD8577/model/download?model=expression-2",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
)
resp.raise_for_status()
with open("A80HVD8577.imx", "wb") as f:
f.write(resp.content)
Node
import { writeFile } from "node:fs/promises";
const resp = await fetch("https://api.bithuman.ai/v1/agent/A80HVD8577/model/download?model=expression-2", {
headers: { "api-secret": process.env.BITHUMAN_API_SECRET },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
await writeFile("A80HVD8577.imx", Buffer.from(await resp.arrayBuffer()));
{"success": true, "data": {"code": "A80HVD8577", "model": "expression-2", "filename": "A80HVD8577.imx", "url": "https://…", "expires_in": 3600}}List the freely downloadable showcase models
GET /v1/models/showcase getShowcaseManifest
Lists the sample avatars anyone can download, with no API secret and no account: slug, agent code, model and size. Each entry’s url is Download an agent’s model. bithuman list and bithuman pull read this list.
curl
curl https://api.bithuman.ai/v1/models/showcase
Python
import requests
resp = requests.get("https://api.bithuman.ai/v1/models/showcase")
print(resp.json())
Node
const resp = await fetch("https://api.bithuman.ai/v1/models/showcase");
console.log(await resp.json());
{"version": 2, "updated": "2026-09-10", "models": [{"slug": "bolt", "name": "Bolt", "model": "expression-2", "agent_code": "X03BOLT", "url": "https://api.bithuman.ai/v1/agent/X03BOLT/model/download?model=expression-2", "size": 198106157}]}List an agent’s live sessions
GET /v1/agent/{code}/sessions listAgentSessions
Lists the agent’s open sessions. Use a room_id whose deliverable is true with speak or add-context.
curl
curl https://api.bithuman.ai/v1/agent/A80HVD8577/sessions -H "api-secret: $BITHUMAN_API_SECRET"
Python
import os, requests
resp = requests.get(
"https://api.bithuman.ai/v1/agent/A80HVD8577/sessions",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
)
print(resp.json())
Node
const resp = await fetch("https://api.bithuman.ai/v1/agent/A80HVD8577/sessions", {
headers: { "api-secret": process.env.BITHUMAN_API_SECRET },
});
console.log(await resp.json());
{"agent_code": "A80HVD8577", "sessions": [{"room_id": "room-A80HVD8577-x1y2", "num_participants": 2, "created_at": 1788480000, "deliverable": true}]}Make an agent speak
POST /v1/agent/{code}/speak agentSpeak
Makes the avatar say message in its live sessions: one session with room_id, or every deliverable session without it. With no live session it returns 404 NOT_FOUND.
curl
curl -X POST https://api.bithuman.ai/v1/agent/A80HVD8577/speak \
-H "Content-Type: application/json" -H "api-secret: $BITHUMAN_API_SECRET" \
-d '{"message": "We have a 20% discount today.", "room_id": "room-A80HVD8577-x1y2"}'
Python
import os, requests
resp = requests.post(
"https://api.bithuman.ai/v1/agent/A80HVD8577/speak",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
json={
"message": "We have a 20% discount today.",
"room_id": "room-A80HVD8577-x1y2",
},
)
print(resp.json())
Node
const resp = await fetch("https://api.bithuman.ai/v1/agent/A80HVD8577/speak", {
method: "POST",
headers: {
"Content-Type": "application/json",
"api-secret": process.env.BITHUMAN_API_SECRET,
},
body: JSON.stringify({
message: "We have a 20% discount today.",
room_id: "room-A80HVD8577-x1y2",
}),
});
console.log(await resp.json());
{"agent_code": "A80HVD8577", "delivered_to_rooms": 1, "rooms": ["room-A80HVD8577-x1y2"], "rooms_skipped": [], "rooms_failed": []}This section moved: docs.bithuman.ai/api/knowledge#inject-knowledge
This section moved: docs.bithuman.ai/api/errors#agent-operations