Docs / SDK / Reference

CLI reference

Every bithuman subcommand, flag, environment variable, exit code and --json shape, in one page. The happy path is on /sdk/cli.

The two-command quickstart is on the CLI page. This page is everything else: the subcommands, the flags, the environment, the cache, the exit codes and the machine-readable contract.

Anything here can be re-derived from the binary itself — bithuman <cmd> --help for one command, bithuman __schema for the whole command / flag / exit-code tree as JSON, generated from the binary so it cannot drift from your install.

Version

$ bithuman --version
libessence <engine core> ABI <n>
bithuman    <cli version>
build       <commit> <target>/release <built at> <build host>
engine      <platform> <engine version> <digest>

The current release and its real transcript live on the CLI page — the one place that names the version — and it is the same version on macOS arm64 and Linux x86_64. The first line names the engine version, a separate axis from the CLI’s own number, printed under the engine’s legacy spelling because it is the string you have to grep for; the product name is essence-2. Do not pin a CLI version unless you have a reason: the installer takes the newest release, and that is the tested one.

Subcommands

CommandWhat it does
bithuman run [avatar]Live avatar. No argument fetches and renders the free Wise Pup expression-2 avatar; pass a file or an agent code to run your own
bithuman render <file> -a <audio>Offline render: model + audio → MP4
bithuman pull <slug | AGENT_CODE>Download a showcase avatar, or your own agent’s model by code
bithuman listBrowse the showcase catalogue (alias: avatars)
bithuman open <file>Can this avatar be opened and run here? Engine, family, and every member the container carries (alias: info)
bithuman login / logoutSign in through the browser and mint a per-device key / revoke it
bithuman accountWho you are signed in as, your plan, your credit balance and your spend
bithuman engine list | installInspect or fetch the per-platform Expression 2 render engine
bithuman doctorHost, credential, brain and cache check
bithuman mcpThe built-in MCP server over stdio; bithuman mcp tools lists its 28 tools
bithuman completion <shell>Completions for bash, zsh, fish, elvish, powershell

Every subcommand takes --help, and each --help ends in a copy-pasteable EXAMPLES: block.

There is one name per task. chat (for run), info (for open) and avatars (for list) are the only aliases; bithuman <cmd> --help names them. From 2.6.27 the surface lost the spellings nothing used — talk, inspect, download, get, ls, browse, gallery, credits and agents-md — and three commands that were a second way to do something the CLI already did: init (use bithuman login, then bithuman run), engine update (use bithuman engine install, which is idempotent), and whoami and usage, which are both bithuman account now. A retired spelling exits 2 with unrecognized subcommand.

Signing in

bithuman login              # opens a browser, mints a per-device key
bithuman login --device     # SSH / headless: prints a short code to enter elsewhere
bithuman account            # who am I, on what plan, with how many credits
bithuman logout             # revokes this device's key on the server

The key is stored in your OS keychain (macOS Keychain, Linux Secret Service), aliased cli@<hostname> so you can recognise and revoke it from Developer → API Keys. With no keychain it falls back to ~/.bithuman/config, a dotenv file at mode 0600. Each device gets its own key, so revoking one laptop leaves the others alone.

In CI, skip login entirely and export BITHUMAN_API_SECRET, or pipe it: printf %s "$KEY" | bithuman login --with-token.

Credential resolution order

First match wins, so an exported key always beats a logged-in one:

  1. BITHUMAN_API_SECRET in the environment
  2. the OS keychain (what bithuman login writes)
  3. ~/.bithuman/config (the dotenv fallback, loaded at every startup)

~/.bithuman/embedded-key is not read, and a .env in the working directory is not auto-loaded.

bithuman run

FlagDefaultWhat
--host127.0.0.1Bind address. 0.0.0.0 also needs --allow-public-bind
--port8088Launcher HTTP port
--max-sessionsCPU countPool cap; launches over the cap are rejected, not degraded
--embedded-livekiton with a model argumentSpawn a self-contained livekit-server child
--embedded-livekit-portMove that child’s port when the default collides
--cloudoffForce a cloud-rendered session instead of rendering locally. Needs an agent code

run sniffs the model family before it launches, so every bitHuman artifact gets an honest answer instead of a deep engine error.

