CLEMA protocol

Appendix: Example Session

# Client connects to wss://server.example.com/ws

# Handshake (must be first). A client declares the commands it sends and the
# signals it wants; the events of its streams come whatever it declares.
→ {"type":"connection.hello","data":{"types":["auth.code","auth.phone","conversation.get","message.send","delivery.set","read.set","contact.list","presence.online","presence.offline","typing.changed"]}}
← {"type":"connection.hello","ok":true,"data":{"types":["auth.code","auth.phone","auth.token","auth.refresh","auth.logout","session.list","session.revoke","message.send","message.forward","message.edit","message.delete","message.react","message.list","message.search","message.clear","read.set","delivery.set","pin.set","pin.remove","pin.list","notification.set","draft.update","block.add","block.remove","block.list","privacy.get","privacy.update","ban.add","ban.remove","ban.list","restriction.set","sync.state","sync.pull","conversation.create","conversation.list","conversation.search","conversation.get","conversation.update","conversation.delete","member.list","member.add","member.update","member.remove","invite.create","invite.list","invite.accept","invite.revoke","invite.update","contact.list","contact.add","contact.rename","contact.delete","presence.get","profile.get","profile.update","user.search","typing.set","file.create","file.get","file.complete","file.abort","transfer.create","transfer.accept","transfer.decline","transfer.cancel","session.revoked","message.created","message.edited","message.deleted","message.reacted","message.cleared","read.updated","delivery.updated","pin.added","pin.removed","notification.updated","draft.updated","block.added","block.removed","privacy.updated","ban.added","ban.removed","restriction.updated","conversation.created","conversation.updated","conversation.deleted","member.added","member.updated","member.removed","invite.created","invite.accepted","invite.revoked","invite.updated","contact.added","contact.requested","presence.online","presence.offline","profile.updated","file.updated","typing.changed","transfer.created","transfer.accepted","transfer.declined","transfer.cancelled"]}}

# Authentication: a code by SMS, then the code for tokens
→ {"id":"0","type":"auth.code","data":{"phone":"+79161234567"}}
← {"id":"0","type":"auth.code","ok":true,"data":{"verification_id":"v_7c1e...","code_length":6,"expires_in":300,"resend_in":60}}
→ {"id":"1","type":"auth.phone","data":{"verification_id":"v_7c1e...","code":"123456","client":{"device_name":"Alice's phone","platform":"Android","name":"Mandarin","version":"1.0.0"}}}
← {"id":"1","type":"auth.phone","ok":true,"data":{"user_id":12345,"access_token":"abc...","refresh_token":"xyz...","expires_in":3600,"created":false,"session":{"session_id":"s_0123...","client":{"device_name":"Alice's phone","platform":"Android","name":"Mandarin","version":"1.0.0"},"created_at":"2026-08-05T10:00:00Z","last_active_at":"2026-08-05T10:00:00Z","expires_at":"2026-09-04T10:00:00Z","current":true,"online":true,"push":false}}}

# Get contacts (user IDs are numbers)
→ {"id":"2","type":"contact.list"}
← {"id":"2","type":"contact.list","ok":true,"data":{"contacts":[{"user_id":67890,"nick":"Bob"}]}}

# Learn the direct conversation with Bob, then write in it
→ {"id":"3","type":"conversation.get","data":{"user_id":67890}}
← {"id":"3","type":"conversation.get","ok":true,"data":{"conversation":{"conversation_id":"direct:12345:67890","kind":"direct","seq":"0","shared":{"title":"Bob","peer_id":67890,"member_count":2,...},"membership":{"role":"member",...},"read":{"unread_count":0,...},"personal":{...}}}}
→ {"id":"4","type":"message.send","data":{"operation_id":"send-4","conversation_id":"direct:12345:67890","kind":"text","payload":{"text":"Hello Bob!"}}}
← {"id":"4","type":"message.send","conversation_id":"direct:12345:67890","seq":"7","ok":true,"data":{"message":{"msg_id":"1001","conversation_id":"direct:12345:67890","from":12345,"kind":"text","payload":{"text":"Hello Bob!"},"time":"2024-01-15T14:29:00Z","revision":1,"reactions":[]}}}

# Receive text message (from = numeric ID)
← {"type":"message.created","conversation_id":"direct:12345:67890","seq":"8","data":{"msg_id":"1002","conversation_id":"direct:12345:67890","from":67890,"kind":"text","payload":{"text":"Hi Alice!"},"time":"2024-01-15T14:30:00Z","revision":1,"reactions":[]}}

# Report it delivered: the cursor covers everything before it too
→ {"id":"5","type":"delivery.set","data":{"operation_id":"delivered-5","through":"1002"}}

# Mark the conversation read through that message
→ {"id":"6","type":"read.set","data":{"operation_id":"read-6","through":"1002"}}

# Contact comes online
← {"type":"presence.online","data":{"user_id":67890,"online":true,"active":true,"state":"available","time":"2024-01-15T15:00:00Z"}}

# Disconnect
→ {"type":"connection.close"}
← {"type":"connection.close","ok":true}