Skip to content

Latest commit

 

History

History
1071 lines (821 loc) · 63.2 KB

API.md

File metadata and controls

1071 lines (821 loc) · 63.2 KB

Server API

How it works?

Tinode is an IM router and a store. Conceptually it loosely follows a publish-subscribe model.

Server connects sessions, users, and topics. Session is a network connection between a client application and the server. User represents a human being who connects to the server with a session. Topic is a named communication channel which routes content between sessions.

Users and topics are assigned unique IDs. User ID is a string with 'usr' prefix followed by base64-URL-encoded pseudo-random 64-bit number, e.g. usr2il9suCbuko. Topic IDs are described below.

Clients such as mobile or web applications create sessions by connecting to the server over a websocket or through long polling. Client authentication is required in order to perform most operations. Client authenticates the session by sending a {login} packet. See Authentication section for details. Once authenticated, the client receives a a token which is used for authentication later. Multiple simultaneous sessions may be established by the same user. Logging out is not supported (and not needed).

Once the session is established, the user can start interacting with other users through topics. The following topic types are available:

  • me is a topic for managing one's profile and receiving notifications about other topics; me topic exists for every user.
  • fnd topic is used for finding other users and topics; fnd topic also exists for every user.
  • Peer to peer topic is a communication channel strictly between two users. Each participant sees topic name as the ID of the other participant: 'usr' prefix followed by a base64-URL-encoded numeric part of user ID, e.g. usr2il9suCbuko.
  • Group topic is a channel for multi-user communication. It's named as 'grp' followed by 11 pseudo-random characters, i.e. grpYiqEXb4QY6s. Group topics must be explicitly created.

Session joins a topic by sending a {sub} packet. Packet {sub} serves three functions: creating a new topic, subscribing user to a topic, and attaching session to a topic. See {sub} section below for details.

Once the session has joined the topic, the user may start generating content by sending {pub} packets. The content is delivered to other attached sessions as {data} packets.

The user may query or update topic metadata by sending {get} and {set} packets.

Changes to topic metadata, such as changes in topic description, or when other users join or leave the topic, is reported to live sessions with {pres} (presence) packet. The {pres} packet is sent either to the topic being affected or to the me topic.

When user's me topic comes online (i.e. an authenticated session attaches to me topic), a {pres} packet is sent to me topics of all other users, who have peer to peer subscriptions with the first user.

General considerations

Timestamps are always represented as RFC 3339-formatted string with precision up to milliseconds and timezone always set to UTC, e.g. "2015-10-06T18:07:29.841Z".

Whenever base64 encoding is mentioned, it means base64 URL encoding with padding characters stripped, see RFC 4648.

The {data} packets have server-issued sequential IDs: base-10 numbers starting at 1 and incrementing by one with every message. They are guaranteed to be unique per topic.

In order to connect requests to responses, client may assign message IDs to all packets set to the server. These IDs are strings defined by the client. Client should make them unique at least per session. The client-assigned IDs are not interpreted by the server, they are returned to the client as is.

Connecting to the server

Client establishes a connection to the server over HTTP(S). Server offers the following endpoints:

  • /v0/channels for websocket connections
  • /v0/channels/lp for long polling
  • /v0/file/u for file uploads
  • /v0/file/s for serving files (downloads)

v0 denotes API version (currently zero). Every HTTP(S) request must include the API key. The server checks for the API key in the following order:

  • HTTP header X-Tinode-APIKey
  • URL query parameter apikey (/v0/file/s/abcdefg.jpeg?apikey=...)
  • Form value apikey
  • Cookie apikey

A default API key is included with every demo app for convenience. Generate your own key for production using keygen utility.

Once the connection is opened, the client must issue a {hi} message to the server. Server responds with a {ctrl} message which indicates either success or an error. The params field of the response contains server's protocol version "params":{"ver":"0.15"} and may include other values.

Websocket

Messages are sent in text frames, one message per frame. Binary frames are reserved for future use. Server allows connections with any value in the Origin header.

Long polling

Long polling works over HTTP POST (preferred) or GET. In response to client's very first request server sends a {ctrl} message containing sid (session ID) in params. Long polling client must include sid in every subsequent request either in the URL or in the request body.

Server allows connections from all origins, i.e. Access-Control-Allow-Origin: *

Out of Band Large Files

Large files are sent out of band using HTTP POST as Content-Type: multipart/form-data. See below for details.

Users

User is meant to represent a person, an end-user: producer and consumer of messages.

Users are assigned one of the two authentication level: authenticated or anonymous. When a connection is first established, the client application can only send either an {acc} or a {login} message. Sending a {login} message will authenticate the user or allow him to continue as anonymous.

Each user is assigned a unique ID. The IDs are composed as usr followed by base64-encoded 64-bit numeric value, e.g. usr2il9suCbuko. Users also have the following properties:

  • created: timestamp when the user record was created
  • updated: timestamp of when user's public was last updated
  • username: unique string used in basic authentication; username is not accessible to other users
  • defacs: object describing user's default access mode for peer to peer conversations with authenticated and anonymous users; see Access control for details
  • auth: default access mode for authenticated users
  • anon: default access for anonymous users
  • public: an application-defined object that describes the user. Anyone who can query user for public data.
  • private: an application-defined object that is unique to the current user and accessible only by the user.
  • tags: discovery and credentials.

A user may maintain multiple simultaneous connections (sessions) with the server. Each session is tagged with a client-provided User Agent string intended to differentiate client software.

Logging out is not supported by design. If an application needs to change the user, it should open a new connection and authenticate it with the new user credentials.

Authentication

The server comes with three authentication methods out of the box: basic, token, and anon:

  • basic provides authentication by a login-password pair.
  • token provides authentication by a cryptographic token.
  • anon is "anonymous authentication" designed for cases where users are temporary, such as handling customer support requests through chat.

Any other authentication method can be implemented using plugins.

