Authentication
The API uses two different kinds of credentials, scoped very differently on purpose.
| Secret key | Client token | |
|---|---|---|
| Format | sk_... | client_token returned by POST /v1/sessions |
| Lives in | Your server only | Your browser (or mobile client) |
| Scope | Full account access | One session, until it expires |
| Used for | Every REST call (Authorization: Bearer sk_...) | Connecting the session’s WebSocket |
| Can create sessions? | Yes | No |
| Can spend money? | Yes | No |
Secret keys
Section titled “Secret keys”Your secret key authenticates every REST call:
curl https://api.2wai.ai/v1/sessions \ -H "Authorization: Bearer sk_live_..."Secret keys are issued when your account is provisioned. If you need an additional key (for example, one per environment) or need to rotate an existing one, contact your 2wai account contact; key management isn’t yet a self-serve REST operation.
Client tokens
Section titled “Client tokens”A client_token is minted per-session by POST /v1/sessions and is only useful for connecting to that one session’s WebSocket. It can’t call any REST endpoint. That’s what makes it safe to send to a browser:
{ "session_id": "sess_4e1f9a2b8c3d", "client_token": "ctk_eyJhbGciOiJIUzI1NiJ9...", "transport": "mse-ws", "websocket_url": "wss://media.2wai.ai/v1/stream/sess_4e1f9a2b8c3d?token=ctk_eyJhbGciOiJIUzI1NiJ9..."}websocket_url already has the client_token embedded as part of its query string. Browsers can’t attach custom headers to a WebSocket handshake, so the token travels in the URL instead. Treat the whole URL as sensitive for the lifetime of the session (don’t log it, don’t put it anywhere long-lived), but it’s fine to pass it straight from your server to your frontend over your own normal request/response cycle.
Client tokens expire. Refresh one, or get a fresh websocket_url after a dropped connection, with:
curl -X POST https://api.2wai.ai/v1/sessions/$SESSION_ID/token \ -H "Authorization: Bearer $TWAI_SECRET_KEY"This call requires your secret key, so it happens on your server; hand the new token to the browser the same way you did the first one. See Sessions for the full reconnect flow.
Putting it together
Section titled “Putting it together”- Browser asks your backend for a session (your backend’s own API, however you design it, not a 2wai endpoint).
- Your backend calls
POST /v1/sessionswithAuthorization: Bearer sk_.... - Your backend returns
client_tokenandwebsocket_urlto the browser. - Browser connects
websocket_urldirectly. No further REST calls needed. - Your backend drives the conversation (
/speak,/audio,/interrupt) using the secret key, in response to whatever your app’s logic decides the avatar should do next.
Next: Concepts for the vocabulary this all builds on, or jump to Sessions for the full session lifecycle.