Skip to content

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.

Try it →

The four selectors

A session mint must name exactly one avatar. The selectors differ in how much you pin:

ParameterNamesMoves when the avatar is edited?Credential
avatar_idAn avatar; resolves to its current published versionYes — you always get the latest publishSecret
avatar_refA specific version idNoSecret
avatar_version_idA specific version, as an explicit pinNoSecret
personaA stock catalog avatar by name ("mei")Yes — curated by CasolaAny

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.

CombinationResult
avatar_id + avatar_ref400 avatar_ref_avatar_id_exclusive
avatar_ref + persona400 avatar_ref_persona_exclusive
avatar_id with nothing published400 avatar_not_published
Unknown or deleted avatar404 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/avatars
GET /api/v1/workspaces/{wsId}/avatars

Required 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:

ValueMeaning
readyBuilt and registered; full experience
buildingA background build is running; sessions work now and improve when it finishes
missingNot 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/avatars

Required scope: avatars:write, plus the custom_avatars feature

{ "name": "Lyra", "kind": "custom" }
FieldRequiredDescription
nameYesHuman-readable label
kindNoDefaults 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}/versions

Required 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}/versions

Required 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:

Terminal window
curl -X PUT https://api.casola.ai{upload_url} \
-H "Authorization: Bearer $CASOLA_API_KEY" \
-H "Content-Type: image/png" \
--data-binary @avatar.png

Once 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.