The token is intended to be the primary means of authentication. Tokens are designed in such a way that token authentication is light weight. For instance, token authenticator generally does not make any database calls, all processing is done in-memory. All other authentication methods are intended to be used sparingly in order to obtain the token. Once the token is obtained, all subsequent logins should use it.

Authenticators are used during account registration {acc} and during {login}.

Creating an Account

When a new account is created, the user must inform the server which authentication method will be later used to gain access to this account as well as provide shared secret, if appropriate. Only basic and anon can be used during account creation. The basic requires the user to generate and send a unique login and password to the server. The anon does not exchange secrets.

User may optionally set {acc login=true} to use the new account for immediate authentication. When login=false (or not set), the new account is created but the authentication status of the session which created the account remains unchanged. When login=true the server will attempt to authenticate the session with the new account, the response to the {acc} request will contain the authentication token on success. This is particularly important for the anon authentication.

Logging in

Logging in is possible with basic and token only. Response to any login is a {ctrl} message with either a code 200 and a token which can be used in subsequent logins with token authentication, or a code 300 request for additional information, such as verifying credentials or responding to a method-dependent challenge in multi-step authentication, or a code 4xx error.

Token has server-configured expiration time so it needs to be periodically refreshed.

Changing Authentication Parameters

User may change authentication parameters, such as changing login and password, by issuing an {acc} request on an already authenticated session. Only basic authentication currently supports changing parameters:

acc: {
  id: "1a2b3", // string, client-provided message id, optional
  scheme: "basic", // authentication scheme being updated
  secret: btoa("new_username:new_password") // new parameters
}

In order to change just the password, username should be left empty, i.e. secret: btoa(":new_password").

Password Recovery

Currently not supported.

Credentials

Server may be optionally configured to require certain credentials associated with the user accounts. For instance, it's possible to require user to provide a unique email or a phone number as a condition of account registration, or to solve a captcha.

The server supports verification of email and phone numbers out of the box. Verification of emails is mostly functional, verification of phone numbers is not functional because a commercial subscription is needed in order to be able to send SMS.

Access control

Access control manages user's access to topics through access control lists (ACLs) or bearer tokens (bearer tokens are not implemented as of version 0.15).

Access control is mostly usable for group topics. Its usability for me and P2P topics is limited to managing presence notifications and banning uses from initiating or continuing P2P conversations.

User's access to a topic is defined by two sets of permissions: user's desired permissions "want", and permissions granted to user by topic's manager(s) "given". Each permission is represented by a bit in a bitmap. It can be either present or absent. The actual access is determined as a bitwise AND of wanted and given permissions. The permissions are communicated in messages as a set of ASCII characters, where presence of a character means a set permission bit:

  • No access: N is not a permission per se but an indicator that permissions are explicitly cleared/not set. It usually indicates that the default permissions should not be applied.
  • Join: J, permission to subscribe to a topic
  • Read: R, permission to receive {data} packets
  • Write: W, permission to {pub} to topic
  • Presence: P, permission to receive presence updates {pres}
  • Approve: A, permission to approve requests to join a topic; a user with such permission is topic's manager
  • Sharing: S, permission to invite other people to join the topic
  • Delete: D, permission to hard-delete messages; only owners can completely delete topics
  • Owner: O, user is the topic owner; topic may have a single owner only; some topics have no owner

Topic's default access is established at the topic creation time by {sub.desc.defacs} and can be subsequently modified by {set} messages. Default access is defined for two categories of users: authenticated and anonymous. This value is applied as a default "given" permission to all new subscriptions.

Client may replace explicit permissions in {sub} and {set} messages with an empty string to tell Tinode to use default permissions. If client specifies no default access permissions at topic creation time, authenticated users will receive a RWP permission, anonymous users will receive and empty permission which means every subscription request must be explicitly approved by the topic manager.

Access permissions can be assigned on a per-user basis by {set} messages.

Topics

Topic is a named communication channel for one or more people. Topics have persistent properties. These topic properties can be queried by {get what="desc"} message.

Topic properties independent of the user making the query:

  • created: timestamp of topic creation time
  • updated: timestamp of when topic's public or private was last updated
  • defacs: object describing topic's default access mode for authenticated and anonymous users; see Access control for details
  • auth: default access mode for authenticated users
  • anon: default access for anonymous users
  • seq: integer server-issued sequential ID of the latest {data} message sent through the topic
  • public: an application-defined object that describes the topic. Anyone who can subscribe to topic can receive topic's public data.

User-dependent topic properties:

  • acs: object describing given user's current access permissions; see Access control for details
  • want: access permission requested by this user
  • given: access permissions given to this user
  • private: an application-defined object that is unique to the current user.

Topic usually have subscribers. One the the subscribers may be designated as topic owner (O access permission) with full access permissions. The list of subscribers can be queries with a {get what="sub"} message. The list of subscribers is returned in a sub section of a {meta} message.

me topic

Topic me is automatically created for every user at the account creation time. It serves as means for account updates, receiving presence notification from people and topics of interest, invites to join topics, requests to approve subscription for topics where this user is a manager (has S permission). Topic me has no owner. The topic cannot be deleted or unsubscribed from. One can leave the topic which will stop all relevant communication and indicate that the user is offline (although the user may still be logged in and may continue to use other topics).

Joining or leaving me generates a {pres} presence update sent to all users who have peer to peer topics with the given user and P permissions set.

Topic me is read-only. {pub} messages to me are rejected.

Message {get what="desc"} to me is automatically replied with a {meta} message containing desc section with the topic parameters (see intro to Topics section). The public parameter of me topic is data that the user wants to show to his/her connections. Changing it changes public not just for the me topic, but also everywhere where user's public is shown, such as public of all user's peer to peer topics.

Message {get what="sub"} to me is different from any other topic as it returns the list of topics that the current user is subscribed to as opposite to the expected user's subscription to me.

  • seq: server-issued numeric id of the last message in the topic
  • read: seq value self-reported by the current user as received
  • recv: seq value self-reported by the current user as read
  • seen: for P2P subscriptions, timestamp of user's last presence and User Agent string are reported
  • when: timestamp when the user was last online
  • ua: user agent string of the user's client software last used

