Casola Avatar Interaction API

1. Authentication

Which credential you hold decides which parameters you may send. This is the single most common source of surprise in the interaction API: the same mint route accepts a rich body from a secret key and refuses most of it from a browser-held key — deliberately.

Credential kinds

Kind Header Where it lives Mint scope
Secret key Authorization: Bearer avatar_… Your server only sessions:write
Publishable key Authorization: Bearer pk_… + Origin Browser bundles session:connect
Device key Authorization: Bearer … Mobile apps (App Attest) session:connect
Trusted-issuer id_token Authorization: Bearer <jwt> + workspace_id in body Your end users’ IdP session:connect
Browser session cookie cookie, via the dashboard BFF app.casola.ai only first-party route

Create secret and publishable keys with POST /api/v1/tokens (scope tokens:write) or from the dashboard. kind defaults to secret.

A secret key is the only credential that can select resources and rewrite behaviour, because both are server-side decisions. A publishable key ships to browsers where anyone can read it; letting it choose which prompt runs would let anyone choose which prompt runs.

What each credential may send

Mint parameter Secret Publishable / device / issuer
persona (stock avatar)
avatar_id / avatar_ref 403 custom_avatar_not_allowed
avatar_version_id 403 avatar_version_pin_not_allowed
profile_id 403 profile_pin_not_allowed
context.system_prompt / context.backstory 403 tools_context_not_allowed
extra_system_prompt / extra_backstory 403 tools_context_not_allowed
tools / extra_tools 403 tools_context_not_allowed
config 403 tools_context_not_allowed
session_cap_seconds 403 session_cap_not_allowed
response_language
observability
domain_allowlist locked to the key’s registered origins

The refusals are explicit rather than silent. A caller that sends tools and gets a session back without them would find out in production; a 403 is found in development.

browser ──▶ your backend (holds the secret key) ──▶ POST /api/v1/sessions
   ▲                                                      │
   └────────── connect_url + session_token ◀──────────────┘
                       │
                       └──▶ direct WebSocket to the GPU edge

Your backend is where you decide who gets which avatar with what overrides — the decisions a browser cannot be trusted with. The session_token it returns is safe to hand to the browser: it is scoped to one session, expires in 60 seconds, and grants nothing but the right to connect.

// your-backend/start-session.ts
app.post('/start-session', async (req, res) => {
  const user = await authenticateYourUser(req);          // your auth, not ours

  const mint = await fetch('https://api.casola.ai/api/v1/sessions', {
    method: 'POST',
    headers: {
      authorization: `Bearer ${process.env.CASOLA_SECRET_KEY}`,
      'content-type': 'application/json',
    },
    body: JSON.stringify({
      avatar_id: pickAvatarFor(user),
      // Per-session context — this user only, never stored:
      extra_system_prompt: `The caller is ${user.name}, a ${user.plan} customer.`,
    }),
  });

  const { connect_url, session_token } = await mint.json();
  res.json({ connect_url, session_token });               // the only two fields the browser needs
});

Never return seat_token to a browser you do not control — it is the capacity handle, and it lets the holder release the seat.

Publishable keys without a backend

If you have no server, a publishable key can mint directly from the browser, restricted to stock avatars and no behaviour overrides:

const r = await fetch('https://api.casola.ai/api/v1/sessions', {
  method: 'POST',
  headers: { authorization: `Bearer ${PUBLISHABLE_KEY}`, 'content-type': 'application/json' },
  body: JSON.stringify({ persona: 'mei' }),
});

The request Origin must match one of the origins registered on the key, and domain_allowlist is forced to those origins regardless of what you send.

The first-party route

POST /api/sessions is the dashboard’s own mint, authenticated by browser session cookie rather than a key. It exists for the “chat with this avatar” button inside app.casola.ai. It accepts persona, avatar_id / avatar_ref, profile_id, response_language, observability, and the two additive text extras — but not tools, extra_tools, context, or config. A caller sending extra_tools gets 400 extra_tools_not_allowed rather than a silent drop.

It is not part of the public integration surface; use POST /api/v1/sessions from your own backend.

Scope Grants
sessions:write Mint sessions (POST /api/v1/sessions)
sessions:read GET /api/v1/sessions/:id
avatars:read List/read avatars, versions, profiles
avatars:write Create/update avatars, publish versions, append profiles
session:connect Mint from a browser-held credential (publishable/device/issuer)

Avatar authoring additionally requires the custom_avatars workspace feature. Reading, renaming, deleting and opening sessions stay available if that feature is ever revoked — you keep using and managing what you already built.


Next: 2. Choosing an avatar →