Embedding API
Mint short-lived JWT tokens from your backend and embed a talking avatar on any website via an iframe.
Embed an avatar
Drop an agent onto any page as an iframe — no SDK install required:
<iframe
src="https://bithuman.ai/embed/A78WKV4515"
allow="microphone *; camera *; autoplay *"
style="width: 100%; height: 600px; border: 0;"
></iframe>
Replace A78WKV4515 with your agent code.
Warning The iframe needs delegated
microphonepermission to hear the user — and the*in theallowattribute is load-bearing. The embed URL redirects cross-origin toagent.viewer.bithuman.ai, so a bareallow="microphone"(which pins to the iframe’ssrcorigin) leaves the mic silently blocked after the redirect. Usemicrophone *(or allowlisthttps://agent.viewer.bithuman.aiexplicitly). The same applies if the embedding page sets a restrictivePermissions-Policy.
Production: mint a token
For per-visitor session tracking and rate limiting, mint a short-lived embed token on your backend (never expose your API secret in frontend code) and append it to the iframe URL.
POST /v1/embed-tokens/request
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | yes | Agent code (e.g. A78WKV4515). |
fingerprint | string | yes | Stable per-device hex string for session tracking and per-visitor rate limiting. |
model | string | no | Request a specific avatar model for the session — a model name (essence-1, expression-1, essence-2, essence-2-max, expression-2 — the pre-rename essence-2-quality still works as a deprecated alias) or a force-tier slug (essence-2-gpu/-ane/-cpu, expression-2-gpu/-cpu/-ane — per model). Validated early: unknown values return 400 listing the accepted names; requesting a family the agent can’t be launched as (missing from its supported_models — a trained model that doesn’t exist yet, or essence-2-max on an agent with no stored identity video — generated internally by Essence creations) returns 409 MODEL_NOT_GENERATED instead of a failed session later. Omitted → the agent’s own default model. |
// server: mint token (api-secret never reaches the browser)
const res = await fetch("https://api.bithuman.ai/v1/embed-tokens/request", {
method: "POST",
headers: {
"api-secret": process.env.BITHUMAN_API_SECRET,
"content-type": "application/json",
},
body: JSON.stringify({
agent_id: "A78WKV4515",
fingerprint: visitorFingerprint, // stable per-device hex
}),
});
const { data: { token } } = await res.json();
Response
{
"status": "success",
"status_code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"sid": "f3c9...",
"supported_models": ["essence-2-quality", "expression-2"]
}
}
The token is a 1-hour, HS256-signed JWT. Mint one per visitor session.
supported_models lists the canonical model families the agent can be
launched as right now (useful for building your own model picker); when you
requested a model, the response also echoes the model baked into the
token.
Use the token in the iframe
Pass it as a query string (or as the data-token attribute on the embed widget
script tag):
<iframe
src="https://bithuman.ai/embed/A78WKV4515?token=THE_TOKEN"
allow="microphone *; camera *; autoplay *"
style="width: 100%; height: 600px; border: 0;"
></iframe>
Notes
- The embed token is more constrained than a runtime token — it’s purpose-built for cross-origin iframe authentication.
- WebRTC requires a secure context: serve the embedding page over HTTPS or
the browser will block microphone access (except on
localhost). - The
fingerprintshould be generated once per device and persisted, so per-visitor rate limits track the same visitor across sessions.
See the interactive API reference for the full request and response schema.