Message {get what="data"} to me is rejected.

fnd and Tags: Finding Users and Topics

Topic fnd is automatically created for every user at the account creation time. It serves as an endpoint for discovering other users and group topics.

Users and group topics can be discovered by optional tags. Tags are optionally assigned at the topic or user creation time then can be updated by using {set what="tags"} against a fnd or a group topic.

A tag is an arbitrary case-insensitive Unicode string (forced to lowercase) starting with a Unicode letter or digit. Tags must not contain the double quote ", \u0022 but may contain spaces. Tags may have a prefix which serves as a namespace. The prefix is a string followed by a colon :, ex. prefixed phone tag tel:14155551212 or prefixed email tag email:[email protected]. Some prefixed tags are optionally enforced to be unique. In that case only one user or topic may have such a tag. Certain tags may be forced to be immutable to the user, i.e. user's attempts to add or remove an immutable tag will be rejected by the server.

The tags are indexed server-side and used in user and topic discovery. Search returns users and topics sorted by the number of matched tags in descending order.

In order to find users or topics, a user sets either public or private parameter of the fnd topic to a search query (see Query language) then issues a {get topic="fnd" what="sub"} request. If both public and private are set, the public query is used. The private query is persisted across sessions and devices, i.e. all user's sessions see the same private query. The value of the public query is ephemeral, i.e. it's not saved to database and not shared between user's sessions. The private query is intended for large queries which do not change often, such as finding matches for everyone in user's contact list on a mobile phone. The public query is intended to be short and specific, such as finding some topic or a user who is not in the contact list.

The system responds with a {meta} message with the sub section listing details of the found users or topics formatted as subscriptions.

Topic fnd is read-only. {pub} messages to fnd are rejected.

(The following functionality is not implemented yet) When a new user registers with tags matching the given query, the fnd topic will receive {pres} notification for the new user.

Plugins support Find service which can be used to replace default search with a custom one.

Query language

Tinode query language is used to define search queries for finding users and topics. The query is a string containing tags separated by spaces or commas. Tags are strings - individual query terms which are matched against user's or topic's tags. The tags can be written in an RTL language but the query as a whole is parsed left to right. Spaces are treated as the AND operator, commas (as well as commas preceded and/or followed by a space) as the OR operator. The order of operators is ignored: all AND tags are grouped together, all OR tags are grouped together. OR takes precedence over AND: if a tag is preceded of followed by a comma, it's an OR tag, otherwise an AND. For example, a AND b OR c is rewritten as (b OR c) AND a.

Tags containing spaces or commas must be enclosed in double quotes (", \u0022): i.e. "abc, def" is treated as a single token abc, def. Tags must start with a Unicode letter or digit. Tags must not contain the double quote (", \u0022).

Some examples:

  • flowers: find topics or users which contain tag flowers.
  • flowers travel: find topics or users which contain both tags flowers and travel.
  • flowers, travel: find topics or users which contain either tag flowers or travel (or both).
  • flowers travel, puppies: find topics or users which contain flowers and either travel or puppies, i.e. (travel OR puppies) AND flowers.
  • flowers, travel puppies, kittens: find topics or users which contain either one of flowers, travel, puppies, or kittens, i.e. flowers OR travel OR puppies OR kittens. The space between travel and puppies is treated as OR due to OR taking precedence over AND.

Incremental updates to queries

Queries, particularly fnd.private could be arbitrarily large, limited only by the message size and by the underlying database. Instead of rewriting the entire query to add or remove a tag, tag can be added or removed incrementally.

The incremental update request is processed left to right. It may contain the same tag multiple times, i.e. -tag+tag is a valid request.

Possible use cases

  • Restricting users to organisations. An immutable tag(s) may be assigned to the user which denotes the organisation the user belongs to. When the user searches for other users or topics, the search can be restricted to always contain the tag. This approach can be used to segment users into organisations with limited visibility into each other.

  • Search by geographical location. Client software may periodically assign a geohash tag to the user based on current location. Searching for users in a given area would mean matching on geohash tags.

  • Search by numerical range, such as age range. The approach is similar to geohashing. The entire range of numbers is covered by the smallest possible power of 2, for instance the range of human ages is covered by 27=128 years. The entire range is split in two halves: the range 0-63 is denoted by 0, 64-127 by 1. The operation is repeated with each subrange, i.e. 0-31 is 00, 32-63 is 01, 0-15 is 000, 32-47 is 010. Once completed, the age 30 will belong to the following ranges: 0 (0-63), 00 (0-31), 001 (16-31), 0011 (24-31), 00111 (28-31), 001111 (30-31), 0011110 (30). A 30 y.o. user is assigned a few tags to indicate the age, i.e. age:00111, age:001111, and age:0011110. Technically, all 7 tags may be assigned but usually it's impractical. To query for anyone in the age range 28-35 convert the range into a minimal number of tags: age:00111 (28-31), age:01000 (32-35). This query will match the 30 y.o. user by tag age:00111.

Peer to Peer Topics

Peer to peer (P2P) topics represent communication channels between strictly two users. The name of the topic is different for each of the two participants. Each of them sees the name of the topic as the user ID of the other participant: usr followed by base64 URL-encoded ID of the user. For example, if two users usrOj0B3-gSBSs and usrIU_LOVwRNsc start a P2P topic, the first one will see it as usrIU_LOVwRNsc, the second as usrOj0B3-gSBSs. The P2P topic has no owner.

A P2P topic is created by one user subscribing to topic with the name equal to the ID of the other user. For instance, user usrOj0B3-gSBSs can establish a P2P topic with user usrIU_LOVwRNsc by sending a {sub topic="usrIU_LOVwRNsc"}. Tinode will respond with a {ctrl} packet with the name of the newly created topic as described above. The other user will receive a {pres} message on me topic with updated access permissions.

The 'public' parameter of P2P topics is user-dependent. For instance a P2P topic between users A and B would show user A's 'public' to user B and vice versa. If a user updates 'public', all user's P2P topics will automatically update 'public' too.

