1   Matrix Specification

Warning

You are viewing an outdated version of this specification. To view the current specification, please click here.

1.1   Version: 0.2.0

This specification has been generated from https://github.com/matrix-org/matrix-doc using https://github.com/matrix-org/matrix-doc/blob/master/scripts/gendoc.py as of revision master,24356d8 - https://github.com/matrix-org/matrix-doc/tree/24356d8

1.1.1   Changelog

This update fundamentally restructures the specification. The specification has been split into more digestible "modules" which each describe a particular function (e.g. typing). This was done in order make the specification easier to maintain and help define which modules are mandatory for certain types of clients. Types of clients along with the mandatory modules can be found in a new "Feature Profiles" section. This update also begins to aggressively standardise on using Swagger and JSON Schema to document HTTP endpoints and Events respectively. It also introduces a number of new concepts to Matrix.

Additions:
  • New section: Feature Profiles.
  • New section: Receipts.
  • New section: Room history visibility.
  • New event: m.receipt.
  • New event: m.room.canonical_alias
  • New event: m.room.history_visibility
  • New keys: /createRoom - allows room "presets" using preset and initial_state keys.
  • New endpoint: /tokenrefresh - Related to refreshing access tokens.
Modifications:
  • Convert most of the older HTTP APIs to Swagger documentation.
  • Convert most of the older event formats to JSON Schema.
  • Move selected client-server sections to be "Modules".

For a full changelog, see https://github.com/matrix-org/matrix-doc/blob/master/CHANGELOG.rst

Table of Contents

2   Introduction

Warning

The Matrix specification is still evolving: the APIs are not yet frozen and this document is in places a work in progress or stale. We have made every effort to clearly flag areas which are still being finalised. We're publishing it at this point because it's complete enough to be more than useful and provide a canonical reference to how Matrix is evolving. Our end goal is to mirror WHATWG's Living Standard.

Matrix is a set of open APIs for open-federated Instant Messaging (IM), Voice over IP (VoIP) and Internet of Things (IoT) communication, designed to create and support a new global real-time communication ecosystem. The intention is to provide an open decentralised pubsub layer for the internet for securely persisting and publishing/subscribing JSON objects. This specification is the ongoing result of standardising the APIs used by the various components of the Matrix ecosystem to communicate with one another.

The principles that Matrix attempts to follow are:

The functionality that Matrix provides includes:

The end goal of Matrix is to be a ubiquitous messaging layer for synchronising arbitrary data between sets of people, devices and services - be that for instant messages, VoIP call setups, or any other objects that need to be reliably and persistently pushed from A to B in an inter-operable and federated manner.

3   Overview

3.1   Architecture

Matrix defines APIs for synchronising extensible JSON objects known as "events" between compatible clients, servers and services. Clients are typically messaging/VoIP applications or IoT devices/hubs and communicate by synchronising communication history with their "homeserver" using the "Client-Server API". Each homeserver stores the communication history and account information for all of its clients, and shares data with the wider Matrix ecosystem by synchronising communication history with other homeservers and their clients.

Clients typically communicate with each other by emitting events in the context of a virtual "room". Room data is replicated across all of the homeservers whose users are participating in a given room. As such, no single homeserver has control or ownership over a given room. Homeservers model communication history as a partially ordered graph of events known as the room's "event graph", which is synchronised with eventual consistency between the participating servers using the "Server-Server API". This process of synchronising shared conversation history between homeservers run by different parties is called "Federation". Matrix optimises for the the Availability and Partitioned properties of CAP theorem at the expense of Consistency.

For example, for client A to send a message to client B, client A performs an HTTP PUT of the required JSON event on its homeserver (HS) using the client-server API. A's HS appends this event to its copy of the room's event graph, signing the message in the context of the graph for integrity. A's HS then replicates the message to B's HS by performing an HTTP PUT using the server-server API. B's HS authenticates the request, validates the event's signature, authorises the event's contents and then adds it to its copy of the room's event graph. Client B then receives the message from his homeserver via a long-lived GET request.

                  How data flows between clients
                  ==============================

{ Matrix client A }                             { Matrix client B }
    ^          |                                    ^          |
    |  events  |  Client-Server API                 |  events  |
    |          V                                    |          V
+------------------+                            +------------------+
|                  |---------( HTTPS )--------->|                  |
|   Home Server    |                            |   Home Server    |
|                  |<--------( HTTPS )----------|                  |
+------------------+      Server-Server API     +------------------+
                       History Synchronisation
                           (Federation)

3.1.1   Users

Each client is associated with a user account, which is identified in Matrix using a unique "User ID". This ID is namespaced to the homeserver which allocated the account and has the form:

@localpart:domain

The localpart of a user ID may be a user name, or an opaque ID identifying this user. The domain of a user ID is the domain of the homeserver.

3.1.2   Events

All data exchanged over Matrix is expressed as an "event". Typically each client action (e.g. sending a message) correlates with exactly one event. Each event has a type which is used to differentiate different kinds of data. type values MUST be uniquely globally namespaced following Java's package naming conventions, e.g. com.example.myapp.event. The special top-level namespace m. is reserved for events defined in the Matrix specification - for instance m.room.message is the event type for instant messages. Events are usually sent in the context of a "Room".

3.1.3   Event Graphs

Events exchanged in the context of a room are stored in a directed acyclic graph (DAG) called an "event graph". The partial ordering of this graph gives the chronological ordering of events within the room. Each event in the graph has a list of zero or more "parent" events, which refer to any preceding events which have no chronological successor from the perspective of the homeserver which created the event.

Typically an event has a single parent: the most recent message in the room at the point it was sent. However, homeservers may legitimately race with each other when sending messages, resulting in a single event having multiple successors. The next event added to the graph thus will have multiple parents. Every event graph has a single root event with no parent.

To order and ease chronological comparison between the events within the graph, homeservers maintain a depth metadata field on each event. An event's depth is a positive integer that is strictly greater than the depths of any of its parents. The root event should have a depth of 1. Thus if one event is before another, then it must have a strictly smaller depth.

3.1.4   Room structure

A room is a conceptual place where users can send and receive events. Events are sent to a room, and all participants in that room with sufficient access will receive the event. Rooms are uniquely identified internally via "Room IDs", which have the form:

!opaque_id:domain

There is exactly one room ID for each room. Whilst the room ID does contain a domain, it is simply for globally namespacing room IDs. The room does NOT reside on the domain specified. Room IDs are not meant to be human readable. They are case-sensitive. The following conceptual diagram shows an m.room.message event being sent to the room !qporfwt:matrix.org:

 { @alice:matrix.org }                             { @bob:domain.com }
         |                                                 ^
         |                                                 |
[HTTP POST]                                  [HTTP GET]
Room ID: !qporfwt:matrix.org                 Room ID: !qporfwt:matrix.org
Event type: m.room.message                   Event type: m.room.message
Content: { JSON object }                     Content: { JSON object }
         |                                                 |
         V                                                 |
 +------------------+                          +------------------+
 |   Home Server    |                          |   Home Server    |
 |   matrix.org     |                          |   domain.com     |
 +------------------+                          +------------------+
         |                                                 ^
         |         [HTTP PUT]                              |
         |         Room ID: !qporfwt:matrix.org            |
         |         Event type: m.room.message              |
         |         Content: { JSON object }                |
         `-------> Pointer to the preceding message  ------`
                   PKI signature from matrix.org
                   Transaction-layer metadata
                   PKI Authorization header

               ...................................
              |           Shared Data             |
              | State:                            |
              |   Room ID: !qporfwt:matrix.org    |
              |   Servers: matrix.org, domain.com |
              |   Members:                        |
              |    - @alice:matrix.org            |
              |    - @bob:domain.com              |
              | Messages:                         |
              |   - @alice:matrix.org             |
              |     Content: { JSON object }      |
              |...................................|

Federation maintains shared data structures per-room between multiple home servers. The data is split into message events and state events.

Message events:
These describe transient 'once-off' activity in a room such as an instant messages, VoIP call setups, file transfers, etc. They generally describe communication activity.
State events:
These describe updates to a given piece of persistent information ('state') related to a room, such as the room's name, topic, membership, participating servers, etc. State is modelled as a lookup table of key/value pairs per room, with each key being a tuple of state_key and event type. Each state event updates the value of a given key.

The state of the room at a given point is calculated by considering all events preceding and including a given event in the graph. Where events describe the same state, a merge conflict algorithm is applied. The state resolution algorithm is transitive and does not depend on server state, as it must consistently select the same event irrespective of the server or the order the events were received in. Events are signed by the originating server (the signature includes the parent relations, type, depth and payload hash) and are pushed over federation to the participating servers in a room, currently using full mesh topology. Servers may also request backfill of events over federation from the other servers participating in a room.

3.1.4.1   Room Aliases

Each room can also have multiple "Room Aliases", which look like:

#room_alias:domain

A room alias "points" to a room ID and is the human-readable label by which rooms are publicised and discovered. The room ID the alias is pointing to can be obtained by visiting the domain specified. Note that the mapping from a room alias to a room ID is not fixed, and may change over time to point to a different room ID. For this reason, Clients SHOULD resolve the room alias to a room ID once and then use that ID on subsequent requests. Room aliases MUST NOT exceed 255 bytes (including the domain).

When resolving a room alias the server will also respond with a list of servers that are in the room that can be used to join via.

     HTTP GET
#matrix:domain.com      !aaabaa:matrix.org
        |                    ^
        |                    |
 _______V____________________|____
|          domain.com            |
| Mappings:                      |
| #matrix >> !aaabaa:matrix.org  |
| #golf   >> !wfeiofh:sport.com  |
| #bike   >> !4rguxf:matrix.org  |
|________________________________|

3.1.5   Identity

Users in Matrix are identified via their matrix user ID (MXID). However, existing 3rd party ID namespaces can also be used in order to identify Matrix users. A Matrix "Identity" describes both the user ID and any other existing IDs from third party namespaces linked to their account. Matrix users can link third-party IDs (3PIDs) such as email addresses, social network accounts and phone numbers to their user ID. Linking 3PIDs creates a mapping from a 3PID to a user ID. This mapping can then be used by Matrix users in order to discover the MXIDs of their contacts. In order to ensure that the mapping from 3PID to user ID is genuine, a globally federated cluster of trusted "Identity Servers" (IS) are used to verify the 3PID and persist and replicate the mappings.

Usage of an IS is not required in order for a client application to be part of the Matrix ecosystem. However, without one clients will not be able to look up user IDs using 3PIDs.

3.1.6   Profiles

Users may publish arbitrary key/value data associated with their account - such as a human readable display name, a profile photo URL, contact information (email address, phone numbers, website URLs etc).

3.1.7   Private User Data

Users may also store arbitrary private key/value data in their account - such as client preferences, or server configuration settings which lack any other dedicated API. The API is symmetrical to managing Profile data.

3.2   API Standards

The mandatory baseline for communication in Matrix is exchanging JSON objects over HTTP APIs. HTTPS is mandated as the baseline for server-server (federation) communication. HTTPS is recommended for client-server communication, although HTTP may be supported as a fallback to support basic HTTP clients. More efficient optional transports for client-server communication will in future be supported as optional extensions - e.g. a packed binary encoding over stream-cipher encrypted TCP socket for low-bandwidth/low-roundtrip mobile usage. For the default HTTP transport, all API calls use a Content-Type of application/json. In addition, all strings MUST be encoded as UTF-8. Clients are authenticated using opaque access_token strings (see Client Authentication for details), passed as a query string parameter on all requests.

Any errors which occur at the Matrix API level MUST return a "standard error response". This is a JSON object which looks like:

{
  "errcode": "<error code>",
  "error": "<error message>"
}

The error string will be a human-readable error message, usually a sentence explaining what went wrong. The errcode string will be a unique string which can be used to handle an error message e.g. M_FORBIDDEN. These error codes should have their namespace first in ALL CAPS, followed by a single _ to ease separating the namespace from the error code. For example, if there was a custom namespace com.mydomain.here, and a FORBIDDEN code, the error code should look like COM.MYDOMAIN.HERE_FORBIDDEN. There may be additional keys depending on the error, but the keys error and errcode MUST always be present.

Some standard error codes are below:

M_FORBIDDEN:Forbidden access, e.g. joining a room without permission, failed login.
M_UNKNOWN_TOKEN:
 The access token specified was not recognised.
M_BAD_JSON:Request contained valid JSON, but it was malformed in some way, e.g. missing required keys, invalid values for keys.
M_NOT_JSON:Request did not contain valid JSON.
M_NOT_FOUND:No resource was found for this request.
M_LIMIT_EXCEEDED:
 Too many requests have been sent in a short period of time. Wait a while then try again.

Some requests have unique error codes:

M_USER_IN_USE:Encountered when trying to register a user ID which has been taken.
M_ROOM_IN_USE:Encountered when trying to create a room which has been taken.
M_BAD_PAGINATION:
 Encountered when specifying bad pagination query parameters.