Every self-hosted run and render is metered. The line to grep for, printed once when the meter attaches — it names the avatar, the product and the endpoint that will be billed:

[selfhost-meter] metering on for identity=/home/you/.cache/bithuman/showcase/wise-pup.imx product=expression-2 basis=… endpoint=https://api.bithuman.ai/v1/

If the service cannot be reached — our outage or your network — the render continues and is never refused, because being unable to ask is not the same as being told no. Every beat says so and the session still ends 0: “beat seq=1 failed to send (…); 5.3s (181 frames) stay UNACKED and will be re-claimed. Rendering continues.”

BITHUMAN_METER_ENFORCE=1 was the operator override for that case through 2.6.20, where it took effect on macOS only. From 2.6.22 it is not what decides: run signs the credential in first, on both platforms. An invalid secret exits 1 — “sign-in failed: auth required (BE_ERR_NO_AUTH)” — before anything starts, with or without the variable; a valid one with the metering service unreachable brings the session up live on Linux and macOS alike, with no refusal before serving — and on 2.6.23 Linux a viewer who then joins is rendered to, with the variable set: “could not reach … /v1/auth/validate … PROCEEDING and metering in the background”, then “beat seq=1 failed to send … 74 frames stay UNACKED … Rendering continues.” Do not rely on the variable to hold a box to a validated credential.

A render with no credential, or one the service rejects, is refused outright on both platforms: render from 2.6.19, and run from 2.6.20 — see below.

Which model files run locally

FamilyThe fileWhat run does
expression-2.avatarRenders locally on macOS Apple Silicon and Linux x86_64. The default Wise Pup avatar is this family
essence-2.imx (releases before 2.6.0 wrote <CODE>.lebundle.imx, a legacy name kept for compatibility)Renders locally on both platforms since 2.6.1. The first play fetches the shared audio encoder and checks the licence with the cloud, so it needs your sign-in. A file missing a required member is refused, exit 69
essence-1.imxRenders locally
expression-1usually noneCloud-served. The exception is an agent that also owns a baked .imx — the download endpoint hands that file out, and it runs like essence-1

Passing a bare agent code rather than a path is different: an essence-2 or expression-2 code opens a live cloud session, and --cloud forces that for an essence-1 code too.

Where the local render happens

On macOS (Apple Silicon) and Linux x86_64, both Expression 2 and Essence 2 render locally, with the runtime inside the tarball. Two tools come from your PATH: render writes its MP4 through ffmpeg, and run spawns livekit-serverinstall names both.

The conversation brain

run renders on its own. To make the avatar answer, it launches a Python worker. Signing in gives you the managed brain and the first run bootstraps ~/.cache/bithuman/brain-venv (a one-time ~200 MB download). The two alternatives are OPENAI_API_KEY for OpenAI Realtime, and BITHUMAN_LOCAL=1 for the fully on-device stack — local mode is the one writer for what that stack needs.

bithuman render

FlagDefaultWhat
-a, --audio <PATH>requiredAny format ffmpeg reads for the second-generation engines; essence-1 wants a 16 kHz mono PCM WAV
-o, --output <PATH>output.mp4Output path
--quality <PRESET>MEDIUMLOW, MEDIUM, HIGH
--target-size <SIZE>1280N (longest side) or WxH. essence-1 only — the second-generation engines emit their native size
--limit <N>noneCap at N frames; the audio is trimmed to N/fps

Writing the MP4 needs ffmpeg on PATH (or $BITHUMAN_FFMPEG).

FamilyResultrc
expression-2A real MP4 at 20 fps — an s-second clip yields ceil(s × 20) frames0
essence-2A real MP4 at 25 fps — ceil(s × 25) frames. New in 2.6.10
essence-1The engine runs, the mux fails, no file is written. Use the Video API70

render refuses without a credential — exit 77, before any model is opened. Re-measured 2026-09-10 on 2.6.5: “bithuman render: not signed in, or the credential is not valid.” That gate fires for every family and says nothing about whether the family would have rendered.

2.6.5 removed a five-minute ceiling on essence-2 renders. Until then the render’s budget was a start-up timeout of 300 s that was never moved, so the longest clip the command could finish was whatever your machine rendered in five minutes; it failed with “engine produced N frames but the audio needs M — refusing to write a truncated render”. If you are on 2.6.4 or earlier and a long clip fails that way, upgrade rather than splitting the audio.