The 'private' parameter of a P2P topic is defined by each participant individually as with any other topic type.

Group Topics

Group topics represent communication channels between multiple users. The name of a group topic is grp followed by a string of characters from base64 URL-encoding set. No other assumptions can be made about internal structure or length of the group name.

A group topic is created by sending a {sub} message with the topic field set to string new optionally followed by any characters, e.g. new or newAbC123 are equivalent. Tinode will respond with a {ctrl} message with the name of the newly created topic, i.e. {sub topic="new"} is replied with {ctrl topic="grpmiKBkQVXnm3P"}. If topic creation fails, the error is reported on the original topic name, i.e. new or newAbC123. The user who created the topic becomes topic owner. Ownership can be transferred to another user with a {set} message but one user must remain the owner at all times.

A user joining or leaving the topic generates a {pres} message to all other users who are currently in the joined state with the topic.

Using Server-Issued Message IDs

Tinode provides basic support for client-side caching of {data} messages in the form of server-issued sequential message IDs. The client may request the last message id from the topic by issuing a {get what="desc"} message. If the returned ID is greater than the ID of the latest received message, the client knows that the topic has unread messages and their count. The client may fetch these messages using {get what="data"} message. The client may also paginate history retrieval by using message IDs.

User Agent and Presence Notifications

A user is reported as being online when one or more of user's sessions are attached to the me topic. Client-side software identifies itself to the server using ua (user agent) field of the {login} message. The user agent is published in {meta} and {pres} messages in the following way:

  • When user's first session attaches to me, the user agent from that session is broadcast in the {pres what="on" ua="..."} message.
  • When multiple user sessions are attached to me, the user agent of the session where the most recent action has happened is reported in {pres what="ua" ua="..."}; the 'action' in this context means any message sent by the client. To avoid potentially excessive traffic, user agent changes are broadcast no more frequently than once a minute.
  • When user's last session detaches from me, the user agent from that session is recorded together with the timestamp; the user agent is broadcast in the {pres what="off" ua="..."} message and subsequently reported as the last online timestamp and user agent.

An empty ua="" user agent is not reported. I.e. if user attaches to me with non-empty user agent then does so with an empty one, the change is not reported. An empty user agent may be disallowed in the future.

Push Notifications Support

Tinode supports mobile push notifications though compile-time plugins. The channel published by the plugin receives a copy of every data message which was attempted to be delivered. The server supports Google FCM out of the box.

Public and Private Fields

Topics and subscriptions have public and private fields. Generally, the fields are application-defined. The server does not enforce any particular structure of these fields except for fnd topic. At the same time, client software should use the same format for interoperability reasons.

Public

The format of the public field is expected to be a vCard:

vcard: {
  fn: "John Doe", // string, formatted name
  n: {
    surname: "Miner", // last of family name
    given: "Coal", // first or given name
    additional: "Diamond", // additional name, such as middle name or patronymic or nickname.
    prefix: "Dr.", // prefix, such as honorary title or gender designation.
    suffix: "Jr.", // suffix, such as 'Jr' or 'II'
  }, // object, user's structured name
  org: "Most Evil Corp", // string, name of the organisation the user belongs to.
  title: "CEO", // string, job title
  tel: [
    {
      type: "HOME", // string, optional designation
      uri: "tel:+17025551234" // string, phone number
    }, ...
  ], // array of objects, list of phone numbers associated with the user
  email: [
    {
      type: "WORK", // string, optional designation
      uri: "email:[email protected]", // string, email address
    }, ...
  ], // array of objects, list of user's email addresses
  impp: [
    {
      type: "OTHER",
      uri: "tinode:usrRkDVe0PYDOo", // string, email address
    }, ...
  ], // array of objects, list of user's IM handles
  photo: {
    type: "jpeg", // image type
    data: "..." // base64-encoded binary image data
  } // object, avatar photo. Java does not have a useful bitmap class, so keeping it as bits here.
}

Private

The format of the private field is expected to be a dictionary. The following fields are currently defined:

private: {
  comment: "some comment", // string, optional user comment about a topic or other user
  arch: true // boolean value indicating that the topic is archived by the user, i.e. should not be shown in the UI with other non-archived topics.
}

Although it's not yet enforced, custom fields should start with x-, e.g. x-example: "abc". The fields should contain primitive types only, i.e. string, boolean, number, null.

Format of Content

Format of content field in {pub} and {data} is application-defined and as such the server does not enforce any particular structure of the field. At the same time, client software should use the same format for interoperability reasons. Currently the following two types of content are supported:

If Drafty is used, message header "head": {"mime": "text/x-drafty"} must be set.

Out-of-Band Handling of Large Files

Large files create problems when sent in-band for multiple reasons:

  • limits on database storage as in-band messages are stored in database fields
  • in-band messages must be downloaded completely as a part of downloading chat history

Tinode provides two endpoints for handling large files: /v0/file/u for uploading files and v0/file/s for downloading. The endpoints require the client to provide both API key and login credentials. The server checks credentials in the following order:

Login credentials

Uploading

To upload a file first create an RFC 2388 multipart request then send it to the server using HTTP POST. The server responds to the request either with a 307 Temporary Redirect or with a 200 OK and a {ctrl} message in response body:

ctrl: {
  params: {
    url: "/v0/file/s/mfHLxDWFhfU.pdf"
  },
  code: 200,
  text: "ok",
  ts: "2018-07-06T18:47:51.265Z"
}

The ctrl.params.url contains the location of the uploaded file relative to the download endpoint /v0/file/s/ at the current HTTP server.

Once the url is received, either immediately or after following the redirect, the client can use the url to send a {pub} message with the uploaded file as an attachment. The url should be used to produce a Drafty-formatted pub.content field and also should be referenced in the pub.head.attachments:

