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 3.1.0 ABI 7
bithuman    2.6.5
build       98fa0b448b9a x86_64-unknown-linux-gnu/release 2026-09-10T11:54:59Z
engine      linux 1.0.0 adc2a18da787

cli-v2.6.5 is the current release (2026-09-10), the same version on macOS arm64 and Linux x86_64. The first line names the essence engine version — 3.1.0 here — which is a separate axis from the CLI’s own number. It is printed under the engine’s legacy spelling, kept because it is the string you have to grep for; the product name is essence-2. bithuman version --json gives both plus the tarball’s provenance. 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 (aliases: avatars, ls, browse)
bithuman info <file>Model metadata: format, engine, family, and the container’s full table of contents (alias: inspect)
bithuman login / logoutSign in through the browser and mint a per-device key / revoke it
bithuman auth statusWho you are signed in as and where the credential lives (alias: whoami)
bithuman accountPlan, credit balance, account status (alias: credits)
bithuman usageRecent credit usage and metering history
bithuman initInteractive wizard: save a secret, pick a brain, pull a starter avatar
bithuman engine list | install | updateInspect 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 26 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.

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 auth status        # who am I, and where is the credential read from
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.

Which model files run locally

FamilyThe fileWhat run does
expression-2<code>.avatarRenders locally on macOS Apple Silicon and Linux x86_64. The default Wise Pup avatar is this family
essence-2<code>.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<code>.imxRenders locally
essence-2-max<code>.pklRecognised, and handed to the cloud — GPU-only by design
expression-1usually noneCloud-served. The exception is an agent that went through the lip step, which owns a baked <code>.imx that 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

PlatformExpression 2Essence 2
macOS (Apple Silicon)CoreML — the Neural Engine carries 84–100% of the operationsYes, runtime in the tarball
Linux x86_64LiteRT on CPUYes, runtime in the tarball

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, essence-2-max, expression-2), or read other_models from bithuman pull <CODE> --json.

What you get, per family

FamilyFileWhat runs it
essence-1<code>.imxThis CLI, the Python SDK, the Android AAR, the cloud
essence-2<code>.imxThis CLI (2.6.1+), the Python SDK, the cloud. Licensed weights — keep the file
expression-2<code>.avatarThis CLI, the Python SDK, the browser via ?render=local, the Apple Expression2 product, the cloud
essence-2-max<code>.pklThe cloud, or the self-hosted GPU container. The .pkl derives on the agent’s first session, so an earlier download answers 404 MODEL_ARTIFACT_NOT_READY
expression-1usually nothing (400 MODEL_NOT_DOWNLOADABLE)The cloud

All but a minority of these are IMX\0 v2 containers — including the expression-2 one, despite its .avatar name. A few expression-2 identities trained before 2026-07-12 are still a CoreML zip and will not be re-published. bithuman info <file> reads either, so run it rather than trusting the extension.

bithuman info

Prints format, 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, an Essence 2 Max bundle essence2-quality — and is never a valid model value; see the engine value is a legacy name.

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

  Format:         IMX v2
  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
bithuman engine install linux  # the other one, for a cross-build
bithuman engine update

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. Rendering and pulling need neither.

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_ENFORCE=1 turns a missing or rejected key into a refusal before the first frame instead of a warning
BITHUMAN_FFMPEGPath to ffmpeg when it is not on PATH
BITHUMAN_VERSIONPins the release the installer fetches (cli-v2.6.5)
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.

pip install bithuman-cli also exists, but only on macOS Apple Silicon (there are no Linux or Intel-Mac wheels). The separate bithuman PyPI package is the Python library, not the CLI.

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"}}

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
66NOINPUTinput, file, slug or model not found
69UNAVAILABLEnetwork, engine or service unavailable; an incomplete model file
70SOFTWAREinternal error (essence-1 render)
77NOPERMnot signed in, out of credits, or forbidden

The shapes

// bithuman version --json
{"abi":7,"cli":"2.6.5","libessence":"3.1.0",
 "build":{"commit_short":"98fa0b448b9a","target":"x86_64-unknown-linux-gnu","built_at":"2026-09-10T11:54:59Z","profile":"release"},
 "engine":{"platform":"linux","runtime":"litert","version":"1.0.0","sha256":"adc2a18da787…","size":92449082},
 "schema_version":1}

// bithuman whoami --json      exit 0 signed in, 1 signed out
{"logged_in":true,"user":"you@example.com","alias":"cli@host-…","source":"env BITHUMAN_API_SECRET"}

// bithuman account --json     exit 77 with no credential
{"email":"…","plan":"creator","credit_balance":5986130,"account_status":"active","out_of_credits":false}

// bithuman list --json
{"version":2,"models":[{"slug":"modern-court-jester","name":"…","description":"…"}]}

// bithuman pull <slug> --json
{"slug":"modern-court-jester","path":"/…/modern-court-jester.imx","cached":true,"sha256":"…"}

// bithuman info <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
{"output":"out.mp4","bytes":1234567,"seconds":3.4,"width":1280,"height":720,"frames":125,"fps":25}

// 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 version --json and info --json objects above were read from CLI 2.6.5 on Linux x86_64 on 2026-09-10.

Introspection

bithuman __schema      # the whole command / flag / exit-code tree as JSON
bithuman __man [DIR]   # roff man pages
bithuman __agents      # this contract, printed offline
bithuman auth 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 26 tools (confirmed on 2.6.5 with bithuman mcp tools): thin wrappers over api.bithuman.aivalidate_api_secret, get_credit_balance, get_usage, list_voices, text_to_speech, generate_agent, get_agent_status, get_agent, update_agent_prompt, delete_agent, list_agents, agent_speak, add_agent_context, get_dynamics, generate_dynamics, create_embed_token, upload_file, and the webhook set — plus four local tools that re-exec the CLI with no network: version, doctor, inspect_model, list_showcase.

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.
bithuman whoami --json | jq -e .logged_in >/dev/null

See also

  • CLI — the two-command quickstart
  • Local mode — the on-device conversation brain
  • Verified transcript — these commands re-run on a clean host with their real exit codes