Skip to content

Authentication

The API uses two different kinds of credentials, scoped very differently on purpose.

Secret keyClient token
Formatsk_...client_token returned by POST /v1/sessions
Lives inYour server onlyYour browser (or mobile client)
ScopeFull account accessOne session, until it expires
Used forEvery REST call (Authorization: Bearer sk_...)Connecting the session’s WebSocket
Can create sessions?YesNo
Can spend money?YesNo

Your secret key authenticates every REST call:

Terminal window
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.

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:

Terminal window
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.

  1. Browser asks your backend for a session (your backend’s own API, however you design it, not a 2wai endpoint).
  2. Your backend calls POST /v1/sessions with Authorization: Bearer sk_....
  3. Your backend returns client_token and websocket_url to the browser.
  4. Browser connects websocket_url directly. No further REST calls needed.
  5. 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.