pub: {
  id: "121103",
  topic: "grpnG99YhENiQU",
  head: {
    attachments: ["/v0/file/s/sJOD_tZDPz0.jpg"],
	mime: "text/x-drafty"
  },
  content: {
    ent: [
	  {
	    data: {
		  mime: "image/jpeg",
		  name: "roses-are-red.jpg",
		  ref:  "/v0/file/s/sJOD_tZDPz0.jpg",
		  size: 437265
		},
	    tp: "EX"
	  }
	],
	fmt: [
	  {
	    at: -1,
		key:0,
		len:1
	  }
	]
  }
}

It's important to list the URLs in the head.attachments field. Tinode server uses this field to maintain the uploaded file's use counter. Once the use counter drops to zero for a given file (for instance, because a message with the shared URL was deleted or because the client failed to include the URL in the head.attachments field), the server will garbage collect the file. Only relative URLs should be used. Absolute URLs in the head.attachments field are ignored.

Downloading

The serving endpoint /v0/file/s serves files in response to HTTP GET requests. The client must evaluate relative URLs against this endpoint, i.e. if it receives a URL mfHLxDWFhfU.pdf or ./mfHLxDWFhfU.pdf it should interpret it as a path /v0/file/s/mfHLxDWFhfU.pdf at the current Tinode HTTP server. As a security measure, the client should not send security credentials if the download URL is absolute and leads to another server.

Messages

A message is a logically associated set of data. Messages are passed as JSON-formatted UTF-8 text.

All client to server messages may have an optional id field. It's set by the client as means to receive an acknowledgement from the server that the message was received and processed. The id is expected to be a session-unique string but it can be any string. The server does not attempt to interpret it other than to check JSON validity. It's returned unchanged by the server when it replies to client messages.

Server requires strictly valid JSON, including double quotes around field names. For brevity the notation below omits double quotes around field names as well as outer curly brackets.

For messages that update application-defined data, such as {set} private or public fields, when server-side data needs to be cleared, use a string with a single Unicode DEL character "␡" "\u2421". I.e. sending "public": null will not clear the field, but sending "public": "\u2421" will.

Client to server messages

{hi}

Handshake message client uses to inform the server of its version and user agent. This message must be the first that the client sends to the server. Server responds with a {ctrl} which contains server build build, wire protocol version ver, and session ID sid in case of long polling, all in ctrl.params.

hi: {
  id: "1a2b3",     // string, client-provided message id, optional
  ver: "0.14",   // string, version of the wire protocol supported by the client, required
  ua: "JS/1.0 (Windows 10)", // string, user agent identifying client software,
                   // optional
  dev: "L1iC2dNtk2", // string, unique value which identifies this specific
				   // connected device for the purpose of push notifications; not
				   // interpreted by the server; optional
				   // see [Push notifications support](#push-notifications-support); optional
  lang: "EN" 	   // human language of the client device; optional
}

The user agent ua is expected to follow RFC 7231 section 5.5.3 recommendation but the format is not enforced. The message can be sent more than once to update ua, dev and lang values. If sent more than once, the ver field of the second and subsequent messages must be either unchanged or not set.

{acc}

Message {acc} creates users or updates tags or authentication credentials scheme and secret of exiting users. To create a new user set user to the string new optionally followed by any character sequence, e.g. newr15gsr. Either authenticated or anonymous session can send an {acc} message to create a new user. To update authentication data or validate a credential of the current user leave user unset.

acc: {
  id: "1a2b3", // string, client-provided message id, optional
  user: "new", // string, "new" to create a new user, default: current user, optional
  scheme: "basic", // authentication scheme for this account, required;
               // "basic" and "anon" are currently supported for account creation.
  secret: btoa("username:password"), // string, base64 encoded secret for the chosen
              // authentication scheme; to delete a scheme use a string with a single DEL
              // Unicode character "\u2421"; "token" and "basic" cannot be deleted
  login: true, // boolean, use the newly created account to authenticate current session,
              // i.e. create account and immediately use it to login.
  tags: ["alice johnson",... ], // array of tags for user discovery; see 'fnd' topic for
              // details, optional (if missing, user will not be discoverable other than
              // by login)
  cred: [
    {
      meth: "email", // string, verification method, e.g. "email", "tel", "recaptcha", etc.
      val: "[email protected]", // string, credential to verify such as email or phone
      resp: "178307", // string, verification response, optional
      params: { ... } // parameters, specific to the verification method, optional
    },
	...
  ],   // account credentials which require verification, such as email or phone number.

  desc: {  // object, user initialisation data closely matching that of table
           // initialisation; optional
    defacs: {
      auth: "JRWS", // string, default access mode for peer to peer conversations
                   // between this user and other authenticated users
      anon: "N"  // string, default access mode for peer to peer conversations
                 // between this user and anonymous (un-authenticated) users
    }, // Default access mode for user's peer to peer topics
    public: { ... }, // application-defined payload to describe user,
                // available to everyone
    private: { ... } // private application-defined payload available only to user
                // through 'me' topic
  }
}

Server responds with a {ctrl} message with params containing details of the new user. If desc.defacs is missing, server will assign server-default access values.

The only supported authentication schemes for account creation are basic and anon.

{login}

Login is used to authenticate the current session.

login: {
  id: "1a2b3",     // string, client-provided message id, optional
  scheme: "basic", // string, authentication scheme, optional; "basic" and "token"
                   // are currently supported
  secret: btoa("username:password"), // string, base64-encoded secret for the chosen
                  // authentication scheme, required
  cred: [
    {
      meth: "email", // string, verification method, e.g. "email", "tel", "captcha", etc, required
      resp: "178307" // string, verification response, required
    },
	...
  ],   // response to a request for credential verification, optional
}

The basic authentication scheme expects secret to be a base64-encoded string of a string composed of a user name followed by a colon : followed by a plan text password. User name in the basic scheme must not contain colon character ':' (ASCII 0x3A). The token expects secret to be a previously obtained security token.

The only supported authentication schemes are basic and token. Although anonymous scheme can be used to create accounts, it cannot be used for logging in.

