Build a client
An OriginChats client needs a WebSocket implementation, JSON parsing, a Rotur authentication flow, and a small event router.
1. Open the server URL
Section titled “1. Open the server URL”Use the server’s public URL with a WebSocket scheme. The API is mounted at /.
const url = new URL('https://chat.example.com');url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';const socket = new WebSocket(url);The first server frame is handshake. Save its val object; it is the source of truth for limits and feature availability.
2. Authenticate
Section titled “2. Authenticate”The handshake includes a unique validator_key. Ask Rotur to sign or validate that challenge using your user’s session, then send the returned validator unchanged:
{ "cmd": "auth", "validator": "<rotur validator>"}Success arrives as two ordered frames: auth_success, then ready. The ready.user value is the stored account object for the current user. Authentication failures use auth_error, not the general error envelope.
3. Advertise client capabilities
Section titled “3. Advertise client capabilities”After authentication, tell the server which optional response shapes you understand:
{ "cmd": "capabilities", "capabilities": [ "channel_get" ]}The legacy { "cmd": "capabilities", "val": [...] } form is also accepted. A client declaring channel_get can receive a focused channel_get refresh instead of the full channels_get list when one channel changes.
4. Request initial state
Section titled “4. Request initial state”for (const cmd of ['channels_get', 'roles_list', 'users_online']) { socket.send(JSON.stringify({ cmd, listener: crypto.randomUUID() }));}Treat listener as an opaque client correlation ID. The server copies it to every direct response produced by that request. Broadcast copies sent to other connections do not include it.
If the handshake advertises message_signatures_v1 or slash_signatures_v1, continue with signing and verification before sending authored content.
5. Handle events
Section titled “5. Handle events”Route on cmd, not on object shape. Commands may produce a differently named event: poll_create produces message_new, and user_roles_set returns/broadcasts user_roles_get.
socket.addEventListener('message', ({ data }) => { const event = JSON.parse(data); if (event.cmd === 'error') rejectPending(event.listener, event.val, event.src); else if (event.listener) resolvePending(event.listener, event); else applyLiveEvent(event);});Respond to server ping frames if your runtime or networking stack expects application-level liveness. The server sends one about every 30 seconds.
