# Casola Avatar Interaction API

Start a live avatar session, choose which avatar answers, and shape how it behaves — from your
own backend, with your own API key.

These docs cover the **interaction** surface: selecting an avatar, minting a session, connecting a
browser to it, and configuring what the avatar says and does. They are written against the live
API; every field, cap and error code here is the one the server actually enforces.

| Environment | Base URL |
|---|---|
| Production | `https://api.casola.ai` |
| Staging | `https://api.casola-staging.net` |

## Start here

| Doc | What it covers |
|---|---|
| [1. Authentication](./01-authentication.md) | Credential kinds, scopes, and which parameters each one is allowed to send |
| [2. Choosing an avatar](./02-avatar-selection.md) | `avatar_id`, `avatar_ref`, `avatar_version_id`, `persona` — and the catalog routes |
| [3. Session lifecycle](./03-session-lifecycle.md) | Mint → connect → heartbeat → release, and the queue |
| [4. Prompt, backstory & overrides](./04-prompt-overrides.md) | **The composition rules.** Defaults vs. overrides, profiles, the additive layer |
| [5. Configuration & tools](./05-configuration.md) | The `config` options group and per-session tool wiring |
| [6. Error reference](./06-errors.md) | Every error code these routes return, and what to do about it |

## The one rule

Everything about avatar behaviour follows a single rule, and it is worth internalizing before
reading anything else:

> **What you edit in the dashboard becomes the avatar's default.
> What you pass as an API parameter is a per-session override that never persists.**

Editing a system prompt in the UI writes a new **profile** — a stored, versioned, hash-identified
row that becomes the avatar's default for every future session. Passing `context.system_prompt` on
a session mint changes **that one session only**; nothing is stored, the next session goes back to
the default, and the dashboard never shows it.

The two never fight, because they sit on different rungs of the same ladder:

```
per-session API parameter   ← wins, this session only
        ↓ (if absent)
stored profile              ← what the UI edits; the avatar's default
        ↓ (if absent)
sealed version baseline     ← what the avatar was created with
```

Resolution is **per field**, not per layer. Override only `context.backstory` and you keep the
profile's system prompt. See [4. Prompt, backstory & overrides](./04-prompt-overrides.md) for the
full contract with worked examples.

## Ten-second version

```bash
# 1. Mint a session against an avatar (server-side, secret key)
curl -sS https://api.casola.ai/api/v1/sessions \
  -H "Authorization: Bearer $CASOLA_SECRET_KEY" \
  -H "content-type: application/json" \
  -d '{"avatar_id": "019fe463-fbd3-7000-80e8-85dad81d3fee"}'
```

```json
{
  "status": "ready",
  "session_id": "019fe497-ac9d-7000-804e-3bad9c970397",
  "connect_url": "https://singapore-rtx6000-1.casola.ai",
  "session_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
  "seat_token": "3f21c8ae-9d44-4c1e-9b7a-2e5f0d6c8a13",
  "expires_at": 1786246631,
  "cap_seconds": 300,
  "profile": { "id": "019fe463-fc2a-…", "hash_short": "a2271f20509d", "source": "default" }
}
```

```ts
// 2. Hand connect_url + session_token to the browser and connect
import { AvatarSession, connectViaToken } from '@casola/avatar-client';

const session = new AvatarSession({
  videoEl: document.querySelector('video#avatar')!,
  connect: connectViaToken({ connectUrl: connect_url, sessionToken: session_token }),
  workletUrl: '/mic-worklet.js',
});
await AvatarSession.ensureMicPermission();
await session.start();
```

The `session_token` is a short-lived (60 s) EdDSA JWT: it is a **connect ticket**, not a bearer
credential. Mint it server-side, hand it straight to the browser, and let the browser connect
directly to the GPU edge — no media flows through your backend.

## Mental model

An avatar is two things, and they are versioned separately:

- **A sealed identity** — face, voice, and the bundle that carries them. Immutable. Changing it
  means publishing a new *version*. This is what your users see and hear.
- **A text/config profile** — system prompt, backstory, tools, and the options group. Cheap,
  append-only, hash-deduped. Changing it is a *profile*, not a new version: no re-seal, no asset
  re-download on the GPU boxes, no risk to the identity.

A session picks one of each — plus whatever you override for that session — and freezes the result
at mint time. Editing the avatar mid-call never changes a call in flight.

## Conventions used in these docs

- Every route exists in two shapes: **token-relative** (`/api/v1/avatars`, workspace inferred from
  your key) and **explicit** (`/api/v1/workspaces/:wsId/avatars`). Examples use the token-relative
  form; both behave identically, and a workspace-bound key naming a different workspace is a `403`.
- `…` in a JSON sample means "truncated for readability", never a literal value.
- Character caps are **characters**, not tokens. Roughly 4 characters ≈ 1 token.

---

Published at **https://api-docs.casola.ai** from this directory — see
[PUBLISHING.md](./PUBLISHING.md).
