Message Format
Every message is a JSON object with a fixed structure:
{
"id": "string",
"type": "string",
"conversation_id": "string",
"seq": "string",
"ok": true,
"data": {},
"error": {}
}
Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | No | Request ID for request-response correlation. Server echoes it in response. |
type |
string | Yes | Message type (e.g., auth.phone, message.send, contact.list) |
conversation_id |
string | Durable events and mutation responses of a conversation stream | The conversation whose stream seq belongs to; absent for the account stream |
seq |
string | Durable events and mutation responses | Place in the event's stream, a decimal string |
ok |
boolean | Responses only | true for success, false for failure. Present only in server responses. |
data |
object | No | Payload. Structure depends on message type. |
error |
object | On failure | Error details. Present only when ok is false. |
When data is present it must be a JSON object with the shape required by the
message type. Senders must omit an optional body rather than encode it as
null; malformed bodies and null are rejected with invalid_format.
Type Naming
Message types are lowercase names in <domain>.<operation> form. Names use
clear domain terms and complete operations rather than opaque abbreviations.
- Commands describe the requested operation:
contact.add,profile.update. - Responses echo the command type unchanged.
- Push events have distinct state/event names:
message.created,profile.updated,presence.online. - Connection-control messages use the same namespace:
connection.hello,connection.ping,connection.closing. - A breaking revision may append a version segment such as
message.send.v2; unversioned names always mean the original contract.
Request-Response Correlation
- Client includes
idin requests that expect a response - Server includes the same
idin the response - Push events from server (e.g., incoming messages) have no
idand nook - Durable events carry their place in a stream:
seq, withconversation_idfor a conversation stream. A state-changing mutation response carries the place of the event its caller's devices receive for it, including on an idempotent retry. No-op mutations, other responses and real-time events omit both.
seq, message IDs and sync cursors are decimal strings. They must not be
decoded as JSON numbers because their range can exceed JavaScript's exact
integer range.
Response Format
Success without data:
{"id": "1", "type": "auth.logout", "ok": true}
Success with data:
{"id": "2", "type": "contact.list", "ok": true, "data": {"contacts": [...]}}
Failure:
{
"id": "3",
"type": "auth.phone",
"ok": false,
"error": {
"code": "invalid_code",
"message": "The code is wrong"
}
}
Error Object
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | Yes | Machine-readable error code (snake_case) |
message |
string | Yes | Human-readable error description |
retry_after |
number | No | Seconds to wait before trying again; given with rate_limited when the wait is known |
Error Codes
| Code | Description |
|---|---|
invalid_token |
Token is invalid or expired |
not_authenticated |
Operation requires authentication |
user_not_found |
User does not exist |
contact_not_found |
Contact not in contact list |
contact_exists |
Contact already in contact list |
session_not_found |
The persistent session named does not exist or is not the caller's |
transfer_not_found |
The direct transfer named is unknown or over |
transfer_expired |
The direct transfer was not carried in time |
not_authorized |
Not authorized for this operation |
invalid_format |
Invalid message format |
rate_limited |
Too many requests, slow down |
internal_error |
Internal server error |
no_common_types |
Client and server share no negotiated message type |
not_found |
Requested message or HTTP resource does not exist |
conflict |
State changed since the supplied revision or an operation ID was reused for another command |
invalid_state |
The command is not valid for the current message or actor |
invalid_code |
The confirmation code is wrong |
verification_expired |
The verification is unknown, expired, already used or out of attempts: a new code has to be requested |
delivery_failed |
The server could not send the code right now; the request may be repeated later |
not_supported |
The server does not serve this request, such as a phone number in a country it does not send to |
HTTP Error Responses
The avatar and file HTTP APIs use the HTTP status code and the same structured error object as WebSocket responses:
{
"ok": false,
"error": {
"code": "not_authenticated",
"message": "Authentication required or invalid"
}
}
HTTP error responses have Content-Type: application/json. They do not contain
WebSocket-only id or type fields.
HTTP Resource Naming
HTTP paths use plural resource nouns. A concrete resource is selected by an ID
or capability token, while me identifies the authenticated user's singleton
resource: /avatars/{user_id}, /avatars/me, /files/{file_id} and
/transfers/{transfer_token}, and a smiley's picture is
/smileys/{pack_id}/{smiley_id}/{format}. Group/channel avatars are subordinate resources
at /conversations/{conversation_id}/avatar. Multipart chunks are subordinate resources at
/files/{file_id}/parts/{part_number}.
Timestamps
All timestamps use ISO 8601 / RFC 3339 format in UTC:
YYYY-MM-DDTHH:mm:ssZ
Example: "2024-01-15T14:30:00Z"