A refused render leaves no file at --output. Count the frames anyway — it is the only check that tells a complete clip from a partial one:

ffprobe -v error -count_frames -select_streams v:0 \
  -show_entries stream=nb_read_frames -of csv=p=0 out.mp4

bithuman pull

bithuman pull modern-court-jester           # a showcase slug → ~/.cache/bithuman/showcase/
bithuman pull A17ZTB0222                    # your agent → ~/.cache/bithuman/agents/<code>/
bithuman pull A31BSK9325 --model essence-2  # a specific family

pull prints the cached path — and only the path — on stdout, so MODEL=$(bithuman pull …) captures it. A showcase slug needs no credential; an agent code goes through the authenticated download endpoint and exits 77 without a sign-in, or 66 carrying the API’s error (including the poll-able MODEL_ARTIFACT_NOT_READY).

One agent can own more than one downloadable model. Adding a model gives the same code a second trained family, and a bare pull hands back the family the agent was created with — not the newest. There is no warning. Name the family with --model (essence-1, essence-2, expression-2), or read other_models from bithuman pull <CODE> --json.

What you get, per family

FamilyFileWhat runs it
essence-1.imxThis CLI, the Python SDK, the Android AAR, the cloud
essence-2.imxThis CLI (2.6.1+), the Python SDK, the cloud. Licensed weights — keep the file
expression-2.avatarThis CLI, the Python SDK, the browser via ?render=local, the Apple Expression2 product, the cloud
expression-1usually nothing (400 MODEL_NOT_DOWNLOADABLE)The cloud

All but a minority of these are the current bitHuman container — including the expression-2 one, despite its .avatar name. A few expression-2 identities trained before 2026-07-12 are still an older zip format and will not be re-published. bithuman open <file> reads either, so run it rather than trusting the extension.

bithuman open

open and render are the two operations — there is no third. open answers one question: can this avatar be opened and run here? It succeeds, or it refuses with one of four kinds (InvalidAvatar, NotSupported, NotAuthorised, Failed). info is its alias and every older page’s spelling.

On success it prints the engine, family, and every member of the container with its byte size. The engine field carries a legacy name kept for compatibility — an Essence 2 bundle reports essence2-light — and is never a valid model value; see the engine value is a legacy name.

Run 2026-09-10 against the showcase identity A08CCD3871.avatar, with no credential anywhere in the environment:

  Engine:         expression2
  Family:         expression-2 (Expression 2)
  Members (17):
    canon.bin  (299520 bytes)
    combined_litert.tflite  (158524428 bytes)
    idle.mp4  (1154851 bytes)

bithuman engine

The Expression 2 render engine ships inside the CLI, so a fresh install needs nothing extra. This subcommand is the manual channel — for a cross-platform build, or when a newer avatar needs a newer engine.

bithuman engine list           # what exists and which one this host uses
bithuman engine install        # this platform — idempotent, so it is also the update
bithuman engine install linux  # the other one, for a cross-build

The platform argument is mac or linux and nothing else; a target triple exits 2. Essence 2 has no engine subcommand and needs none — its runtime is in the tarball, and the one thing it fetches is the shared audio encoder.

bithuman doctor

Checks versions, host, RAM, credential, brain and cache sizes, and exits 0 only when both a credential and a brain resolve — signed out it exits 1, and that is the check working. Pulling a showcase avatar needs neither; render and run need a credential.

Environment variables

VariableWhat
BITHUMAN_API_SECRETThe credential. BITHUMAN_API_KEY is accepted as an alias for cross-SDK parity
OPENAI_API_KEYSelects the OpenAI Realtime conversation brain
BITHUMAN_LOCAL=1 selects the on-device brain — local mode
BITHUMAN_LOCAL_*, BITHUMAN_INSTRUCTIONSBrain-side tuning, read by the Python worker rather than the binary — local mode
BITHUMAN_METER_ENFORCELegacy. Read, but not decisive from 2.6.22 — run signs the credential in first regardless; see credential resolution and bithuman run
BITHUMAN_FFMPEGPath to ffmpeg when it is not on PATH
BITHUMAN_VERSIONPins the release tag the installer fetches; unset, it takes the current release named on the CLI page
BITHUMAN_INSTALL_DIRWhere the installer puts the binary (default ~/.local/bin, or /usr/local/bin as root)
BITHUMAN_JSON, BITHUMAN_QUIET, BITHUMAN_NO_COLORFlip the matching global flag’s default; an explicit flag still wins
RUST_LOGTracing filter. Default bithuman_serve=info,warn

