Reference
Talking avatars
The agent’s face — a 2D look, a 3D model, or an animated photo — on the agent row and in the embedded widget when the embed key enables it.
A talking avatar is the agent’s face: a 2D illustration, a 3D model, or a photo the browser animates while the agent speaks. It lives on the agent row, beside emoji. It is never part of the manifest, so saving a face does not publish a new version.
The embedded widget draws that face only when the embed key’s config.avatar.enabled is true. Author it in the console on the agent’s Avatar tab, or with the routes on this page. The same face is what the Workspace and the agent lists show.
Three modes
| mode | What you get |
|---|---|
2d | A parametric illustrated face. `look` is enums plus `#rrggbb` colours. The default when an agent has no uploaded model or photo. |
3d | A `.glb` or `.vrm` you upload (≤ 25 MB). The server reads the file and records the lip-sync vocabulary. Switching to 3D before an upload answers `422 AVATAR_MODEL_REQUIRED`. |
photo | A stored portrait the browser animates from five face points. Upload requires consent. Switching to photo before an upload answers `422 AVATAR_PHOTO_REQUIRED`. |
A model and a photo can both be stored. mode chooses which one is shown. Deleting the model returns the avatar to 2d and leaves a stored photo in place. Deleting the photo returns the avatar to 2d when the current mode is photo. { "avatar": null } removes the face and deletes both files.
What the widget shows
Turn it on in the key’s display config (POST / PATCH /v1/embed-keys). There is no host option that enables it — a page can only style the result.
{
"config": {
"avatar": { "enabled": true, "size": "stage" }
}
}enabled defaults to false. size is compact (header) or stage (the panel above the transcript). GET /v1/embed/config returns agent.avatar as null until the key enables it and the agent has a face. The shape is { mode, size, look, framing, model, photo }. model is present only for mode 3d ({ url, vocabulary, size_bytes }). photo is present only for mode photo ({ url, points }).
The widget lazy-loads hive-embed-avatar.js the first time a face is shown. A 3D face also loads hive-embed-avatar3d.js. The core bundle stays the same. While a voice session is live, the mouth follows that session’s audio and the pose follows listening, thinking or speaking. A typed reply drives the mouth from the streamed text. If the photo or the model fails to load, the 2D look stays up.
The bytes are GET /v1/embed/avatar/model and GET /v1/embed/avatar/photo, with the same publishable key and origin allowlist as config. The two routes share a bucket of 6 requests per minute per IP. Responses are private, max-age=86400. The ?v= on the URL is a cache-buster; the route does not read it. A key that does not show that face gets 404.
Set a 2D look
PATCH /v1/agents/:id with an admin API key. The file itself never travels in this body — only mode, look, framing, and, for a photo already uploaded, points. Omit look to keep the current one. framing.zoom is 0.5 to 2.5; framing.y is -1 to 1.
curl -sS -X PATCH "$HIVE_API_URL/v1/agents/$AGENT_ID" \
-H "Authorization: Bearer $HIVE_API_KEY" -H "X-Tenant-Id: $HIVE_TENANT_ID" \
-H "Content-Type: application/json" \
-d '{
"avatar": {
"mode": "2d",
"look": {
"face": "oval",
"skin": "#b98660",
"hair": "none",
"facialHair": "short_beard",
"headwear": "ghutra_agal",
"headwearColor": "#ffffff",
"outfit": "kandura",
"outfitColor": "#f7f5f0"
},
"framing": { "zoom": 1.1, "y": 0 }
}
}'Every look field is optional. An unknown key is 400 VALIDATION. Colours are #rrggbb: skin, eyeColor, hairColor, headwearColor, outfitColor, accentColor, backgroundColor, lipColor.
| Field | Values |
|---|---|
face | oval, round, square, long, heart, diamond |
eyes | almond, round, narrow, hooded, wide |
brows | soft, straight, arched, thick, bushy, thin |
hair | none, short, side_part, curly, long, bun, buzz, wavy, ponytail, afro, bob, receding, braids |
facialHair | none, stubble, short_beard, full_beard, moustache, goatee, long_beard |
headwear | none, ghutra_agal, shayla, hijab, cap, shemagh, kufi, turban |
outfit | kandura, abaya, suit, shirt, uniform, blazer, tshirt, hoodie |
glasses | none, round, square, aviator, sunglasses |
background | none, circle, soft |
nose | straight, small, wide, long, aquiline |
lips | medium, thin, full |
eyelashes | none, natural, long |
age | adult, young, mature, senior |
marks | none, freckles, mole, dimples |
earrings | none, studs, hoops |
The console Avatar tab offers named presets (an Emirati officer, an officer in a shayla, a professional, a friendly face, an elder in a shemagh, a young professional). The API stores the resulting fields. There is no preset name on the wire.
Create a look from a photo
POST /v1/agents/:id/avatar/from-photo sends one JPEG, PNG or WebP (≤ 5 MB) to the platform vision model and returns a look to preview. Nothing is saved until you PATCH it. The picture is never stored or logged. The audit row agent.avatar.from_photo and the usage kind avatar_vision_call record the model name and the byte count. No clear face answers 422 AVATAR_NO_FACE. No vision model configured answers 503 UNAVAILABLE.
curl -sS -X POST "$HIVE_API_URL/v1/agents/$AGENT_ID/avatar/from-photo" \
-H "Authorization: Bearer $HIVE_API_KEY" -H "X-Tenant-Id: $HIVE_TENANT_ID" \
-F "photo=@portrait.jpg;type=image/jpeg"
# → { "look": { "face": "oval", "skin": "#c9966c", … } }
# Nothing is saved. PATCH the look you accept.Animated photo
POST /v1/agents/:id/avatar/photo stores a portrait and switches mode to photo. The multipart body carries the file, consent=true, and points — JSON for the five face points leftEye, rightEye, mouthLeft, mouthRight, chin, each {x, y} in the picture’s own 0..1 coordinates. Confirming consent is required (422 AVATAR_CONSENT_REQUIRED). Who consented and when stay on the server; clients receive photo: { url, points }. A stored 3D model is kept.
curl -sS -X POST "$HIVE_API_URL/v1/agents/$AGENT_ID/avatar/photo" \
-H "Authorization: Bearer $HIVE_API_KEY" -H "X-Tenant-Id: $HIVE_TENANT_ID" \
-F "photo=@portrait.jpg;type=image/jpeg" \
-F "consent=true" \
-F 'points={"leftEye":{"x":0.37,"y":0.40},"rightEye":{"x":0.63,"y":0.40},"mouthLeft":{"x":0.42,"y":0.62},"mouthRight":{"x":0.58,"y":0.62},"chin":{"x":0.50,"y":0.78}}'
# → 201 { "avatar": { "mode": "photo", "photo": { "url": "/v1/agents/…/avatar/photo?v=…", "points": {…} }, … } }Members download the bytes with GET /v1/agents/:id/avatar/photo (private, max-age=86400, served only from that agent’s prefix). DELETE removes the photo (204; 404 when there is none) and, when the mode was photo, returns the avatar to 2d. Re-place the markers later with PATCH points — that requires a photo already on the agent (422 AVATAR_PHOTO_REQUIRED).
3D model
POST /v1/agents/:id/avatar/model accepts .glb or .vrm, up to 25 MB. The server checks glTF 2.0 and at least one mesh, stores the file, and switches mode to 3d. A stored photo is kept. The response is 201 with avatar and the inspection report. avatar.model is { url, vocabulary, features, size_bytes, filename }. features is { lipSync, blink, head }. Vocabulary is one of arkit, oculus, vrm, jaw, none.
curl -sS -X POST "$HIVE_API_URL/v1/agents/$AGENT_ID/avatar/model" \
-H "Authorization: Bearer $HIVE_API_KEY" -H "X-Tenant-Id: $HIVE_TENANT_ID" \
-F "model=@agent.vrm;type=model/gltf-binary"
# → 201 { "avatar": { "mode": "3d", "model": { "url": "…", "vocabulary": "vrm",
# "features": { "lipSync": true, "blink": true, "head": true }, … } }, "report": {…} }PATCH with mode: "3d" before an upload answers 422 AVATAR_MODEL_REQUIRED. Members download with GET /v1/agents/:id/avatar/model (model/gltf-binary). DELETE removes the model and sets mode to 2d. No object store configured answers 503 UNAVAILABLE.
Errors
| Code | Status | When |
|---|---|---|
AVATAR_MODEL_REQUIRED | 422 | PATCH with mode: "3d" while the agent has no uploaded model. |
AVATAR_PHOTO_REQUIRED | 422 | PATCH with mode: "photo" or points while the agent has no uploaded photo. |
AVATAR_CONSENT_REQUIRED | 422 | Photo upload without the multipart field consent=true. |
AVATAR_NO_FACE | 422 | Create-from-photo found no clear front-facing face. Nothing is saved. |
INVALID_MODEL | 415 | The upload is not a readable glTF 2.0 file with at least one mesh. |
INVALID_IMAGE | 415 | The file bytes do not match the declared JPEG, PNG or WebP type. |
UNSUPPORTED_TYPE | 415 | Model is not .glb / .vrm, or the photo is not JPEG, PNG or WebP. |
FILE_TOO_LARGE | 413 | Model over 25 MB, or photo over 5 MB. |
NO_FILE | 400 | The multipart body has no file. |
VALIDATION | 400 | Face points are missing or outside 0..1, or the look contains an unknown field. |
UNAVAILABLE | 503 | No object store is configured (model and photo bytes), or no vision model is configured (create-from-photo). |
NOT_FOUND | 404 | Unknown agent, or a download / delete of a model or photo the agent does not have. |
Audit
agent.avatar.updated on a PATCH (the mode only). agent.avatar.model.upload / agent.avatar.model.remove, agent.avatar.photo.upload / agent.avatar.photo.remove, and agent.avatar.from_photo. Upload rows record the storage key, the previous key, and the size. They never record the picture.