Server responds to a {login} packet with a {ctrl} message. The params of the message contains the id of the logged in user as user. The token contains an encrypted string which can be used for authentication. Expiration time of the token is passed as expires.

{sub}

The {sub} packet serves the following functions:

  • creating a topic
  • subscribing user to a topic
  • attaching session to a topic
  • fetching topic data

User creates a new group topic by sending {sub} packet with the topic field set to "new". Server will create a topic and respond back to session with the name of the newly created topic.

User creates a new peer to peer topic by sending {sub} packet with topic set to peer's user ID.

The user is always subscribed to and the session is attached to the newly created topic.

If the user had no relationship with the topic, sending {sub} packet creates it. Subscribing means to establish a relationship between session's user and the topic where no relationship existed in the past.

Joining (attaching to) a topic means for the session to start consuming content from the topic. Server automatically differentiates between subscribing and joining/attaching based on context: if the user had no prior relationship with the topic, the server subscribes the user then attaches the current session to the topic. If relationship existed, the server only attaches the session to the topic. When subscribing, the server checks user's access permissions against topic's access control list. It may grant immediate access, deny access, may generate a request for approval from topic managers.

Server replies to the {sub} with a {ctrl}.

The {sub} message may include a get and browse fields which mirror what and browse fields of a {get} message. If included, server will treat them as a subsequent {get} message on the same topic. In that case the reply may also include {meta} and {data} messages.

sub: {
  id: "1a2b3",  // string, client-provided message id, optional
  topic: "me",   // topic to be subscribed or attached to

  // Object with topic initialisation data, new topics & new
  // subscriptions only, mirrors {set} message
  set: {
	// New topic parameters, mirrors {set desc}
    desc: {
      defacs: {
        auth: "JRWS", // string, default access for new authenticated subscribers
        anon: "N"    // string, default access for new anonymous (un-authenticated)
                     // subscribers
      }, // Default access mode for the new topic
      public: { ... }, // application-defined payload to describe topic
      private: { ... } // per-user private application-defined content
    }, // object, optional

    // Subscription parameters, mirrors {set sub}. 'sub.user' must be blank
    sub: {
      mode: "JRWS", // string, requested access mode, optional;
                   // default: server-defined
    }, // object, optional

    // Optional update to tags (see fnd topic description)
    tags: [ // array of strings
        "email:[email protected]", "tel:1234567890"
    ]
  },

  get: {
    // Metadata to request from the topic; space-separated list, valid strings
    // are "desc", "sub", "data", "tags"; default: request nothing; unknown strings are
    // ignored; see {get  what} for details
    what: "desc sub data", // string, optional

    // Optional parameters for {get what="desc"}
    desc: {
      ims: "2015-10-06T18:07:30.038Z" // timestamp, "if modified since" - return
              // public and private values only if at least one of them has been
              // updated after the stated timestamp, optional

    },

    // Optional parameters for {get what="sub"}
    sub: {
      ims: "2015-10-06T18:07:30.038Z", // timestamp, "if modified since" - return
              // public and private values only if at least one of them has been
              // updated after the stated timestamp, optional
	  user: "usr2il9suCbuko", // string, return results for a single user,
	                          // any topic other than 'me', optional
	  topic: "usr2il9suCbuko", // string, return results for a single topic,
	                          // 'me' topic only, optional
      limit: 20 // integer, limit the number of returned objects
    },

    // Optional parameters for {get what="data"}, see {get what="data"} for details
    data: {
      since: 123, // integer, load messages with server-issued IDs greater or equal
  				 // to this (inclusive/closed), optional
      before: 321, // integer, load messages with server-issued sequential IDs less
  				  // than this (exclusive/open), optional
      limit: 20, // integer, limit the number of returned objects,
                 // default: 32, optional
    } // object, optional
  }
}

See Public and Private Fields for private and public format considerations.

{leave}

This is a counterpart to {sub} message. It also serves two functions:

  • leaving the topic without unsubscribing (unsub=false)
  • unsubscribing (unsub=true)

Server responds to {leave} with a {ctrl} packet. Leaving without unsubscribing affects just the current session. Leaving with unsubscribing will affect all user's sessions.

leave: {
  id: "1a2b3",  // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND",   // string, topic to leave, unsubscribe, or
                              // delete, required
  unsub: true // boolean, leave and unsubscribe, optional, default: false
}

{pub}

The message is used to distribute content to topic subscribers.

pub: {
  id: "1a2b3", // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND", // string, topic to publish to, required
  noecho: false, // boolean, suppress echo (see below), optional
  head: { key: "value", ... }, // set of string key-value pairs,
               // passed to {data} unchanged, optional
  content: { ... }  // object, application-defined content to publish
               // to topic subscribers, required
}

Topic subscribers receive the content in the {data} message. By default the originating session gets a copy of {data} like any other session currently attached to the topic. If for some reason the originating session does not want to receive the copy of the data it just published, set noecho to true.

See Format of Content for content format considerations.

{get}

Query topic for metadata, such as description or a list of subscribers, or query message history.

get: {
  id: "1a2b3", // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND", // string, name of topic to request data from
  what: "sub desc data del", // string, space-separated list of parameters to query;
                        // unknown strings are ignored; required

  // Optional parameters for {get what="desc"}
  desc: {
    ims: "2015-10-06T18:07:30.038Z" // timestamp, "if modified since" - return
          // public and private values only if at least one of them has been
          // updated after the stated timestamp, optional
  },

  // Optional parameters for {get what="sub"}
  sub: {
    ims: "2015-10-06T18:07:30.038Z", // timestamp, "if modified since" - return
          // public and private values only if at least one of them has been
          // updated after the stated timestamp, optional
	user: "usr2il9suCbuko", // string, return results for a single user,
	                        // any topic other than 'me', optional
	topic: "usr2il9suCbuko", // string, return results for a single topic,
	                         // 'me' topic only, optional
    limit: 20 // integer, limit the number of returned objects
  },

  // Optional parameters for {get what="data"}
  data: {
    since: 123, // integer, load messages with server-issued IDs greater or equal
				 // to this (inclusive/closed), optional
    before: 321, // integer, load messages with server-issed sequential IDs less
				  // than this (exclusive/open), optional
    limit: 20, // integer, limit the number of returned objects, default: 32,
               // optional
  },

  // Optional parameters for {get what="del"}
  del: {
    since: 5, // integer, load deleted ranges with the delete transaction IDs greater
				// or equal to this (inclusive/closed), optional
    before: 12, // integer, load deleted ranges with the delete transaction IDs less
				  // than this (exclusive/open), optional
    limit: 25, // integer, limit the number of returned objects, default: 32,
               // optional
  }
}
  • {get what="desc"}

