A mint must name exactly one avatar. There are four ways to do it, and they differ in how much you pin: from “whatever this avatar currently publishes” to “this exact immutable version”.
| 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 — stock catalog is curated by Casola | Any |
Use avatar_id. It is the addressing form you want in almost every integration: you name the
avatar, and whatever it currently publishes is what runs. You never have to learn or store version
ids, 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 — that is a historical quirk worth
remembering when reading old integration code, because the name suggests otherwise. It still works
and still means exactly what it always meant.
| Combination | Result |
|---|---|
avatar_id + avatar_ref |
400 avatar_ref_avatar_id_exclusive |
avatar_ref + persona |
400 avatar_ref_persona_exclusive |
avatar_id with no published version |
400 avatar_not_published |
avatar_id that does not exist / is deleted |
404 avatar_not_found |
avatar_not_published is deliberate: an avatar whose wizard run never finished has draft assets,
and resolving to a draft would put an unfinished face in front of a user.
avatar_version_id pins the sealed assets to one immutable version, so a long-running integration
can adopt new versions on its own schedule:
{
"avatar_id": "019fe463-fbd3-…",
"avatar_version_id": "019fe463-fc00-7000-806c-c9723f5f81cb"
}
Used together, the version must belong to that avatar. Used alone, the version names the avatar
implicitly. The version must be published and sealed (400 version_not_published otherwise).
Pinning the assets is orthogonal to pinning the text: profile_id pins the prompt layer,
and the two compose freely. See 4. Prompt, backstory & overrides.
curl -sS https://api.casola.ai/api/v1/avatars \
-H "Authorization: Bearer $CASOLA_SECRET_KEY"
{
"data": [
{
"id": "019fe463-fbd3-7000-80e8-85dad81d3fee",
"name": "Lyra",
"slug": "lyra",
"kind": "custom",
"current_version_id": "019fe463-fc00-7000-806c-c9723f5f81cb",
"default_profile_id": "019fe463-fc2a-7000-8093-1f0b2ac6e8d1",
"assets_status": "ready",
"created_at": 1786248112
}
]
}
assets_status reports the avatar’s derived idle media (the loop clips it plays while listening):
| Value | Meaning |
|---|---|
ready |
Idle media 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 simply 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.
curl -sS https://api.casola.ai/api/v1/avatars/019fe463-fbd3-… \
-H "Authorization: Bearer $CASOLA_SECRET_KEY"
curl -sS https://api.casola.ai/api/v1/avatars/019fe463-fbd3-…/versions \
-H "Authorization: Bearer $CASOLA_SECRET_KEY"
curl -sS https://api.casola.ai/api/gallery
No authentication. Returns the stock avatars available to persona, with display names, taglines
and preview image URLs — this is what the public demo widget renders.
Entries carry a tier. That tier gates the consumer surfaces (the anonymous demo and signed-in
consumer plans); API key traffic is deliberately not gated on a plan, because API access is
granted by your workspace’s api_access feature rather than bought as a consumer subscription. A
lapsed consumer plan can never silently break your integration.
| Route | Method | Purpose |
|---|---|---|
/api/v1/avatars |
GET |
List |
/api/v1/avatars |
POST |
Create ({"name": "Lyra"}) → 201 |
/api/v1/avatars/:avatarId |
GET |
Read |
/api/v1/avatars/:avatarId |
PATCH |
Rename only ({"name": "…"}) |
/api/v1/avatars/:avatarId |
DELETE |
Soft-delete |
/api/v1/avatars/:avatarId/versions |
GET / POST |
List / seal a new version |
/api/v1/avatars/:avatarId/profiles |
GET / POST |
The text + config layer — see doc 4 |
PATCH takes a display name 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.
Creating an avatar with a face and voice is the job of the avatar creation wizard
(/api/v1/design/drafts/*), which runs portrait generation, character-brief drafting and voice
design on the GPU fleet and finalizes into an avatar + sealed version + default profile. That
surface is documented separately; these docs cover interacting with avatars that already exist.
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: the wizard’s
POST /api/v1/design/drafts/from-avatar/:avatarId copies its sealed face and voice into a draft
you own, and finalizing gives you an avatar whose prompt you control.
Per-session overrides work fine against a stock avatar, since they change nothing stored:
{
"persona": "mei",
"extra_system_prompt": "The caller is asking about their order #4471."
}
Next: 3. Session lifecycle →