Cache layout

PathContents
~/.cache/bithuman/showcaseShowcase avatars from bithuman pull <slug>
~/.cache/bithuman/agents/<code>Your own agents’ models
~/.cache/bithuman/runPer-run scratch: session state and logs
~/.cache/bithuman/brain-venvThe auto-bootstrapped conversation-brain venv
~/.bithuman/avatars/<code>An unpacked avatar lane, staged on first play
~/.bithuman/enginesExpression 2 engines (bithuman engine install)
~/.bithuman/engines/essence-2The shared Essence 2 audio encoder, ~377 MB, fetched once by content digest
~/.cache/huggingface, ~/.cache/supertonicLocal-mode brain weights

bithuman doctor prints the current size of each. rm -rf ~/.cache/bithuman is safe — it regenerates.

Platforms with no binary

The installer builds a target triple from uname and asks the release for that tarball. Exactly two targets carry one: aarch64-apple-darwin and x86_64-unknown-linux-gnu. On anything else it reads the release’s asset list, names the two it does carry, and exits 1 before downloading a byte:

install: error: the bithuman CLI is NOT published for aarch64-unknown-linux-gnu.
install: error:   release carries:
install: error:     bithuman-aarch64-apple-darwin.tar.gz
install: error:     bithuman-x86_64-unknown-linux-gnu.tar.gz
rc=1

So an Intel Mac and a Linux ARM box cannot install the CLI: there is no flag, no fallback and no Rosetta path. BITHUMAN_VERSION=cli-v2.3.27 still resolves a published Linux-ARM tarball whose sha256 verifies, but it is months of render work behind and whether that binary still runs on a current ARM distribution was never tested — treat it as a stopgap. On an Intel Mac, use the cloud API or run the Linux binary in a container.

There is no PyPI route to the CLI, and there is no longer a bithuman-cli wheel: it was removed from PyPI on 2026-09-15, so asking pip for that name fails outright. It never gave you the bithuman command in any case. The bithuman PyPI package is the Python library — the only bitHuman package on PyPI — and it installs no command at all.

The machine-readable contract

Pass --json to any command and get exactly one JSON object on stdout, and nothing else on stdout — no logs, no progress. Every success object carries "schema_version" (currently 1); pin it.

A failure prints one object to stderr and leaves stdout empty:

{"error":{"code":"SLUG_NOT_FOUND","message":"slug 'x' not found in manifest. Try `bithuman list`.","command":"pull"}}

// when there is a next step, the object carries a `hint` beside the cause:
{"error":{"code":"NOT_AUTHENTICATED","command":"account","hint":"run `bithuman login` (free, one tap) — or set BITHUMAN_API_SECRET","kind":"NotAuthorised","message":"not signed in"}}

Colour is emitted only to an interactive TTY, so --json, NO_COLOR, CI, TERM=dumb and any pipe all silence it.

Exit codes

A stable sysexits subset. Branch on these rather than parsing text.

codenamemeaning
0success
1GENERICunclassified runtime error (also doctor when not ready)
2usagebad arguments; also a refused public bind (--host 0.0.0.0 without --allow-public-bind) and an unparseable --host, from 2.6.20
66NOINPUTinput, file, slug or model not found; also NOT_IMX (the file is not an avatar container) and CLOUD_NEEDS_AGENT_CODE (run <file> --cloud — a cloud session takes an agent code, not a path), both kind: InvalidAvatar
69UNAVAILABLEnetwork, engine or service unavailable; an incomplete model file
70SOFTWAREinternal error (essence-1 render)
77NOPERMnot signed in, out of credits, or forbidden
130interrupted (Ctrl-C) on a local Essence 2 preview: the CLI’s own meter delivers its final beat, then the process exits 130. A live Expression 2 session (run wise-pup) exits 0 on Ctrl-C after its 2 s drain; from 2.6.25 that drain carries the render host’s final beat on Linux ([selfhost-meter] beat … delivered (final), ~1.5 s after the signal — 2.6.24 lost it)

