Runtime sessions
List live sessions, read a transcript, end a session, or revoke every runtime key.
Overview
See and control the avatar sessions running on your account: list live sessions with the credits they use per minute, read a session’s transcript, terminate one, or (in an emergency) revoke every runtime key at once.
Which one to use:
- Stop a cloud avatar you started: End a cloud avatar session.
- Stop any session on your account: Terminate a session.
- A secret leaked: Revoke all keys.
Base URL https://api.bithuman.ai. Authenticate with your api-secret. The {user_id} in the
path is your own account id — get it from GET /v1/me.
Set it once in your shell before the examples below — with $USER_ID unset the
paths collapse to /v2//… and the API answers 404 NOT_FOUND:
export USER_ID=$(curl -s https://api.bithuman.ai/v1/me \
-H "api-secret: $BITHUMAN_API_SECRET" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['data']['user_id'])")
List sessions
GET /v2/{user_id}/runtime-sessions listRuntimeSessions
GET /v2/{user_id}/runtime-sessions: your sessions, plus account-wide totals.
| Query | Type | Default | Description |
|---|---|---|---|
window | string | all | live (only running), recent (idle + ended), or all. |
kind | string | all | conversations (cloud), self_hosted, or all. |
limit | int | 50 | Max sessions to return (1–200). |
The totals (active_count, live_burn_rate_cr_per_min, credits_this_hour) are account-wide over
the last hour; sessions is the filtered list. billing_type is the pricing code the session bills under; see GET /v1/pricing. Use a session’s id for the calls below.
curl
curl "https://api.bithuman.ai/v2/$USER_ID/runtime-sessions?window=live" \
-H "api-secret: $BITHUMAN_API_SECRET"
Python
import os, requests
resp = requests.get(
f"https://api.bithuman.ai/v2/{os.environ['USER_ID']}/runtime-sessions?window=live",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
)
print(resp.json())
Node
const resp = await fetch(`https://api.bithuman.ai/v2/${process.env.USER_ID}/runtime-sessions?window=live`, {
headers: { "api-secret": process.env.BITHUMAN_API_SECRET },
});
console.log(await resp.json());
{
"success": true,
"data": {
"active_count": 1,
"live_burn_rate_cr_per_min": 4,
"credits_this_hour": 52,
"sessions": [
{
"id": "a3f1c8e2-…-9b02",
"agent_code": "A80HVD8577",
"agent_name": "Greeter",
"billing_type": "usage_essence_2_model_cloud",
"key_alias": "prod-server",
"room_name": "room_a3f1c8e2",
"started": "2026-07-15T15:20:00Z",
"last_seen": "2026-07-15T15:33:00Z",
"minutes": 13.0,
"credits": 52,
"burn_rate_cr_per_min": 4,
"status": "live",
"has_transcript": true
}
]
}
}Read a transcript
GET /v2/{user_id}/runtime-sessions/{session_id}/messages getRuntimeSessionMessages
GET /v2/{user_id}/runtime-sessions/{session_id}/messages — the conversation, oldest first.
Only cloud voice/chat sessions record a transcript; self-hosted sessions return messages: [].
curl
curl "https://api.bithuman.ai/v2/$USER_ID/runtime-sessions/a3f1c8e2-…/messages" \
-H "api-secret: $BITHUMAN_API_SECRET"
Python
import os, requests
resp = requests.get(
f"https://api.bithuman.ai/v2/{os.environ['USER_ID']}/runtime-sessions/a3f1c8e2-…/messages",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
)
print(resp.json())
Node
const resp = await fetch(`https://api.bithuman.ai/v2/${process.env.USER_ID}/runtime-sessions/a3f1c8e2-…/messages`, {
headers: { "api-secret": process.env.BITHUMAN_API_SECRET },
});
console.log(await resp.json());
{
"success": true,
"data": {
"session_id": "a3f1c8e2-…",
"agent_code": "A80HVD8577",
"started": "2026-07-15T15:20:00Z",
"ended": "2026-07-15T15:33:00Z",
"live": true,
"messages": [
{ "timestamp": "2026-07-15T15:20:03Z", "role": "user", "message": "Hi there" },
{ "timestamp": "2026-07-15T15:20:05Z", "role": "assistant", "message": "Hello! How can I help?" }
]
}
}End a cloud avatar session
POST /v1/runtime-sessions/{session_id}/end endCloudAvatarSession
POST /v1/runtime-sessions/{session_id}/end — end a cloud avatar you
started. session_id is the session_id the start returned. No body.
Billing stops at the end call: the session is billed its active time up to that moment, and nothing after. Its concurrent-session slot frees at once.
When ended is true, the avatar
has left your room. When it is false, note says when it leaves: within about a minute, or,
for an essence-1 avatar, when you remove it or the room closes. Deleting the room removes it
at once. Your LiveKit room is never deleted. Calling it again returns the same answer and
bills nothing. Errors: 404 no such session (or not yours).
curl
curl -X POST "https://api.bithuman.ai/v1/runtime-sessions/$SESSION_ID/end" \
-H "api-secret: $BITHUMAN_API_SECRET"
Python
import os, requests
resp = requests.post(
f"https://api.bithuman.ai/v1/runtime-sessions/{os.environ['SESSION_ID']}/end",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
)
print(resp.json())
Node
const resp = await fetch(`https://api.bithuman.ai/v1/runtime-sessions/${process.env.SESSION_ID}/end`, {
method: "POST",
headers: { "api-secret": process.env.BITHUMAN_API_SECRET },
});
console.log(await resp.json());
{ "session_id": "cs_4f1c…", "status": "ended", "agent_id": "A23KSG5258",
"model": "essence-2", "room_name": "my-room", "started_at": "…", "ended_at": "…",
"billed_seconds": 131.2, "credits": 9, "ended": true, "slot_released": true,
"note": "Session ended: the avatar left the room and billing stopped." }Read a cloud avatar session
GET /v1/runtime-sessions/{session_id} getCloudAvatarSession
GET /v1/runtime-sessions/{session_id} — status (starting, live or ended), times,
billed_seconds and credits so far. Errors: 404 no such session (or not yours).
curl
curl "https://api.bithuman.ai/v1/runtime-sessions/$SESSION_ID" \
-H "api-secret: $BITHUMAN_API_SECRET"
Python
import os, requests
resp = requests.get(
f"https://api.bithuman.ai/v1/runtime-sessions/{os.environ['SESSION_ID']}",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
)
print(resp.json())
Node
const resp = await fetch(`https://api.bithuman.ai/v1/runtime-sessions/${process.env.SESSION_ID}`, {
headers: { "api-secret": process.env.BITHUMAN_API_SECRET },
});
console.log(await resp.json());
Terminate a session
POST /v2/{user_id}/runtime-sessions/{session_id}/terminate terminateRuntimeSession
POST /v2/{user_id}/runtime-sessions/{session_id}/terminate — stop one session. Never
touches an API secret.
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | no | Free-text stop reason. Default user-terminated. |
The session stops counting toward your plan’s concurrent sessions at once. What else happens depends on where it runs:
- A conversation on bitHuman’s servers (the web embed, the Console): its room is closed and
endedistrue. - A cloud avatar in your own LiveKit room: billing stops,
endedisfalse, and the avatar leaves at its next billing check, within about a minute. Anessence-1avatar stays until you remove it or the room closes. Delete the room on your LiveKit server to end it at once. - A self-hosted runtime: the activity record is closed; stop the process on your hardware.
To find a cloud avatar’s session_id, list sessions with window=live&kind=conversations and take the id whose room_name is your room. A cloud avatar you started can instead be ended by the session_id its start returned (cs_…). Errors: 404 no such session (or not yours).
curl
curl -X POST "https://api.bithuman.ai/v2/$USER_ID/runtime-sessions/a3f1c8e2-…/terminate" \
-H "api-secret: $BITHUMAN_API_SECRET" -H "content-type: application/json" -d '{}'
Python
import os, requests
resp = requests.post(
f"https://api.bithuman.ai/v2/{os.environ['USER_ID']}/runtime-sessions/a3f1c8e2-…/terminate",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
json={},
)
print(resp.json())
Node
const resp = await fetch(`https://api.bithuman.ai/v2/${process.env.USER_ID}/runtime-sessions/a3f1c8e2-…/terminate`, {
method: "POST",
headers: {
"api-secret": process.env.BITHUMAN_API_SECRET,
"content-type": "application/json",
},
body: JSON.stringify({ }),
});
console.log(await resp.json());
{
"success": true,
"data": { "session_id": "a3f1c8e2-…", "closed": true, "ended": true, "self_hosted": false,
"note": "Session ended — the avatar disconnected and the conversation closed." }
}Revoke all keys
POST /v2/{user_id}/runtime/revoke-all revokeAllRuntimeKeys
POST /v2/{user_id}/runtime/revoke-all — the emergency stop. Disables every runtime key on
the account (e.g. a leak). Live sessions stop within one refresh cycle (~5 min).
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | no | Free-text reason. |
This does not delete your API secrets — it suspends runtime token issuance. It’s self-recoverable: creating a new API secret clears the suspension.
The two calls below are made for you by the bitHuman SDKs, the CLI and the plugins on self-hosted and on-device sessions; you need them only to build your own runtime.
curl
curl -X POST "https://api.bithuman.ai/v2/$USER_ID/runtime/revoke-all" \
-H "api-secret: $BITHUMAN_API_SECRET" -H "content-type: application/json" -d '{}'
Python
import os, requests
resp = requests.post(
f"https://api.bithuman.ai/v2/{os.environ['USER_ID']}/runtime/revoke-all",
headers={"api-secret": os.environ["BITHUMAN_API_SECRET"]},
json={},
)
print(resp.json())
Node
const resp = await fetch(`https://api.bithuman.ai/v2/${process.env.USER_ID}/runtime/revoke-all`, {
method: "POST",
headers: {
"api-secret": process.env.BITHUMAN_API_SECRET,
"content-type": "application/json",
},
body: JSON.stringify({ }),
});
console.log(await resp.json());
{
"success": true,
"data": { "revoked": true, "effective_in_seconds": 320, "runtime_suspended": true,
"note": "All runtime keys disabled. Creating a new API key restores runtime access." }
}Live key check for a self-hosted runtime
POST /v1/auth/validate validateSelfHostedKey
The bitHuman SDKs, the CLI and the plugins call this for you when a self-hosted session starts and while it runs; you call it only if you build your own runtime. Deleting a key in the dashboard stops a running self-hosted session at its next check.
A runtime treats the three answers differently:
| status | meaning | what a runtime should do |
|---|---|---|
200 | valid | serve |
401 | definite: unknown, revoked or disabled | stop |
503 | we cannot tell right now | retry, keep serving |
A 503 is never a statement about your key: keep serving and retry.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
billing_type | body | string | no | The self-hosted product this runtime serves. Always send the key for the product you run. |
curl
curl -X POST https://api.bithuman.ai/v1/auth/validate \
-H "Authorization: Bearer $BITHUMAN_API_SECRET" \
-H "Content-Type: application/json" \
-d '{"billing_type": "self-hosted-essence-2-model"}'
# → {"user_id": "...", "key_id": "key_1a2b3c4d5e6f7a8b"}
Python
import os, requests
resp = requests.post(
"https://api.bithuman.ai/v1/auth/validate",
headers={"Authorization": f"Bearer {os.environ['BITHUMAN_API_SECRET']}"},
json={"billing_type": "self-hosted-essence-2-model"},
)
print(resp.json())
Node
const resp = await fetch("https://api.bithuman.ai/v1/auth/validate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITHUMAN_API_SECRET}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ billing_type: "self-hosted-essence-2-model" }),
});
console.log(await resp.json());
{
"key_id": "key_1a2b3c4d5e6f7a8b"
}Report usage from a self-hosted session
POST /v1/meter/beats postMeterBeat
The bitHuman SDKs, the CLI and the plugins call this for you: a self-hosted or on-device session reports its running time here, and that is what bills it (Pricing). You call it only if you build your own runtime.
A report covers the seconds the session ran, idle included, not frames
rendered. The product (billing_type) is fixed by the first report of a
session.
Read only the status code:
| status | meaning | what a runtime should do |
|---|---|---|
2xx | the interval is recorded | count it as reported |
402 | balance depleted / account suspended | keep rendering, report it again later |
401 | definite bad key | stop |
503 | we cannot tell right now | retry |
A 402 never stops a running session; the next session is refused
until you top up. A retried report (same session_id, install_id
and seq) is never charged twice.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
session_id | body | string | yes | Stable id for the session being billed. |
install_id | body | string | no | Stable id for this installation. |
seq | body | integer | yes | Beat sequence number within the session. |
served_s | body | number | yes | Seconds of session covered by this beat. Clamped server-side; wall-clock, idle included. |
billing_type | body | string | no | The self-hosted product wire key. Always send the key for the product you run. |
curl
curl -X POST https://api.bithuman.ai/v1/meter/beats \
-H "Authorization: Bearer $BITHUMAN_API_SECRET" \
-H "Content-Type: application/json" \
-d '{"session_id":"s-1","install_id":"i-1","seq":1,
"served_s":60,"billing_type":"self-hosted-essence-2-model"}'
Python
import os, requests
resp = requests.post(
"https://api.bithuman.ai/v1/meter/beats",
headers={"Authorization": f"Bearer {os.environ['BITHUMAN_API_SECRET']}"},
json={
"session_id": "s-1",
"install_id": "i-1",
"seq": 1,
"served_s": 60,
"billing_type": "self-hosted-essence-2-model",
},
)
print(resp.json())
Node
const resp = await fetch("https://api.bithuman.ai/v1/meter/beats", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BITHUMAN_API_SECRET}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
session_id: "s-1",
install_id: "i-1",
seq: 1,
served_s: 60,
billing_type: "self-hosted-essence-2-model",
}),
});
console.log(await resp.json());