Query topic description. Server responds with a {meta} message containing requested data. See {meta} for details. If ims is specified and data has not been updated, the message will skip public and private fields.

  • {get what="sub"}

Get a list of subscribers. Server responds with a {meta} message containing a list of subscribers. See {meta} for details. For me topic the request returns a list of user's subscriptions. If ims is specified and data has not been updated, responds with a {ctrl} "not modified" message.

  • {get what="tags"}

Query indexed tags. Server responds with a {meta} message containing an array of string tags. See {meta} and fnd topic for details. Supported only for me and group topics.

  • {get what="data"}

Query message history. Server sends {data} messages matching parameters provided in the data field of the query. The id field of the data messages is not provided as it's common for data messages. When all {data} messages are transmitted, a {ctrl} message is sent.

  • {get what="del"}

Query message deletion history. Server responds with a {meta} message containing a list of deleted message ranges.

See Public and Private Fields for private and public format considerations.

{set}

Update topic metadata, delete messages or topic.

set: {
  id: "1a2b3", // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND", // string, name of topic to update, required

  // Optional payload to update topic description
  desc: {
    defacs: { // new default access mode
      auth: "JRWP",  // access permissions for authenticated users
      anon: "JRW" // access permissions for anonymous users
    },
    public: { ... }, // application-defined payload to describe topic
    private: { ... } // per-user private application-defined content
  },

  // Optional payload to update subscription(s)
  sub: {
    user: "usr2il9suCbuko", // string, user affected by this request;
                            // default (empty) means current user
    mode: "JRWP" // string, access mode change, either given ('user'
				  // is defined) or requested ('user' undefined)
  }, // object, payload for what == "sub"

  // Optional update to tags (see fnd topic description)
  tags: [ // array of strings
    "email:[email protected]", "tel:1234567890"
  ]
}

{del}

Delete messages or topic.

del: {
  id: "1a2b3", // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND", // string, topic affect, required
  what: "msg", // string, either "topic" or "sub" or "msg"; what to delete - the
               // entire topic or subscription or just the messages;
               // optional, default: "msg"
  hard: false, // boolean, request to delete messages for all users, default: false
  delseq: [{low: 123, hi: 125}, {low: 156}], // array of ranges of message IDs
				// to delete, inclusive-exclusive, i.e. [low, hi), optional
  user: "usr2il9suCbuko" // string, user whose subscription is being deleted
               // (what="sub"), optional
}

User can soft-delete or hard-delete messages what="msg". Soft-deleting messages hides them from the requesting user but does not delete them from storage. An R permission is required to soft-delete messages hard=false (default). Messages can be deleted in bulk by specifying one or more message ID ranges in delseq parameter. Hard-deleting messages deletes them from storage affecting all users. The D permission is needed to hard-delete messages.

Deleting a subscription what="sub" removes specified user from topic subscribers. It requires an A permission. A user cannot delete own subscription. A {leave} should be used instead.

Deleting a topic what="topic" deletes the topic including all subscriptions, and all messages. The hard parameter has no effect on topic deletion: all topic deletions are hard-deletions. Only the owner can delete a topic. The greatest deleted ID is reported back in the clear of the {meta} message.

{note}

Client-generated ephemeral notification for forwarding to other clients currently attached to the topic, such as typing notifications or delivery receipts. The message is "fire and forget": not stored to disk per se and not acknowledged by the server. Messages deemed invalid are silently dropped. The {note.recv} and {note.read} do alter persistent state on the server. The value is stored and reported back in the corresponding fields of the {meta.sub} message.

note: {
  topic: "grp1XUtEhjv6HND", // string, topic to notify, required
  what: "kp", // string, one of "kp" (key press), "read" (read notification),
              // "rcpt" (received notification), any other string will cause
              // message to be silently ignored, required
  seq: 123, // integer, ID of the message being acknowledged, required for
            // rcpt & read
}

The following actions are currently recognised:

  • kp: key press, i.e. a typing notification. The client should use it to indicate that the user is composing a new message.
  • recv: a {data} message is received by the client software but not yet seen by user.
  • read: a {data} message is seen by the user. It implies recv as well.

Server to client messages

Messages to a session generated in response to a specific request contain an id field equal to the id of the originating message. The id is not interpreted by the server.

Most server to client messages have a ts field which is a timestamp when the message was generated by the server.

{data}

Content published in the topic. These messages are the only messages persisted in database; {data} messages are broadcast to all topic subscribers with an R permission.

data: {
  topic: "grp1XUtEhjv6HND", // string, topic which distributed this message,
                            // always present
  from: "usr2il9suCbuko", // string, id of the user who published the
                          // message; could be missing if the message was
                          // generated by the server
  head: { key: "value", ... }, // set of string key-value pairs, passed
						   // unchanged from {pub}, optional
  ts: "2015-10-06T18:07:30.038Z", // string, timestamp
  seq: 123, // integer, server-issued sequential ID
  content: { ... } // object, application-defined content exactly as published
              // by the user in the {pub} message
}

Data messages have a seq field which holds a sequential numeric ID generated by the server. The IDs are guaranteed to be unique within a topic. IDs start from 1 and sequentially increment with every successful {pub} message received by the topic. See Format of Content for content format considerations.

{ctrl}

Generic response indicating an error or a success condition. The message is sent to the originating session.