The Client-Server API typically uses HTTP POST to submit requests. This means these requests are not idempotent. The C-S API also allows HTTP PUT to make requests idempotent. In order to use a PUT, paths should be suffixed with /{txnId}. {txnId} is a unique client-generated transaction ID which identifies the request, and is scoped to a given Client (identified by that client's access_token). Crucially, it only serves to identify new requests from retransmits. After the request has finished, the {txnId} value should be changed (how is not specified; a monotonically increasing integer is recommended). It is preferable to use HTTP PUT to make sure requests to send messages do not get sent more than once should clients need to retransmit requests.

Valid requests look like:

POST /some/path/here?access_token=secret
{
  "key": "This is a post."
}

PUT /some/path/here/11?access_token=secret
{
  "key": "This is a put with a txnId of 11."
}

In contrast, these are invalid requests:

POST /some/path/here/11?access_token=secret
{
  "key": "This is a post, but it has a txnId."
}

PUT /some/path/here?access_token=secret
{
  "key": "This is a put but it is missing a txnId."
}

4   Client-Server API

The client-server API provides a simple lightweight API to let clients send messages, control rooms and synchronise conversation history. It is designed to support both lightweight clients which store no state and lazy-load data from the server as required - as well as heavyweight clients which maintain a full local persistent copy of server state.

This mostly describes v1 of the Client-Server API as featured in the original September 2014 launch of Matrix, apart from user-interactive authentication where it is encouraged to move to v2, therefore this is the version documented here. Version 2 is currently in development (as of Jan-March 2015) as an incremental but backwards-incompatible refinement of Version 1 and will be released shortly.

Documentation for the old V1 authentication is still available separately.

4.1   Client Authentication

Most API endpoints require the user to identify themselves by presenting previously obtained credentials in the form of an access_token query parameter.

In API version 2, when credentials are missing or invalid, the HTTP call will return with a status of 401 and the error code, M_MISSING_TOKEN or M_UNKNOWN_TOKEN respectively.

4.1.1   User-Interactive Authentication API

This section refers to API Version 2.

Some API endpoints such as login or register require authentication that interacts with the user. The home server may provide many different ways of authenticating, such as user/password auth, login via a social network (OAuth2), login by confirming a token sent to their email address, etc. This specification does not define how home servers should authorise their users but instead defines the standard interface which implementations should follow so that ANY client can login to ANY home server.

The process takes the form of one or more stages, where at each stage the client submits a set of data for a given stage type and awaits a response from the server, which will either be a final success or a request to perform an additional stage. This exchange continues until the final success.

Authentication works by client and server exchanging dictionaries. This specification covers how this is done over JSON HTTP POST.

For each endpoint, a server offers one of more 'flows' that the client can use to authenticate itself. Each flow comprises one or more 'stages'. Flows may have more than one stage to implement n-factor auth. When all stages are complete, authentication is complete and the API call succeeds. To establish what flows a server supports for an endpoint, a client sends the request with no authentication. A request to an endpoint that uses User-Interactive Authentication never succeeds without auth. Home Servers may allow requests that don't require auth by offering a stage with only the m.login.dummy auth type. The home server returns a response with HTTP status 401 and a JSON object as follows:

{
  "flows": [
    {
      "stages": [ "example.type.foo", "example.type.bar" ]
    },
    {
      "stages": [ "example.type.foo", "example.type.baz" ]
    }
  ],
  "params": {
      "example.type.baz": {
          "example_key": "foobar"
      }
  },
  "session": "xxxxxx"
}

In addition to the flows, this object contains some extra information:

params
This section contains any information that the client will need to know in order to use a given type of authentication. For each login stage type presented, that type may be present as a key in this dictionary. For example, the public part of an OAuth client ID could be given here.
session
This is a session identifier that the client must pass back to the home server, if one is provided, in subsequent attempts to authenticate in the same API call.

The client then chooses a flow and attempts to complete one of the stages. It does this by resubmitting the same request with the the addition of an 'auth' key in the object that it submits. This dictionary contains a type key whose value is the name of the stage type that the client is attempting to complete. It must also contains a session key with the value of the session key given by the home server, if one was given. It also contains other keys dependent on the stage type being attempted. For example, if the client is attempting to complete login type example.type.foo, it might submit something like this:

{
  "a_request_parameter": "something",
  "another_request_parameter": "something else",
  "auth": {
      "type": "example.type.foo",
      "session", "xxxxxx",
      "example_credential": "verypoorsharedsecret"
  }
}

If the home server deems the authentication attempt to be successful but still requires more stages to be completed, it returns HTTP status 401 along with the same object as when no authentication was attempted, with the addition of the completed key which is an array of stage type the client has completed successfully:

{
  "completed": [ "example.type.foo" ],
  "flows": [
    {
      "stages": [ "example.type.foo", "example.type.bar" ]
    },
    {
      "stages": [ "example.type.foo", "example.type.baz" ]
    }
  ],
  "params": {
      "example.type.baz": {
          "example_key": "foobar"
      }
  },
  "session": "xxxxxx"
}

If the home server decides the attempt was unsuccessful, it returns an error message in the standard format:

{
  "errcode": "M_EXAMPLE_ERROR",
  "error": "Something was wrong"
}

Individual stages may require more than one request to complete, in which case the response will be as if the request was unauthenticated with the addition of any other keys as defined by the login type.

If the client has completed all stages of a flow, the home server performs the API call and returns the result as normal.

Some authentication types may be completed by means other than through the Matrix client, for example, an email confirmation may be completed when the user clicks on the link in the email. In this case, the client retries the request with an auth dict containing only the session key. The response to this will be the same as if the client were attempting to complete an auth state normally, i.e. the request will either complete or request auth, with the presence or absence of that login stage type in the 'completed' array indicating whether that stage is complete.

4.1.1.1   Example

At a high level, the requests made for an API call completing an auth flow with three stages will resemble the following diagram:

 _______________________
|       Stage 1         |
| type: "<stage type1>" |
|  ___________________  |
| |_Request_1_________| | <-- Returns "session" key which is used throughout.
|  ___________________  |
| |_Request_2_________| |
|_______________________|
          |
          |
 _________V_____________
|       Stage 2         |
| type: "<stage type2>" |
|  ___________________  |
| |_Request_1_________| |
|  ___________________  |
| |_Request_2_________| |
|  ___________________  |
| |_Request_3_________| |
|_______________________|
          |
          |
 _________V_____________
|       Stage 3         |
| type: "<stage type3>" |
|  ___________________  |
| |_Request_1_________| | <-- Returns API response
|_______________________|
This specification defines the following login types:
  • m.login.password
  • m.login.recaptcha
  • m.login.oauth2
  • m.login.email.identity
  • m.login.token
  • m.login.dummy

4.1.1.2   Password-based

Type:m.login.password
Description:The client submits a username and secret password, both sent in plain-text.

To respond to this type, reply with an auth dict as follows:

{
  "type": "m.login.password",
  "user": "<user_id or user localpart>",
  "password": "<password>"
}

Warning

Clients SHOULD enforce that the password provided is suitably complex. The password SHOULD include a lower-case letter, an upper-case letter, a number and a symbol and be at a minimum 8 characters in length. Servers MAY reject weak passwords with an error code M_WEAK_PASSWORD.

4.1.1.3   Google ReCaptcha

Type:m.login.recaptcha
Description:The user completes a Google ReCaptcha 2.0 challenge

To respond to this type, reply with an auth dict as follows:

{
  "type": "m.login.recaptcha",
  "response": "<captcha response>"
}

4.1.1.4   Token-based

Type:m.login.token
Description:The client submits a username and token.

To respond to this type, reply with an auth dict as follows:

{
  "type": "m.login.token",
  "user": "<user_id or user localpart>",
  "token": "<token>",
  "txn_id": "<client generated nonce>"
}

The nonce should be a random string generated by the client for the request. The same nonce should be used if retrying the request.

There are many ways a client may receive a token, including via an email or from an existing logged in device.

The txn_id may be used by the server to disallow other devices from using the token, thus providing "single use" tokens while still allowing the device to retry the request. This would be done by tying the token to the txn_id server side, as well as potentially invalidating the token completely once the device has successfully logged in (e.g. when we receive a request from the newly provisioned access_token).

The token must be a macaroon.

4.1.1.5   OAuth2-based

Type:m.login.oauth2
Description:Authentication is supported via OAuth2 URLs. This login consists of multiple requests.
Parameters:uri: Authorization Request URI OR service selection URI. Both contain an encoded redirect URI.

The home server acts as a 'confidential' client for the purposes of OAuth2. If the uri is a service selection URI, it MUST point to a webpage which prompts the user to choose which service to authorize with. On selection of a service, this MUST link through to an Authorization Request URI. If there is only one service which the home server accepts when logging in, this indirection can be skipped and the "uri" key can be the Authorization Request URI.

The client then visits the Authorization Request URI, which then shows the OAuth2 Allow/Deny prompt. Hitting 'Allow' redirects to the redirect URI with the auth code. Home servers can choose any path for the redirect URI. Once the OAuth flow has completed, the client retries the request with the session only, as above.

4.1.1.6   Email-based (identity server)

Type:m.login.email.identity
Description:Authentication is supported by authorising an email address with an identity server.

Prior to submitting this, the client should authenticate with an identity server. After authenticating, the session information should be submitted to the home server.

To respond to this type, reply with an auth dict as follows:

{
  "type": "m.login.email.identity",
  "threepidCreds": [
    {
      "sid": "<identity server session id>",
      "client_secret": "<identity server client secret>",
      "id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>"
    }
  ]
}

4.1.1.7   Dummy Auth

Type:m.login.dummy
Description:Dummy authentication always succeeds and requires no extra parameters. Its purpose is to allow servers to not require any form of User-Interactive Authentication to perform a request.

To respond to this type, reply with an auth dict with just the type and session, if provided:

{
  "type": "m.login.dummy",
}

4.1.1.8   Fallback

Clients cannot be expected to be able to know how to process every single login type. If a client does not know how to handle a given login type, it can direct the user to a web browser with the URL of a fallback page which will allow the user to complete that login step out-of-band in their web browser. The URL it should open is the Home Server base URL plus prefix, plus:

/auth/<stage type>/fallback/web?session=<session ID>

Where stage type is the type name of the stage it is attempting and session id is the ID of the session given by the home server.

This MUST return an HTML page which can perform this authentication stage. This page must attempt to call the JavaScript function window.onAuthDone when the authentication has been completed.

4.1.2   API calls using the User-Interactive Authentication mechanism

This section refers to API Version 2. These API calls currently use the prefix /_matrix/client/v2_alpha.

4.1.2.1   POST /_matrix/client/api/v2_alpha/register

Register for an account on this homeserver.

There are two kinds of user account:

  • user accounts. These accounts may use the full API described in this specification.
  • guest accounts. These accounts may have limited permissions and may not be supported by all servers.
Rate-limited:Yes.

Request format:

Parameter Type Description
query parameters
kind enum The kind of account to register. Defaults to user. One of: ["guest", "user"]
JSON body parameters
username string The local part of the desired Matrix ID. If omitted, the homeserver MUST generate a Matrix ID local part.
bind_email boolean If true, the server binds the email used for authentication to the Matrix ID with the ID Server.
password string Required. The desired password for the account.

Response format:

Parameter Type Description
access_token string An access token for the account. This access token can then be used to authorize other requests. The access token may expire at some point, and if so, it SHOULD come with a refresh_token. There is no specific error message to indicate that a request has failed because an access token has expired; instead, if a client has reason to believe its access token is valid, and it receives an auth error, they should attempt to refresh for a new token on failure, and retry the request with the new token.
home_server string The hostname of the Home Server on which the account has been registered.
refresh_token string (optional) A refresh_token may be exchanged for a new access_token using the /tokenrefresh API endpoint.
user_id string The fully-qualified Matrix ID that has been registered.

Example request:

POST /_matrix/client/api/v2_alpha/register?kind=guest HTTP/1.1
Content-Type: application/json

{
  "username": "cheeky_monkey",
  "password": "ilovebananas",
  "bind_email": false
}

Responses:

Status code 200:

The account has been registered.

Example

{
  "user_id": "@cheeky_monkey:matrix.org",
  "access_token": "abc123",
  "home_server": "matrix.org",
  "refresh_token": "def456"
}

Status code 400:

Part of the request was invalid. This may include one of the following error codes:

  • M_USER_IN_USE : The desired user ID is already taken.
  • M_EXCLUSIVE : The desired user ID is in the exclusive namespace claimed by an application service.

These errors may be returned at any stage of the registration process, including after authentication if the requested user ID was registered whilst the client was performing authentication.

Home Servers MUST perform the relevant checks and return these codes before performing User-Interactive Authentication, although they may also return them after authentication is completed if, for example, the requested user ID was registered whilst the client was performing authentication.

Example

{
    "errcode": "M_USER_IN_USE",
    "error": "Desired user ID is already taken."
}

Old V1 API docs: /register

4.1.2.2   POST /_matrix/client/api/v1/login

Authenticates the user by password, and issues an access token they can use to authorize themself in subsequent requests.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
JSON body parameters
password string Required. The user's password.
type string Required. The login type being used. Currently only "m.login.password" is supported.
user string Required. The fully qualified user ID or just local part of the user ID, to log in.

Response format:

Parameter Type Description
access_token string An access token for the account. This access token can then be used to authorize other requests. The access token may expire at some point, and if so, it SHOULD come with a refresh_token. There is no specific error message to indicate that a request has failed because an access token has expired; instead, if a client has reason to believe its access token is valid, and it receives an auth error, they should attempt to refresh for a new token on failure, and retry the request with the new token.
home_server string The hostname of the Home Server on which the account has been registered.
refresh_token string (optional) A refresh_token may be exchanged for a new access_token using the /tokenrefresh API endpoint.
user_id string The fully-qualified Matrix ID that has been registered.

Example request:

POST /_matrix/client/api/v1/login HTTP/1.1
Content-Type: application/json

{
  "type": "m.login.pasword",
  "user": "cheeky_monkey",
  "password": "ilovebananas"
}

Responses:

Status code 200:

The user has been authenticated.

Example

{
  "user_id": "@cheeky_monkey:matrix.org",
  "access_token": "abc123",
  "home_server": "matrix.org"
}

Status code 400:

Part of the request was invalid. For example, the login type may not be recognised.

Example

{
    "errcode": "M_UNKNOWN",
    "error": "Bad login type."
}

Status code 403:

The login attempt failed. For example, the password may have been incorrect.

Example

{"errcode": "M_FORBIDDEN"}

4.1.2.3   POST /_matrix/client/api/v1/tokenrefresh

Exchanges a refresh token for a new access token. This is intended to be used if the access token has expired.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
JSON body parameters
refresh_token string Required. The refresh token which was issued by the server.

Response format:

Parameter Type Description
access_token string An access token for the account. This access token can then be used to authorize other requests. The access token may expire at some point, and if so, it SHOULD come with a refresh_token.
refresh_token string (optional) A refresh_token may be exchanged for a new access_token using the TODO Linkify /tokenrefresh API endpoint.

Example request:

POST /_matrix/client/api/v1/tokenrefresh HTTP/1.1
Content-Type: application/json

{
  "refresh_token": "a1b2c3"
}

Responses:

Status code 200:

The refresh token was accepted, and a new access token has been issued. The passed refresh token is no longer valid and cannot be used. A new refresh token will have been returned unless some policy does not allow the user to continue to renew their session.

Example

{
  "access_token": "bearwithme123",
  "refresh_token": "exchangewithme987"
}

Status code 403:

The exchange attempt failed. For example, the refresh token may have already been used.

Example

{"errcode": "M_FORBIDDEN"}

4.1.2.4   Login Fallback

If a client does not recognize any or all login flows it can use the fallback login API:

GET /_matrix/static/client/login/

This returns an HTML and JavaScript page which can perform the entire login process. The page will attempt to call the JavaScript function window.onLogin when login has been successfully completed.

4.1.2.5   Changing Password

Request:

POST $V2PREFIX/account/password

This API endpoint uses the User-Interactive Authentication API. An access token should be submitted to this endpoint if the client has an active session. The Home Server may change the flows available depending on whether a valid access token is provided.

The body of the POST request is a JSON object containing:

new_password
The new password for the account.

On success, an empty JSON object is returned.

The error code M_NOT_FOUND is returned if the user authenticated with a third party identifier but the Home Server could not find a matching account in its database.

4.1.2.6   Adding Account Administrative Contact Information

Request:

POST $V2PREFIX/account/3pid

Used to add contact information to the user's account.

The body of the POST request is a JSON object containing:

threePidCreds
An object containing contact information.
bind
Optional. A boolean indicating whether the Home Server should also bind this third party identifier to the account's matrix ID with the Identity Server. If supplied and true, the Home Server must bind the 3pid accordingly.

The contact information object comprises:

id_server
The colon-separated hostname and port of the Identity Server used to authenticate the third party identifier. If the port is the default, it and the colon should be omitted.
sid
The session ID given by the Identity Server
client_secret
The client secret used in the session with the Identity Server.

On success, the empty JSON object is returned.

May also return error codes:

M_THREEPID_AUTH_FAILED
If the credentials provided could not be verified with the ID Server.

4.1.2.7   Fetching Currently Associated Contact Information

Request:

GET $V2PREFIX/account/3pid

This returns a list of third party identifiers that the Home Server has associated with the user's account. This is not the same as the list of third party identifiers bound to the user's Matrix ID in Identity Servers. Identifiers in this list may be used by the Home Server as, for example, identifiers that it will accept to reset the user's account password.

Returns a JSON object with the key threepids whose contents is an array of objects with the following keys:

medium
The medium of the 3pid (eg, email)
address
The textual address of the 3pid, eg. the email address

4.3   Events

The model of conversation history exposed by the client-server API can be considered as a list of events. The server 'linearises' the eventually-consistent event graph of events into an 'event stream' at any given point in time:

[E0]->[E1]->[E2]->[E3]->[E4]->[E5]->[E6]->[E7]->[E8]->[E9]

Clients can add to the stream by POSTing message or state events, and can read from the stream via the /initialSync, /rooms/<room_id>/initialSync, Event Stream and /rooms/<room_id>/messages APIs.

For reading events, the intended flow of operation is to call $PREFIX/initialSync, which returns all of the state and the last N events in the event stream for each room, including start and end values describing the pagination of each room's event stream. For instance, $PREFIX/initialSync?limit=5 might return the events for a room in the rooms[0].messages.chunk[] array, with tokens describing the start and end of the range in rooms[0].messages.start as '1-2-3' and rooms[0].messages.end as 'a-b-c'.

You can visualise the range of events being returned as:

[E0]->[E1]->[E2]->[E3]->[E4]->[E5]->[E6]->[E7]->[E8]->[E9]
                            ^                             ^
                            |                             |
                      start: '1-2-3'                end: 'a-b-c'

Now, to receive future events in real-time on the event stream, you simply GET $PREFIX/events with a from parameter of 'a-b-c': in other words passing in the end token returned by initial sync. The request blocks until new events are available or until your specified timeout elapses, and then returns a new paginatable chunk of events alongside new start and end parameters:

[E0]->[E1]->[E2]->[E3]->[E4]->[E5]->[E6]->[E7]->[E8]->[E9]->[E10]
                                                          ^      ^
                                                          |      |
                                                          |  end: 'x-y-z'
                                                    start: 'a-b-c'

To resume polling the events stream, you pass in the new end token as the from parameter of $PREFIX/events and poll again.

Similarly, to paginate events backwards in order to lazy-load in previous history from the room, you simply GET $PREFIX/rooms/<room_id>/messages specifying the from token to paginate backwards from and a limit of the number of messages to retrieve. For instance, calling this API with a from parameter of '1-2-3' and a limit of 5 would return:

[E0]->[E1]->[E2]->[E3]->[E4]->[E5]->[E6]->[E7]->[E8]->[E9]->[E10]
^                            ^
|                            |
start: 'u-v-w'          end: '1-2-3'

To continue paginating backwards, one calls the /messages API again, supplying the new start value as the from parameter.

4.3.1   Types of room events

Room events are split into two categories:

State Events:These are events which update the metadata state of the room (e.g. room topic, room membership etc). State is keyed by a tuple of event type and a state_key. State in the room with the same key-tuple will be overwritten.
Message events:These are events which describe transient "once-off" activity in a room: typically communication such as sending an instant message or setting up a VoIP call.

This specification outlines several events, all with the event type prefix m.. (See Room Events for the m. event specification.) However, applications may wish to add their own type of event, and this can be achieved using the REST API detailed in the following sections. If new events are added, the event type key SHOULD follow the Java package naming convention, e.g. com.example.myapp.event. This ensures event types are suitably namespaced for each application and reduces the risk of clashes.

4.3.2   Syncing

Clients receive new events by "long-polling" the home server via the events API. This involves specifying a timeout in the request which will hold open the HTTP connection for a short period of time waiting for new events, returning early if an event occurs. Only the events API supports long-polling. All events which are visible to the client will appear in the events API. When the request returns, an end token is included in the response. This token can be used in the next request to continue where the last request left off. Multiple events can be returned per long-poll.

Warning

Events are ordered in this API according to the arrival time of the event on the homeserver. This can conflict with other APIs which order events based on their partial ordering in the event graph. This can result in duplicate events being received (once per distinct API called). Clients SHOULD de-duplicate events based on the event ID when this happens.

When the client first logs in, they will need to initially synchronise with their home server. This is achieved via the initial sync API described below. This API also returns an end token which can be used with the event stream.

4.3.2.1   GET /_matrix/client/api/v1/events

This will listen for new events and return them to the caller. This will block until an event is received, or until the timeout is reached.

Requires auth:Yes.

Request format:

Parameter Type Description
query parameters
from string The token to stream from. This token is either from a previous request to this API or from the initial sync API.
timeout integer The maximum time in milliseconds to wait for an event.

Response format:

Parameter Type Description
chunk [Event] An array of events.
end string A token which correlates to the last value in chunk. This token should be used in the next request to /events.
start string A token which correlates to the first value in chunk. This is usually the same token supplied to from=.

Event

Parameter Type Description
event_id string The globally unique event identifier.
room_id string The ID of the room associated with this event.
user_id string Contains the fully-qualified ID of the user who sent this event.

Example request:

GET /_matrix/client/api/v1/events?from=s3456_9_0&timeout=35000 HTTP/1.1

Response:

Status code 200:

The events received, which may be none.

Example

{
  "start": "s3456_9_0",
  "end": "s3457_9_0",
  "chunk": [
    {
      "age": 32,
      "content": {
          "body": "incoming message",
          "msgtype": "m.text"
      },
      "event_id": "$14328055551tzaee:localhost",
      "origin_server_ts": 1432804485886,
      "room_id": "!TmaZBKYIFrIPVGoUYp:localhost",
      "type": "m.room.message",
      "user_id": "@bob:localhost"
    }
  ]
}

4.3.2.2   GET /_matrix/client/api/v1/events/{eventId}

Get a single event based on event_id. You must have permission to retrieve this event e.g. by being a member in the room for this event.

Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
eventId string Required. The event ID to get.

Example request:

GET /_matrix/client/api/v1/events/%24asfDuShaf7Gafaw%3Amatrix.org HTTP/1.1

Response:

Status code 200:

The full event.

Example

{
  "content": {
    "body": "Hello world!",
    "msgtype": "m.text"
  },
  "room_id:": "!wfgy43Sg4a:matrix.org",
  "user_id": "@bob:matrix.org",
  "event_id": "$asfDuShaf7Gafaw:matrix.org",
  "type": "m.room.message"
}

4.3.2.3   GET /_matrix/client/api/v1/initialSync

This returns the full state for this user, with an optional limit on the number of messages per room to return.

Requires auth:Yes.

Request format:

Parameter Type Description
query parameters
limit integer The maximum number of messages to return for each room.
archived boolean Whether to include rooms that the user has left. If false then only rooms that the user has been invited to or has joined are included. If set to true then rooms that the user has left are included as well. By default this is false.

Response format:

Parameter Type Description
end string A token which correlates to the last value in chunk. This token should be used with the /events API to listen for new events.
presence [Event] A list of presence events.
rooms [RoomInfo]  

Event

Parameter Type Description
content {EventContent} The fields in this object will vary depending on the type of event. When interacting with the REST API, this is the HTTP body.
type string The type of event. This SHOULD be namespaced similar to Java package naming conventions e.g. 'com.example.subdomain.event.type'

RoomInfo

Parameter Type Description
invite {InviteEvent} The invite event if membership is invite
membership enum The user's membership state in this room. One of: ["invite", "join", "leave", "ban"]
messages {PaginationChunk} The pagination chunk for this room.
room_id string The ID of this room.
state [StateEvent] If the user is a member of the room this will be the current state of the room as a list of events. If the user has left the room this will be the state of the room when they left it.
visibility enum Whether this room is visible to the /publicRooms API or not." One of: ["private", "public"]

InviteEvent

Parameter Type Description
content {EventContent}  
invite_room_state [StrippedState] A subset of the state of the room at the time of the invite, if membership is invite
state_key string The user_id this membership event relates to.
type string Must be 'm.room.member'.

EventContent

Parameter Type Description
avatar_url string The avatar URL for this user, if any. This is added by the homeserver.
displayname string or null The display name for this user, if any. This is added by the homeserver.
membership enum The membership state of the user. One of: ["invite", "join", "knock", "leave", "ban"]
third_party_invite {Invite}  

Invite

Parameter Type Description
signed {signed}  

signed

Parameter Type Description
mxid string The invited matrix user ID. Must be equal to the user_id property of the event.
signatures {Signatures} A single signature from the verifying server, in the format specified by the Signing Events section.
token string The token property of the containing third_party_invite object.

StrippedState

Parameter Type Description
content {EventContent} The content for the event.
state_key string The state_key for the event.
type enum The type for the event. One of: ["m.room.join_rules", "m.room.canonical_alias", "m.room.avatar", "m.room.name"]

PaginationChunk

Parameter Type Description
chunk [RoomEvent] If the user is a member of the room this will be a list of the most recent messages for this room. If the user has left the room this will be the messages that preceeded them leaving. This array will consist of at most limit elements.
end string A token which correlates to the last value in chunk. Used for pagination.
start string A token which correlates to the first value in chunk. Used for pagination.

RoomEvent

Parameter Type Description
event_id string The globally unique event identifier.
room_id string The ID of the room associated with this event.
user_id string Contains the fully-qualified ID of the user who sent this event.

StateEvent

Parameter Type Description
prev_content {EventContent} Optional. The previous content for this event. If there is no previous content, this key will be missing.
state_key string A unique key which defines the overwriting semantics for this piece of room state. This value is often a zero-length string. The presence of this key makes this event a State Event. The key MUST NOT start with '_'.

Example request:

GET /_matrix/client/api/v1/initialSync?limit=2&archived=true HTTP/1.1

Response:

Status code 200:

The user's current state.

Example

{
    "end": "s3456_9_0",
    "presence": [
        {
            "content": {
                "avatar_url": "mxc://localhost/GCmhgzMPRjqgpODLsNQzVuHZ#auto",
                "displayname": "Bob",
                "last_active_ago": 31053,
                "presence": "online",
                "user_id": "@bob:localhost"
            },
            "type": "m.presence"
        }
    ],
    "rooms": [
        {
            "membership": "join",
            "messages": {
                "chunk": [
                    {
                        "age": 343513403,
                        "content": {
                            "body": "foo",
                            "msgtype": "m.text"
                        },
                        "event_id": "$14328044851tzTJS:localhost",
                        "origin_server_ts": 1432804485886,
                        "room_id": "!TmaZBKYIFrIPVGoUYp:localhost",
                        "type": "m.room.message",
                        "user_id": "@alice:localhost"
                    },
                    {
                        "age": 343511809,
                        "content": {
                            "body": "bar",
                            "msgtype": "m.text"
                        },
                        "event_id": "$14328044872spjFg:localhost",
                        "origin_server_ts": 1432804487480,
                        "room_id": "!TmaZBKYIFrIPVGoUYp:localhost",
                        "type": "m.room.message",
                        "user_id": "@bob:localhost"
                    }
                ],
                "end": "s3456_9_0",
                "start": "t44-3453_9_0"
            },
            "room_id": "!TmaZBKYIFrIPVGoUYp:localhost",
            "state": [
                {
                    "age": 7148266897,
                    "content": {
                        "join_rule": "public"
                    },
                    "event_id": "$14259997323TLwtb:localhost",
                    "origin_server_ts": 1425999732392,
                    "room_id": "!TmaZBKYIFrIPVGoUYp:localhost",
                    "state_key": "",
                    "type": "m.room.join_rules",
                    "user_id": "@alice:localhost"
                },
                {
                    "age": 6547561012,
                    "content": {
                        "avatar_url": "mxc://localhost/fzysBrHpPEeTGANCVLXWXNMI#auto",
                        "displayname": null,
                        "membership": "join"
                    },
                    "event_id": "$1426600438280zExKY:localhost",
                    "membership": "join",
                    "origin_server_ts": 1426600438277,
                    "room_id": "!TmaZBKYIFrIPVGoUYp:localhost",
                    "state_key": "@alice:localhost",
                    "type": "m.room.member",
                    "user_id": "@alice:localhost"
                },
                {
                    "age": 7148267200,
                    "content": {
                        "creator": "@alice:localhost"
                    },
                    "event_id": "$14259997320KhbwJ:localhost",
                    "origin_server_ts": 1425999732089,
                    "room_id": "!TmaZBKYIFrIPVGoUYp:localhost",
                    "state_key": "",
                    "type": "m.room.create",
                    "user_id": "@alice:localhost"
                },
                {
                    "age": 1622568720,
                    "content": {
                        "avatar_url": "mxc://localhost/GCmhgzMPRjqgpODLsNQzVuHZ#auto",
                        "displayname": "Bob",
                        "membership": "join"
                    },
                    "event_id": "$1431525430134MxlLX:localhost",
                    "origin_server_ts": 1431525430569,
                    "replaces_state": "$142652023736BSXcM:localhost",
                    "room_id": "!TmaZBKYIFrIPVGoUYp:localhost",
                    "state_key": "@bob:localhost",
                    "type": "m.room.member",
                    "user_id": "@bob:localhost"
                },
                {
                    "age": 7148267004,
                    "content": {
                        "ban": 50,
                        "events": {
                            "m.room.name": 100,
                            "m.room.power_levels": 100
                        },
                        "events_default": 0,
                        "kick": 50,
                        "redact": 50,
                        "state_default": 50,
                        "users": {
                            "@alice:localhost": 100
                        },
                        "users_default": 0
                    },
                    "event_id": "$14259997322mqfaq:localhost",
                    "origin_server_ts": 1425999732285,
                    "room_id": "!TmaZBKYIFrIPVGoUYp:localhost",
                    "state_key": "",
                    "type": "m.room.power_levels",
                    "user_id": "@alice:localhost"
                }
            ],
            "visibility": "private"
        }
    ]
}

4.3.2.4   GET /_matrix/client/v2_alpha/sync

Synchronise the client's state with the latest state on the server. Clients use this API when they first log in to get an initial snapshot of the state on the server, and then continue to call this API to get incremental deltas to the state, and to receive new messages.

Requires auth:Yes.

Request format:

Parameter Type Description
query parameters
filter string The ID of a filter created using the filter API.
since string A point in time to continue a sync from.
full_state boolean

Controls whether to include the full state for all rooms the user is a member of.

If this is set to true, then all state events will be returned, even if since is non-empty. The timeline will still be limited by the since parameter. In this case, the timeout parameter will be ignored and the query will return immediately, possibly with an empty timeline.

If false, and since is non-empty, only state which has changed since the point indicated by since will be returned.

By default, this is false.

set_presence enum Controls whether the client is automatically marked as online by polling this API. If this parameter is omitted then the client is automatically marked as online when it uses this API. Otherwise if the parameter is set to "offline" then the client is not marked as being online when it uses this API. One of: ["offline"]
timeout integer The maximum time to poll in milliseconds before returning this request.

Response format:

Parameter Type Description
next_batch string The batch token to supply in the since param of the next /sync request.
presence {Presence} The updates to the presence status of other users.
rooms {Rooms} Updates to rooms.

Presence

Parameter Type Description
events [NO_TITLE] List of events

NO_TITLE

Parameter Type Description
content {EventContent} The content of this event. The fields in this object will vary depending on the type of event.
origin_server_ts integer Timestamp in milliseconds on originating homeserver when this event was sent.
sender string The MXID of the user who sent this event.
state_key string Optional. This key will only be present for state events. A unique key which defines the overwriting semantics for this piece of room state.
type string The type of event.
unsigned {Unsigned} Information about this event which was not sent by the originating homeserver

Unsigned

Parameter Type Description
age integer Time in milliseconds since the event was sent.
prev_content {EventContent} Optional. The previous content for this state. This will be present only for state events appearing in the timeline. If this is not a state event, or there is no previous content, this key will be missing.
replaces_state string Optional. The event_id of the previous event for this state. This will be present only for state events appearing in the timeline. If this is not a state event, or there is no previous content, this key will be missing.
transaction_id string Optional. The transaction ID set when this message was sent. This key will only be present for message events sent by the device calling this API.

Rooms

Parameter Type Description
invite {string: Invited Room} The rooms that the user has been invited to.
join {string: Joined Room} The rooms that the user has joined.
leave {string: Left Room} The rooms that the user has left or been banned from.

Invited Room

Parameter Type Description
invite_state {InviteState} The state of a room that the user has been invited to. These state events may only have the sender, type, state_key and content keys present. These events do not replace any state that the client already has for the room, for example if the client has archived the room. Instead the client should keep two separate copies of the state: the one from the invite_state and one from the archived state. If the client joins the room then the current state will be given as a delta against the archived state not the invite_state.

InviteState

Parameter Type Description
events [NO_TITLE] List of events

Joined Room

Parameter Type Description
ephemeral {Ephemeral} The ephemeral events in the room that aren't recorded in the timeline or state of the room. e.g. typing.
state {State} Updates to the state, between the time indicated by the since parameter, and the start of the timeline (or all state up to the start of the timeline, if since is not given, or full_state is true).
timeline {Timeline} The timeline of messages and state changes in the room.

Ephemeral

Parameter Type Description
events [NO_TITLE] List of events

State

Parameter Type Description
events [NO_TITLE] List of events

Timeline

Parameter Type Description
limited boolean True if the number of events returned was limited by the limit on the filter
prev_batch string If the batch was limited then this is a token that can be supplied to the server to retrieve earlier events

Left Room

Parameter Type Description
state {State} The state updates for the room up to the start of the timeline.
timeline {Timeline} The timeline of messages and state changes in the room up to the point when the user left.

Example request:

GET /_matrix/client/v2_alpha/sync?filter=66696p746572&since=s72594_4483_1934&full_state=false&set_presence=offline&timeout=30000 HTTP/1.1

Response:

Status code 200:

The initial snapshot or delta for the client to use to update their state.

Example

{
  "next_batch": "s72595_4483_1934",
  "presence": {
    "events": [
      {
        "sender": "@alice:example.com",
        "type": "m.presence",
        "content": {"presence": "online"}
      }
    ]
  },
  "rooms": {
    "join": {
      "!726s6s6q:example.com": {
        "state": {
          "events": [
            {
              "sender": "@alice:example.com",
              "type": "m.room.member",
              "state_key": "@alice:example.com",
              "content": {"membership": "join"},
              "origin_server_ts": 1417731086795,
              "event_id": "$66697273743031:example.com"
            }
          ]
        },
        "timeline": {
          "events": [
            {
              "sender": "@bob:example.com",
              "type": "m.room.member",
              "state_key": "@bob:example.com",
              "content": {"membership": "join"},
              "prev_content": {"membership": "invite"},
              "origin_server_ts": 1417731086795,
              "event_id": "$7365636s6r6432:example.com"
            },
            {
              "sender": "@alice:example.com",
              "type": "m.room.message",
              "age": 124524,
              "txn_id": "1234",
              "content": {
                "body": "I am a fish",
                "msgtype": "m.text"
              },
              "origin_server_ts": 1417731086797,
              "event_id": "$74686972643033:example.com"
            }
          ],
          "limited": true,
          "prev_batch": "t34-23535_0_0"
        },
        "ephemeral": {
          "events": [
            {
              "type": "m.typing",
              "content": {"user_ids": ["@alice:example.com"]}
            }
          ]
        }
      }
    },
    "invite": {
      "!696r7674:example.com": {
        "invite_state": {
          "events": [
            {
              "sender": "@alice:example.com",
              "type": "m.room.name",
              "state_key": "",
              "content": {"name": "My Room Name"}
            },
            {
              "sender": "@alice:example.com",
              "type": "m.room.member",
              "state_key": "@bob:example.com",
              "content": {"membership": "invite"}
            }
          ]
        }
      }
    },
    "leave": {}
  }
}

4.3.3   Getting events for a room

There are several APIs provided to GET events for a room:

4.3.3.1   GET /_matrix/client/api/v1/rooms/{roomId}/initialSync

Get a copy of the current state and the most recent messages in a room.

Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room to get the data.

Response format:

RoomInfo

Parameter Type Description
membership enum The user's membership state in this room. One of: ["invite", "join", "leave", "ban"]
messages {PaginationChunk} The pagination chunk for this room.
room_id string The ID of this room.
state [StateEvent] If the user is a member of the room this will be the current state of the room as a list of events. If the user has left the room this will be the state of the room when they left it.
visibility enum Whether this room is visible to the /publicRooms API or not." One of: ["private", "public"]

PaginationChunk

Parameter Type Description
chunk [RoomEvent] If the user is a member of the room this will be a list of the most recent messages for this room. If the user has left the room this will be the messages that preceeded them leaving. This array will consist of at most limit elements.
end string A token which correlates to the last value in chunk. Used for pagination.
start string A token which correlates to the first value in chunk. Used for pagination.

RoomEvent

Parameter Type Description
event_id string The globally unique event identifier.
room_id string The ID of the room associated with this event.
user_id string Contains the fully-qualified ID of the user who sent this event.

StateEvent

Parameter Type Description
prev_content {EventContent} Optional. The previous content for this event. If there is no previous content, this key will be missing.
state_key string A unique key which defines the overwriting semantics for this piece of room state. This value is often a zero-length string. The presence of this key makes this event a State Event. The key MUST NOT start with '_'.

Example request:

GET /_matrix/client/api/v1/rooms/%21636q39766251%3Aexample.com/initialSync HTTP/1.1

Response:

Status code 200:

The current state of the room

Example

{
  "membership": "join",
  "messages": {
    "chunk": [
      {
        "age": 343513403,
        "content": {
          "body": "foo",
          "msgtype": "m.text"
        },
        "event_id": "$14328044851tzTJS:example.com",
        "origin_server_ts": 1432804485886,
        "room_id": "!636q39766251:example.com",
        "type": "m.room.message",
        "user_id": "@alice:example.com"
      },
      {
        "age": 343511809,
        "content": {
          "body": "bar",
          "msgtype": "m.text"
        },
        "event_id": "$14328044872spjFg:example.com",
        "origin_server_ts": 1432804487480,
        "room_id": "!636q39766251:example.com",
        "type": "m.room.message",
        "user_id": "@bob:example.com"
      }
    ],
    "end": "s3456_9_0",
    "start": "t44-3453_9_0"
  },
  "room_id": "!636q39766251:example.com",
  "state": [
    {
      "age": 7148266897,
      "content": {
        "join_rule": "public"
      },
      "event_id": "$14259997323TLwtb:example.com",
      "origin_server_ts": 1425999732392,
      "room_id": "!636q39766251:example.com",
      "state_key": "",
      "type": "m.room.join_rules",
      "user_id": "@alice:example.com"
    },
    {
      "age": 6547561012,
      "content": {
        "avatar_url": "mxc://example.com/fzysBrHpPEeTGANCVLXWXNMI#auto",
        "displayname": null,
        "membership": "join"
      },
      "event_id": "$1426600438280zExKY:example.com",
      "membership": "join",
      "origin_server_ts": 1426600438277,
      "room_id": "!636q39766251:example.com",
      "state_key": "@alice:example.com",
      "type": "m.room.member",
      "user_id": "@alice:example.com"
    },
    {
      "age": 7148267200,
      "content": {
          "creator": "@alice:example.com"
      },
      "event_id": "$14259997320KhbwJ:example.com",
      "origin_server_ts": 1425999732089,
      "room_id": "!636q39766251:example.com",
      "state_key": "",
      "type": "m.room.create",
      "user_id": "@alice:example.com"
    },
    {
      "age": 1622568720,
      "content": {
          "avatar_url": "mxc://example.com/GCmhgzMPRjqgpODLsNQzVuHZ#auto",
          "displayname": "Bob",
          "membership": "join"
      },
      "event_id": "$1431525430134MxlLX:example.com",
      "origin_server_ts": 1431525430569,
      "replaces_state": "$142652023736BSXcM:example.com",
      "room_id": "!636q39766251:example.com",
      "state_key": "@bob:example.com",
      "type": "m.room.member",
      "user_id": "@bob:example.com"
    },
    {
      "age": 7148267004,
      "content": {
        "ban": 50,
        "events": {
          "m.room.name": 100,
          "m.room.power_levels": 100
        },
        "events_default": 0,
        "kick": 50,
        "redact": 50,
        "state_default": 50,
        "users": {
          "@alice:example.com": 100
        },
        "users_default": 0
      },
      "event_id": "$14259997322mqfaq:example.com",
      "origin_server_ts": 1425999732285,
      "room_id": "!636q39766251:example.com",
      "state_key": "",
      "type": "m.room.power_levels",
      "user_id": "@alice:example.com"
    }
  ],
  "visibility": "private"
}

4.3.3.2   GET /_matrix/client/api/v1/rooms/{roomId}/members

Get the list of members for this room.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room to get the member events for.

Response format:

Parameter Type Description
chunk [MemberEvent]  

MemberEvent

Parameter Type Description
content {EventContent}  
invite_room_state [StrippedState] A subset of the state of the room at the time of the invite, if membership is invite
state_key string The user_id this membership event relates to.
type string Must be 'm.room.member'.

EventContent

Parameter Type Description
avatar_url string The avatar URL for this user, if any. This is added by the homeserver.
displayname string or null The display name for this user, if any. This is added by the homeserver.
membership enum The membership state of the user. One of: ["invite", "join", "knock", "leave", "ban"]
third_party_invite {Invite}  

Invite

Parameter Type Description
signed {signed}  

signed

Parameter Type Description
mxid string The invited matrix user ID. Must be equal to the user_id property of the event.
signatures {Signatures} A single signature from the verifying server, in the format specified by the Signing Events section.
token string The token property of the containing third_party_invite object.

StrippedState

Parameter Type Description
content {EventContent} The content for the event.
state_key string The state_key for the event.
type enum The type for the event. One of: ["m.room.join_rules", "m.room.canonical_alias", "m.room.avatar", "m.room.name"]

Example request:

GET /_matrix/client/api/v1/rooms/%21636q39766251%3Aexample.com/members HTTP/1.1

Response:

Status code 200:

A list of members of the room. If you are joined to the room then this will be the current members of the room. If you have left te room then this will be the members of the room when you left.

Example

{
  "chunk": [
    {
      "age": 6547561012,
      "content": {
        "avatar_url": "mxc://example.com/fzysBrHpPEeTGANCVLXWXNMI#auto",
        "displayname": null,
        "membership": "join"
      },
      "event_id": "$1426600438280zExKY:example.com",
      "membership": "join",
      "origin_server_ts": 1426600438277,
      "room_id": "!636q39766251:example.com",
      "state_key": "@alice:example.com",
      "type": "m.room.member",
      "user_id": "@alice:example.com"
    },
    {
      "age": 1622568720,
      "content": {
          "avatar_url": "mxc://example.com/GCmhgzMPRjqgpODLsNQzVuHZ#auto",
          "displayname": "Bob",
          "membership": "join"
      },
      "event_id": "$1431525430134MxlLX:example.com",
      "origin_server_ts": 1431525430569,
      "replaces_state": "$142652023736BSXcM:example.com",
      "room_id": "!636q39766251:example.com",
      "state_key": "@bob:example.com",
      "type": "m.room.member",
      "user_id": "@bob:example.com"
    }
  ]
}

4.3.3.3   GET /_matrix/client/api/v1/rooms/{roomId}/state

Get the state events for the current state of a room.

Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room to look up the state for.

Response format:

RoomState

Parameter Type Description
N/A [StateEvent] If the user is a member of the room this will be the current state of the room as a list of events. If the user has left the room then this will be the state of the room when they left as a list of events.

Example request:

GET /_matrix/client/api/v1/rooms/%21636q39766251%3Aexample.com/state HTTP/1.1

Response:

Status code 200:

The current state of the room

Example

[
  {
      "age": 7148266897,
      "content": {
          "join_rule": "public"
      },
      "event_id": "$14259997323TLwtb:example.com",
      "origin_server_ts": 1425999732392,
      "room_id": "!636q39766251:example.com",
      "state_key": "",
      "type": "m.room.join_rules",
      "user_id": "@alice:example.com"
  },
  {
      "age": 6547561012,
      "content": {
          "avatar_url": "mxc://example.com/fzysBrHpPEeTGANCVLXWXNMI#auto",
          "displayname": null,
          "membership": "join"
      },
      "event_id": "$1426600438280zExKY:example.com",
      "membership": "join",
      "origin_server_ts": 1426600438277,
      "room_id": "!636q39766251:example.com",
      "state_key": "@alice:example.com",
      "type": "m.room.member",
      "user_id": "@alice:example.com"
  },
  {
      "age": 7148267200,
      "content": {
          "creator": "@alice:example.com"
      },
      "event_id": "$14259997320KhbwJ:example.com",
      "origin_server_ts": 1425999732089,
      "room_id": "!636q39766251:example.com",
      "state_key": "",
      "type": "m.room.create",
      "user_id": "@alice:example.com"
  },
  {
      "age": 1622568720,
      "content": {
          "avatar_url": "mxc://example.com/GCmhgzMPRjqgpODLsNQzVuHZ#auto",
          "displayname": "Bob",
          "membership": "join"
      },
      "event_id": "$1431525430134MxlLX:example.com",
      "origin_server_ts": 1431525430569,
      "replaces_state": "$142652023736BSXcM:example.com",
      "room_id": "!636q39766251:example.com",
      "state_key": "@bob:example.com",
      "type": "m.room.member",
      "user_id": "@bob:example.com"
  },
  {
      "age": 7148267004,
      "content": {
          "ban": 50,
          "events": {
              "m.room.name": 100,
              "m.room.power_levels": 100
           },
           "events_default": 0,
           "kick": 50,
           "redact": 50,
           "state_default": 50,
           "users": {
               "@alice:example.com": 100
           },
           "users_default": 0
      },
      "event_id": "$14259997322mqfaq:example.com",
      "origin_server_ts": 1425999732285,
      "room_id": "!636q39766251:example.com",
      "state_key": "",
      "type": "m.room.power_levels",
      "user_id": "@alice:example.com"
  }
]

4.3.3.4   GET /_matrix/client/api/v1/rooms/{roomId}/state/{eventType}/{stateKey}

Looks up the contents of a state event in a room. If the user is joined to the room then the state is taken from the current state of the room. If the user has left the room then the state is taken from the state of the room when they left.

Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room to look up the state in.
eventType string Required. The type of state to look up.
stateKey string Required. The key of the state to look up. Defaults to the empty string.

Example request:

GET /_matrix/client/api/v1/rooms/%21636q39766251%3Aexample.com/state/m.room.name/ HTTP/1.1

Response:

Status code 200:

The content of the state event.

Example

{"name": "Example room name"}

4.3.3.5   GET /_matrix/client/api/v1/rooms/{roomId}/messages

This API returns a list of message and state events for a room. It uses pagination query parameters to paginate history in the room.

Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room to get events from.
query parameters
from string Required. The token to start returning events from. This token can be obtained from the initial sync API.
dir enum Required. The direction to return events from. One of: ["b", "f"]
limit integer The maximum number of events to return. Default: 10.

Response format:

Parameter Type Description
chunk [RoomEvent] A list of room events.
end string The token the pagination ends at. If dir=b this token should be used again to request even earlier events.
start string The token to start paginating from. If dir=b this will be the token supplied in from.

Example request:

GET /_matrix/client/api/v1/rooms/%21636q39766251%3Aexample.com/messages?from=s345_678_333&dir=b&limit=3 HTTP/1.1

Response:

Status code 200:

A list of messages with a new token to request more.

Example

{
  "start": "t47429-4392820_219380_26003_2265",
  "end": "t47409-4357353_219380_26003_2265",
  "chunk": [
    {
      "origin_server_ts": 1444812213737,
      "user_id": "@alice:example.com",
      "event_id": "$1444812213350496Caaaa:example.com",
      "content": {
        "body": "hello world",
        "msgtype":"m.text"
      },
      "room_id":"!Xq3620DUiqCaoxq:example.com",
      "type":"m.room.message",
      "age": 1042
    },
    {
      "origin_server_ts": 1444812194656 ,
      "user_id": "@bob:example.com",
      "event_id": "$1444812213350496Cbbbb:example.com",
      "content": {
        "body": "the world is big",
        "msgtype":"m.text"
      },
      "room_id":"!Xq3620DUiqCaoxq:example.com",
      "type":"m.room.message",
      "age": 20123
    },
    {
      "origin_server_ts": 1444812163990,
      "user_id": "@bob:example.com",
      "event_id": "$1444812213350496Ccccc:example.com",
      "content": {
        "name": "New room name"
      },
      "prev_content": {
        "name": "Old room name"
      },
      "state_key": "",
      "room_id":"!Xq3620DUiqCaoxq:example.com",
      "type":"m.room.name",
      "age": 50789
    }
  ]
}

4.3.4   Sending events to a room

4.3.4.1   PUT /_matrix/client/api/v1/rooms/{roomId}/state/{eventType}/{stateKey}

State events can be sent using this endpoint. These events will be overwritten if <room id>, <event type> and <state key> all match. If the state event has an empty state_key, it can be omitted from the path.

Requests to this endpoint cannot use transaction IDs like other PUT paths because they cannot be differentiated from the state_key. Furthermore, POST is unsupported on state paths.

The body of the request should be the content object of the event; the fields in this object will vary depending on the type of event. See Room Events for the m. event specification.

Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room to set the state in
eventType string Required. The type of event to send.
stateKey string Required. The state_key for the state to send. Defaults to the empty string.

Response format:

Parameter Type Description
event_id string A unique identifier for the event.

Example request:

PUT /_matrix/client/api/v1/rooms/%21636q39766251%3Aexample.com/state/m.room.name/ HTTP/1.1
Content-Type: application/json

{
    "name": "New name for the room"
}

Response:

Status code 200:

An ID for the sent event.

Example

{
    "event_id": "YUwRidLecu"
}

Examples

Valid requests look like:

PUT /rooms/!roomid:domain/state/m.example.event
{ "key" : "without a state key" }

PUT /rooms/!roomid:domain/state/m.another.example.event/foo
{ "key" : "with 'foo' as the state key" }

In contrast, these requests are invalid:

POST /rooms/!roomid:domain/state/m.example.event/
{ "key" : "cannot use POST here" }

PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11
{ "key" : "txnIds are not supported" }

Care should be taken to avoid setting the wrong state key:

PUT /rooms/!roomid:domain/state/m.another.example.event/11
{ "key" : "with '11' as the state key, but was probably intended to be a txnId" }

The state_key is often used to store state about individual users, by using the user ID as the state_key value. For example:

PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Adomain.com
{ "animal" : "cat", "reason": "fluffy" }

In some cases, there may be no need for a state_key, so it can be omitted:

PUT /rooms/!roomid:domain/state/m.room.bgd.color
{ "color": "red", "hex": "#ff0000" }

4.3.4.2   POST /_matrix/client/api/v1/rooms/{roomId}/send/{eventType}

This endpoint can be used to send a message event to a room; however the lack of a transaction ID means that it is possible to cause message duplication if events are resent on error, so it is preferable to use PUT /_matrix/client/api/v1/rooms/{roomId}/send/{eventType}/{txnId}.

Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room to send the event to.
eventType string Required. The type of event to send.

Response format:

Parameter Type Description
event_id string A unique identifier for the event.

Example request:

POST /_matrix/client/api/v1/rooms/%21636q39766251%3Aexample.com/send/m.room.message HTTP/1.1
Content-Type: application/json

{
  "msgtype": "m.text",
  "body": "hello"
}

Response:

Status code 200:

An ID for the sent event.

Example

{
    "event_id": "YUwRidLecu"
}

4.3.4.3   PUT /_matrix/client/api/v1/rooms/{roomId}/send/{eventType}/{txnId}

This endpoint is used to send a message event to a room. Message events allow access to historical events and pagination, making them suited for "once-off" activity in a room.

The body of the request should be the content object of the event; the fields in this object will vary depending on the type of event. See Room Events for the m. event specification.

Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room to send the event to.
eventType string Required. The type of event to send.
txnId string Required. The transaction ID for this event. Clients should generate a unique ID; it will be used by the server to ensure idempotency of requests.

Response format:

Parameter Type Description
event_id string A unique identifier for the event.

Example request:

PUT /_matrix/client/api/v1/rooms/%21636q39766251%3Aexample.com/send/m.room.message/35 HTTP/1.1
Content-Type: application/json

{
  "msgtype": "m.text",
  "body": "hello"
}

Response:

Status code 200:

An ID for the sent event.

Example

{
    "event_id": "YUwRidLecu"
}

4.3.5   Redactions

Since events are extensible it is possible for malicious users and/or servers to add keys that are, for example offensive or illegal. Since some events cannot be simply deleted, e.g. membership events, we instead 'redact' events. This involves removing all keys from an event that are not required by the protocol. This stripped down event is thereafter returned anytime a client or remote server requests it. Redacting an event cannot be undone, allowing server owners to delete the offending content from the databases. Events that have been redacted include a redacted_because key whose value is the event that caused it to be redacted, which may include a reason.

Upon receipt of a redaction event, the server should strip off any keys not in the following list:

  • event_id
  • type
  • room_id
  • user_id
  • state_key
  • prev_state
  • content

The content object should also be stripped of all keys, unless it is one of one of the following event types:

  • m.room.member allows key membership

  • m.room.create allows key creator

  • m.room.join_rules allows key join_rule

  • m.room.power_levels allows keys ban, events, events_default,

    kick, redact, state_default, users, users_default.

  • m.room.aliases allows key aliases

The redaction event should be added under the key redacted_because. When a client receives a redaction event it should change the redacted event in the same way a server does.

4.4   Rooms

4.4.1   Creation

The home server will create an m.room.create event when a room is created, which serves as the root of the event graph for this room. This event also has a creator key which contains the user ID of the room creator. It will also generate several other events in order to manage permissions in this room. This includes:

  • m.room.power_levels : Sets the power levels of users and required power

    levels for various actions within the room such as sending events.

  • m.room.join_rules : Whether the room is "invite-only" or not.

See Room Events for more information on these events. To create a room, a client has to use the the following API.

4.4.1.1   POST /_matrix/client/api/v1/createRoom

Create a new room with various configuration options.

Requires auth:Yes.

Request format:

Parameter Type Description
JSON body parameters
invite array[string] A list of user IDs to invite to the room. This will tell the server to invite everyone in the list to the newly created room.
room_alias_name string The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room. The alias will belong on the same home server which created the room. For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com.
visibility string A public visibility indicates that the room will be shown in the published room list. A private visibility will hide the room from the published room list. Rooms default to private visibility if this key is not included. NB: This should not be confused with join_rules which also uses the word public.
topic string If this is included, an m.room.topic event will be sent into the room to indicate the topic for the room. See Room Events for more information on m.room.topic.
preset string

Convenience parameter for setting various default state events based on a preset. Must be either:

private_chat => join_rules is set to invite. history_visibility is set to shared.

trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the room creator.

public_chat: => join_rules is set to public. history_visibility is set to shared.

creation_content object Extra keys to be added to the content of the m.room.create. The server will clober the following keys: creator. Future versions of the specification may allow the server to clobber other keys.
initial_state array[object] A list of state events to set in the new room. This allows the user to override the default state events set in the new room. The expected format of the state events are an object with type, state_key and content keys set. Takes precedence over events set by presets, but gets overriden by name and topic keys.
initial_state[0].content string  
initial_state[0].state_key string  
initial_state[0].type string  
name string If this is included, an m.room.name event will be sent into the room to indicate the name of the room. See Room Events for more information on m.room.name.

Response format:

Parameter Type Description
room_id string The created room's ID.

Example request:

POST /_matrix/client/api/v1/createRoom HTTP/1.1
Content-Type: application/json

{
  "preset": "public_chat",
  "room_alias_name": "thepub",
  "name": "The Grand Duke Pub",
  "topic": "All about happy hour",
  "creation_content": {
      "m.federate": false
  }
}

Response:

Status code 200:

Information about the newly created room.

Example

{
  "room_id": "!sefiuhWgwghwWgh:example.com"
}

4.4.2   Room aliases

Note

This section is a work in progress.

Room aliases can be created by sending a PUT /directory/room/<room alias>:

{
  "room_id": <room id>
}

They can be deleted by sending a DELETE /directory/room/<room alias> with no content. Only some privileged users may be able to delete room aliases, e.g. server admins, the creator of the room alias, etc. This specification does not outline the privilege level required for deleting room aliases.

As room aliases are scoped to a particular home server domain name, it is likely that a home server will reject attempts to maintain aliases on other domain names. This specification does not provide a way for home servers to send update requests to other servers.

Rooms store a partial list of room aliases via the m.room.aliases state event. This alias list is partial because it cannot guarantee that the alias list is in any way accurate or up-to-date, as room aliases can point to different room IDs over time. Crucially, the aliases in this event are purely informational and SHOULD NOT be treated as accurate. They SHOULD be checked before they are used or shared with another user. If a room appears to have a room alias of #alias:example.com, this SHOULD be checked to make sure that the room's ID matches the room_id returned from the request.

Room aliases can be checked in the same way they are resolved; by sending a GET /directory/room/<room alias>:

{
  "room_id": <room id>,
  "servers": [ <domain>, <domain2>, <domain3> ]
}

Home servers can respond to resolve requests for aliases on other domains than their own by using the federation API to ask other domain name home servers.

4.4.3   Permissions

Note

This section is a work in progress.

Permissions for rooms are done via the concept of power levels - to do any action in a room a user must have a suitable power level. Power levels are stored as state events in a given room. The power levels required for operations and the power levels for users are defined in m.room.power_levels, where both a default and specific users' power levels can be set. By default all users have a power level of 0, other than the room creator whose power level defaults to 100. Users can grant other users increased power levels up to their own power level. For example, user A with a power level of 50 could increase the power level of user B to a maximum of level 50. Power levels for users are tracked per-room even if the user is not present in the room. The keys contained in m.room.power_levels determine the levels required for certain operations such as kicking, banning and sending state events. See m.room.power_levels for more information.

4.4.4   Joining rooms

Users need to be a member of a room in order to send and receive events in that room. There are several states in which a user may be, in relation to a room:

  • Unrelated (the user cannot send or receive events in the room)
  • Invited (the user has been invited to participate in the room, but is not yet participating)
  • Joined (the user can send and receive events in the room)
  • Banned (the user is not allowed to join the room)

There is an exception to the requirement that a user join a room before sending events to it: users may send an m.room.member event to a room with content.membership set to leave to reject an invitation if they have currently been invited to a room but have not joined it.

Some rooms require that users be invited to it before they can join; others allow anyone to join. Whether a given room is an "invite-only" room is determined by the room config key m.room.join_rules. It can have one of the following values:

public
This room is free for anyone to join without an invite.
invite
This room can only be joined if you were invited.

4.4.4.1   POST /_matrix/client/api/v1/rooms/{roomId}/invite

Note that there are two forms of this API, which are documented separately. This version of the API requires that the inviter knows the Matrix identifier of the invitee. The other is documented in the third party invites section.

This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.

Only users currently in a particular room can invite other users to join that room.

If the user was invited to the room, the home server will append a m.room.member event to the room.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room identifier (not alias) to which to invite the user.
JSON body parameters
user_id string Required. The fully qualified user ID of the invitee.

Example request:

POST /_matrix/client/api/v1/rooms/%21d41d8cd%3Amatrix.org/invite  HTTP/1.1
Content-Type: application/json

{
  "user_id": "@cheeky_monkey:matrix.org"
}

Responses:

Status code 200:

The user has been invited to join the room.

Example

{}

Status code 403:

You do not have permission to invite the user to the room. A meaningful errcode and description error text will be returned. Example reasons for rejections are:

  • The invitee has been banned from the room.
  • The invitee is already a member of the room.
  • The inviter is not currently in the room.
  • The inviter's power level is insufficient to invite users to the room.

Example

{"errcode": "M_FORBIDDEN", "error": "@cheeky_monkey:matrix.org is banned from the room"}

4.4.4.2   POST /_matrix/client/api/v1/rooms/{roomId}/join

This API starts a user participating in a particular room, if that user is allowed to participate in that room. After this call, the client is allowed to see all current state events in the room, and all subsequent events associated with the room until the user leaves the room.

After a user has joined a room, the room will appear as an entry in the response of the /initialSync API.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room identifier or room alias to join.

Example request:

POST /_matrix/client/api/v1/rooms/%23monkeys%3Amatrix.org/join HTTP/1.1

Responses:

Status code 200:

The room has been joined.

The joined room ID must be returned in the room_id field.

Example

{"room_id": "!d41d8cd:matrix.org"}

Status code 403:

You do not have permission to join the room. A meaningful errcode and description error text will be returned. Example reasons for rejection are:

  • The room is invite-only and the user was not invited.
  • The user has been banned from the room.

Example

{"errcode": "M_FORBIDDEN", "error": "You are not invited to this room."}

4.4.4.3   POST /join/{roomId}

/join/{roomId} is an alias for /_matrix/client/api/v1/rooms/{roomId}/join.

4.4.5   Leaving rooms

A user can leave a room to stop receiving events for that room. A user must have been invited to or have joined the room before they are eligible to leave the room. Leaving a room to which the user has been invited rejects the invite. Once a user leaves a room, it will no longer appear on the /initialSync API.

Whether or not they actually joined the room, if the room is an "invite-only" room they will need to be re-invited before they can re-join the room.

4.4.5.1   POST /_matrix/client/api/v1/rooms/{roomId}/forget

This API stops a user remembering about a particular room.

In general, history is a first class citizen in Matrix. After this API is called, however, a user will no longer be able to retrieve history for this room. If all users on a homeserver forget a room, the room is eligible for deletion from that homeserver.

If the user is currently joined to the room, they will implicitly leave the room as part of this API call.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room identifier to forget.

Example request:

POST /_matrix/client/api/v1/rooms/%21au1ba7o%3Amatrix.org/forget HTTP/1.1

Response:

Status code 200:

The room has been forgotten.

Example

{}

4.4.5.2   POST /_matrix/client/api/v1/rooms/{roomId}/leave

This API stops a user participating in a particular room.

If the user was already in the room, they will no longer be able to see new events in the room. If the room requires an invite to join, they will need to be re-invited before they can re-join.

If the user was invited to the room, but had not joined, this call serves to reject the invite.

The user will still be allowed to retrieve history from the room which they were previously allowed to see.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room identifier to leave.

Example request:

POST /_matrix/client/api/v1/rooms/%21nkl290a%3Amatrix.org/leave HTTP/1.1

Response:

Status code 200:

The room has been left.

Example

{}

4.4.6   Banning users in a room

A user may decide to ban another user in a room. 'Banning' forces the target user to leave the room and prevents them from re-joining the room. A banned user will not be treated as a joined user, and so will not be able to send or receive events in the room. In order to ban someone, the user performing the ban MUST have the required power level. To ban a user, a request should be made to /rooms/<room_id>/ban with:

{
  "user_id": "<user id to ban"
  "reason": "string: <reason for the ban>"
}

Banning a user adjusts the banned member's membership state to ban and adjusts the power level of this event to a level higher than the banned person. Like with other membership changes, a user can directly adjust the target member's state, by making a request to /rooms/<room id>/state/m.room.member/<user id>:

{
  "membership": "ban"
}

4.4.7   Listing rooms

4.4.7.1   GET /_matrix/client/api/v1/publicRooms

Lists the public rooms on the server.

This API returns paginated responses.

Request format:

No parameters

Response format:

Parameter Type Description
chunk [PublicRoomsChunk] A paginated chunk of public rooms.
end string A pagination token for the response.
start string A pagination token for the response.

PublicRoomsChunk

Parameter Type Description
aliases [string] Aliases of the room. May be empty.
guest_can_join boolean Whether guest users may join the room and participate in it. If they can, they will be subject to ordinary power level rules like any other user.
name string The name of the room, if any. May be null.
num_joined_members number The number of members joined to the room.
room_id string The ID of the room.
topic string The topic of the room, if any. May be null.
world_readable boolean Whether the room may be viewed by guest users without joining.

Example request:

GET /_matrix/client/api/v1/publicRooms HTTP/1.1

Response:

Status code 200:

A list of the rooms on the server.

Example

{
  "chunk": [
    {
      "aliases": ["#murrays:cheese.bar"],
      "guest_can_join": false,
      "name": "CHEESE",
      "num_joined_members": 37,
      "room_id": "!ol19s:bleecker.street",
      "topic": "Tasty tasty cheese",
      "world_readable": true
    }
  ],
  "start": "p190q",
  "end": "p1902"
}

4.5   Profiles

4.5.1   PUT /_matrix/client/api/v1/profile/{userId}/displayname

This API sets the given user's display name. You must have permission to set this user's display name, e.g. you need to have their access_token.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
userId string Required. The user whose display name to set.
JSON body parameters
displayname string The new display name for this user.

Example request:

PUT /_matrix/client/api/v1/profile/%40alice%3Aexample.com/displayname HTTP/1.1
Content-Type: application/json

{
  "displayname": "Alice Margatroid"
}

Response:

Status code 200:

The display name was set.

Example

{}

4.5.2   GET /_matrix/client/api/v1/profile/{userId}/displayname

Get the user's display name. This API may be used to fetch the user's own displayname or to query the name of other users; either locally or on remote homeservers.

Request format:

Parameter Type Description
path parameters
userId string Required. The user whose display name to get.

Response format:

Parameter Type Description
displayname string The user's display name if they have set one.

Example request:

GET /_matrix/client/api/v1/profile/%40alice%3Aexample.com/displayname HTTP/1.1

Response:

Status code 200:

The display name for this user.

Example

{
  "displayname": "Alice Margatroid"
}

4.5.3   PUT /_matrix/client/api/v1/profile/{userId}/avatar_url

This API sets the given user's avatar URL. You must have permission to set this user's avatar URL, e.g. you need to have their access_token.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
userId string Required. The user whose avatar URL to set.
JSON body parameters
avatar_url string The new avatar URL for this user.

Example request:

PUT /_matrix/client/api/v1/profile/%40alice%3Aexample.com/avatar_url HTTP/1.1
Content-Type: application/json

{
  "avatar_url": "mxc://matrix.org/wefh34uihSDRGhw34"
}

Response:

Status code 200:

The avatar URL was set.

Example

{}

4.5.4   GET /_matrix/client/api/v1/profile/{userId}/avatar_url

Get the user's avatar URL. This API may be used to fetch the user's own avatar URL or to query the URL of other users; either locally or on remote homeservers.

Request format:

Parameter Type Description
path parameters
userId string Required. The user whose avatar URL to get.

Response format:

Parameter Type Description
avatar_url string The user's avatar URL if they have set one.

Example request:

GET /_matrix/client/api/v1/profile/%40alice%3Aexample.com/avatar_url HTTP/1.1

Response:

Status code 200:

The avatar URL for this user.

Example

{
  "avatar_url": "mxc://matrix.org/SDGdghriugerRg"
}

4.5.5   GET /_matrix/client/api/v1/profile/{userId}

Get the combined profile information for this user. This API may be used to fetch the user's own profile information or other users; either locally or on remote homeservers. This API may return keys which are not limited to displayname or avatar_url.

Request format:

Parameter Type Description
path parameters
userId string Required. The user whose avatar URL to get.

Response format:

Parameter Type Description
avatar_url string The user's avatar URL if they have set one.
displayname string The user's display name if they have set one.

Example request:

GET /_matrix/client/api/v1/profile/%40alice%3Aexample.com HTTP/1.1

Response:

Status code 200:

The avatar URL for this user.

Example

{
  "avatar_url": "mxc://matrix.org/SDGdghriugerRg",
  "displayname": "Alice Margatroid"
}

4.5.6   Events on Change of Profile Information

Because the profile display name and avatar information are likely to be used in many places of a client's display, changes to these fields cause an automatic propagation event to occur, informing likely-interested parties of the new values. This change is conveyed using two separate mechanisms:

  • a m.room.member event is sent to every room the user is a member of, to update the displayname and avatar_url.
  • a m.presence presence status update is sent, again containing the new values of the displayname and avatar_url keys, in addition to the required presence key containing the current presence state of the user.

Both of these should be done automatically by the home server when a user successfully changes their display name or avatar URL fields.

Additionally, when home servers emit room membership events for their own users, they should include the display name and avatar URL fields in these events so that clients already have these details to hand, and do not have to perform extra round trips to query it.

4.6   Security

4.6.1   Rate limiting

Home servers SHOULD implement rate limiting to reduce the risk of being overloaded. If a request is refused due to rate limiting, it should return a standard error response of the form:

{
  "errcode": "M_LIMIT_EXCEEDED",
  "error": "string",
  "retry_after_ms": integer (optional)
}

The retry_after_ms key SHOULD be included to tell the client how long they have to wait in milliseconds before they can try again.

4.7   Event Structure

All communication in Matrix is expressed in the form of data objects called Events. These are the fundamental building blocks common to the client-server, server-server and application-service APIs, and are described below.

4.7.1   Event Fields

The basic set of fields all events must have.

Key Type Description
content object The fields in this object will vary depending on the type of event. When interacting with the REST API, this is the HTTP body.
type string The type of event. This SHOULD be namespaced similar to Java package naming conventions e.g. 'com.example.subdomain.event.type'

4.7.2   Room Event Fields

In addition to the Event fields, Room Events MUST have the following additional field.

Key Type Description
event_id string The globally unique event identifier.
room_id string The ID of the room associated with this event.
user_id string Contains the fully-qualified ID of the user who sent this event.

4.7.3   State Event Fields

In addition to the Room Event fields, State Events have the following additional fields.

Key Type Description
prev_content object Optional. The previous content for this event. If there is no previous content, this key will be missing.
state_key string A unique key which defines the overwriting semantics for this piece of room state. This value is often a zero-length string. The presence of this key makes this event a State Event. The key MUST NOT start with '_'.

4.7.4   Differences between /v1 and /v2 events

There are a few differences between how events are formatted for sending between servers over federation and how they are formatted for sending between a server and its clients.

Additionally there are a few differences between the format of events in the responses to client APIs with a /v1 prefix and responses APIs with a /v2 prefix.

Events in responses for APIs with the /v2 prefix are generated from an event formatted for federation by:

  • Removing the following keys: auth_events, prev_events, hashes, signatures, depth, origin, prev_state.
  • Adding an age to the unsigned object which gives the time in milliseconds that has elapsed since the event was sent.
  • Adding prev_content and prev_sender to the unsigned object if the event is a state event, which give the previous content and previous sender of that state key
  • Adding a redacted_because to the unsigned object if the event was redacted which gives the event that redacted it.
  • Adding a transaction_id to the unsigned object if the event was sent by the client requesting it.

Events in responses for APIs with the /v1 prefix are generated from an event formatted for the /v2 prefix by:

  • Moving the folling keys from the unsigned object to the top level event object: age, redacted_because, replaces_state, prev_content.
  • Removing the unsigned object.
  • Rename the sender key to user_id.
  • If the event was an m.room.member with membership set to invite then adding a invite_room_state key to the top level event object.

4.7.5   Size limits

The total size of any event MUST NOT exceed 65 KB. There are additional restrictions on sizes per key:

  • user_id MUST NOT exceed 255 bytes (including domain).
  • room_id MUST NOT exceed 255 bytes.
  • state_key MUST NOT exceed 255 bytes.
  • type MUST NOT exceed 255 bytes.
  • event_id MUST NOT exceed 255 bytes.

Some event types have additional size restrictions which are specified in the description of the event. Additional keys have no limit other than that implied by the total 65 KB limit on events.

4.7.6   Room Events

Note

This section is a work in progress.

This specification outlines several standard event types, all of which are prefixed with m.

4.7.6.1   m.room.aliases

State Event
state_key: The homeserver domain which owns these room aliases.

This event is sent by a homeserver directly to inform of changes to the list of aliases it knows about for that room. The state_key for this event is set to the homeserver which owns the room alias. The entire set of known aliases for the room is the union of all the m.room.aliases events, one for each homeserver. Clients should check the validity of any room alias given in this list before presenting it to the user as trusted fact. The lists given by this event should be considered simply as advice on which aliases might exist, for which the client can perform the lookup to confirm whether it receives the correct room ID.

Content Key Type Description
aliases [string] A list of room aliases.

Example:

{
    "age": 242352,
    "content": {
        "aliases": [
            "#somewhere:localhost",
            "#another:localhost"
        ]
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "state_key": "localhost",
    "type": "m.room.aliases",
    "user_id": "@example:localhost"
}

4.7.6.2   m.room.canonical_alias

State Event
state_key: A zero-length string.

This event is used to inform the room about which alias should be considered the canonical one. This could be for display purposes or as suggestion to users which alias to use to advertise the room.

Content Key Type Description
alias string The canonical alias.

Example:

{
    "age": 242352,
    "content": {
        "alias": "#somewhere:localhost"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "state_key": "",
    "type": "m.room.canonical_alias",
    "user_id": "@example:localhost"
}

4.7.6.3   m.room.create

State Event
state_key: A zero-length string.

This is the first event in a room and cannot be changed. It acts as the root of all other events.

Content Key Type Description
creator string The user_id of the room creator. This is set by the homeserver.
m.federate boolean Whether users on other servers can join this room. Defaults to true if key does not exist.

Example:

{
    "age": 242352,
    "content": {
        "creator": "@example:localhost"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "state_key": "",
    "type": "m.room.create",
    "user_id": "@example:localhost"
}

4.7.6.4   m.room.join_rules

State Event
state_key: A zero-length string.

A room may be public meaning anyone can join the room without any prior action. Alternatively, it can be invite meaning that a user who wishes to join the room must first receive an invite to the room from someone already inside of the room. Currently, knock and private are reserved keywords which are not implemented.

Content Key Type Description
join_rule enum The type of rules used for users wishing to join this room. One of: ["public", "knock", "invite", "private"]

Example:

{
    "age": 242352,
    "content": {
        "join_rule": "public"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "state_key": "",
    "type": "m.room.join_rules",
    "user_id": "@example:localhost"
}

4.7.6.5   m.room.member

State Event
state_key: The user_id this membership event relates to.

Adjusts the membership state for a user in a room. It is preferable to use the membership APIs (/rooms/<room id>/invite etc) when performing membership actions rather than adjusting the state directly as there are a restricted set of valid transformations. For example, user A cannot force user B to join a room, and trying to force this state change directly will fail.

The third_party_invite property will be set if this invite is an invite event and is the successor of an m.room.third_party_invite event, and absent otherwise.

This event may also include an invite_room_state key outside the content key. If present, this contains an array of StrippedState Events. These events provide information on a few select state events such as the room name. EventContent

EventContent Key Type Description
avatar_url string The avatar URL for this user, if any. This is added by the homeserver.
displayname string or null The display name for this user, if any. This is added by the homeserver.
membership enum The membership state of the user. One of: ["invite", "join", "knock", "leave", "ban"]
third_party_invite {Invite}  

Invite

Invite Key Type Description
signed {signed}  

signed

signed Key Type Description
mxid string The invited matrix user ID. Must be equal to the user_id property of the event.
signatures {Signatures} A single signature from the verifying server, in the format specified by the Signing Events section.
token string The token property of the containing third_party_invite object.

StrippedState

StrippedState Key Type Description
content {EventContent} The content for the event.
state_key string The state_key for the event.
type enum The type for the event. One of: ["m.room.join_rules", "m.room.canonical_alias", "m.room.avatar", "m.room.name"]

Example:

{
    "age": 242352,
    "content": {
        "avatar_url": "mxc://localhost/SEsfnsuifSDFSSEF#auto",
        "displayname": "Alice Margatroid",
        "membership": "join"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "invite_room_state": [
        {
            "content": {
                "name": "Forest of Magic"
            },
            "state_key": "",
            "type": "m.room.name"
        },
        {
            "content": {
                "join_rules": "invite"
            },
            "state_key": "",
            "type": "m.room.join_rules"
        }
    ],
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "state_key": "@alice:localhost",
    "type": "m.room.member",
    "user_id": "@example:localhost"
}

4.7.6.6   m.room.power_levels

State Event
state_key: A zero-length string.

This event specifies the minimum level a user must have in order to perform a certain action. It also specifies the levels of each user in the room. If a user_id is in the users list, then that user_id has the associated power level. Otherwise they have the default level users_default. If users_default is not supplied, it is assumed to be 0. The level required to send a certain event is governed by events, state_default and events_default. If an event type is specified in events, then the user must have at least the level specified in order to send that event. If the event type is not supplied, it defaults to events_default for Message Events and state_default for State Events.

Content Key Type Description
ban number The level required to ban a user.
events {string: number} The level required to send specific event types. This is a mapping from event type to power level required.
events_default number The default level required to send message events. Can be overridden by the events key.
kick number The level required to kick a user.
redact number The level required to redact an event.
state_default number The default level required to send state events. Can be overridden by the events key.
users {string: number} The power levels for specific users. This is a mapping from user_id to power level for that user.
users_default number The default power level for every user in the room, unless their user_id is mentioned in the users key.

Example:

{
    "age": 242352,
    "content": {
        "ban": 50,
        "events": {
            "m.room.name": 100,
            "m.room.power_levels": 100
        },
        "events_default": 0,
        "kick": 50,
        "redact": 50,
        "state_default": 50,
        "users": {
            "@example:localhost": 100
        },
        "users_default": 0
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "state_key": "",
    "type": "m.room.power_levels",
    "user_id": "@example:localhost"
}

4.7.6.7   m.room.redaction

Message Event

Events can be redacted by either room or server admins. Redacting an event means that all keys not required by the protocol are stripped off, allowing admins to remove offensive or illegal content that may have been attached to any event. This cannot be undone, allowing server owners to physically delete the offending data. There is also a concept of a moderator hiding a message event, which can be undone, but cannot be applied to state events. The event that has been redacted is specified in the redacts event level key.

Content Key Type Description
reason string The reason for the redaction, if any.

Example:

{
    "age": 242352,
    "content": {
        "reason": "Spamming"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "redacts": "!fukweghifu23:localhost",
    "room_id": "!Cuyf34gef24t:localhost",
    "type": "m.room.redaction",
    "user_id": "@example:localhost"
}

4.8   Signing Events

4.8.1   Canonical JSON

Matrix events are represented using JSON objects. If we want to sign JSON events we need to encode the JSON as a binary string. Unfortunately the same JSON can be encoded in different ways by changing how much white space is used or by changing the order of keys within objects. Therefore we have to define an encoding which can be reproduced byte for byte by any JSON library.

We define the canonical JSON encoding for a value to be the shortest UTF-8 JSON encoding with dictionary keys lexicographically sorted by unicode codepoint. Numbers in the JSON must be integers in the range [-(2**53)+1, (2**53)-1].

We pick UTF-8 as the encoding as it should be available to all platforms and JSON received from the network is likely to be already encoded using UTF-8. We sort the keys to give a consistent ordering. We force integers to be in the range where they can be accurately represented using IEEE double precision floating point numbers since a number of JSON libraries represent all numbers using this representation.

import json

def canonical_json(value):
    return json.dumps(
        value,
        # Encode code-points outside of ASCII as UTF-8 rather than \u escapes
        ensure_ascii=False,
        # Remove unnecessary white space.
        separators=(',',':'),
        # Sort the keys of dictionaries.
        sort_keys=True,
        # Encode the resulting unicode as UTF-8 bytes.
    ).encode("UTF-8")

4.8.1.1   Grammar

Adapted from the grammar in http://tools.ietf.org/html/rfc7159 removing insignificant whitespace, fractions, exponents and redundant character escapes

value     = false / null / true / object / array / number / string
false     = %x66.61.6c.73.65
null      = %x6e.75.6c.6c
true      = %x74.72.75.65
object    = %x7B [ member *( %x2C member ) ] %7D
member    = string %x3A value
array     = %x5B [ value *( %x2C value ) ] %5B
number    = [ %x2D ] int
int       = %x30 / ( %x31-39 *digit )
digit     = %x30-39
string    = %x22 *char %x22
char      = unescaped / %x5C escaped
unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
escaped   = %x22 ; "    quotation mark  U+0022
          / %x5C ; \    reverse solidus U+005C
          / %x62 ; b    backspace       U+0008
          / %x66 ; f    form feed       U+000C
          / %x6E ; n    line feed       U+000A
          / %x72 ; r    carriage return U+000D
          / %x74 ; t    tab             U+0009
          / %x75.30.30.30 (%x30-37 / %x62 / %x65-66) ; u000X
          / %x75.30.30.31 (%x30-39 / %x61-66)        ; u001X

4.8.2   Signing JSON

We can now sign a JSON object by encoding it as a sequence of bytes, computing the signature for that sequence and then adding the signature to the original JSON object.

4.8.2.1   Signing Details

JSON is signed by encoding the JSON object without signatures or keys grouped as unsigned, using the canonical encoding described above. The JSON bytes are then signed using the signature algorithm and the signature encoded using base64 with the padding stripped. The resulting base64 signature is added to an object under the signing key identifier which is added to the signatures object under the name of the server signing it which is added back to the original JSON object along with the unsigned object.

The signing key identifier is the concatenation of the signing algorithm and a key version. The signing algorithm identifies the algorithm used to sign the JSON. The currently support value for signing algorithm is ed25519 as implemented by NACL (http://nacl.cr.yp.to/). The key version is used to distinguish between different signing keys used by the same entity.

The unsigned object and the signatures object are not covered by the signature. Therefore intermediate servers can add unsigned data such as timestamps and additional signatures.

{
   "name": "example.org",
   "signing_keys": {
     "ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ"
   },
   "unsigned": {
      "age_ts": 922834800000
   },
   "signatures": {
      "example.org": {
         "ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw"
      }
   }
}
def sign_json(json_object, signing_key, signing_name):
    signatures = json_object.pop("signatures", {})
    unsigned = json_object.pop("unsigned", None)

    signed = signing_key.sign(encode_canonical_json(json_object))
    signature_base64 = encode_base64(signed.signature)

    key_id = "%s:%s" % (signing_key.alg, signing_key.version)
    signatures.setdefault(signing_name, {})[key_id] = signature_base64

    json_object["signatures"] = signatures
    if unsigned is not None:
        json_object["unsigned"] = unsigned

    return json_object

4.8.2.2   Checking for a Signature

To check if an entity has signed a JSON object a server does the following

  1. Checks if the signatures object contains an entry with the name of the entity. If the entry is missing then the check fails.
  2. Removes any signing key identifiers from the entry with algorithms it doesn't understand. If there are no signing key identifiers left then the check fails.
  3. Looks up verification keys for the remaining signing key identifiers either from a local cache or by consulting a trusted key server. If it cannot find a verification key then the check fails.
  4. Decodes the base64 encoded signature bytes. If base64 decoding fails then the check fails.
  5. Checks the signature bytes using the verification key. If this fails then the check fails. Otherwise the check succeeds.

4.8.3   Signing Events

Signing events is a more complicated process since servers can choose to redact non-essential parts of an event. Before signing the event it is encoded as Canonical JSON and hashed using SHA-256. The resulting hash is then stored in the event JSON in a hash object under a sha256 key.

def hash_event(event_json_object):

    # Keys under "unsigned" can be modified by other servers.
    # They are useful for conveying information like the age of an
    # event that will change in transit.
    # Since they can be modifed we need to exclude them from the hash.
    unsigned = event_json_object.pop("unsigned", None)

    # Signatures will depend on the current value of the "hashes" key.
    # We cannot add new hashes without invalidating existing signatures.
    signatures = event_json_object.pop("signatures", None)

    # The "hashes" key might contain multiple algorithms if we decide to
    # migrate away from SHA-2. We don't want to include an existing hash
    # output in our hash so we exclude the "hashes" dict from the hash.
    hashes = event_json_object.pop("hashes", {})

    # Encode the JSON using a canonical encoding so that we get the same
    # bytes on every server for the same JSON object.
    event_json_bytes = encode_canonical_json(event_json_bytes)

    # Add the base64 encoded bytes of the hash to the "hashes" dict.
    hashes["sha256"] = encode_base64(sha256(event_json_bytes).digest())

    # Add the "hashes" dict back the event JSON under a "hashes" key.
    event_json_object["hashes"] = hashes
    if unsigned is not None:
        event_json_object["unsigned"] = unsigned
    return event_json_object

The event is then stripped of all non-essential keys both at the top level and within the content object. Any top-level keys not in the following list MUST be removed:

auth_events
depth
event_id
hashes
membership
origin
origin_server_ts
prev_events
prev_state
room_id
sender
signatures
state_key
type

A new content object is constructed for the resulting event that contains only the essential keys of the original content object. If the original event lacked a content object at all, a new empty JSON object is created for it.

The keys that are considered essential for the content object depend on the the type of the event. These are:

type is "m.room.aliases":
  aliases

type is "m.room.create":
  creator

type is "m.room.history_visibility":
  history_visibility

type is "m.room.join_rules":
  join_rule

type is "m.room.member":
  membership

type is "m.room.power_levels":
  ban
  events
  events_default
  kick
  redact
  state_default
  users
  users_default

The resulting stripped object with the new content object and the original hashes key is then signed using the JSON signing algorithm outlined below:

def sign_event(event_json_object, name, key):

    # Make sure the event has a "hashes" key.
    if "hashes" not in event_json_object:
        event_json_object = hash_event(event_json_object)

    # Strip all the keys that would be removed if the event was redacted.
    # The hashes are not stripped and cover all the keys in the event.
    # This means that we can tell if any of the non-essential keys are
    # modified or removed.
    stripped_json_object = strip_non_essential_keys(event_json_object)

    # Sign the stripped JSON object. The signature only covers the
    # essential keys and the hashes. This means that we can check the
    # signature even if the event is redacted.
    signed_json_object = sign_json(stripped_json_object)

    # Copy the signatures from the stripped event to the original event.
    event_json_object["signatures"] = signed_json_oject["signatures"]
    return event_json_object

Servers can then transmit the entire event or the event with the non-essential keys removed. If the entire event is present, receiving servers can then check the event by computing the SHA-256 of the event, excluding the hash object. If the keys have been redacted, then the hash object is included when calculating the SHA-256 instead.

New hash functions can be introduced by adding additional keys to the hash object. Since the hash object cannot be redacted a server shouldn't allow too many hashes to be listed, otherwise a server might embed illict data within the hash object. For similar reasons a server shouldn't allow hash values that are too long.

5   Modules

5.1   Feature Profiles

Matrix supports many different kinds of clients: from embedded IoT devices to desktop clients. Not all clients can provide the same feature sets as other clients e.g. due to lack of physical hardware such as not having a screen. Clients can fall into one of several profiles and each profile contains a set of features that the client MUST support. This section details a set of "feature profiles". Clients are expected to implement a profile in its entirety in order for it to be classified as that profile.

5.1.1   Summary

Module / Profile Web Mobile Desktop CLI Embedded
Instant Messaging Required Required Required Required Optional
Presence Required Required Required Required Optional
Push Notifications Optional Required Optional Optional Optional
Receipts Required Required Required Required Optional
Typing Notifications Required Required Required Required Optional
VoIP Required Required Required Optional Optional
Content Repository Required Required Required Optional Optional
Managing History Visibility Required Required Required Required Optional
End-to-End Encryption Optional Optional Optional Optional Optional
Server Side Search Optional Optional Optional Optional Optional

Please see each module for more details on what clients need to implement.

5.1.2   Clients

5.1.2.1   Stand-alone web (Web)

This is a web page which heavily uses Matrix for communication. Single-page web apps would be classified as a stand-alone web client, as would multi-page web apps which use Matrix on nearly every page.

5.1.2.2   Mobile (Mobile)

This is a Matrix client specifically designed for consumption on mobile devices. This is typically a mobile app but need not be so provided the feature set can be reached (e.g. if a mobile site could display push notifications it could be classified as a mobile client).

5.1.2.3   Desktop (Desktop)

This is a native GUI application which can run in its own environment outside a browser.

5.1.2.4   Command Line Interface (CLI)

This is a client which is used via a text-based terminal.

5.1.2.5   Embedded (Embedded)

This is a client which is embedded into another application or an embedded device.

5.1.2.5.1   Application

This is a Matrix client which is embedded in another website, e.g. using iframes. These embedded clients are typically for a single purpose related to the website in question, and are not intended to be fully-fledged communication apps.

5.1.2.5.2   Device

This is a client which is typically running on an embedded device such as a kettle, fridge or car. These clients tend to perform a few operations and run in a resource constrained environment. Like embedded applications, they are not intended to be fully-fledged communication systems.

5.2   Instant Messaging

This module adds support for sending human-readable messages to a room. It also adds support for associating human-readable information with the room itself such as a room name and topic.

5.2.1   Events

5.2.1.1   m.room.message

Message Event

This event is used when sending messages in a room. Messages are not limited to be text. The msgtype key outlines the type of message, e.g. text, audio, image, video, etc. The body key is text and MUST be used with every kind of msgtype as a fallback mechanism for when a client cannot render a message. This allows clients to display something even if it is just plain text. For more information on msgtypes, see m.room.message msgtypes.

Content Key Type Description
body string The textual representation of this message.
msgtype string The type of message, e.g. m.image, m.text

Example:

{
    "age": 242352,
    "content": {
        "body": "This is an example text message",
        "msgtype": "m.text"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "type": "m.room.message",
    "user_id": "@example:localhost"
}

5.2.1.2   m.room.message.feedback

Message Event

NB: Usage of this event is discouraged in favour of the receipts module. Most clients will not recognise this event. Feedback events are events sent to acknowledge a message in some way. There are two supported acknowledgements: delivered (sent when the event has been received) and read (sent when the event has been observed by the end-user). The target_event_id should reference the m.room.message event being acknowledged.

Content Key Type Description
target_event_id string The event that this feedback is related to.
type enum The type of feedback. One of: ["delivered", "read"]

Example:

{
    "age": 242352,
    "content": {
        "target_event_id": "$WEIGFHFW:localhost",
        "type": "delivered"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "type": "m.room.message.feedback",
    "user_id": "@example:localhost"
}
Usage of this event is discouraged for several reasons:
  • The number of feedback events will grow very quickly with the number of users in the room. This event provides no way to "batch" feedback, unlike the receipts module.
  • Pairing feedback to messages gets complicated when paginating as feedback arrives before the message it is acknowledging.
  • There are no guarantees that the client has seen the event ID being acknowledged.

5.2.1.3   m.room.name

State Event
state_key: A zero-length string.

A room has an opaque room ID which is not human-friendly to read. A room alias is human-friendly, but not all rooms have room aliases. The room name is a human-friendly string designed to be displayed to the end-user. The room name is not unique, as multiple rooms can have the same room name set. The room name can also be set when creating a room using /createRoom with the name key.

Content Key Type Description
name string The name of the room. This MUST NOT exceed 255 bytes.

Example:

{
    "age": 242352,
    "content": {
        "name": "The room name"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "state_key": "",
    "type": "m.room.name",
    "user_id": "@example:localhost"
}

5.2.1.4   m.room.topic

State Event
state_key: A zero-length string.

A topic is a short message detailing what is currently being discussed in the room. It can also be used as a way to display extra information about the room, which may not be suitable for the room name. The room topic can also be set when creating a room using /createRoom with the topic key.

Content Key Type Description
topic string The topic text.

Example:

{
    "age": 242352,
    "content": {
        "topic": "A room topic"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "state_key": "",
    "type": "m.room.topic",
    "user_id": "@example:localhost"
}

5.2.1.5   m.room.avatar

State Event
state_key: A zero-length string.

A picture that is associated with the room. This can be displayed alongside the room information.

Content Key Type Description
info {ImageInfo} Metadata about the image referred to in url.
thumbnail_info {ImageInfo} Metadata about the image referred to in thumbnail_url.
thumbnail_url string The URL to the thumbnail of the image.
url string The URL to the image.

ImageInfo

ImageInfo Key Type Description
h integer The height of the image in pixels.
mimetype string The mimetype of the image, e.g. image/jpeg.
size integer Size of the image in bytes.
w integer The width of the image in pixels.

Example:

{
    "age": 242352,
    "content": {
        "info": {
            "h": 398,
            "mimetype": "image/jpeg",
            "size": 31037,
            "w": 394
        },
        "url": "mxc://localhost/JWEIFJgwEIhweiWJE"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "state_key": "",
    "type": "m.room.avatar",
    "user_id": "@example:localhost"
}

5.2.1.6   m.room.message msgtypes

Each m.room.message MUST have a msgtype key which identifies the type of message being sent. Each type has their own required and optional keys, as outlined below. If a client cannot display the given msgtype then it SHOULD display the fallback plain text body key instead.

5.2.1.6.1   m.text

This message is the most basic message and is used to represent text.

Content Key Type Description
body string The body of the message.
msgtype string Must be 'm.text'.

Example:

{
    "age": 242352,
    "content": {
        "body": "This is an example text message",
        "msgtype": "m.text"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "type": "m.room.message",
    "user_id": "@example:localhost"
}

5.2.1.6.2   m.emote

This message is similar to m.text except that the sender is 'performing' the action contained in the body key, similar to /me in IRC. This message should be prefixed by the name of the sender. This message could also be represented in a different colour to distinguish it from regular m.text messages.

Content Key Type Description
body string The emote action to perform.
msgtype string Must be 'm.emote'.

Example:

{
    "age": 242352,
    "content": {
        "body": "thinks this is an example emote",
        "msgtype": "m.emote"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "type": "m.room.message",
    "user_id": "@example:localhost"
}

5.2.1.6.3   m.notice

A m.notice message should be considered similar to a plain m.text message except that clients should visually distinguish it in some way. It is intended to be used by automated clients, such as bots, bridges, and other entities, rather than humans. Additionally, such automated agents which watch a room for messages and respond to them ought to ignore m.notice messages. This helps to prevent infinite-loop situations where two automated clients continuously exchange messages, as each responds to the other.

Content Key Type Description
body string The notice text to send.
msgtype string Must be 'm.notice'.

Example:

{
    "age": 242352,
    "content": {
        "body": "This is an example notice",
        "msgtype": "m.notice"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "type": "m.room.message",
    "user_id": "@example:localhost"
}

5.2.1.6.4   m.image

This message represents a single image and an optional thumbnail.

Content Key Type Description
body string A textual representation of the image. This could be the alt text of the image, the filename of the image, or some kind of content description for accessibility e.g. 'image attachment'.
info {ImageInfo} Metadata about the image referred to in url.
msgtype string Must be 'm.image'.
thumbnail_info {ImageInfo} Metadata about the image referred to in thumbnail_url.
thumbnail_url string The URL to the thumbnail of the image.
url string The URL to the image.

ImageInfo

ImageInfo Key Type Description
h integer The height of the image in pixels.
mimetype string The mimetype of the image, e.g. image/jpeg.
size integer Size of the image in bytes.
w integer The width of the image in pixels.

Example:

{
    "age": 242352,
    "content": {
        "body": "filename.jpg",
        "info": {
            "h": 398,
            "mimetype": "image/jpeg",
            "size": 31037,
            "w": 394
        },
        "msgtype": "m.image",
        "url": "mxc://localhost/JWEIFJgwEIhweiWJE"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "type": "m.room.message",
    "user_id": "@example:localhost"
}

5.2.1.6.5   m.file

This message represents a generic file.

Content Key Type Description
body string A human-readable description of the file. This is recommended to be the filename of the original upload.
filename string The original filename of the uploaded file.
info {FileInfo} Information about the file referred to in url.
msgtype string Must be 'm.file'.
thumbnail_info {ImageInfo} Metadata about the image referred to in thumbnail_url.
thumbnail_url string The URL to the thumbnail of the file.
url string The URL to the file.

FileInfo

FileInfo Key Type Description
mimetype string The mimetype of the file e.g. application/msword.
size integer The size of the file in bytes.

Example:

{
    "age": 146,
    "content": {
        "body": "something-important.doc",
        "filename": "something-important.doc",
        "info": {
            "mimetype": "application/msword",
            "size": 46144
        },
        "msgtype": "m.file",
        "url": "mxc://localhost/FHyPlCeYUSFFxlgbQYZmoEoe"
    },
    "event_id": "$143273582443PhrSn:localhost",
    "origin_server_ts": 1432735824653,
    "room_id": "!jEsUZKDJdhlrceRyVU:localhost",
    "type": "m.room.message",
    "user_id": "@example:localhost"
}

5.2.1.6.6   m.location

This message represents a real-world location.

Content Key Type Description
body string A description of the location e.g. 'Big Ben, London, UK', or some kind of content description for accessibility e.g. 'location attachment'.
geo_uri string A geo URI representing this location.
msgtype string Must be 'm.location'.
thumbnail_info {ImageInfo}  
thumbnail_url string The URL to a thumbnail of the location being represented.

Example:

{
    "age": 146,
    "content": {
        "body": "Big Ben, London, UK",
        "geo_uri": "geo:51.5008,0.1247",
        "msgtype": "m.location",
        "thumbnail_info": {
            "h": 300,
            "mimetype": "image/jpeg",
            "size": 46144,
            "w": 300
        },
        "thumbnail_url": "mxc://localhost/FHyPlCeYUSFFxlgbQYZmoEoe"
    },
    "event_id": "$143273582443PhrSn:localhost",
    "origin_server_ts": 1432735824653,
    "room_id": "!jEsUZKDJdhlrceRyVU:localhost",
    "type": "m.room.message",
    "user_id": "@example:localhost"
}

5.2.1.6.7   m.video

This message represents a single video clip.

Content Key Type Description
body string A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'.
info {VideoInfo} Metadata about the video clip referred to in url.
msgtype string Must be 'm.video'.
url string The URL to the video clip.

VideoInfo

VideoInfo Key Type Description
duration integer The duration of the video in milliseconds.
h integer The height of the video in pixels.
mimetype string The mimetype of the video e.g. video/mp4.
size integer The size of the video in bytes.
thumbnail_info {ImageInfo}  
thumbnail_url string The URL to a thumbnail of the video clip.
w integer The width of the video in pixels.

Example:

{
    "age": 146,
    "content": {
        "body": "Gangnam Style",
        "info": {
            "duration": 2140786,
            "h": 320,
            "mimetype": "video/mp4",
            "size": 1563685,
            "thumbnail_info": {
                "h": 300,
                "mimetype": "image/jpeg",
                "size": 46144,
                "w": 300
            },
            "thumbnail_url": "mxc://localhost/FHyPlCeYUSFFxlgbQYZmoEoe",
            "w": 480
        },
        "msgtype": "m.video",
        "url": "mxc://localhost/a526eYUSFFxlgbQYZmo442"
    },
    "event_id": "$143273582443PhrSn:localhost",
    "origin_server_ts": 1432735824653,
    "room_id": "!jEsUZKDJdhlrceRyVU:localhost",
    "type": "m.room.message",
    "user_id": "@example:localhost"
}

5.2.1.6.8   m.audio

This message represents a single audio clip.

Content Key Type Description
body string A description of the audio e.g. 'Bee Gees - Stayin' Alive', or some kind of content description for accessibility e.g. 'audio attachment'.
info {AudioInfo} Metadata for the audio clip referred to in url.
msgtype string Must be 'm.audio'.
url string The URL to the audio clip.

AudioInfo

AudioInfo Key Type Description
duration integer The duration of the audio in milliseconds.
mimetype string The mimetype of the audio e.g. audio/aac.
size integer The size of the audio clip in bytes.

Example:

{
    "age": 146,
    "content": {
        "body": "Bee Gees - Stayin' Alive",
        "info": {
            "duration": 2140786,
            "mimetype": "audio/mpeg",
            "size": 1563685
        },
        "msgtype": "m.audio",
        "url": "mxc://localhost/ffed755USFFxlgbQYZGtryd"
    },
    "event_id": "$143273582443PhrSn:localhost",
    "origin_server_ts": 1432735824653,
    "room_id": "!jEsUZKDJdhlrceRyVU:localhost",
    "type": "m.room.message",
    "user_id": "@example:localhost"
}

5.2.2   Client behaviour

Clients SHOULD verify the structure of incoming events to ensure that the expected keys exist and that they are of the right type. Clients can discard malformed events or display a placeholder message to the user. Redacted m.room.message events MUST be removed from the client. This can either be replaced with placeholder text (e.g. "[REDACTED]") or the redacted message can be removed entirely from the messages view.

Events which have attachments (e.g. m.image, m.file) SHOULD be uploaded using the content repository module where available. The resulting mxc:// URI can then be used in the url key.

5.2.2.1   Recommendations when sending messages

Clients can send messages using POST or PUT requests. Clients SHOULD use PUT requests with transaction IDs to make requests idempotent. This ensures that messages are sent exactly once even under poor network conditions. Clients SHOULD retry requests using an exponential-backoff algorithm for a certain amount of time T. It is recommended that T is no longer than 5 minutes. After this time, the client should stop retrying and mark the message as "unsent". Users should be able to manually resend unsent messages.

Users may type several messages at once and send them all in quick succession. Clients SHOULD preserve the order in which they were sent by the user. This means that clients should wait for the response to the previous request before sending the next request. This can lead to head-of-line blocking. In order to reduce the impact of head-of-line blocking, clients should use a queue per room rather than a global queue, as ordering is only relevant within a single room rather than between rooms.

5.2.2.2   Local echo

Messages SHOULD appear immediately in the message view when a user presses the "send" button. This should occur even if the message is still sending. This is referred to as "local echo". Clients SHOULD implement "local echo" of messages. Clients MAY display messages in a different format to indicate that the server has not processed the message. This format should be removed when the server responds.

Clients need to be able to match the message they are sending with the same message which they receive from the event stream. The echo of the same message from the event stream is referred to as "remote echo". Both echoes need to be identified as the same message in order to prevent duplicate messages being displayed. Ideally this pairing would occur transparently to the user: the UI would not flicker as it transitions from local to remote. Flickering cannot be fully avoided in the current client-server API. Two scenarios need to be considered:

  • The client sends a message and the remote echo arrives on the event stream after the request to send the message completes.
  • The client sends a message and the remote echo arrives on the event stream before the request to send the message completes.

In the first scenario, the client will receive an event ID when the request to send the message completes. This ID can be used to identify the duplicate event when it arrives on the event stream. However, in the second scenario, the event arrives before the client has obtained an event ID. This makes it impossible to identify it as a duplicate event. This results in the client displaying the message twice for a fraction of a second before the the original request to send the message completes. Once it completes, the client can take remedial actions to remove the duplicate event by looking for duplicate event IDs. A future version of the client-server API will resolve this by attaching the transaction ID of the sending request to the event itself.

5.2.2.3   Calculating the display name for a user

Clients may wish to show the human-readable display name of a room member as part of a membership list, or when they send a message. However, different members may have conflicting display names. Display names MUST be disambiguated before showing them to the user, in order to prevent spoofing of other users.

To ensure this is done consistently across clients, clients SHOULD use the following algorithm to calculate a disambiguated display name for a given user:

  1. Inspect the m.room.member state event for the relevant user id.
  2. If the m.room.member state event has no displayname field, or if that field has a null value, use the raw user id as the display name. Otherwise:
  3. If the m.room.member event has a displayname which is unique among members of the room with membership: join or membership: invite, use the given displayname as the user-visible display name. Otherwise:
  4. The m.room.member event has a non-unique displayname. This should be disambiguated using the user id, for example "display name (@id:homeserver.org)".

Developers should take note of the following when implementing the above algorithm:

  • The user-visible display name of one member can be affected by changes in the state of another member. For example, if @user1:matrix.org is present in a room, with displayname: Alice, then when @user2:example.com joins the room, also with displayname: Alice, both users must be given disambiguated display names. Similarly, when one of the users then changes their display name, there is no longer a clash, and both users can be given their chosen display name. Clients should be alert to this possibility and ensure that all affected users are correctly renamed.
  • The display name of a room may also be affected by changes in the membership list. This is due to the room name sometimes being based on user display names (see Calculating the display name for a room).
  • If the entire membership list is searched for clashing display names, this leads to an O(N^2) implementation for building the list of room members. This will be very inefficient for rooms with large numbers of members. It is recommended that client implementations maintain a hash table mapping from displayname to a list of room members using that name. Such a table can then be used for efficient calculation of whether disambiguation is needed.

5.2.2.4   Displaying membership information with messages

Clients may wish to show the display name and avatar URL of the room member who sent a message. This can be achieved by inspecting the m.room.member state event for that user ID (see Calculating the display name for a user).

When a user paginates the message history, clients may wish to show the historical display name and avatar URL for a room member. This is possible because older m.room.member events are returned when paginating. This can be implemented efficiently by keeping two sets of room state: old and current. As new events arrive and/or the user paginates back in time, these two sets of state diverge from each other. New events update the current state and paginated events update the old state. When paginated events are processed sequentially, the old state represents the state of the room at the time the event was sent. This can then be used to set the historical display name and avatar URL.

5.2.2.5   Calculating the display name for a room

Clients may wish to show a human-readable name for a room. There are a number of possibilities for choosing a useful name. To ensure that rooms are named consistently across clients, clients SHOULD use the following algorithm to choose a name:

  1. If the room has an m.room.name state event, use the name given by that event.

  2. If the room has an m.room.canonical_alias state event, use the alias given by that event.

  3. If neither of the above events are present, a name should be composed based on the members of the room. Clients should consider m.room.member events for users other than the logged-in user, with membership: join or membership: invite.

    1. If there is only one such event, the display name for the room should be the disambiguated display name of the corresponding user.

    2. If there are two such events, they should be lexicographically sorted by their state_key (i.e. the corresponding user IDs), and the display name for the room should be the disambiguated display name of both users: "<user1> and <user2>", or a localised variant thereof.

    3. If there are three or more such events, the display name for the room should be based on the disambiguated display name of the user corresponding to the first such event, under a lexicographical sorting according to their state_key. The display name should be in the format "<user1> and <N> others" (or a localised variant thereof), where N is the number of m.room.member events with membership: join or membership: invite, excluding the logged-in user and "user1".

      For example, if Alice joins a room, where Bob (whose user id is @superuser:example.com), Carol (user id @carol:example.com) and Dan (user id @dan:matrix.org) are in conversation, Alice's client should show the room name as "Carol and 2 others".

  4. If the room has no m.room.name or m.room.canonical_alias events, and no active members other than the current user, clients should consider m.room.member events with membership: leave. If such events exist, a display name such as "Empty room (was <user1> and <N> others)" (or a localised variant thereof) should be used, following similar rules as for active members (see above).

  5. A complete absence of m.room.name, m.room.canonical_alias, and m.room.member events is likely to indicate a problem with creating the room or synchronising the state table; however clients should still handle this situation. A display name such as "Empty room" (or a localised variant thereof) should be used in this situation.

Clients SHOULD NOT use m.room.aliases events as a source for room names, as it is difficult for clients to agree on the best alias to use, and aliases can change unexpectedly.

5.2.3   Server behaviour

Homeservers SHOULD reject m.room.message events which don't have a msgtype key, or which don't have a textual body key, with an HTTP status code of 400.

5.2.4   Security considerations

Messages sent using this module are not encrypted. Messages can be encrypted using the E2E module.

Clients should sanitise all displayed keys for unsafe HTML to prevent Cross-Site Scripting (XSS) attacks. This includes room names and topics.

5.3   Voice over IP

This module outlines how two users in a room can set up a Voice over IP (VoIP) call to each other. Voice and video calls are built upon the WebRTC 1.0 standard. Call signalling is achieved by sending message events to the room. As a result, this means that clients MUST only send call events to rooms with exactly two participants as currently the WebRTC standard is based around two-party communication.

5.3.1   Events

5.3.1.1   m.call.invite

Message Event

This event is sent by the caller when they wish to establish a call.

Content Key Type Description
call_id string A unique identifer for the call.
lifetime integer The time in milliseconds that the invite is valid for. Once the invite age exceeds this value, clients should discard it. They should also no longer show the call as awaiting an answer in the UI.
offer {Offer} The session description object
version integer The version of the VoIP specification this message adheres to. This specification is version 0.

Offer

Offer Key Type Description
sdp string The SDP text of the session description.
type string The type of session description. Must be 'offer'.

Example:

{
    "age": 242352,
    "content": {
        "call_id": "12345",
        "lifetime": 60000,
        "offer": {
            "sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]",
            "type": "offer"
        },
        "version": 0
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "type": "m.call.invite",
    "user_id": "@example:localhost"
}

5.3.1.2   m.call.candidates

Message Event

This event is sent by callers after sending an invite and by the callee after answering. Its purpose is to give the other party additional ICE candidates to try using to communicate.

Content Key Type Description
call_id string The ID of the call this event relates to.
candidates [Candidate] Array of objects describing the candidates.
version integer The version of the VoIP specification this messages adheres to. This specification is version 0.

Candidate

Candidate Key Type Description
candidate string The SDP 'a' line of the candidate.
sdpMLineIndex number The index of the SDP 'm' line this candidate is intended for.
sdpMid string The SDP media type this candidate is intended for.

Example:

{
    "age": 242352,
    "content": {
        "call_id": "12345",
        "candidates": [
            {
                "candidate": "candidate:863018703 1 udp 2122260223 10.9.64.156 43670 typ host generation 0",
                "sdpMLineIndex": 0,
                "sdpMid": "audio"
            }
        ],
        "version": 0
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "type": "m.call.candidates",
    "user_id": "@example:localhost"
}

5.3.1.3   m.call.answer

Message Event

This event is sent by the callee when they wish to answer the call.

Content Key Type Description
answer {Answer} The session description object
call_id string The ID of the call this event relates to.
version number  

Answer

Answer Key Type Description
sdp string The SDP text of the session description.
type string The type of session description. Must be 'answer'.

Example:

{
    "age": 242352,
    "content": {
        "answer": {
            "sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]",
            "type": "answer"
        },
        "call_id": "12345",
        "lifetime": 60000,
        "version": 0
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "type": "m.call.answer",
    "user_id": "@example:localhost"
}

5.3.1.4   m.call.hangup

Message Event

Sent by either party to signal their termination of the call. This can be sent either once the call has has been established or before to abort the call.

Content Key Type Description
call_id string The ID of the call this event relates to.
version integer The version of the VoIP specification this message adheres to. This specification is version 0.

Example:

{
    "age": 242352,
    "content": {
        "call_id": "12345",
        "version": 0
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "type": "m.call.hangup",
    "user_id": "@example:localhost"
}

5.3.2   Client behaviour

A call is set up with message events exchanged as follows:

  Caller                    Callee
[Place Call]
m.call.invite ----------->
m.call.candidate -------->
[..candidates..] -------->
                          [Answers call]
         <--------------- m.call.answer
   [Call is active and ongoing]
         <--------------- m.call.hangup

Or a rejected call:

  Caller                      Callee
m.call.invite ------------>
m.call.candidate --------->
[..candidates..] --------->
                           [Rejects call]
           <-------------- m.call.hangup

Calls are negotiated according to the WebRTC specification.

5.3.2.1   Glare

"Glare" is a problem which occurs when two users call each other at roughly the same time. This results in the call failing to set up as there already is an incoming/outgoing call. A glare resolution algorithm can be used to determine which call to hangup and which call to answer. If both clients implement the same algorithm then they will both select the same call and the call will be successfully connected.

As calls are "placed" to rooms rather than users, the glare resolution algorithm outlined below is only considered for calls which are to the same room. The algorithm is as follows:

  • If an m.call.invite to a room is received whilst the client is preparing to send an m.call.invite to the same room:
    • the client should cancel its outgoing call and instead automatically accept the incoming call on behalf of the user.
  • If an m.call.invite to a room is received after the client has sent an m.call.invite to the same room and is waiting for a response:
    • the client should perform a lexicographical comparison of the call IDs of the two calls and use the lesser of the two calls, aborting the greater. If the incoming call is the lesser, the client should accept this call on behalf of the user.

The call setup should appear seamless to the user as if they had simply placed a call and the other party had accepted. This means any media stream that had been setup for use on a call should be transferred and used for the call that replaces it.

5.3.3   Server behaviour

The homeserver MAY provide a TURN server which clients can use to contact the remote party. The following HTTP API endpoints will be used by clients in order to get information about the TURN server.

5.3.3.1   GET /_matrix/client/api/v1/turnServer

This API provides credentials for the client to use when initiating calls.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

No parameters

Response format:

Parameter Type Description
password string The password to use.
ttl integer The time-to-live in seconds
uris [string] A list of TURN URIs
username string The username to use.

Example request:

GET /_matrix/client/api/v1/turnServer HTTP/1.1

Response:

Status code 200:

The TURN server credentials.

Example

{
  "username":"1443779631:@user:example.com",
  "password":"JlKfBy1QwLrO20385QyAtEyIv0=",
  "uris":[
    "turn:turn.example.com:3478?transport=udp",
    "turn:10.20.30.40:3478?transport=tcp",
    "turns:10.20.30.40:443?transport=tcp"
  ],
  "ttl":86400
}

5.3.4   Security considerations

Calls should only be placed to rooms with one other user in them. If they are placed to group chat rooms it is possible that another user will intercept and answer the call.

5.4   Typing Notifications

Users may wish to be informed when another user is typing in a room. This can be achieved using typing notifications. These are ephemeral events scoped to a room_id. This means they do not form part of the Event Graph but still have a room_id key.

5.4.1   Events

5.4.1.1   m.typing

Typing Event

Informs the client of the list of users currently typing.

Content Key Type Description
user_ids [string] The list of user IDs typing in this room, if any.

Example:

{
    "content": {
        "user_ids": [
            "@alice:matrix.org",
            "@bob:example.com"
        ]
    },
    "room_id": "!z0mnsuiwhifuhwwfw:matrix.org",
    "type": "m.typing"
}

5.4.2   Client behaviour

When a client receives an m.typing event, it MUST use the user ID list to REPLACE its knowledge of every user who is currently typing. The reason for this is that the server does not remember users who are not currently typing as that list gets big quickly. The client should mark as not typing any user ID who is not in that list.

It is recommended that clients store a boolean indicating whether the user is typing or not. Whilst this value is true a timer should fire periodically every N seconds to send a typing HTTP request. The value of N is recommended to be no more than 20-30 seconds. This request should be re-sent by the client to continue informing the server the user is still typing. As subsequent requests will replace older requests, a safety margin of 5 seconds before the expected timeout runs out is recommended. When the user stops typing, the state change of the boolean to false should trigger another HTTP request to inform the server that the user has stopped typing.

5.4.2.1   PUT /_matrix/client/api/v1/rooms/{roomId}/typing/{userId}

This tells the server that the user is typing for the next N milliseconds where N is the value specified in the timeout key. Alternatively, if typing is false, it tells the server that the user has stopped typing.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
userId string Required. The user who has started to type.
roomId string Required. The room in which the user is typing.
JSON body parameters
timeout integer The length of time in milliseconds to mark this user as typing.
typing boolean Required. Whether the user is typing or not. If false, the timeout key can be omitted.

Example request:

PUT /_matrix/client/api/v1/rooms/%21wefh3sfukhs%3Aexample.com/typing/%40alice%3Aexample.com HTTP/1.1
Content-Type: application/json

{
  "typing": true,
  "timeout": 30000
}

Response:

Status code 200:

The new typing state was set.

Example

{}

5.4.3   Server behaviour

Servers MUST emit typing EDUs in a different form to m.typing events which are shown to clients. This form looks like:

{
  "type": "m.typing",
  "content": {
    "room_id": "!room-id-here:matrix.org",
    "user_id": "@user-id-here:matrix.org",
    "typing": true/false
  }
}

This does not contain timing information so it is up to originating homeservers to ensure they eventually send "stop" notifications.

5.4.4   Security considerations

Clients may not wish to inform everyone in a room that they are typing and instead only specific users in the room.

5.5   Receipts

This module adds in support for receipts. These receipts are a form of acknowledgement of an event. This module defines a single acknowledgement: m.read which indicates that the user has read up to a given event.

Sending a receipt for each event can result in sending large amounts of traffic to a homeserver. To prevent this from becoming a problem, receipts are implemented using "up to" markers. This marker indicates that the acknowledgement applies to all events "up to and including" the event specified. For example, marking an event as "read" would indicate that the user had read all events up to the referenced event.

5.5.1   Events

Each user_id, receipt_type pair must be associated with only a single event_id.

5.5.1.1   m.receipt

Receipt Event

Informs the client of new receipts.

Content Key Type Description
$EVENT_ID {Receipts} The mapping of event ID to a collection of receipts for this event ID. The event ID is the ID of the event being acknowledged and not an ID for the receipt itself.

Receipts

Receipts Key Type Description
m.read {Users} A collection of users who have sent m.read receipts for this event.

Users

Users Key Type Description
$USER_ID {Receipt} The mapping of user ID to receipt. The user ID is the entity who sent this receipt.

Receipt

Receipt Key Type Description
ts number The timestamp the receipt was sent at.

Example:

{
    "content": {
        "$1435641916114394fHBLK:matrix.org": {
            "m.read": {
                "@rikj:jki.re": {
                    "ts": 1436451550453
                }
            }
        }
    },
    "room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org",
    "type": "m.receipt"
}

5.5.2   Client behaviour

In v1 /initialSync, receipts are listed in a separate top level receipts key. In v2 /sync, receipts are contained in the ephemeral block for a room. New receipts that come down the event streams are deltas which update existing mappings. Clients should replace older receipt acknowledgements based on user_id and receipt_type pairs. For example:

Client receives m.receipt:
  user = @alice:example.com
  receipt_type = m.read
  event_id = $aaa:example.com

Client receives another m.receipt:
  user = @alice:example.com
  receipt_type = m.read
  event_id = $bbb:example.com

The client should replace the older acknowledgement for $aaa:example.com with
this one for $bbb:example.com

Clients should send read receipts when there is some certainty that the event in question has been displayed to the user. Simply receiving an event does not provide enough certainty that the user has seen the event. The user SHOULD need to take some action such as viewing the room that the event was sent to or dismissing a notification in order for the event to count as "read".

A client can update the markers for its user by interacting with the following HTTP APIs.

5.5.2.1   POST /_matrix/client/v2_alpha/rooms/{roomId}/receipt/{receiptType}/{eventId}

This API updates the marker for the given receipt type to the event ID specified.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room in which to send the event.
receiptType enum Required. The type of receipt to send. One of: ["m.read"]
eventId string Required. The event ID to acknowledge up to.

Example request:

POST /_matrix/client/v2_alpha/rooms/%21wefuh21ffskfuh345%3Aexample.com/receipt/m.read/%241924376522eioj%3Aexample.com HTTP/1.1
Content-Type: application/json

{}

Response:

Status code 200:

The receipt was sent.

Example

{}

5.5.3   Server behaviour

For efficiency, receipts SHOULD be batched into one event per room before delivering them to clients.

Receipts are sent across federation as EDUs with type m.receipt. The format of the EDUs are:

{
    <room_id>: {
        <receipt_type>: {
            <user_id>: { <content> }
        },
        ...
    },
    ...
}

These are always sent as deltas to previously sent receipts. Currently only a single <receipt_type> should be used: m.read.

5.5.4   Security considerations

As receipts are sent outside the context of the event graph, there are no integrity checks performed on the contents of m.receipt events.

5.6   Presence

Each user has the concept of presence information. This encodes:

  • Whether the user is currently online
  • How recently the user was last active (as seen by the server)
  • Whether a given client considers the user to be currently idle
  • Arbitrary information about the user's current status (e.g. "in a meeting").

This information is collated from both per-device (online, idle, last_active) and per-user (status) data, aggregated by the user's homeserver and transmitted as an m.presence event. This is one of the few events which are sent outside the context of a room. Presence events are sent to all users who subscribe to this user's presence through a presence list or by sharing membership of a room.

A presence list is a list of user IDs whose presence the user wants to follow. To be added to this list, the user being added must be invited by the list owner who must accept the invitation.

User's presence state is represented by the presence key, which is an enum of one of the following:

  • online : The default state when the user is connected to an event stream.
  • unavailable : The user is not reachable at this time e.g. they are idle.
  • offline : The user is not connected to an event stream or is explicitly suppressing their profile information from being sent.
  • free_for_chat : The user is generally willing to receive messages moreso than default.

5.6.1   Events

5.6.1.1   m.presence

Presence Event

Informs the client of a user's presence state change.

Content Key Type Description
avatar_url string The current avatar URL for this user, if any.
displayname string The current display name for this user, if any.
last_active_ago number The last time since this used performed some action, in milliseconds.
presence enum The presence state for this user. One of: ["online", "offline", "unavailable", "free_for_chat", "hidden"]
user_id string The user's ID.

Example:

{
    "content": {
        "avatar_url": "mxc://localhost:wefuiwegh8742w",
        "last_active_ago": 2478593,
        "presence": "online",
        "user_id": "@example:localhost"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "type": "m.presence"
}

5.6.2   Client behaviour

Clients can manually set/get their presence/presence list using the HTTP APIs listed below.

5.6.2.1   PUT /_matrix/client/api/v1/presence/{userId}/status

This API sets the given user's presence state. When setting the status, the activity time is updated to reflect that activity; the client does not need to specify the last_active_ago field. You cannot set the presence state of another user.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
userId string Required. The user whose presence state to update.
JSON body parameters
status_msg string The status message to attach to this state.
presence string Required. The new presence state.

Example request:

PUT /_matrix/client/api/v1/presence/%40alice%3Aexample.com/status HTTP/1.1
Content-Type: application/json

{
  "presence": "online",
  "status_msg": "I am here."
}

Response:

Status code 200:

The new presence state was set.

Example

{}

5.6.2.2   GET /_matrix/client/api/v1/presence/{userId}/status

Get the given user's presence state.

Request format:

Parameter Type Description
path parameters
userId string Required. The user whose presence state to get.

Response format:

Parameter Type Description
last_active_ago integer The length of time in milliseconds since an action was performed by this user.
presence enum This user's presence. One of: ["online", "offline", "unavailable", "free_for_chat"]
status_msg string or null The state message for this user if one was set.

Example request:

GET /_matrix/client/api/v1/presence/%40alice%3Aexample.com/status HTTP/1.1

Response:

Status code 200:

The presence state for this user.

Example

{
  "presence": "unavailable",
  "last_active_ago": 420845,
  "status_msg": null
}

5.6.2.3   POST /_matrix/client/api/v1/presence/list/{userId}

Adds or removes users from this presence list.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
userId string Required. The user whose presence list is being modified.
JSON body parameters
drop array[string] A list of user IDs to remove from the list.
invite array[string] A list of user IDs to add to the list.

Example request:

POST /_matrix/client/api/v1/presence/list/%40alice%3Aexample.com HTTP/1.1
Content-Type: application/json

{
  "invite": [
    "@bob:matrix.org"
  ],
  "drop": [
    "@alice:matrix.org"
  ]
}

Response:

Status code 200:

The list was updated.

Example

{}

5.6.2.4   GET /_matrix/client/api/v1/presence/list/{userId}

Retrieve a list of presence events for every user on this list.

Request format:

Parameter Type Description
path parameters
userId string Required. The user whose presence list should be retrieved.

Response format:

Parameter Type Description
N/A [PresenceEvent]  

Example request:

GET /_matrix/client/api/v1/presence/list/%40alice%3Aexample.com HTTP/1.1

Response:

Status code 200:

A list of presence events for this list.

Example

[
  {
    "content": {
      "avatar_url": "mxc://matrix.org/AfwefuigfWEfhuiPP",
      "displayname": "Alice Margatroid",
      "last_active_ago": 395,
      "presence": "offline",
      "user_id": "@alice:matrix.org"
    },
    "type": "m.presence"
  },
  {
    "content": {
      "avatar_url": "mxc://matrix.org/FWEhuiwegfWEfhuiwf",
      "displayname": "Marisa Kirisame",
      "last_active_ago": 16874,
      "presence": "online",
      "user_id": "@marisa:matrix.org"
    },
    "type": "m.presence"
  }
]

5.6.2.5   Idle timeout

Clients SHOULD implement an "idle timeout". This is a timer which fires after a period of inactivity on the client. The definition of inactivity varies depending on the client. For example, web implementations may determine inactivity to be not moving the mouse for a certain period of time. When this timer fires it should set the presence state to unavailable. When the user becomes active again (e.g. by moving the mouse) the client should set the presence state to online. A timeout value between 1 and 5 minutes is recommended.

5.6.3   Server behaviour

Each user's home server stores a "presence list" per user. Once a user accepts a presence list, both user's HSes must track the subscription.

5.6.3.1   Propagating profile information

Because the profile display name and avatar information are likely to be used in many places of a client's display, changes to these fields SHOULD cause an automatic propagation event to occur, informing likely-interested parties of the new values. One of these change mechanisms SHOULD be via m.presence events. These events should set displayname and avatar_url to the new values along with the presence-specific keys. This SHOULD be done automatically by the home server when a user successfully changes their display name or avatar URL.

Rationale

The intention for sending this information in m.presence is so that any "user list" can display the current name/presence for a user ID outside the scope of a room e.g. for a user page. This is bundled into a single event for several reasons. The user's display name can change per room. This event provides the "canonical" name for the user. In addition, the name is bundled into a single event for the ease of client implementations. If this was not done, the client would need to search all rooms for their own membership event to pull out the display name.

5.6.3.2   Last active ago

The server maintains a timestamp of the last time it saw a pro-active event from the user. A pro-active event may be sending a message to a room or changing presence state to a higher level of availability. Levels of availability are defined from low to high as follows:

  • offline
  • unavailable
  • online
  • free_for_chat

Based on this list, changing state from unavailable to online counts as a pro-active event, whereas online to unavailable does not. This timestamp is presented via a key called last_active_ago which gives the relative number of milliseconds since the pro-active event.

5.6.4   Security considerations

Presence information is shared with all users who share a room with the target user. In large public rooms this could be undesirable.

5.7   Content repository

This module allows users to upload content to their homeserver which is retrievable from other homeservers. Its' purpose is to allow users to share attachments in a room. Content locations are represented as Matrix Content (MXC) URIs. They look like:

mxc://<server-name>/<media-id>

<server-name> : The name of the homeserver where this content originated, e.g. matrix.org
<media-id> : An opaque ID which identifies the content.

Uploads are POSTed to a resource on the user's local homeserver which returns a token which is used to GET the download. Content is downloaded from the recipient's local homeserver, which must first transfer the content from the origin homeserver using the same API (unless the origin and destination homeservers are the same).

5.7.1   Client behaviour

Clients can upload and download content using the following HTTP APIs.

5.7.1.1   GET /_matrix/media/v1/download/{serverName}/{mediaId}

Download content from the content repository.

Request format:

Parameter Type Description
path parameters
serverName string Required. The server name from the mxc:// URI (the authoritory component)
mediaId string Required. The media ID from the mxc:// URI (the path component)

Response format:

Parameter Type Description
<file> file The content that was previously uploaded.
Content-Type Header<string> The content type of the file that was previously uploaded.
Content-Disposition Header<string> The name of the file that was previously uploaded, if set.

Example request:

GET /_matrix/media/v1/download/matrix.org/ascERGshawAWawugaAcauga HTTP/1.1

5.7.1.2   GET /_matrix/media/v1/thumbnail/{serverName}/{mediaId}

Download a thumbnail of the content from the content repository.

Request format:

Parameter Type Description
path parameters
serverName string Required. The server name from the mxc:// URI (the authoritory component)
mediaId string Required. The media ID from the mxc:// URI (the path component)
query parameters
width integer The desired width of the thumbnail. The actual thumbnail may not match the size specified.
height integer The desired height of the thumbnail. The actual thumbnail may not match the size specified.
method enum The desired resizing method. One of: ["crop", "scale"]

Response format:

Parameter Type Description
<file> file A thumbnail of the requested content.
Content-Type Header<string> The content type of the thumbnail.

Example request:

GET /_matrix/media/v1/thumbnail/matrix.org/ascERGshawAWawugaAcauga?width=64&height=64&method=scale HTTP/1.1

5.7.1.3   POST /_matrix/media/v1/upload

Upload some content to the content repository.

Request format:

Parameter Type Description
body parameters
<content> byte Required. The content to be uploaded.
header parameters
Content-Type string The content type of the file being uploaded

Response format:

Parameter Type Description
content_uri string The MXC URI to the uploaded content.

Example request:

POST /_matrix/media/v1/upload HTTP/1.1
Content-Type: application/json

<bytes>

Response:

Status code 200:

The MXC URI for the uploaded content.

Example

{
  "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"
}

5.7.1.4   Thumbnails

The thumbnail methods are "crop" and "scale". "scale" tries to return an image where either the width or the height is smaller than the requested size. The client should then scale and letterbox the image if it needs to fit within a given rectangle. "crop" tries to return an image where the width and height are close to the requested size and the aspect matches the requested size. The client should scale the image if it needs to fit within a given rectangle.

In summary:
  • "scale" maintains the original aspect ratio of the image
  • "crop" provides an image in the aspect ratio of the sizes given in the request

5.7.2   Server behaviour

Homeservers may generate thumbnails for content uploaded to remote homeservers themselves or may rely on the remote homeserver to thumbnail the content. Homeservers may return thumbnails of a different size to that requested. However homeservers should provide exact matches where reasonable. Homeservers must never upscale images.

5.7.3   Security considerations

The HTTP GET endpoint does not require any authentication. Knowing the URL of the content is sufficient to retrieve the content, even if the entity isn't in the room.

MXC URIs are vulnerable to directory traversal attacks such as mxc://127.0.0.1/../../../some_service/etc/passwd. This would cause the target homeserver to try to access and return this file. As such, homeservers MUST sanitise MXC URIs by allowing only alphanumeric (A-Za-z0-9), _ and - characters in the server-name and media-id values. This set of whitelisted characters allows URL-safe base64 encodings specified in RFC 4648. Applying this character whitelist is preferable to blacklisting . and / as there are techniques around blacklisted characters (percent-encoded characters, UTF-8 encoded traversals, etc).

Homeservers have additional content-specific concerns:

  • Clients may try to upload very large files. Homeservers should not store files that are too large and should not serve them to clients.
  • Clients may try to upload very large images. Homeservers should not attempt to generate thumbnails for images that are too large.
  • Remote homeservers may host very large files or images. Homeservers should not proxy or thumbnail large files or images from remote homeservers.
  • Clients may try to upload a large number of files. Homeservers should limit the number and total size of media that can be uploaded by clients.
  • Clients may try to access a large number of remote files through a homeserver. Homeservers should restrict the number and size of remote files that it caches.
  • Clients or remote homeservers may try to upload malicious files targeting vulnerabilities in either the homeserver thumbnailing or the client decoders.

5.8   End-to-End Encryption

Matrix optionally supports end-to-end encryption, allowing rooms to be created whose conversation contents is not decryptable or interceptable on any of the participating homeservers.

End-to-end crypto is still being designed and prototyped - notes on the design may be found at https://lwn.net/Articles/634144/

5.9   Room History Visibility

This module adds support for controlling the visibility of previous events in a room.

In all cases except world_readable, a user needs to join a room to view events in that room. Once they have joined a room, they will gain access to a subset of events in the room. How this subset is chosen is controlled by the m.room.history_visibility event outlined below. After a user has left a room, they may seen any events which they were allowed to see before they left the room, but no events received after they left.

The four options for the m.room.history_visibility event are:

  • shared - Previous events are always accessible to newly joined members. All events in the room are accessible, even those sent when the member was not a part of the room.
  • invited - Previous events are accessible to newly joined members from the point they were invited onwards. Events stop being accessible when the member's state changes to something other than invite or join.
  • joined - Previous events are accessible to newly joined members from the point they joined the room onwards. Events stop being accessible when the member's state changes to something other than join.
  • world_readable - All events while this is the m.room.history_visibility value may be shared by any participating homeserver with anyone, regardless of whether they have ever joined the room.

Warning

These options are applied at the point an event is sent. Checks are performed with the state of the m.room.history_visibility event when the event in question is added to the DAG. This means clients cannot retrospectively choose to show or hide history to new users if the setting at that time was more restrictive.

5.9.1   Events

5.9.1.1   m.room.history_visibility

State Event
state_key: A zero-length string.

This event controls whether a user can see the events that happened in a room from before they joined.

Content Key Type Description
history_visibility enum Who can see the room history. One of: ["invited", "joined", "shared", "world_readable"]

Example:

{
    "age": 242352,
    "content": {
        "history_visibility": "shared"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "state_key": "",
    "type": "m.room.history_visibility",
    "user_id": "@example:localhost"
}

5.9.2   Client behaviour

Clients that implement this module MUST present to the user the possible options for setting history visibility when creating a room.

Clients may want to display a notice that their events may be read by non-joined people if the value is set to world_readable.

5.9.3   Server behaviour

By default if no history_visibility is set, or if the value is not understood, the visibility is assumed to be shared. The rules governing whether a user is allowed to see an event depend solely on the state of the room at that event:

  1. If the user was joined, allow.
  2. If the user was invited and the history_visibility was set to invited or shared, allow.
  3. If the user was neither invited nor joined but the history_visibility was set to shared, allow.
  4. Otherwise, deny.

5.9.4   Security considerations

The default value for history_visibility is shared for backwards-compatibility reasons. Clients need to be aware that by not setting this event they are exposing all of their room history to anyone in the room.

5.10   Push Notifications

                                  +--------------------+  +-------------------+
                 Matrix HTTP      |                    |  |                   |
            Notification Protocol |   App Developer    |  |   Device Vendor   |
                                  |                    |  |                   |
          +-------------------+   | +----------------+ |  | +---------------+ |
          |                   |   | |                | |  | |               | |
          | Matrix Home Server+----->  Push Gateway  +------> Push Provider | |
          |                   |   | |                | |  | |               | |
          +-^-----------------+   | +----------------+ |  | +----+----------+ |
            |                     |                    |  |      |            |
   Matrix   |                     |                    |  |      |            |
Client/Server API  +              |                    |  |      |            |
            |      |              +--------------------+  +-------------------+
            |   +--+-+                                           |
            |   |    <-------------------------------------------+
            +---+    |
                |    |          Provider Push Protocol
                +----+

        Mobile Device or Client

This module adds support for push notifications. Homeservers send notifications of events to user-configured HTTP endpoints. Users may also configure a number of rules that determine which events generate notifications. These are all stored and managed by the user's homeserver. This allows user-specific push settings to be reused between client applications.

The above diagram shows the flow of push notifications being sent to a handset where push notifications are submitted via the handset vendor, such as Apple's APNS or Google's GCM. This happens as follows:

  1. The client app signs in to a homeserver.
  2. The client app registers with its vendor's Push Provider and obtains a routing token of some kind.
  3. The mobile app uses the Client/Server API to add a 'pusher', providing the URL of a specific Push Gateway which is configured for that application. It also provides the routing token it has acquired from the Push Provider.
  4. The homeserver starts sending HTTP requests to the Push Gateway using the supplied URL. The Push Gateway relays this notification to the Push Provider, passing the routing token along with any necessary private credentials the provider requires to send push notifications.
  5. The Push Provider sends the notification to the device.

Definitions for terms used in this section are below:

Push Provider
A push provider is a service managed by the device vendor which can send notifications directly to the device. Google Cloud Messaging (GCM) and Apple Push Notification Service (APNS) are two examples of push providers.
Push Gateway
A push gateway is a server that receives HTTP event notifications from homeservers and passes them on to a different protocol such as APNS for iOS devices or GCM for Android devices. Clients inform the homeserver which Push Gateway to send notifications to when it sets up a Pusher.
Pusher
A pusher is a worker on the homeserver that manages the sending of HTTP notifications for a user. A user can have multiple pushers: one per device.
Push Rule
A push rule is a single rule that states under what conditions an event should be passed onto a push gateway and how the notification should be presented. These rules are stored on the user's homeserver. They are manually configured by the user, who can create and view them via the Client/Server API.
Push Ruleset
A push ruleset scopes a set of rules according to some criteria. For example, some rules may only be applied for messages from a particular sender, a particular room, or by default. The push ruleset contains the entire set of scopes and rules.

5.10.1   Client behaviour

Clients MUST configure a Pusher before they will receive push notifications. There is a single API endpoint for this, as described below.

5.10.1.1   POST /_matrix/client/api/v1/pushers/set

This endpoint allows the creation, modification and deletion of pushers for this user ID. The behaviour of this endpoint varies depending on the values in the JSON body.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
JSON body parameters
lang string Required. The preferred language for receiving notifications (e.g. 'en' or 'en-US')
kind string Required. The kind of pusher to configure. "http" makes a pusher that sends HTTP pokes. null deletes the pusher.
app_display_name string Required. A string that will allow the user to identify what application owns this pusher.
device_display_name string Required. A string that will allow the user to identify what device owns this pusher.
app_id string Required. This is a reverse-DNS style identifier for the application. It is recommended that this end with the platform, such that different platform versions get different app identifiers. Max length, 64 chars.
profile_tag string Required. This is a string that determines what set of device rules will be matched when evaluating push rules for this pusher. It is an arbitrary string. Multiple devices may use the same profile_tag. It is advised that when an app's data is copied or restored to a different device, this value remain the same. Client apps should offer ways to change the profile_tag, optionally copying rules from the old profile tag. Max length, 32 bytes.
pushkey string Required. This is a unique identifier for this pusher. The value you should use for this is the routing or destination address information for the notification, for example, the APNS token for APNS or the Registration ID for GCM. If your notification client has no such concept, use any unique identifier. Max length, 512 bytes.
data object Required. A dictionary of information for the pusher implementation itself. If kind is http, this should contain url which is the URL to use to send notifications to.
data.url string Required if kind is http. The URL to use to send notifications to.
append boolean If true, the homeserver should add another pusher with the given pushkey and App ID in addition to any others with different user IDs. Otherwise, the Home Server must remove any other pushers with the same App ID and pushkey for different users. The default is false.

Example request:

POST /_matrix/client/api/v1/pushers/set HTTP/1.1
Content-Type: application/json

{
  "lang": "en",
  "kind": "http",
  "app_display_name": "Mat Rix",
  "device_display_name": "iPhone 9",
  "app_id": "com.example.app.ios",
  "profile_tag": "4bea66906d0111e59d70feff819cdc9f",
  "pushkey": "APA91bHPRgkF3JUikC4ENAHEeMrd41Zxv3hVZjC9KtT8OvPVGJ-hQMRKRrZuJAEcl7B338qju59zJMjw2DELjzEvxwYv7hH5Ynpc1ODQ0aT4U4OFEeco8ohsN5PjL1iC2dNtk2BAokeMCg2ZXKqpc8FXKmhX94kIxQ",
  "data": {
    "url": "https://push-gateway.location.here"
  },
  "append": false
}

Responses:

Status code 200:

The pusher was set.

Example

{}

Status code 400:

One or more of the pusher values were invalid.

Example

{
  "error": "Missing parameters: lang, data",
  "errcode": "M_MISSING_PARAM"
}

5.10.1.2   Push Rules

A push rule is a single rule that states under what conditions an event should be passed onto a push gateway and how the notification should be presented. There are different "kinds" of push rules and each rule has an associated priority. Every push rule MUST have a kind and rule_id. The rule_id is a unique string within the kind of rule and its' scope: rule_ids do not need to be unique between rules of the same kind on different devices. Rules may have extra keys depending on the value of kind.The different kinds of rule in descending order of priority are:

Override Rules override
The highest priority rules are user-configured overrides.
Content-specific Rules content
These configure behaviour for (unencrypted) messages that match certain patterns. Content rules take one parameter: pattern, that gives the glob pattern to match against. This is treated in the same way as pattern for event_match.
Room-specific Rules room
These rules change the behaviour of all messages for a given room. The rule_id of a room rule is always the ID of the room that it affects.
Sender-specific rules sender
These rules configure notification behaviour for messages from a specific Matrix user ID. The rule_id of Sender rules is always the Matrix user ID of the user whose messages they'd apply to.
Underride rules underride
These are identical to override rules, but have a lower priority than content, room and sender rules.

Push rules may be either global or device-specific. Device specific rules only affect delivery of notifications via pushers with a matching profile_tag. All device-specific rules have a higher priority than global rules. This means that the full list of rule kinds, in descending priority order, is as follows:

  • Device-specific Override
  • Device-specific Content
  • Device-specific Room
  • Device-specific Sender
  • Device-specific Underride
  • Global Override
  • Global Content
  • Global Room
  • Global Sender
  • Global Underride

Rules with the same kind can specify an ordering priority. This determines which rule is selected in the event of multiple matches. For example, a rule matching "tea" and a separate rule matching "time" would both match the sentence "It's time for tea". The ordering of the rules would then resolve the tiebreak to determine which rule is executed. Only actions for highest priority rule will be sent to the Push Gateway.

Each rule can be enabled or disabled. Disabled rules never match. If no rules match an event, the homeserver MUST NOT notify the Push Gateway for that event. Homeservers MUST NOT notify the Push Gateway for events that the user has sent themselves.

5.10.1.2.1   Actions

All rules have an associated list of actions. An action affects if and how a notification is delivered for a matching event. The following actions are defined:

notify
This causes each matching event to generate a notification.
dont_notify
This prevents each matching event from generating a notification
coalesce
This enables notifications for matching events but activates homeserver specific behaviour to intelligently coalesce multiple events into a single notification. Not all homeservers may support this. Those that do not support it should treat it as the notify action.
set_tweak
Sets an entry in the tweaks dictionary key that is sent in the notification request to the Push Gateway. This takes the form of a dictionary with a set_tweak key whose value is the name of the tweak to set. It may also have a value key which is the value to which it should be set.

Actions that have no parameters are represented as a string. Otherwise, they are represented as a dictionary with a key equal to their name and other keys as their parameters, e.g. { "set_tweak": "sound", "value": "default" }

5.10.1.2.1.1   Tweaks

The set_tweak action is used to add an entry to the 'tweaks' dictionary that is sent in the notification request to the Push Gateway. The following tweaks are defined:

sound
A string representing the sound to be played when this notification arrives. A value of default means to play a default sound.
highlight
A boolean representing whether or not this message should be highlighted in the UI. This will normally take the form of presenting the message in a different colour and/or style. The UI might also be adjusted to draw particular attention to the room in which the event occurred. The value may be omitted from the highlight tweak, in which case it should default to true.

Tweaks are passed transparently through the homeserver so client applications and Push Gateways may agree on additional tweaks. For example, a tweak may be added to specify how to flash the notification light on a mobile device.

5.10.1.2.2   Predefined Rules

Homeservers can specify "server-default rules" which operate at a lower priority than "user-defined rules". The rule_id for all server-default rules MUST start with a dot (".") to identify them as "server-default". The following server-default rules are specified:

.m.rule.contains_user_name

Matches any message whose content is unencrypted and contains the local part of the user's Matrix ID, separated by word boundaries.

Definition (as a content rule):

{
    "rule_id": ".m.rule.contains_user_name"
    "pattern": "[the local part of the user's Matrix ID]",
    "actions": [
        "notify",
        {
            "set_tweak": "sound",
            "value": "default"
        }
    ],
}
.m.rule.contains_display_name

Matches any message whose content is unencrypted and contains the user's current display name in the room in which it was sent.

Definition (this rule can only be an override or underride rule):

{
    "rule_id": ".m.rule.contains_display_name"
    "conditions": [
        {
            "kind": "contains_display_name"
        }
    ],
    "actions": [
        "notify",
        {
            "set_tweak": "sound",
            "value": "default"
        }
    ],
}
.m.rule.room_one_to_one

Matches any message sent in a room with exactly two members.

Definition (this rule can only be an override or underride rule):

{
    "rule_id": ".m.rule.room_two_members"
    "conditions": [
        {
            "is": "2",
            "kind": "room_member_count"
        }
    ],
    "actions": [
        "notify",
        {
            "set_tweak": "sound",
            "value": "default"
        }
    ],
}
.m.rule.suppress_notices

Matches messages with a msgtype of notice. This should be an override rule so that it takes priority over content / sender / room rules.

Definition:

{
    'rule_id': '.m.rule.suppress_notices',
    'conditions': [
        {
            'kind': 'event_match',
            'key': 'content.msgtype',
            'pattern': 'm.notice',
        }
    ],
    'actions': [
        'dont-notify',
    ]
}
.m.rule.fallback

Matches any message. Used to define the behaviour of messages that match no other rules. If homeservers define this it should be the lowest priority underride rule.

Definition:

{
    "rule_id": ".m.rule.fallback"
    "conditions": [],
    "actions": [
        "notify"
    ],
}

5.10.1.2.3   Conditions

Override, Underride and Default Rules MAY have a list of 'conditions'. All conditions must hold true for an event in order to apply the action for the event. A rule with no conditions always matches. Room, Sender, User and Content rules do not have conditions in the same way, but instead have predefined conditions. These conditions can be configured using the parameters outlined below. In the cases of room and sender rules, the rule_id of the rule determines its behaviour. The following conditions are defined:

event_match

This is a glob pattern match on a field of the event. Parameters:

  • key: The dot-separated field of the event to match, e.g. content.body
  • pattern: The glob-style pattern to match against. Patterns with no special glob characters should be treated as having asterisks prepended and appended when testing the condition.
profile_tag

Matches the profile_tag of the device that the notification would be delivered to. Parameters:

  • profile_tag: The profile_tag to match with.
contains_display_name
This matches unencrypted messages where content.body contains the owner's display name in that room. This is a separate rule because display names may change and as such it would be hard to maintain a rule that matched the user's display name. This condition has no parameters.
room_member_count

This matches the current number of members in the room. Parameters:

  • is: A decimal integer optionally prefixed by one of, ==, <, >, >= or <=. A prefix of < matches rooms where the member count is strictly less than the given number and so forth. If no prefix is present, this parameter defaults to ==.

5.10.1.3   Push Rules: API

Clients can retrieve, add, modify and remove push rules globally or per-device using the APIs below.

5.10.1.3.1   GET /_matrix/client/api/v1/pushrules/

Retrieve all push rulesets for this user. Clients can "drill-down" on the rulesets by suffixing a scope to this path e.g. /pushrules/global/. This will return a subset of this data under the specified key e.g. the global key.

Requires auth:Yes.

Request format:

No parameters

Response format:

Parameter Type Description
device {$PROFILE_TAG: Ruleset} A dictionary of profile tags to rulesets.
global {Ruleset} The global ruleset.

Ruleset

Parameter Type Description
content [PushRule]  
override [PushRule]  
room [PushRule]  
sender [PushRule]  
underride [PushRule]  

PushRule

Parameter Type Description
actions [object or string]  
default boolean  
enabled boolean  
rule_id string  

Example request:

GET /_matrix/client/api/v1/pushrules/ HTTP/1.1

Response:

Status code 200:

All the push rulesets for this user.

Example

{
  "device": {},
  "global": {
      "content": [
          {
              "actions": [
                  "notify",
                  {
                      "set_tweak": "sound",
                      "value": "default"
                  },
                  {
                      "set_tweak": "highlight"
                  }
              ],
              "default": true,
              "enabled": true,
              "pattern": "alice",
              "rule_id": ".m.rule.contains_user_name"
          }
      ],
      "override": [
          {
              "actions": [
                  "dont_notify"
              ],
              "conditions": [],
              "default": true,
              "enabled": false,
              "rule_id": ".m.rule.master"
          },
          {
              "actions": [
                  "dont_notify"
              ],
              "conditions": [
                  {
                      "key": "content.msgtype",
                      "kind": "event_match",
                      "pattern": "m.notice"
                  }
              ],
              "default": true,
              "enabled": true,
              "rule_id": ".m.rule.suppress_notices"
          }
      ],
      "room": [],
      "sender": [],
      "underride": [
          {
              "actions": [
                  "notify",
                  {
                      "set_tweak": "sound",
                      "value": "ring"
                  },
                  {
                      "set_tweak": "highlight",
                      "value": false
                  }
              ],
              "conditions": [
                  {
                      "key": "type",
                      "kind": "event_match",
                      "pattern": "m.call.invite"
                  }
              ],
              "default": true,
              "enabled": true,
              "rule_id": ".m.rule.call"
          },
          {
              "actions": [
                  "notify",
                  {
                      "set_tweak": "sound",
                      "value": "default"
                  },
                  {
                      "set_tweak": "highlight"
                  }
              ],
              "conditions": [
                  {
                      "kind": "contains_display_name"
                  }
              ],
              "default": true,
              "enabled": true,
              "rule_id": ".m.rule.contains_display_name"
          },
          {
              "actions": [
                  "notify",
                  {
                      "set_tweak": "sound",
                      "value": "default"
                  },
                  {
                      "set_tweak": "highlight",
                      "value": false
                  }
              ],
              "conditions": [
                  {
                      "is": "2",
                      "kind": "room_member_count"
                  }
              ],
              "default": true,
              "enabled": true,
              "rule_id": ".m.rule.room_one_to_one"
          },
          {
              "actions": [
                  "notify",
                  {
                      "set_tweak": "sound",
                      "value": "default"
                  },
                  {
                      "set_tweak": "highlight",
                      "value": false
                  }
              ],
              "conditions": [
                  {
                      "key": "type",
                      "kind": "event_match",
                      "pattern": "m.room.member"
                  },
                  {
                      "key": "content.membership",
                      "kind": "event_match",
                      "pattern": "invite"
                  },
                  {
                      "key": "state_key",
                      "kind": "event_match",
                      "pattern": "@alice:example.com"
                  }
              ],
              "default": true,
              "enabled": true,
              "rule_id": ".m.rule.invite_for_me"
          },
          {
              "actions": [
                  "notify",
                  {
                      "set_tweak": "highlight",
                      "value": false
                  }
              ],
              "conditions": [
                  {
                      "key": "type",
                      "kind": "event_match",
                      "pattern": "m.room.member"
                  }
              ],
              "default": true,
              "enabled": true,
              "rule_id": ".m.rule.member_event"
          },
          {
              "actions": [
                  "notify",
                  {
                      "set_tweak": "highlight",
                      "value": false
                  }
              ],
              "conditions": [
                  {
                      "key": "type",
                      "kind": "event_match",
                      "pattern": "m.room.message"
                  }
              ],
              "default": true,
              "enabled": true,
              "rule_id": ".m.rule.message"
          }
      ]
  }
}

5.10.1.3.2   PUT /_matrix/client/api/v1/pushrules/{scope}/{kind}/{ruleId}

This endpoint allows the creation, modification and deletion of pushers for this user ID. The behaviour of this endpoint varies depending on the values in the JSON body.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
scope string Required. Either global or device/<profile_tag> to specify global rules or device rules for the given profile_tag.
kind enum Required. The kind of rule One of: ["override", "underride", "sender", "room", "content"]
ruleId string Required. The identifier for the rule.
JSON body parameters
conditions array[object] The conditions that must hold true for an event in order for a rule to be applied to an event. A rule with no conditions always matches.
conditions[0].kind enum One of: ["event_match", "profile_tag", "contains_display_name", "room_member_count"]
actions array[string] Required. The action(s) to perform when the conditions for this rule are met.
query parameters
before string Use 'before' with a rule_id as its value to make the new rule the next-most important rule with respect to the given rule.
after string This makes the new rule the next-less important rule relative to the given rule.

Example request:

PUT /_matrix/client/api/v1/pushrules/global/room/%23spam%3Aexample.com?before=someRuleId&after=anotherRuleId HTTP/1.1
Content-Type: application/json

{
  "pattern": "cake*lie",
  "actions": ["notify"]
}

Responses:

Status code 200:

The pusher was set.

Example

{}

Status code 400:

There was a problem configuring this push rule.

Example

{
  "error": "before/after rule not found: someRuleId",
  "errcode": "M_UNKNOWN"
}

5.10.1.3.3   GET /_matrix/client/api/v1/pushrules/{scope}/{kind}/{ruleId}

Retrieve a single specified push rule.

Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
scope string Required. Either global or device/<profile_tag> to specify global rules or device rules for the given profile_tag.
kind enum Required. The kind of rule One of: ["override", "underride", "sender", "room", "content"]
ruleId string Required. The identifier for the rule.

Example request:

GET /_matrix/client/api/v1/pushrules/global/room/%23spam%3Aexample.com HTTP/1.1

Response:

Status code 200:

The specific push rule. This will also include keys specific to the rule itself such as the rule's actions and conditions if set.

Example

{
  "actions": [
      "dont_notify"
  ],
  "rule_id": "#spam:matrix.org",
  "enabled": true
}

5.10.1.3.4   DELETE /_matrix/client/api/v1/pushrules/{scope}/{kind}/{ruleId}

This endpoint removes the push rule defined in the path.

Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
scope string Required. Either global or device/<profile_tag> to specify global rules or device rules for the given profile_tag.
kind enum Required. The kind of rule One of: ["override", "underride", "sender", "room", "content"]
ruleId string Required. The identifier for the rule.

Example request:

DELETE /_matrix/client/api/v1/pushrules/global/room/%23spam%3Aexample.com HTTP/1.1

Response:

Status code 200:

The push rule was deleted.

Example

{}

5.10.1.3.5   PUT /_matrix/client/api/v1/pushrules/{scope}/{kind}/{ruleId}/enabled

This endpoint allows clients to enable or disable the specified push rule.

Requires auth:Yes.

Request format:

Parameter Type Description
body parameters
<body> boolean Required. Whether the push rule is enabled or not.
path parameters
scope string Required. Either global or device/<profile_tag> to specify global rules or device rules for the given profile_tag.
kind enum Required. The kind of rule One of: ["override", "underride", "sender", "room", "content"]
ruleId string Required. The identifier for the rule.

Example request:

PUT /_matrix/client/api/v1/pushrules/global/room/%23spam%3Aexample.com/enabled HTTP/1.1
Content-Type: application/json

true

Response:

Status code 200:

The push rule was enabled or disabled.

Example

{}

5.10.1.3.6   Examples

To create a rule that suppresses notifications for the room with ID !dj234r78wl45Gh4D:matrix.org:

curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/api/v1/pushrules/global/room/%21dj234r78wl45Gh4D%3Amatrix.org?access_token=123456" -d \
'{
   "actions" : ["dont_notify"]
 }'

To suppress notifications for the user @spambot:matrix.org:

curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/api/v1/pushrules/global/sender/%40spambot%3Amatrix.org?access_token=123456" -d \
'{
   "actions" : ["dont_notify"]
 }'

To always notify for messages that contain the work 'cake' and set a specific sound (with a rule_id of SSByZWFsbHkgbGlrZSBjYWtl):

curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/api/v1/pushrules/global/content/SSByZWFsbHkgbGlrZSBjYWtl?access_token=123456" -d \
'{
   "pattern": "cake",
   "actions" : ["notify", {"set_sound":"cakealarm.wav"}]
 }'

To add a rule suppressing notifications for messages starting with 'cake' but ending with 'lie', superseding the previous rule:

curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/api/v1/pushrules/global/content/U3BvbmdlIGNha2UgaXMgYmVzdA?access_token=123456&before=SSByZWFsbHkgbGlrZSBjYWtl" -d \
'{
   "pattern": "cake*lie",
   "actions" : ["notify"]
 }'

To add a custom sound for notifications messages containing the word 'beer' in any rooms with 10 members or fewer (with greater importance than the room, sender and content rules):

curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/api/v1/pushrules/global/override/U2VlIHlvdSBpbiBUaGUgRHVrZQ?access_token=123456" -d \
'{
   "conditions": [
     {"kind": "event_match", "key": "content.body", "pattern": "beer" },
     {"kind": "room_member_count", "is": "<=10"}
   ],
   "actions" : [
     "notify",
     {"set_sound":"beeroclock.wav"}
   ]
 }'

5.10.2   Server behaviour

This describes the format used by "HTTP" pushers to send notifications of events to Push Gateways. If the endpoint returns an HTTP error code, the homeserver SHOULD retry for a reasonable amount of time using exponential-backoff.

5.10.2.1   POST /_matrix/push/v1/notify

This endpoint is invoked by HTTP pushers to notify a push gateway about an event. NB: Notifications are sent to the URL configured when the pusher is created. This means that the HTTP path may be different depending on the push gateway.

Request format:

Parameter Type Description
JSON body parameters
notification object Required. Information about the push notification
notification.content {EventContent} The content field from the event, if present. If the event had no content field, this field is omitted.
notification.id string Required. An identifier for this notification that may be used to detect duplicate notification requests. This is not necessarily the ID of the event that triggered the notification.
notification.prio enum The priority of the notification. If omitted, high is assumed. This may be used by push gateways to deliver less time-sensitive notifications in a way that will preserve battery power on mobile devices. One of: ["high", "low"]
notification.room_alias string An alias to display for the room in which the event occurred.
notification.room_id string Required. The ID of the room in which this event occurred.
notification.room_name string The name of the room in which the event occurred.
notification.sender string Required. The sender of the event as in the corresponding event field.
notification.sender_display_name string The current display name of the sender in the room in which the event occurred.
notification.type string Required. The type of the event as in the event's type field.
notification.user_is_target boolean This is true if the user receiving the notification is the subject of a member event (i.e. the state_key of the member event is equal to the user's Matrix ID).
counts object Required. This is a dictionary of the current number of unacknowledged communications for the recipient user. Counts whose value is zero are omitted.
counts.missed_calls integer The number of unacknowledged missed calls a user has across all rooms of which they are a member.
counts.unread integer The number of unread messages a user has across all of the rooms they are a member of.
devices array[object] Required. This is an array of devices that the notification should be sent to.
devices[0].app_id string The app_id given when the pusher was created.
devices[0].data {PusherData} A dictionary of additional pusher-specific data. For 'http' pushers, this is the data dictionary passed in at pusher creation minus the url key.
devices[0].pushkey string The pushkey given when the pusher was created.
devices[0].pushkey_ts integer The unix timestamp (in seconds) when the pushkey was last updated.
devices[0].tweaks {Tweaks} A dictionary of customisations made to the way this notification is to be presented. These are added by push rules.

Response format:

Parameter Type Description
rejected [string] A list of all pushkeys given in the notification request that are not valid. These could have been rejected by an upstream gateway because they have expired or have never been valid. Homeservers must cease sending notification requests for these pushkeys and remove the associated pushers. It may not necessarily be the notification in the request that failed: it could be that a previous notification to the same pushkey failed.

Example request:

POST /_matrix/push/v1/notify HTTP/1.1
Content-Type: application/json

{
  "notification": {
    "id": "$3957tyerfgewrf384",
    "room_id": "!slw48wfj34rtnrf:example.com",
    "type": "m.room.message",
    "sender": "@exampleuser:matrix.org",
    "sender_display_name": "Major Tom",
    "room_name": "Mission Control",
    "room_alias": "#exampleroom:matrix.org",
    "prio": "high",
    "content": {
      "msgtype": "m.text",
      "body": "I'm floating in a most peculiar way."
    }
  },
  "counts": {
     "unread" : 2,
     "missed_calls": 1
  },
  "devices": [
    {
       "app_id": "org.matrix.matrixConsole.ios",
       "pushkey": "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/",
       "pushkey_ts": 12345678,
       "data" : {},
       "tweaks": {
         "sound": "bing"
        }
    }
  ]
}

Response:

Status code 200:

A list of rejected push keys.

Example

{
  "rejected": [ "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/" ]
}

5.10.3   Push Gateway behaviour

5.10.3.1   Recommendations for APNS

The exact format for sending APNS notifications is flexible and up to the client app and its' push gateway to agree on. As APNS requires that the sender has a private key owned by the app developer, each app must have its own push gateway. It is recommended that:

  • The APNS token be base64 encoded and used as the pushkey.
  • A different app_id be used for apps on the production and sandbox APS environments.
  • APNS push gateways do not attempt to wait for errors from the APNS gateway before returning and instead to store failures and return 'rejected' responses next time that pushkey is used.

5.10.4   Security considerations

Clients specify the Push Gateway URL to use to send event notifications to. This URL should be over HTTPS and never over HTTP.

As push notifications will pass through a Push Provider, message content shouldn't be sent in the push itself where possible. Instead, Push Gateways should send a "sync" command to instruct the client to get new events from the homeserver directly.

5.11   Third party invites

This module adds in support for inviting new members to a room where their Matrix user ID is not known, instead addressing them by a third party identifier such as an email address. There are two flows here; one if a Matrix user ID is known for the third party identifier, and one if not. Either way, the client calls /invite with the details of the third party identifier.

The homeserver asks the identity server whether a Matrix user ID is known for that identifier:

  • If it is, an invite is simply issued for that user.
  • If it is not, the homeserver asks the identity server to record the details of the invitation, and to notify the invitee's homeserver of this pending invitation if it gets a binding for this identifier in the future. The identity server returns a token and public key to the inviting homeserver.

When the invitee's homeserver receives the notification of the binding, it should insert an m.room.member event into the room's graph for that user, with content.membership = invite, as well as a content.third_party_invite property which contains proof that the invitee does indeed own that third party identifier.

5.11.1   Events

5.11.1.1   m.room.third_party_invite

State Event
state_key: The token, of which a signature must be produced in order to join the room.

Acts as an m.room.member invite event, where there isn't a target user_id to invite. This event contains a token and a public key whose private key must be used to sign the token. Any user who can present that signature may use this invitation to join the target room.

Content Key Type Description
display_name string A user-readable string which represents the user who has been invited. This should not contain the user's third party ID, as otherwise when the invite is accepted it would leak the association between the matrix ID and the third party ID.
key_validity_url string A URL which can be fetched, with querystring public_key=public_key, to validate whether the key has been revoked. The URL must return a JSON object containing a boolean property named 'valid'.
public_key string A base64-encoded ed25519 key with which token must be signed.

Example:

{
    "age": 242352,
    "content": {
        "display_name": "Alice Margatroid",
        "key_validity_url": "https://magic.forest/verifykey",
        "public_key": "abc123"
    },
    "event_id": "$WLGTSEFSEF:localhost",
    "origin_server_ts": 1431961217939,
    "room_id": "!Cuyf34gef24t:localhost",
    "sender": "@example:localhost",
    "state_key": "pc98",
    "type": "m.room.third_party_invite"
}

5.11.2   Client behaviour

A client asks a server to invite a user by their third party identifier.

5.11.2.1   POST /_matrix/client/api/v1/rooms/{roomId}/invite

Note that there are two forms of this API, which are documented separately. This version of the API does not require that the inviter know the Matrix identifier of the invitee, and instead relies on third party identifiers. The homeserver uses an identity server to perform the mapping from third party identifier to a Matrix identifier. The other is documented in the joining rooms section.

This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.

Only users currently in a particular room can invite other users to join that room.

If the identity server did know the Matrix user identifier for the third party identifier, the home server will append a m.room.member event to the room.

If the identity server does not know a Matrix user identifier for the passed third party identifier, the homeserver will issue an invitation which can be accepted upon providing proof of ownership of the third party identifier. This is achieved by the identity server generating a token, which it gives to the inviting homeserver. The homeserver will add an m.room.third_party_invite event into the graph for the room, containing that token.

When the invitee binds the invited third party identifier to a Matrix user ID, the identity server will give the user a list of pending invitations, each containing:

  • The room ID to which they were invited
  • The token given to the homeserver
  • A signature of the token, signed with the identity server's private key
  • The matrix user ID who invited them to the room

If a token is requested from the identity server, the home server will append a m.room.third_party_invite event to the room.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
path parameters
roomId string Required. The room identifier (not alias) to which to invite the user.
JSON body parameters
id_server string Required. The hostname+port of the identity server which should be used for third party identifier lookups.
medium string Required. The kind of address being passed in the address field, for example email.
address string Required. The invitee's third party identifier.

Example request:

POST /_matrix/client/api/v1/rooms/%21d41d8cd%3Amatrix.org/invite HTTP/1.1
Content-Type: application/json

{
  "id_server": "matrix.org",
  "medium": "email",
  "address": "cheeky@monkey.com"
}

Responses:

Status code 200:

The user has been invited to join the room.

Example

{}

Status code 403:

You do not have permission to invite the user to the room. A meaningful errcode and description error text will be returned. Example reasons for rejections are:

  • The invitee has been banned from the room.
  • The invitee is already a member of the room.
  • The inviter is not currently in the room.
  • The inviter's power level is insufficient to invite users to the room.

Example

{"errcode": "M_FORBIDDEN", "error": "@cheeky_monkey:matrix.org is banned from the room"}

5.11.3   Server behaviour

All homeservers MUST verify the signature in the event's content.third_party_invite.signed object.

When a homeserver inserts an m.room.member invite event into the graph because of an m.room.third_party_invite event, that homesever MUST validate that the public key used for signing is still valid, by checking key_validity_url from the m.room.third_party_invite. It does this by making an HTTP GET request to key_validity_url:

Schema:

=> GET $key_validity_url?public_key=$public_key
<= HTTP/1.1 200 OK
{
    "valid": true|false
}

Example:

key_validity_url = https://identity.server/is_valid
public_key = ALJWLAFQfqffQHFqFfeqFUOEHf4AIHfefh4
=> GET https://identity.server/is_valid?public_key=ALJWLAFQfqffQHFqFfeqFUOEHf4AIHfefh4
<= HTTP/1.1 200 OK
{
    "valid": true
}

with the querystring ?public_key=``public_key``. A JSON object will be returned. The invitation is valid if the object contains a key named valid which is true. Otherwise, the invitation MUST be rejected. This request is idempotent and may be retried by the homeserver.

If a homeserver is joining a room for the first time because of an m.room.third_party_invite, the server which is already participating in the room (which is chosen as per the standard server-server specification) MUST validate that the public key used for signing is still valid, by checking key_validity_url in the above described way.

No other homeservers may reject the joining of the room on the basis of key_validity_url, this is so that all homeservers have a consistent view of the room. They may, however, indicate to their clients that a member's' membership is questionable.

For example:

  1. Room R has two participating homeservers, H1, H2
  2. User A on H1 invites a third party identifier to room R
  3. H1 asks the identity server for a binding to a Matrix user ID, and has none, so issues an m.room.third_party_invite event to the room.
  4. When the third party user validates their identity, their homeserver H3 is notified and attempts to issue an m.room.member event to participate in the room.
  5. H3 validates the signature given to it by the identity server.
  6. H3 then asks H1 to join it to the room. H1 must validate the signed property and check key_validity_url.
  7. Having validated these things, H1 writes the invite event to the room, and H3 begins participating in the room. H2 must accept this event.

The reason that no other homeserver may reject the event based on checking key_validity_url is that we must ensure event acceptance is deterministic. If some other participating server doesn't have a network path to the keyserver, or if the keyserver were to go offline, or revoke its keys, that other server would reject the event and cause the participating servers' graphs to diverge. This relies on participating servers trusting each other, but that trust is already implied by the server-server protocol. Also, the public key signature verification must still be performed, so the attack surface here is minimized.

5.11.4   Security considerations

There are a number of privary and trust implications to this module.

It is important for user privacy that leaking the mapping between a matrix user ID and a third party identifier is hard. In particular, being able to look up all third party identifiers from a matrix user ID (and accordingly, being able to link each third party identifier) should be avoided wherever possible. To this end, the third party identifier is not put in any event, rather an opaque display name provided by the identity server is put into the events. Clients should not remember or display third party identifiers from invites, other than for the use of the inviter themself.

Homeservers are not required to trust any particular identity server(s). It is generally a client's responsibility to decide which identity servers it trusts, not a homeserver's. Accordingly, this API takes identity servers as input from end users, and doesn't have any specific trusted set. It is possible some homeservers may want to supply defaults, or reject some identity servers for its users, but no homeserver is allowed to dictate which identity servers other homeservers' users trust.

There is some risk of denial of service attacks by flooding homeservers or identity servers with many requests, or much state to store. Defending against these is left to the implementer's discretion.

5.12   Server Side Search

5.12.1   Client behaviour

5.12.1.1   POST /_matrix/client/api/v1/search

Performs a full text search across different categories.

Rate-limited:Yes.
Requires auth:Yes.

Request format:

Parameter Type Description
JSON body parameters
search_categories object Required. Describes which categories to search in and their criteria.
search_categories.room_events {Room Events} Mapping of category name to search criteria.
search_categories.room_events.filter {Filter} The filter to apply to search results. This has the same format as v2 filter API.
search_categories.room_events.keys [enum] The keys to search. Defaults to all. One of: ["content.body", "content.name", "content.topic"]
search_categories.room_events.search_term string Required. The string to search events for

Response format:

Results

Parameter Type Description
search_categories {Categories} Describes which categories to search in and their criteria.

Categories

Parameter Type Description
room_events {Room Event Results} Mapping of category name to search criteria.

Room Event Results

Parameter Type Description
count number Total number of results found
results {string: Result} Mapping of event_id to result.

Result

Parameter Type Description
rank number A number that describes how closely this result matches the search. Higher is closer.
result {Event} The event that matched.

Event

Parameter Type Description
event_id string The globally unique event identifier.
room_id string The ID of the room associated with this event.
user_id string Contains the fully-qualified ID of the user who sent this event.

Example request:

POST /_matrix/client/api/v1/search HTTP/1.1
Content-Type: application/json

{
  "search_categories": {
    "room_events": {
      "keys": [
        "content.body"
      ],
      "search_term": "martians and men"
    }
  }
}

Response:

Status code 200:

Results of the search.

Example

{
   "search_categories": {
     "room_events": {
       "count": 24,
       "results": {
         "$144429830826TWwbB:localhost": {
           "rank": 0.00424866,
           "result": {
             "age": 526228296,
             "content": {
               "body": "Test content",
               "msgtype": "m.text"
             },
             "event_id": "$144429830826TWwbB:localhost",
             "origin_server_ts": 1444298308034,
             "room_id": "!qPewotXpIctQySfjSy:localhost",
             "type": "m.room.message",
             "user_id": "@test:localhost"
           }
         }
       }
     }
   }
 }

5.12.2   Search Categories

The search API allows clients to search in different categories of items. Currently the only specified category is room_events.

5.12.2.1   room_events

This category covers all events that the user is allowed to see, including events in rooms that they have left. The search is performed on certain keys of certain event types.

The supported keys to search over are:

  • content.body in m.room.message
  • content.name in m.room.name
  • content.topic in m.room.topic

The search will not include rooms that are end to end encrypted.

The results include a rank key that can be used to sort the results by revelancy. The higher the rank the more relevant the result is.

The value of count may not match the number of results. For example due to the search query matching 1000s of results and the server truncating the response.

5.12.3   Security considerations

The server must only return results that the user has permission to see.

5.13   Guest access

There are times when it is desirable for clients to be able to interact with rooms without having to fully register for an account on a homeserver or join the room. This module specifies how these clients should interact with servers in order to participate in rooms as guests.

Guest users retrieve access tokens from a homeserver using the ordinary register endpoint, specifying the kind parameter as guest. They may then interact with the client-server API as any other user would, but will only have access to a subset of the API as described the Client behaviour subsection below. Homeservers may choose not to allow this access at all to their local users, but have no information about whether users on other homeservers are guests or not.

This module does not fully factor in federation; it relies on individual homeservers properly adhering to the rules set out in this module, rather than allowing all homeservers to enforce the rules on each other.

5.13.1   Events

5.13.1.1   m.room.guest_access

State Event
state_key: A zero-length string.

This event controls whether guest users are allowed to join rooms. If this event is absent, servers should act as if it is present and has the guest_access value "forbidden".

Content Key Type Description
guest_access enum Whether guests can join the room. One of: ["can_join", "forbidden"]

Example:

{
    "age": 242353,
    "content": {
        "guest_access": "can_join"
    },
    "event_id": "$WLGTSEFSEG:localhost",
    "origin_server_ts": 1431961217938,
    "room_id": "!Cuyf34gef24u:localhost",
    "state_key": "",
    "type": "m.room.guest_access",
    "user_id": "@example:localhost"
}

5.13.2   Client behaviour

The following API endpoints are allowed to be accessed by guest accounts for retrieving events:

There is also a special version of the GET /events endpoint:

5.13.2.1   GET /_matrix/client/api/v1/events

This will listen for new events related to a particular room and return them to the caller. This will block until an event is received, or until the timeout is reached.

This API is the same as the non-guest /events endpoint, but can be called by guest users.

Requires auth:Yes.

Request format:

Parameter Type Description
query parameters
from string The token to stream from. This token is either from a previous request to this API or from the initial sync API.
timeout integer The maximum time in milliseconds to wait for an event.
room_id array The room IDs for which events should be returned.

Response format:

Parameter Type Description
chunk [Event] An array of events.
end string A token which correlates to the last value in chunk. This token should be used in the next request to /events.
start string A token which correlates to the first value in chunk. This is usually the same token supplied to from=.

Event

Parameter Type Description
event_id string The globally unique event identifier.
room_id string The ID of the room associated with this event.
user_id string Contains the fully-qualified ID of the user who sent this event.

Example request:

GET /_matrix/client/api/v1/events?from=s3456_9_0&timeout=35000&room_id=%21somewhere%3Aover&room_id=%21the%3Arainbow HTTP/1.1

Response:

Status code 200:

The events received, which may be none.

Example

{
  "start": "s3456_9_0",
  "end": "s3457_9_0",
  "chunk": [
    {
      "age": 32,
      "content": {
          "body": "incoming message",
          "msgtype": "m.text"
      },
      "event_id": "$14328055551tzaee:localhost",
      "origin_server_ts": 1432804485886,
      "room_id": "!TmaZBKYIFrIPVGoUYp:localhost",
      "type": "m.room.message",
      "user_id": "@bob:localhost"
    }
  ]
}

They will only return events which happened while the room state had the m.room.history_visibility state event present with history_visibility value world_readable. Guest clients do not need to join rooms in order to receive events for them.

The following API endpoints are allowed to be accessed by guest accounts for sending events:

Guest clients do need to join rooms in order to send events to them.

The following API endpoints are allowed to be accessed by guest accounts for their own account maintenance:

5.13.3   Server behaviour

Servers are required to only return events to guest accounts for rooms where the room state at the event had the m.room.history_visibility state event present with history_visibility value world_readable. These events may be returned even if the anonymous user is not joined to the room.

Servers MUST only allow guest users to join rooms if the m.room.guest_access state event is present on the room, and has the guest_access value can_join. If the m.room.guest_access event is changed to stop this from being the case, the server MUST set those users' m.room.member state to leave.

5.13.4   Security considerations

Each homeserver manages its own guest accounts itself, and whether an account is a guest account or not is not information passed from server to server. Accordingly, any server participating in a room is trusted to properly enforce the permissions outlined in this section.

Clients may wish to display to their users that rooms which are world_readable may be showing messages to non-joined users. There is no way using this module to find out whether any non-joined guest users do see events in the room, or to list or count any guest users.

Homeservers may want to enable protections such as captchas for guest registration to prevent spam, denial of service, and similar attacks.

6   Application Service API

The Matrix client-server API and server-server APIs provide the means to implement a consistent self-contained federated messaging fabric. However, they provide limited means of implementing custom server-side behaviour in Matrix (e.g. gateways, filters, extensible hooks etc). The Application Service API (AS API) defines a standard API to allow such extensible functionality to be implemented irrespective of the underlying homeserver implementation.

6.1   Application Services

Application services are passive and can only observe events from a given homeserver. They can inject events into rooms they are participating in. They cannot prevent events from being sent, nor can they modify the content of the event being sent. In order to observe events from a homeserver, the homeserver needs to be configured to pass certain types of traffic to the application service. This is achieved by manually configuring the homeserver with information about the application service (AS).

6.1.1   Registration

Note

Previously, application services could register with a homeserver via HTTP APIs. This was removed as it was seen as a security risk. A compromised application service could re-register for a global * regex and sniff all traffic on the homeserver. To protect against this, application services now have to register via configuration files which are linked to the homeserver configuration file. The addition of configuration files allows homeserver admins to sanity check the registration for suspicious regex strings.

Application services register "namespaces" of user IDs, room aliases and room IDs. These namespaces are represented as regular expressions. An application service is said to be "interested" in a given event if one of the IDs in the event match the regular expression provided by the application service. An application service can also state whether they should be the only ones who can manage a specified namespace. This is referred to as an "exclusive" namespace. An exclusive namespace prevents humans and other application services from creating/deleting entities in that namespace. Typically, exclusive namespaces are used when the rooms represent real rooms on another service (e.g. IRC). Non-exclusive namespaces are used when the application service is merely augmenting the room itself (e.g. providing logging or searching facilities). Namespaces are represented by POSIX extended regular expressions and look like:

users:
  - exclusive: true
    regex: @irc.freenode.net_.*

The registration is represented by a series of key-value pairs, which this specification will present as YAML. An example HS configuration required to pass traffic to the AS is:

url: <base url of AS>
as_token: <token AS will add to requests to HS>
hs_token: <token HS will add to requests to AS>
sender_localpart: <localpart of AS user>
namespaces:
  users:  # Namespaces of users which should be delegated to the AS
    - exclusive: <bool>
      regex: <regex>
    - ...
  aliases: []  # Namespaces of room aliases which should be delegated to the AS
  rooms: [] # Namespaces of room ids which should be delegated to the AS

Warning

If the homeserver in question has multiple application services, each as_token MUST be unique per application service as this token is used to identify the application service. The homeserver MUST enforce this.

6.1.2   Home Server -> Application Service API

6.1.2.1   Pushing events

The application service API provides a transaction API for sending a list of events. Each list of events includes a transaction ID, which works as follows:

Typical
HS ---> AS : Home server sends events with transaction ID T.
   <---    : AS sends back 200 OK.

AS ACK Lost
HS ---> AS : Home server sends events with transaction ID T.
   <-/-    : AS 200 OK is lost.
HS ---> AS : Home server retries with the same transaction ID of T.
   <---    : AS sends back 200 OK. If the AS had processed these events
             already, it can NO-OP this request (and it knows if it is the same
             events based on the transaction ID).

The events sent to the application service should be linearised, as if they were from the event stream. The homeserver MUST maintain a queue of transactions to send to the AS. If the application service cannot be reached, the homeserver SHOULD backoff exponentially until the application service is reachable again. As application services cannot modify the events in any way, these requests can be made without blocking other aspects of the homeserver. Homeservers MUST NOT alter (e.g. add more) events they were going to send within that transaction ID on retries, as the AS may have already processed the events.

6.1.2.2   Querying

The application service API includes two querying APIs: for room aliases and for user IDs. The application service SHOULD create the queried entity if it desires. During this process, the application service is blocking the homeserver until the entity is created and configured. If the homeserver does not receive a response to this request, the homeserver should retry several times before timing out. This should result in an HTTP status 408 "Request Timeout" on the client which initiated this request (e.g. to join a room alias).

Rationale

Blocking the homeserver and expecting the application service to create the entity using the client-server API is simpler and more flexible than alternative methods such as returning an initial sync style JSON blob and get the HS to provision the room/user. This also meant that there didn't need to be a "backchannel" to inform the application service about information about the entity such as room ID to room alias mappings.

6.1.2.3   HTTP APIs

This contains application service APIs which are used by the home server. All application services MUST implement these APIs. These APIs are defined below.

6.1.2.3.1   GET /rooms/{roomAlias}

This endpoint is invoked by the homeserver on an application service to query the existence of a given room alias. The homeserver will only query room aliases inside the application service's aliases namespace. The homeserver will send this request when it receives a request to join a room alias within the application service's namespace.

Request format:

Parameter Type Description
path parameters
roomAlias string Required. The room alias being queried.

Example request:

GET /rooms/%23magicforest%3Aexample.com HTTP/1.1

Responses:

Status code 200:

The application service indicates that this room alias exists. The application service MUST have created a room and associated it with the queried room alias using the client-server API. Additional information about the room such as its name and topic can be set before responding.

Example

{}

Status code 401:

The homeserver has not supplied credentials to the application service. Optional error information can be included in the body of this response.

Example

{
  "errcode": "COM.EXAMPLE.MYAPPSERVICE_UNAUTHORIZED"
}

Status code 403:

The credentials supplied by the homeserver were rejected.

Example

{
  "errcode": "M_FORBIDDEN"
}

Status code 404:

The application service indicates that this room alias does not exist. Optional error information can be included in the body of this response.

Example

{
  "errcode": "COM.EXAMPLE.MYAPPSERVICE_NOT_FOUND"
}

6.1.2.3.2   PUT /transactions/{txnId}

This API is called by the HS when the HS wants to push an event (or batch of events) to the AS.

Request format:

Parameter Type Description
path parameters
txnId string Required. The transaction ID for this set of events. Homeservers generate these IDs and they are used to ensure idempotency of requests.
JSON body parameters
events array[object] Required. A list of events

Example request:

PUT /transactions/35 HTTP/1.1
Content-Type: application/json

{
  "events": [
    {
      "age": 32,
      "content": {
          "body": "incoming message",
          "msgtype": "m.text"
      },
      "event_id": "$14328055551tzaee:localhost",
      "origin_server_ts": 1432804485886,
      "room_id": "!TmaZBKYIFrIPVGoUYp:localhost",
      "type": "m.room.message",
      "user_id": "@bob:localhost"
    },
    {
      "age": 1984,
      "content": {
          "body": "another incoming message",
          "msgtype": "m.text"
      },
      "event_id": "$1228055551ffsef:localhost",
      "origin_server_ts": 1432804485886,
      "room_id": "!TmaZBKYIFrIPVGoUYp:localhost",
      "type": "m.room.message",
      "user_id": "@bob:localhost"
    }
  ]
}

Response:

Status code 200:

The transaction was processed successfully.

Example

{}

6.1.2.3.3   GET /users/{userId}

This endpoint is invoked by the homeserver on an application service to query the existence of a given user ID. The homeserver will only query user IDs inside the application service's users namespace. The homeserver will send this request when it receives an event for an unknown user ID in the application service's namespace.

Request format:

Parameter Type Description
path parameters
userId string Required. The user ID being queried.

Example request:

GET /users/%40alice%3Aexample.com HTTP/1.1

Responses:

Status code 200:

The application service indicates that this user exists. The application service MUST create the user using the client-server API.

Example

{}

Status code 401:

The homeserver has not supplied credentials to the application service. Optional error information can be included in the body of this response.

Example

{
  "errcode": "COM.EXAMPLE.MYAPPSERVICE_UNAUTHORIZED"
}

Status code 403:

The credentials supplied by the homeserver were rejected.

Example

{
  "errcode": "M_FORBIDDEN"
}

Status code 404:

The application service indicates that this user does not exist. Optional error information can be included in the body of this response.

Example

{
  "errcode": "COM.EXAMPLE.MYAPPSERVICE_NOT_FOUND"
}

6.1.3   Client-Server v2 API Extensions

Application services can utilise a more powerful version of the client-server API by identifying itself as an application service to the home server.

6.1.3.1   Identity assertion

The client-server API infers the user ID from the access_token provided in every request. It would be an annoying amount of book-keeping to maintain tokens for every virtual user. It would be preferable if the application service could use the CS API with its own as_token instead, and specify the virtual user they wish to be acting on behalf of. For real users, this would require additional permissions granting the AS permission to masquerade as a matrix user.

Inputs:
  • Application service token (access_token)
  • User ID in the AS namespace to act as.
Notes:
  • This will apply on all aspects of the CS API, except for Account Management.
  • The as_token is inserted into access_token which is usually where the client token is. This is done on purpose to allow application services to reuse client SDKs.
/path?access_token=$token&user_id=$userid

Query Parameters:
  access_token: The application service token
  user_id: The desired user ID to act as.

6.1.3.2   Timestamp massaging

The application service may want to inject events at a certain time (reflecting the time on the network they are tracking e.g. irc, xmpp). Application services need to be able to adjust the origin_server_ts value to do this.

Inputs:
  • Application service token (as_token)
  • Desired timestamp
Notes:
  • This will only apply when sending events.
/path?access_token=$token&ts=$timestamp

Query Parameters added to the send event APIs only:
  access_token: The application service token
  ts: The desired timestamp

6.1.3.3   Server admin style permissions

The home server needs to give the application service full control over its namespace, both for users and for room aliases. This means that the AS should be able to create/edit/delete any room alias in its namespace, as well as create/delete any user in its namespace. No additional API changes need to be made in order for control of room aliases to be granted to the AS. Creation of users needs API changes in order to:

  • Work around captchas.
  • Have a 'passwordless' user.

This involves bypassing the registration flows entirely. This is achieved by including the AS token on a /register request, along with a login type of m.login.application_service to set the desired user ID without a password.

/register?access_token=$as_token

Content:
{
  type: "m.login.application_service",
  user: "<desired user localpart in AS namespace>"
}

Application services which attempt to create users or aliases outside of their defined namespaces will receive an error code M_EXCLUSIVE. Similarly, normal users who attempt to create users or aliases inside an application service-defined namespace will receive the same M_EXCLUSIVE error code, but only if the application service has defined the namespace as exclusive.

6.1.4   ID conventions

This concerns the well-defined conventions for mapping 3P network IDs to matrix IDs, which we expect clients to be able to do by themselves.

6.1.4.1   User IDs

Matrix users may wish to directly contact a virtual user, e.g. to send an email. The URI format is a well-structured way to represent a number of different ID types, including:

As a result, virtual user IDs SHOULD relate to their URI counterpart. This mapping from URI to user ID can be expressed in a number of ways:

  • Expose a C-S API on the HS which takes URIs and responds with user IDs.
  • Munge the URI with the user ID.

Exposing an API would allow HSes to internally map user IDs however they like, at the cost of an extra round trip (of which the response can be cached). Munging the URI would allow clients to apply the mapping locally, but would force user X on service Y to always map to the same munged user ID. Considering the exposed API could just be applying this munging, there is more flexibility if an API is exposed.

GET /_matrix/app/v1/user?uri=$url_encoded_uri

Returns 200 OK:
{
  user_id: <complete user ID on local HS>
}

6.1.4.2   Room Aliases

We may want to expose some 3P network rooms so Matrix users can join them directly, e.g. IRC rooms. We don't want to expose every 3P network room though, e.g. mailto, tel. Rooms which are publicly accessible (e.g. IRC rooms) can be exposed as an alias by the application service. Private rooms (e.g. sending an email to someone) should not be exposed in this way, but should instead operate using normal invite/join semantics. Therefore, the ID conventions discussed below are only valid for public rooms which expose room aliases.

Matrix users may wish to join XMPP rooms (e.g. using XEP-0045) or IRC rooms. In both cases, these rooms can be expressed as URIs. For consistency, these "room" URIs SHOULD be mapped in the same way as "user" URIs.

GET /_matrix/app/v1/alias?uri=$url_encoded_uri

Returns 200 OK:
{
  alias: <complete room alias on local HS>
}

6.1.4.3   Event fields

We recommend that any events that originated from a remote network should include an external_url field in their content to provide a way for Matrix clients to link into the 'native' client from which the event originated. For instance, this could contain the message-ID for emails/nntp posts, or a link to a blog comment when bridging blog comment traffic in & out of Matrix.

7   Federation API

Matrix home servers use the Federation APIs (also known as server-server APIs) to communicate with each other. Home servers use these APIs to push messages to each other in real-time, to request historic messages from each other, and to query profile and presence information about users on each other's servers.

The APIs are implemented using HTTPS GETs and PUTs between each of the servers. These HTTPS requests are strongly authenticated using public key signatures at the TLS transport layer and using public key signatures in HTTP Authorization headers at the HTTP layer.

There are three main kinds of communication that occur between home servers:

Persisted Data Units (PDUs):

These events are broadcast from one home server to any others that have joined the same room (identified by Room ID). They are persisted in long-term storage and record the history of messages and state for a room.

Like email, it is the responsibility of the originating server of a PDU to deliver that event to its recipient servers. However PDUs are signed using the originating server's public key so that it is possible to deliver them through third-party servers.

Ephemeral Data Units (EDUs):
These events are pushed between pairs of home servers. They are not persisted and are not part of the history of a room, nor does the receiving home server have to reply to them.
Queries:
These are single request/response interactions between a given pair of servers, initiated by one side sending an HTTPS GET request to obtain some information, and responded by the other. They are not persisted and contain no long-term significant history. They simply request a snapshot state at the instant the query is made.

EDUs and PDUs are further wrapped in an envelope called a Transaction, which is transferred from the origin to the destination home server using an HTTPS PUT request.

7.1   Server Discovery

7.1.1   Resolving Server Names

Each matrix home server is identified by a server name consisting of a DNS name and an optional TLS port.

server_name = dns_name [ ":" tls_port]
dns_name = <host, see [RFC 3986], Section 3.2.2>
tls_port = *DIGIT

If the port is present then the server is discovered by looking up an AAAA or A record for the DNS name and connecting to the specified TLS port. If the port is absent then the server is discovered by looking up a _matrix._tcp SRV record for the DNS name. If this record does not exist then the server is discovered by looking up an AAAA or A record on the DNS name and taking the default fallback port number of 8448. Home servers may use SRV records to load balance requests between multiple TLS endpoints or to failover to another endpoint if an endpoint fails.

7.1.2   Retrieving Server Keys

7.1.2.1   Version 2

Each home server publishes its public keys under /_matrix/key/v2/server/. Home servers query for keys by either getting /_matrix/key/v2/server/ directly or by querying an intermediate notary server using a /_matrix/key/v2/query API. Intermediate notary servers query the /_matrix/key/v2/server/ API on behalf of another server and sign the response with their own key. A server may query multiple notary servers to ensure that they all report the same public keys.

This approach is borrowed from the Perspectives Project, but modified to include the NACL keys and to use JSON instead of XML. It has the advantage of avoiding a single trust-root since each server is free to pick which notary servers they trust and can corroborate the keys returned by a given notary server by querying other servers.

7.1.2.1.1   Publishing Keys

Home servers publish the allowed TLS fingerprints and signing keys in a JSON object at /_matrix/key/v2/server/{key_id}. The response contains a list of verify_keys that are valid for signing federation requests made by the server and for signing events. It contains a list of old_verify_keys which are only valid for signing events. Finally the response contains a list of TLS certificate fingerprints to validate any connection made to the server.

A server may have multiple keys active at a given time. A server may have any number of old keys. It is recommended that servers return a single JSON response listing all of its keys whenever any key_id is requested to reduce the number of round trips needed to discover the relevant keys for a server. However a server may return a different responses for a different key_id.

The tls_certificates contain a list of hashes of the X.509 TLS certificates currently used by the server. The list must include SHA-256 hashes for every certificate currently in use by the server. These fingerprints are valid until the millisecond POSIX timestamp in valid_until_ts.

The verify_keys can be used to sign requests and events made by the server until the millisecond POSIX timestamp in valid_until_ts. If a Home Server receives an event with a origin_server_ts after the valid_until_ts then it should request that key_id for the originating server to check whether the key has expired.

The old_verify_keys can be used to sign events with an origin_server_ts before the expired_ts. The expired_ts is a millisecond POSIX timestamp of when the originating server stopped using that key.

Intermediate notary servers should cache a response for half of its remaining life time to avoid serving a stale response. Originating servers should avoid returning responses that expire in less than an hour to avoid repeated requests for an about to expire certificate. Requesting servers should limit how frequently they query for certificates to avoid flooding a server with requests.

If a server goes offline intermediate notary servers should continue to return the last response they received from that server so that the signatures of old events sent by that server can still be checked.

Key Type Description
server_name String DNS name of the home server.
verify_keys Object Public keys of the home server for verifying digital signatures.
old_verify_keys Object The public keys that the server used to use and when it stopped using them.
signatures Object Digital signatures for this object signed using the verify_keys.
tls_fingerprints Array of Objects Hashes of X.509 TLS certificates used by this this server encoded as base64.
valid_until_ts Integer POSIX timestamp when the list of valid keys should be refreshed.
{
    "old_verify_keys": {
        "ed25519:auto1": {
            "expired_ts": 922834800000,
            "key": "Base+64+Encoded+Old+Verify+Key"
        }
    },
    "server_name": "example.org",
    "signatures": {
        "example.org": {
            "ed25519:auto2": "Base+64+Encoded+Signature"
        }
    },
    "tls_fingerprints": [
        {
            "sha256": "Base+64+Encoded+SHA-256-Fingerprint"
        }
    ],
    "valid_until_ts": 1052262000000,
    "verify_keys": {
        "ed25519:auto2": {
            "key": "Base+64+Encoded+Signature+Verification+Key"
        }
    }
}

7.1.2.1.2   Querying Keys Through Another Server

Servers may offer a query API _matrix/key/v2/query/ for getting the keys for another server. This API can be used to GET at list of JSON objects for a given server or to POST a bulk query for a number of keys from a number of servers. Either way the response is a list of JSON objects containing the JSON published by the server under _matrix/key/v2/server/ signed by both the originating server and by this server.

The minimum_valid_until_ts is a millisecond POSIX timestamp indicating when the returned certificate will need to be valid until to be useful to the requesting server. This can be set using the maximum origin_server_ts of an batch of events that a requesting server is trying to validate. This allows an intermediate notary server to give a prompt cached response even if the originating server is offline.

This API can return keys for servers that are offline be using cached responses taken from when the server was online. Keys can be queried from multiple servers to mitigate against DNS spoofing.

Requests:

GET /_matrix/key/v2/query/${server_name}/${key_id}/?minimum_valid_until_ts=${minimum_valid_until_ts} HTTP/1.1

POST /_matrix/key/v2/query HTTP/1.1
Content-Type: application/json

{
    "server_keys": {
        "$server_name": {
            "$key_id": {
                "minimum_valid_until_ts": $posix_timestamp
            }
        }
    }
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json
{
    "server_keys": [
       # List of responses with same format as /_matrix/key/v2/server
       # signed by both the originating server and this server.
    ]
}

7.1.2.2   Version 1

Warning

Version 1 of key distribution is obsolete

Home servers publish their TLS certificates and signing keys in a JSON object at /_matrix/key/v1.

Key Type Description
server_name String DNS name of the home server.
verify_keys Object Public keys of the home server for verifying digital signatures.
signatures Object Digital signatures for this object signed using the verify_keys.
tls_certificate String The X.509 TLS certificate used by this this server encoded as base64.
{
    "server_name": "example.org",
    "signatures": {
        "example.org": {
            "ed25519:auto": "Base+64+Encoded+Signature"
        }
    },
    "tls_certificate": "Base+64+Encoded+DER+Encoded+X509+TLS+Certificate"
    "verify_keys": {
        "ed25519:auto": "Base+64+Encoded+Signature+Verification+Key"
    }
}

When fetching the keys for a server the client should check that the TLS certificate in the JSON matches the TLS server certificate for the connection and should check that the JSON signatures are correct for the supplied verify_keys

7.2   Transactions

Warning

This section may be misleading or inaccurate.

The transfer of EDUs and PDUs between home servers is performed by an exchange of Transaction messages, which are encoded as JSON objects, passed over an HTTP PUT request. A Transaction is meaningful only to the pair of home servers that exchanged it; they are not globally-meaningful.

Each transaction has:
  • An opaque transaction ID.
  • A timestamp (UNIX epoch time in milliseconds) generated by its origin server.
  • An origin and destination server name.
  • A list of "previous IDs".
  • A list of PDUs and EDUs - the actual message payload that the Transaction carries.

7.2.1   Transaction Fields

Key Type Description
origin String DNS name of homeserver making this transaction.
origin_server_ts Integer Timestamp in milliseconds on originating homeserver when this transaction started.
previous_ids List of Strings List of transactions that were sent immediately prior to this transaction.
pdus List of Objects List of persistent updates to rooms.
edus List of Objects List of ephemeral messages.
{
 "transaction_id":"916d630ea616342b42e98a3be0b74113",
 "ts":1404835423000,
 "origin":"red",
 "prev_ids":["e1da392e61898be4d2009b9fecce5325"],
 "pdus":[...],
 "edus":[...]
}

The prev_ids field contains a list of previous transaction IDs that the origin server has sent to this destination. Its purpose is to act as a sequence checking mechanism - the destination server can check whether it has successfully received that Transaction, or ask for a re-transmission if not.

The pdus field of a transaction is a list, containing zero or more PDUs.[*] Each PDU is itself a JSON object containing a number of keys, the exact details of which will vary depending on the type of PDU. Similarly, the edus field is another list containing the EDUs. This key may be entirely absent if there are no EDUs to transfer.

(* Normally the PDU list will be non-empty, but the server should cope with receiving an "empty" transaction, as this is useful for informing peers of other transaction IDs they should be aware of. This effectively acts as a push mechanism to encourage peers to continue to replicate content.)

7.3   PDUs

All PDUs have:

  • An ID to identify the PDU itself
  • A room ID that it relates to
  • A declaration of their type
  • A list of other PDU IDs that have been seen recently in that room (regardless of which origin sent them)

7.3.1   Required PDU Fields

Key Type Description
context String Room identifier
user_id String The ID of the user sending the PDU
origin String DNS name of homeserver that created this PDU
pdu_id String Unique identifier for PDU on the originating homeserver
origin_server_ts Integer Timestamp in milliseconds on origin homeserver when this PDU was created.
pdu_type String PDU event type
content Object The content of the PDU.
prev_pdus List of (String, String, Object) Triplets The originating homeserver, PDU ids and hashes of the most recent PDUs the homeserver was aware of for the room when it made this PDU
depth Integer The maximum depth of the previous PDUs plus one
is_state Boolean True if this PDU is updating room state
{
 "context":"#example:green.example.com",
 "origin":"green.example.com",
 "pdu_id":"a4ecee13e2accdadf56c1025af232176",
 "origin_server_ts":1404838188000,
 "pdu_type":"m.room.message",
 "prev_pdus":[
   ["blue.example.com","99d16afbc8",
       {"sha256":"abase64encodedsha256hashshouldbe43byteslong"}]
 ],
 "hashes":{"sha256":"thishashcoversallfieldsincasethisisredacted"},
 "signatures":{
   "green.example.com":{
     "ed25519:key_version:":"these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus"
   }
 },
 "is_state":false,
 "content": {...}
}

In contrast to Transactions, it is important to note that the prev_pdus field of a PDU refers to PDUs that any origin server has sent, rather than previous IDs that this origin has sent. This list may refer to other PDUs sent by the same origin as the current one, or other origins.

Because of the distributed nature of participants in a Matrix conversation, it is impossible to establish a globally-consistent total ordering on the events. However, by annotating each outbound PDU at its origin with IDs of other PDUs it has received, a partial ordering can be constructed allowing causality relationships to be preserved. A client can then display these messages to the end-user in some order consistent with their content and ensure that no message that is semantically in reply of an earlier one is ever displayed before it.

7.3.2   State Update PDU Fields

PDUs fall into two main categories: those that deliver Events, and those that synchronise State. For PDUs that relate to State synchronisation, additional keys exist to support this:

Key Type Description
state_key String Combined with the pdu_type this identifies the which part of the room state is updated
required_power_level Integer The required power level needed to replace this update.
prev_state_id String The homeserver of the update this replaces
prev_state_origin String The PDU id of the update this replaces.
user_id String The user updating the state.
{...,
 "is_state":true,
 "state_key":TODO-doc
 "required_power_level":TODO-doc
 "prev_state_id":TODO-doc
 "prev_state_origin":TODO-doc
}

7.4   EDUs

EDUs, by comparison to PDUs, do not have an ID, a room ID, or a list of "previous" IDs. The only mandatory fields for these are the type, origin and destination home server names, and the actual nested content.

Key Type Description
edu_type String The type of the ephemeral message.
content Object Content of the ephemeral message.
{
 "edu_type":"m.presence",
 "origin":"blue",
 "destination":"orange",
 "content":{...}
}

7.5   Protocol URLs

Warning

This section may be misleading or inaccurate.

All these URLs are name-spaced within a prefix of:

/_matrix/federation/v1/...

For active pushing of messages representing live activity "as it happens":

PUT .../send/<transaction_id>/
  Body: JSON encoding of a single Transaction
  Response: TODO-doc

The transaction_id path argument will override any ID given in the JSON body. The destination name will be set to that of the receiving server itself. Each embedded PDU in the transaction body will be processed.

To fetch a particular PDU:

GET .../pdu/<origin>/<pdu_id>/
  Response: JSON encoding of a single Transaction containing one PDU

Retrieves a given PDU from the server. The response will contain a single new Transaction, inside which will be the requested PDU.

To fetch all the state of a given room:

GET .../state/<room_id>/
  Response: JSON encoding of a single Transaction containing multiple PDUs

Retrieves a snapshot of the entire current state of the given room. The response will contain a single Transaction, inside which will be a list of PDUs that encode the state.

To backfill events on a given room:

GET .../backfill/<room_id>/
  Query args: v, limit
  Response: JSON encoding of a single Transaction containing multiple PDUs

Retrieves a sliding-window history of previous PDUs that occurred on the given room. Starting from the PDU ID(s) given in the "v" argument, the PDUs that preceded it are retrieved, up to a total number given by the "limit" argument. These are then returned in a new Transaction containing all of the PDUs.

To stream events all the events:

GET .../pull/
  Query args: origin, v
  Response: JSON encoding of a single Transaction consisting of multiple PDUs

Retrieves all of the transactions later than any version given by the "v" arguments.

To make a query:

GET .../query/<query_type>
  Query args: as specified by the individual query types
  Response: JSON encoding of a response object

Performs a single query request on the receiving home server. The Query Type part of the path specifies the kind of query being made, and its query arguments have a meaning specific to that kind of query. The response is a JSON-encoded object whose meaning also depends on the kind of query.

To join a room:

GET .../make_join/<room_id>/<user_id>
  Response: JSON encoding of a join proto-event

PUT .../send_join/<room_id>/<event_id>
  Response: JSON encoding of the state of the room at the time of the event

Performs the room join handshake. For more information, see "Joining Rooms" below.

7.6   Joining Rooms

When a new user wishes to join room that the user's homeserver already knows about, the homeserver can immediately determine if this is allowable by inspecting the state of the room, and if it is acceptable, it can generate, sign, and emit a new m.room.member state event adding the user into that room. When the homeserver does not yet know about the room it cannot do this directly. Instead, it must take a longer multi-stage handshaking process by which it first selects a remote homeserver which is already participating in that room, and uses it to assist in the joining process. This is the remote join handshake.

This handshake involves the homeserver of the new member wishing to join (referred to here as the "joining" server), the directory server hosting the room alias the user is requesting to join with, and a homeserver where existing room members are already present (referred to as the "resident" server).

In summary, the remote join handshake consists of the joining server querying the directory server for information about the room alias; receiving a room ID and a list of join candidates. The joining server then requests information about the room from one of the residents. It uses this information to construct a m.room.member event which it finally sends to a resident server.

Conceptually these are three different roles of homeserver. In practice the directory server is likely to be resident in the room, and so may be selected by the joining server to be the assisting resident. Likewise, it is likely that the joining server picks the same candidate resident for both phases of event construction, though in principle any valid candidate may be used at each time. Thus, any join handshake can potentially involve anywhere from two to four homeservers, though most in practice will use just two.

Client         Joining                Directory       Resident
               Server                 Server          Server

join request -->
               |
               directory request ------->
               <---------- directory response
               |
               make_join request ----------------------->
               <------------------------------- make_join response
               |
               send_join request ----------------------->
               <------------------------------- send_join response
               |
<---------- join response

The first part of the handshake usually involves using the directory server to request the room ID and join candidates. This is covered in more detail on the directory server documentation, below. In the case of a new user joining a room as a result of a received invite, the joining user's homeserver could optimise this step away by picking the origin server of that invite message as the join candidate. However, the joining server should be aware that the origin server of the invite might since have left the room, so should be prepared to fall back on the regular join flow if this optimisation fails.

Once the joining server has the room ID and the join candidates, it then needs to obtain enough information about the room to fill in the required fields of the m.room.member event. It obtains this by selecting a resident from the candidate list, and requesting the make_join endpoint using a GET request, specifying the room ID and the user ID of the new member who is attempting to join.

The resident server replies to this request with a JSON-encoded object having a single key called event; within this is an object whose fields contain some of the information that the joining server will need. Despite its name, this object is not a full event; notably it does not need to be hashed or signed by the resident homeserver. The required fields are:

Key Type Description
type String The value m.room.member
auth_events List An event-reference list containing the authorization events that would allow this member to join
content Object The event content
depth Integer (this field must be present but is ignored; it may be 0)
event_id String A new event ID specified by the resident homeserver
origin String The name of the resident homeserver
origin_server_ts Integer A timestamp added by the resident homeserver
prev_events List An event-reference list containing the immediate predecessor events
room_id String The room ID of the room
sender String The user ID of the joining member
state_key String The user ID of the joining member

The content field itself must be an object, containing:

Key Type Description
membership String The value join

The joining server now has sufficient information to construct the real join event from these protoevent fields. It copies the values of most of them, adding (or replacing) the following fields:

Key Type Description
event_id String A new event ID specified by the joining homeserver
origin String The name of the joining homeserver
origin_server_ts Integer A timestamp added by the joining homeserver

This will be a true event, so the joining server should apply the event-signing algorithm to it, resulting in the addition of the hashes and signatures fields.

To complete the join handshake, the joining server must now submit this new event to an resident homeserver, by using the send_join endpoint. This is invoked using the room ID and the event ID of the new member event.

The resident homeserver then accepts this event into the room's event graph, and responds to the joining server with the full set of state for the newly- joined room. This is returned as a two-element list, whose first element is the integer 200, and whose second element is an object which contains the following keys:

Key Type Description
auth_chain List A list of events giving the authorization chain for this join event
state List A complete list of the prevailing state events at the instant just before accepting the new m.room.member event

7.7   Backfilling

Note

This section is a work in progress.

7.8   Authentication

7.8.1   Request Authentication

Every HTTP request made by a homeserver is authenticated using public key digital signatures. The request method, target and body are signed by wrapping them in a JSON object and signing it using the JSON signing algorithm. The resulting signatures are added as an Authorization header with an auth scheme of X-Matrix. Note that the target field should include the full path starting with /_matrix/..., including the ? and any query parameters if present, but should not include the leading https:, nor the destination server's hostname.

Step 1 sign JSON:

 {
     "method": "GET",
     "uri": "/target",
     "origin": "origin.hs.example.com",
     "destintation": "destination.hs.example.com",
     "content": { JSON content ... },
     "signatures": {
         "origin.hs.example.com": {
             "ed25519:key1": "ABCDEF..."
         }
     }
}

Step 2 add Authorization header:

GET /target HTTP/1.1
Authorization: X-Matrix origin=origin.example.com,key="ed25519:key1",sig="ABCDEF..."
Content-Type: application/json

{ JSON content ... }

Example python code:

def authorization_headers(origin_name, origin_signing_key,
                          destination_name, request_method, request_target,
                          content_json=None):
    request_json = {
         "method": request_method,
         "uri": request_target,
         "origin": origin_name,
         "destination": destination_name,
    }

    if content_json is not None:
        request["content"] = content_json

    signed_json = sign_json(request_json, origin_name, origin_signing_key)

    authorization_headers = []

    for key, sig in signed_json["signatures"][origin_name].items():
        authorization_headers.append(bytes(
            "X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % (
                origin_name, key, sig,
            )
        ))

    return ("Authorization", authorization_headers)

7.8.2   Response Authentication

Responses are authenticated by the TLS server certificate. A homeserver should not send a request until it has authenticated the connected server to avoid leaking messages to eavesdroppers.

7.8.3   Client TLS Certificates

Requests are authenticated at the HTTP layer rather than at the TLS layer because HTTP services like Matrix are often deployed behind load balancers that handle the TLS and these load balancers make it difficult to check TLS client certificates.

A home server may provide a TLS client certificate and the receiving home server may check that the client certificate matches the certificate of the origin home server.

7.10   State Conflict Resolution

Note

This section is a work in progress.

7.11   Presence

The server API for presence is based entirely on exchange of the following EDUs. There are no PDUs or Federation Queries involved.

Performing a presence update and poll subscription request:

EDU type: m.presence

Content keys:
  push: (optional): list of push operations.
    Each should be an object with the following keys:
      user_id: string containing a User ID
      presence: "offline"|"unavailable"|"online"|"free_for_chat"
      status_msg: (optional) string of free-form text
      last_active_ago: milliseconds since the last activity by the user

  poll: (optional): list of strings giving User IDs

  unpoll: (optional): list of strings giving User IDs

The presence of this combined message is two-fold: it informs the recipient server of the current status of one or more users on the sending server (by the push key), and it maintains the list of users on the recipient server that the sending server is interested in receiving updates for, by adding (by the poll key) or removing them (by the unpoll key). The poll and unpoll lists apply changes to the implied list of users; any existing IDs that the server sent as poll operations in a previous message are not removed until explicitly requested by a later unpoll.

On receipt of a message containing a non-empty poll list, the receiving server should immediately send the sending server a presence update EDU of its own, containing in a push list the current state of every user that was in the original EDU's poll list.

Sending a presence invite:

EDU type: m.presence_invite

Content keys:
  observed_user: string giving the User ID of the user whose presence is
    requested (i.e. the recipient of the invite)
  observer_user: string giving the User ID of the user who is requesting to
    observe the presence (i.e. the sender of the invite)

Accepting a presence invite:

EDU type: m.presence_accept

Content keys - as for m.presence_invite

Rejecting a presence invite:

EDU type: m.presence_deny

Content keys - as for m.presence_invite

7.12   Profiles

The server API for profiles is based entirely on the following Federation Queries. There are no additional EDU or PDU types involved, other than the implicit m.presence and m.room.member events (see section below).

Querying profile information:

Query type: profile

Arguments:
  user_id: the ID of the user whose profile to return
  field: (optional) string giving a field name

Returns: JSON object containing the following keys:
  displayname: string of free-form text
  avatar_url: string containing an HTTP-scheme URL

If the query contains the optional field key, it should give the name of a result field. If such is present, then the result should contain only a field of that name, with no others present. If not, the result should contain as much of the user's profile as the home server has available and can make public.

7.13   Directory

The server API for directory queries is also based on Federation Queries.

Querying directory information:

Query type: directory

Arguments:
  room_alias: the room alias to query

Returns: JSON object containing the following keys:
  room_id: string giving the underlying room ID the alias maps to
  servers: list of strings giving the join candidates

The list of join candidates is a list of server names that are likely to hold the given room; these are servers that the requesting server may wish to use as resident servers as part of the remote join handshake. This list may or may not include the server answering the query.

8   Identity Servers

Note

This section is a work in progress.

9   Appendices

9.1   Security Threat Model

9.1.1   Denial of Service

The attacker could attempt to prevent delivery of messages to or from the victim in order to:

  • Disrupt service or marketing campaign of a commercial competitor.
  • Censor a discussion or censor a participant in a discussion.
  • Perform general vandalism.

9.1.1.1   Threat: Resource Exhaustion

An attacker could cause the victims server to exhaust a particular resource (e.g. open TCP connections, CPU, memory, disk storage)

9.1.1.2   Threat: Unrecoverable Consistency Violations

An attacker could send messages which created an unrecoverable "split-brain" state in the cluster such that the victim's servers could no longer derive a consistent view of the chatroom state.

9.1.1.3   Threat: Bad History

An attacker could convince the victim to accept invalid messages which the victim would then include in their view of the chatroom history. Other servers in the chatroom would reject the invalid messages and potentially reject the victims messages as well since they depended on the invalid messages.

9.1.1.4   Threat: Block Network Traffic

An attacker could try to firewall traffic between the victim's server and some or all of the other servers in the chatroom.

9.1.1.5   Threat: High Volume of Messages

An attacker could send large volumes of messages to a chatroom with the victim making the chatroom unusable.

9.1.1.6   Threat: Banning users without necessary authorisation

An attacker could attempt to ban a user from a chatroom with the necessary authorisation.

9.1.2   Spoofing

An attacker could try to send a message claiming to be from the victim without the victim having sent the message in order to:

  • Impersonate the victim while performing illicit activity.
  • Obtain privileges of the victim.

9.1.2.1   Threat: Altering Message Contents

An attacker could try to alter the contents of an existing message from the victim.

9.1.2.2   Threat: Fake Message "origin" Field

An attacker could try to send a new message purporting to be from the victim with a phony "origin" field.

9.1.3   Spamming

The attacker could try to send a high volume of solicited or unsolicited messages to the victim in order to:

  • Find victims for scams.
  • Market unwanted products.

9.1.3.1   Threat: Unsolicited Messages

An attacker could try to send messages to victims who do not wish to receive them.

9.1.3.2   Threat: Abusive Messages

An attacker could send abusive or threatening messages to the victim

9.1.4   Spying

The attacker could try to access message contents or metadata for messages sent by the victim or to the victim that were not intended to reach the attacker in order to:

  • Gain sensitive personal or commercial information.
  • Impersonate the victim using credentials contained in the messages. (e.g. password reset messages)
  • Discover who the victim was talking to and when.

9.1.4.1   Threat: Disclosure during Transmission

An attacker could try to expose the message contents or metadata during transmission between the servers.

9.1.4.2   Threat: Disclosure to Servers Outside Chatroom

An attacker could try to convince servers within a chatroom to send messages to a server it controls that was not authorised to be within the chatroom.

9.1.5   Threat: Disclosure to Servers Within Chatroom

An attacker could take control of a server within a chatroom to expose message contents or metadata for messages in that room.

9.2   Cryptographic Test Vectors

To assist in the development of compatible implementations, the following test values may be useful for verifying the cryptographic event signing code.

9.2.1   Signing Key

The following test vectors all use the 32-byte value given by the following Base64-encoded string as the seed for generating the ed25519 signing key:

SIGNING_KEY_SEED = decode_base64(
    "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA1"
)

In each case, the server name and key ID are as follows:

SERVER_NAME = "domain"

KEY_ID = "ed25519:1"

9.2.2   JSON Signing

Given an empty JSON object:

{}

The JSON signing algorithm should emit the following signed data:

{
    "signatures": {
        "domain": {
            "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
        }
    }
}

Given the following JSON object with data values in it:

{
    "one": 1,
    "two": "Two"
}

The JSON signing algorithm should emit the following signed JSON:

{
    "one": 1,
    "signatures": {
        "domain": {
            "ed25519:1": "KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"
        }
    },
    "two": "Two"
}

9.2.3   Event Signing

Given the following minimally-sized event:

{
    "event_id": "$0:domain",
    "origin": "domain",
    "origin_server_ts": 1000000,
    "signatures": {},
    "type": "X",
    "unsigned": {
        "age_ts": 1000000
    }
}

The event signing algorithm should emit the following signed event:

{
    "event_id": "$0:domain",
    "hashes": {
        "sha256": "6tJjLpXtggfke8UxFhAKg82QVkJzvKOVOOSjUDK4ZSI"
    },
    "origin": "domain",
    "origin_server_ts": 1000000,
    "signatures": {
        "domain": {
            "ed25519:1": "2Wptgo4CwmLo/Y8B8qinxApKaCkBG2fjTWB7AbP5Uy+aIbygsSdLOFzvdDjww8zUVKCmI02eP9xtyJxc/cLiBA"
        }
    },
    "type": "X",
    "unsigned": {
        "age_ts": 1000000
    }
}

Given the following event containing redactable content:

{
    "content": {
        "body": "Here is the message content",
    },
    "event_id": "$0:domain",
    "origin": "domain",
    "origin_server_ts": 1000000,
    "type": "m.room.message",
    "room_id": "!r:domain",
    "sender": "@u:domain",
    "signatures": {},
    "unsigned": {
        "age_ts": 1000000
    }
}

The event signing algorithm should emit the following signed event:

{
    "content": {
        "body": "Here is the message content",
    },
    "event_id": "$0:domain",
    "hashes": {
        "sha256": "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g"
    },
    "origin": "domain",
    "origin_server_ts": 1000000,
    "type": "m.room.message",
    "room_id": "!r:domain",
    "sender": "@u:domain",
    "signatures": {
        "domain": {
            "ed25519:1": "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUwu6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA"
        }
    },
    "unsigned": {
        "age_ts": 1000000
    }
}