First message in six commands
This is the shortest path that proves the stack end to end: an account, a key, a room, a token, a socket, and a message arriving on it. About five minutes.
- Create an account
Signup provisions your tenant and returns an
as_…session for the account plane.curl -X POST https://api.example.com/v1/account/signup \ -H "Content-Type: application/json" \ -d '{"email":"you@example.com","password":"a-long-passphrase"}' # → { "tenant_id": "acct_…", "session_token": "as_…", "expires_at": "…" } - Create a server API key
This is the credential your backend uses. It is tenant-pinned, scoped, and shown once — store it
somewhere your clients cannot reach.
curl -X POST https://api.example.com/v1/account/api-keys \ -H "Authorization: Bearer as_…" \ -H "Content-Type: application/json" \ -d '{"name":"backend","environment":"live"}' # → { "key_id": "…", "api_key": "ak_live_…" ← shown once } - Provision a room with two members
external_refmakes creation idempotent — run it twice and you get the same room.curl -X POST https://api.example.com/v1/server/rooms \ -H "Authorization: Bearer ak_live_…" \ -H "Content-Type: application/json" \ -d '{"created_by":"alice","external_ref":"demo-room"}' # → { "id": "0f5c…", … } for p in alice bob; do curl -X POST https://api.example.com/v1/server/rooms/0f5c…/members \ -H "Authorization: Bearer ak_live_…" \ -H "Content-Type: application/json" \ -d "{\"principal_id\":\"$p\",\"role\":\"member\"}" done - Mint a room-scoped token per user
Do this once per user per room. The response carries both socket URLs and the ICE servers.
curl -X POST https://api.example.com/v1/server/session-tokens \ -H "Authorization: Bearer ak_live_…" \ -H "Content-Type: application/json" \ -d '{"room_id":"0f5c…","principal_id":"alice"}' # → { "access_token": "eyJ…", "chat_websocket_url": "wss://…", "rtc_websocket_url": "wss://…", "ice_servers": [ … ] } - Listen on the chat socket as Bob
The token rides the WebSocket subprotocol, never the URL.
// paste in a browser console, with bob's access_token const ws = new WebSocket("wss://api.example.com/v1/chat/ws", ["chatbox", "bearer." + BOB_TOKEN]); ws.onmessage = (e) => console.log(JSON.parse(e.data)); // now run the curl above as alice — bob sees message.created arrive - Send as Alice and watch it arrive
You can send over REST (below) or over Alice's own socket — the durability and ordering are the
same either way.
curl -X POST https://api.example.com/v1/rooms/0f5c…/messages \ -H "Authorization: Bearer eyJ…" \ -H "Content-Type: application/json" \ -d '{"client_message_id":"first-1","body":{"text":"hello from curl"}}'
What you just proved. The message was committed and assigned a per-room sequence
before Bob saw it. That ordering is what makes gap-free reconnection work — see
Chat for catch-up and receipts.
Where to go next #
- Integration checklist — the full sequence including calls and push, plus what to verify before you ship.
- Turnkey identity — skip the app-server entirely and let clients sign up with a phone number.
- Audio & video — the same token opens the RTC socket.
- Authentication planes — which credential belongs where, and which must never ship in an app.