Avatars
An avatar is two things, versioned separately:
- A sealed identity — face, voice, and the bundle that carries them. Immutable; changing it publishes a new version.
- A text/config layer — system prompt, backstory, tools, options. Cheap and append-only; changing it appends a profile, not a version.
Both are workspace-scoped. A session picks one of each and freezes the result at mint time.
The four selectors
A session mint must name exactly one avatar. The selectors differ in how much you pin:
| Parameter | Names | Moves when the avatar is edited? | Credential |
|---|---|---|---|
avatar_id | An avatar; resolves to its current published version | Yes — you always get the latest publish | Secret |
avatar_ref | A specific version id | No | Secret |
avatar_version_id | A specific version, as an explicit pin | No | Secret |
persona | A stock catalog avatar by name ("mei") | Yes — curated by Casola | Any |
Use avatar_id. You name the avatar, and whatever it currently publishes is what runs — no
version ids to learn or store, and publishing a new version rolls out without a code change.
{ "avatar_id": "019fe463-fbd3-7000-80e8-85dad81d3fee" }avatar_ref predates it and names a version, not an avatar — worth remembering when reading
older integration code, since the name suggests otherwise. It still means exactly what it always
meant.
Pinning a version keeps a long-running integration on known assets while you publish new ones:
{ "avatar_id": "019fe463-…", "avatar_version_id": "019fe463-fc00-7000-806c-c9723f5f81cb" }Used together, the version must belong to that avatar; used alone, it names the avatar implicitly.
Pinning the assets this way is orthogonal to pinning the text with profile_id — see
Prompt & overrides.
| Combination | Result |
|---|---|
avatar_id + avatar_ref | 400 avatar_ref_avatar_id_exclusive |
avatar_ref + persona | 400 avatar_ref_persona_exclusive |
avatar_id with nothing published | 400 avatar_not_published |
| Unknown or deleted avatar | 404 avatar_not_found |
avatar_not_published is deliberate: an avatar whose creation never finished has draft assets, and
resolving to a draft would put an unfinished face in front of a user.
List avatars
GET /api/v1/avatarsGET /api/v1/workspaces/{wsId}/avatarsRequired scope: avatars:read
Response 200
{ "data": [ { "id": "019...", "workspace_id": "019...", "name": "Lyra", "slug": "lyra", "description": "best friend", "kind": "custom", "current_version_id": "019...", "default_profile_id": "019...", "assets_status": "ready", "status": "active", "deleted_at": null, "region_tag": "us", "created_at": 1719000000, "updated_at": 1719000000 } ]}description is the one-line label for your own catalog UI — editable with PATCH below, and never
shown to the model. When an avatar has none of its own, this falls back to the description its
published version was created with, so the field is populated for every avatar.
assets_status reports the avatar’s derived idle media — the loop clips it plays while
listening:
| Value | Meaning |
|---|---|
ready | Built and registered; full experience |
building | A background build is running; sessions work now and improve when it finishes |
missing | Not built yet, or a build failed. Sessions still work — the avatar holds a still frame while idle |
Sessions never require derived assets, so missing is never a reason to hide an avatar from your
users. It is a quality signal, not a gate.
Create an avatar
POST /api/v1/avatarsRequired scope: avatars:write, plus the custom_avatars feature
{ "name": "Lyra", "kind": "custom" }| Field | Required | Description |
|---|---|---|
name | Yes | Human-readable label |
kind | No | Defaults to "stock" |
Response 201 — the avatar object shown in the list above.
Get an avatar
GET /api/v1/avatars/{avatarId}Required scope: avatars:read
Rename an avatar, or edit its description
PATCH /api/v1/avatars/{avatarId} { "name": "Lyra" }PATCH /api/v1/avatars/{avatarId} { "description": "best friend" }PATCH takes a display name, a one-line description, or both — and nothing else. That is not an
oversight: the persona fields are not mutable columns. Changing the face or voice publishes a new
version; changing the prompt, backstory, tools or config appends a profile. Both
are additive operations with their own identity, which is what makes “what exactly was this session
running?” answerable after the fact.
Only the fields you send are written, and at least one must be. description is a label for your
own catalog UI — up to 200 characters, never shown to the model — and sending null or an empty
string clears it, at which point the avatar’s list entry falls back to the description its published
version was created with.
Delete an avatar
DELETE /api/v1/avatars/{avatarId} → { "ok": true, "id": "019…", "status": "deleted" }List avatar versions
GET /api/v1/avatars/{avatarId}/versionsRequired scope: avatars:read
{ "data": [ { "id": "019...", "avatar_id": "019...", "version_number": 1, "image_r2_key": "workspaces/.../image", "system_prompt": "You are a helpful assistant.", "config_json": null, "created_by": "019...", "created_at": 1719000000 } ]}A version’s system_prompt / backstory are the baseline the profile layer resolves against —
the bottom rung of the ladder. To change what an avatar
says, append a profile; publish a new version only when the face or voice changes.
Create an avatar version
POST /api/v1/avatars/{avatarId}/versionsRequired scope: avatars:write
{ "system_prompt": "You are a helpful assistant.", "config_json": {}, "source_persona": "mia"}source_persona is optional. It reuses an immutable stock persona’s current face and voice objects
without copying bytes; the new version still owns its prompt and config. When supplied, the version
is ready immediately and no image upload is required. It cannot be combined with inherit_image.
Response 201
{ "id": "019...", "avatar_id": "019...", "version_number": 1, "image_r2_key": null, "system_prompt": "You are a helpful assistant.", "config_json": null, "created_by": "019...", "created_at": 1719000000, "upload_url": "/api/v1/workspaces/{wsId}/avatars/{avatarId}/versions/{id}/upload"}When source_persona is omitted, PUT the avatar image bytes to upload_url:
curl -X PUT https://api.casola.ai{upload_url} \ -H "Authorization: Bearer $CASOLA_API_KEY" \ -H "Content-Type: image/png" \ --data-binary @avatar.pngOnce the image is uploaded, image_r2_key is set and the version becomes the avatar’s
current_version_id. Mint sessions against it with avatar_id (recommended) or by passing this
version’s id as avatar_ref.
Stock avatars
Stock avatars (ws_stock) can be read, listed and used by anyone, but their profiles cannot be
edited — 403 stock_read_only. To customize one, clone it into your own workspace. Per-session
overrides work against stock avatars too, since they change nothing stored.
Related
- Profiles & config — the avatar’s stored prompt, backstory, tools and options
- Prompt & overrides — how those compose with per-session parameters
- Sessions — where an avatar is put on screen