ctrl: {
  id: "1a2b3", // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND", // string, topic name, if this is a response in context
                            // of a topic, optional
  code: 200, // integer, code indicating success or failure of the request, follows
             // the HTTP status codes model, always present
  text: "OK", // string, text with more details about the result, always present
  params: { ... }, // object, generic response parameters, context-dependent,
                   // optional
  ts: "2015-10-06T18:07:30.038Z", // string, timestamp
}

{meta}

Information about topic metadata or subscribers, sent in response to {set} or {sub} message to the originating session.

meta: {
  id: "1a2b3", // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND", // string, topic name, if this is a response in
                            // context of a topic, optional
  ts: "2015-10-06T18:07:30.038Z", // string, timestamp
  desc: {
    created: "2015-10-24T10:26:09.716Z",
    updated: "2015-10-24T10:26:09.716Z",
    defacs: { // topic's default access permissions; present only if the current
              //user has 'S' permission
      auth: "JRWP", // default access for authenticated users
      anon: "N" // default access for anonymous users
    },
    acs: {  // user's actual access permissions
      want: "JRWP", // string, requested access permission
      given: "JRWP", // string, granted access permission
	  mode: "JRWP" // string, combination of want and given
    },
    seq: 123, // integer, server-issued id of the last {data} message
    read: 112, // integer, ID of the message user claims through {note} message
              // to have read, optional
    recv: 115, // integer, like 'read', but received, optional
    clear: 12, // integer, in case some messages were deleted, the greatest ID
               // of a deleted message, optional
    public: { ... }, // application-defined data that's available to all topic
                     // subscribers
    private: { ...} // application-defined data that's available to the current
                    // user only
  }, // object, topic description, optional
  sub:  [ // array of objects, topic subscribers or user's subscriptions, optional
    {
      user: "usr2il9suCbuko", // string, ID of the user this subscription
                            // describes, absent when querying 'me'.
      updated: "2015-10-24T10:26:09.716Z", // timestamp of the last change in the
                                           // subscription, present only for
                                           // requester's own subscriptions
      touched: "2017-11-02T09:13:55.530Z", // timestamp of the last message in the
                                           // topic (may also include other events
                                           // in the future, such as new subscribers)
      acs: {  // user's access permissions
        want: "JRWP", // string, requested access permission, present for user's own
					 // subscriptions and when the requester is topic's manager or owner
        given: "JRWP", // string, granted access permission, optional exactly as 'want'
	    mode: "JRWP" // string, combination of want and given
      },
      read: 112, // integer, ID of the message user claims through {note} message
                 // to have read, optional
      recv: 315, // integer, like 'read', but received, optional
      clear: 12, // integer, in case some messages were deleted, the greatest ID
                 // of a deleted message, optional
      private: { ... } // application-defined user's 'private' object, present only
                       // for the requester's own subscriptions.
      online: true, // boolean, current online status of the user; if this is a
                    // group or a p2p topic, it's user's online status in the topic,
                    // i.e. if the user is attached and listening to messages; if this
                    // is a response to a 'me' query, it tells if the topic is
                    // online; p2p is considered online if the other party is
                    // online, not necessarily attached to topic; a group topic
                    // is considered online if it has at least one active
                    // subscriber.

      // The following fields are present only when querying 'me' topic

      topic: "grp1XUtEhjv6HND", // string, topic this subscription describes
      seq: 321, // integer, server-issued id of the last {data} message

      // The following fields are present only when querying 'me' topic and the
      // topic described is a P2P topic

      public: { ... }, // application-defined user's 'public' object, present for
                      // P2P topics only
      seen: { // object, if this is a P2P topic, info on when the peer was last
              //online
        when: "2015-10-24T10:26:09.716Z", // timestamp
        ua: "Tinode/1.0 (Android 5.1)" // string, user agent of peer's client
      }
    },
    ...
  ],
  tags: [ // array of tags that the topic or user (in case of "me" topic) is indexed by
	"email:[email protected]", "tel:1234567890"
  ],
  del: {
	clear: 3, // ID of the latest applicable 'delete' transaction
	delseq: [{low: 15}, {low: 22, hi: 28}, ...], // ranges of IDs of deleted messages
  }
}

{pres}

Tinode uses {pres} message to inform clients of important events. A separate document explains all possible use cases.

pres: {
  topic: "me", // string, topic which receives the notification, always present
  src: "grp1XUtEhjv6HND", // string, topic or user affected by the change, always present
  what: "on", // string, what's changed, always present
  seq: 123, // integer, "what" is "msg", a server-issued ID of the message,
           // optional
  clear: 15, // integer, "what" is "del", an update to the delete transaction ID.
  delseq: [{low: 123}, {low: 126, hi: 136}], // array of ranges, "what" is "del",
			// ranges of IDs of deleted messages, optional
  ua: "Tinode/1.0 (Android 2.2)", // string, a User Agent string identifying client
						// software if "what" is "on" or "ua", optional
  act: "usr2il9suCbuko",	// string, user who performed the action, optional
  tgt: "usrRkDVe0PYDOo", 	// string, user affected by the action, optional
  acs: {want: "+AS-D", given: "+S"} // object, changes to access mode, "what" is "acs",
			// optional
}

The {pres} messages are purely transient: they are not stored and no attempt is made to deliver them later if the destination is temporarily unavailable.

Timestamp is not present in {pres} messages.

{info}

Forwarded client-generated notification {note}. Server guarantees that the message complies with this specification and that content of topic and from fields is correct. The other content is copied from the {note} message verbatim and may potentially be incorrect or misleading if the originator so desires.

info: {
  topic: "grp1XUtEhjv6HND", // string, topic affected, always present
  from: "usr2il9suCbuko", // string, id of the user who published the
                          // message, always present
  what: "read", // string, one of "kp", "recv", "read", see client-side {note},
                // always present
  seq: 123, // integer, ID of the message that client has acknowledged,
            // guaranteed 0 < read <= recv <= {ctrl.params.seq}; present for rcpt &
            // read
}