Sessions
A persistent authentication session is an independently revocable device login with one rotating access/refresh token pair. A connection is a temporary transport channel. Several connections may authenticate with the same session, and one user may own several sessions. This distinction is independent of the selected transport and storage adapters.
For a message mutation, the initiating connection gets the updated state in its
response while the user's other connections receive the durable event at the
same place in its stream. A connection may be authenticated once; auth.phone or
auth.token on an authenticated connection returns not_authorized.
List Sessions
{"id":"sessions-1","type":"session.list"}
{"id":"sessions-1","type":"session.list","ok":true,"data":{"sessions":[{"session_id":"s_0123...","transport":"websocket","client":{"device_name":"Alice's laptop","platform":"macOS","name":"Mandarin Web","version":"0.1.0"},"created_at":"2026-08-05T10:00:00Z","last_active_at":"2026-08-05T11:00:00Z","expires_at":"2026-09-04T11:00:00Z","current":true,"online":true,"push":false}]}}
Only the authenticated user's unexpired sessions are returned. Exactly one is
current; online means at least one live connection uses that session, and
push that its device is registered for push
notifications.
transport names the adapter the login was made through, so an account's
CLEMA devices and its clients on other protocols appear in one list and are
revoked the same way. Unlike client, it is derived by the server and cannot be
influenced by the client. It is empty for logins created before the field
existed.
An account keeps at most 100 persistent sessions. Creating session 101
atomically removes the least recently active session, deauthenticates its live
connections, and then creates the new one; Mandarin tells the
account of a CLEMA client's session removed so. This hard bound keeps session.list
small enough to return without pagination.
Revoke Sessions
Revoke one session by its opaque ID:
{"id":"sessions-2","type":"session.revoke","data":{"session_id":"s_0123..."}}
Or revoke every session except the current one:
{"id":"sessions-3","type":"session.revoke","data":{"scope":"others"}}
Exactly one selector is required. The response contains the affected IDs:
{"id":"sessions-3","type":"session.revoke","ok":true,"data":{"session_ids":["s_abcd..."]}}
Every live connection of an affected session is immediately deauthenticated and
receives {"type":"session.revoked","data":{"session_id":"s_abcd...","reason":"revoked"}},
except the requesting connection when its successful response already conveys
the result. Revoking another user's ID returns session_not_found and reveals
nothing about its existence.
reason is revoked for an explicit revoke or logout, expired when
expires_at is reached, and replaced when the per-account session limit
evicts the least recently active login.
Presence follows reachability. A user is online while they can be reached:
at least one of their connections is open, active or
not, or at least one of their sessions is registered for
pushes. Contacts receive presence.online when the first
such way appears and presence.offline when the last one goes. Extra
connections or sessions cause no transition. A user who made themselves
invisible is offline as far as their contacts can tell, however
they can be reached.
Slow clients are dropped. Outgoing messages are queued per connection. A client that stops reading long enough to fill that queue is disconnected rather than allowed to stall senders.