The shapes

// bithuman version --json
{"abi":7,"cli":"2.6.26","libessence":"2.11.5",
 "build":{"commit_short":"…","target":"x86_64-unknown-linux-gnu","built_at":"…","profile":"release"},
 "engine":{"platform":"linux","runtime":"litert","version":"1.0.1","sha256":"…","size":92473490},
 "schema_version":1}

// bithuman account --json     exit 0 when the account could be read, 77 with no credential
{"logged_in":true,"source":"env BITHUMAN_API_SECRET","email":"…","plan":"creator",
 "credit_balance":5986130,"account_status":"active","out_of_credits":false,
 "usage":{"data":[{"created_at":"…","activity_type":"…","agent_code":"…","credits_change":-42}],
          "pagination":{"total":128}}}

// bithuman list --json     the gallery; every row carries the CODE you can pull
{"version":2,"models":[{"agent_code":"A02HCY0444","slug":"shelly-tidewater","name":"…",
                       "model":"expression-2","size":198632867,"description":"…"}],
 "schema_version":1}

// bithuman pull <CODE> --json      a gallery CODE needs no key and costs nothing
{"code":"A02HCY0444","path":"/…/A02HCY0444.imx","cached":false,"family":"expression-2",
 "model":"expression-2","model_source":"birth","other_models":[],"runnable_locally":true,
 "schema_version":1}

// bithuman open <file> --json
{"path":"…","format_version":2,"size_bytes":82583342,"engine":"essence1","family":"essence-1",
 "manifest":{},"members":[{"name":"manifest.json","size_bytes":1030},],"schema_version":1}

// bithuman render … --json     frames is read back from the finished file
{"output":"out.mp4","bytes":1234567,"seconds":3.4,"width":1280,"height":720,"frames":125,
 "fps":25,"render_seconds":3.18,"render_fps":39.3,"lead_in_frames_dropped":10}

`fps` is the rate of the file you get (the container's frame rate). `render_fps`
is the speed the engine produced those frames: `frames / render_seconds`, timed
from the first audio push after the model has loaded and warmed up to the last
frame handed to the writer — the number the performance page quotes. Both are
present from 2.6.9.

// bithuman doctor --json      exit 0 iff "ready":true
{"ready":false,"versions":{},"host":{},"auth":{},"brain":{},"runtime_assets":{}}

// bithuman run … --json       one event on stdout when the session is live
{"event":"session_started","url":"http://127.0.0.1:8088/","host":"127.0.0.1","port":8088}

The values above are examples — what matters is the shape, which is stable across releases. schema_version tells you when it is not.

Introspection

bithuman __schema      # the whole command / flag / exit-code tree as JSON
bithuman __agents      # this contract, printed offline
bithuman token         # the resolved secret on stdout (exit 77 if none)

MCP server

{ "mcpServers": { "bithuman": { "command": "bithuman", "args": ["mcp"] } } }

bithuman mcp speaks Model Context Protocol over stdio and exposes 28 tools (bithuman mcp tools --json): six local tools that re-exec the CLI with no network — version, doctor, inspect_model, list_showcase, pull, render — and 22 that wrap api.bithuman.ai and the platform status page. The MCP server guide lists every tool with its endpoint.

It is the built-in successor to the standalone bithuman-mcp Python package: one tool to install, the same tool names. Auth comes from the resolved secret and is never logged. delete_agent and delete_webhook are flagged destructive; generate_agent, text_to_speech and generate_dynamics consume credits, so check get_credit_balance first. generate_agent refuses an empty request and is image-only — video is not a creation input, and the API rejects any request carrying it with 400 VIDEO_INPUT_NOT_SUPPORTED.

Recipes

# Is this install ready to serve an avatar? (exit 0 = yes)
bithuman doctor --json | jq -e .ready >/dev/null

# Pick the top showcase avatar, fetch it, render a clip — all by exit code.
SLUG=$(bithuman list --json | jq -r '.models[0].slug')
MODEL=$(bithuman pull "$SLUG") || exit $?
bithuman render "$MODEL" -a in.wav -o out.mp4 --json | jq -r .output

# Confirm a credential without a browser (exit 0 signed in, 77 not).
bithuman account --json >/dev/null

See also

  • CLI — the two-command quickstart
  • Local mode — the on-device conversation brain