Internet-Draft | Linearized Matrix | June 2023 |
Ralston & Hodgson | Expires 29 December 2023 | [Page] |
This document specifies Linearized Matrix for use in messaging interoperability.¶
This note is to be removed before publishing as an RFC.¶
The latest revision of this draft can be found at https://turt2live.github.io/ietf-mimi-linearized-matrix/draft-ralston-mimi-linearized-matrix.html. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-ralston-mimi-linearized-matrix/.¶
Discussion of this document takes place on the More Instant Messaging Interoperability Working Group mailing list (mailto:mimi@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/mimi/. Subscribe at https://www.ietf.org/mailman/listinfo/mimi/.¶
Source for this draft and an issue tracker can be found at https://github.com/turt2live/ietf-mimi-linearized-matrix.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 29 December 2023.¶
Copyright (c) 2023 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Alongside messaging, Matrix operates as an openly federated communications protocol for VoIP, IoT, and more. The existing Matrix network uses fully decentralized access control within rooms (conversations) and is highly extensible in its structure. These features are not critically important to a strict focus on messaging interoperability, however.¶
This document specifies "Linearized Matrix": a modified room model based upon Matrix's existing room model. This document does not explore how to interconnect Linearized Matrix with the existing Matrix room model - interested readers may wish to review [MSC3995] within the Matrix Specification process.¶
At a high level, a central server is designated as the "hub" server, responsible for ensuring events in a given room are provided to all other participants equally. Servers communicate with each other over HTTPS and JSON, using the specified API endpoints.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This document uses [I-D.ralston-mimi-terminology] where possible.¶
This document additionally uses the following definitions:¶
Further terms are introduced in-context within this document.¶
TODO: We should move/copy those definitions up here anyways.¶
For a given conversation/room:¶
In this diagram, Server A is acting as a hub for the other two servers. Servers B and C do not converse directly when sending events to the room: those events are instead sent to the hub which then distributes them back out to all participating servers.¶
Clients are shown in the diagram here for demonstrative purposes only. No Client-Server API or other requirements of clients are specified in this document.¶
This leads to two distinct roles:¶
OPEN QUESTION: Should we support having multiple hubs for increased trust between participant and hub? (participant can pick the hub it wants to use rather than being forced to use a single hub)¶
Each server has a "domain name" or "server name" to uniquely identify it. This server name is then used to namespace user IDs, room IDs/aliases, etc.¶
A server name MUST be compliant with Section 2.1 of [RFC1123] and, when an IPv6 address,
encoded per Section 2.2 of [RFC4291] surrounded by square brackets ([]
). Improper server
names MUST be considered "uncontactable" by a server.¶
A server SHOULD NOT use a literal IPv4 or IPv6 address as a server name. Doing so reduces the ability for the server to move to another internet address later, and IP addresses are generally difficult to acquire certificates for (required in Section 13). Additionally, servers SHOULD NOT use an explicit port in their server name for similar portability reasons.¶
The approximate ABNF [RFC5234] grammar for a server name is:¶
server_name = hostname [ ":" port ] port = 1*5DIGIT hostname = IPv4address / "[" IPv6address "]" / dns-name IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT IPv6address = 2*45IPv6char IPv6char = DIGIT / %x41-46 / %x61-66 / ":" / "." ; 0-9, A-F, a-f, :, . dns-name = 1*255dns-char dns-char = DIGIT / ALPHA / "-" / "."¶
Server names MUST be treated as case sensitive (eXaMpLe.OrG
, example.org
, and EXAMPLE.ORG
are 3 different servers). Server names SHOULD be lower case (example.org
) and SHOULD NOT exceed
230 characters for ease of use. The 230 characters specifically gives room for a suitably long
localpart while being within the 255 allowable characters from Section 2.1 of [RFC1123].¶
Examples:¶
example.org
(DNS host name)¶
example.org:5678
(DNS host name with explicit port)¶
127.0.0.1
(literal IPv4 address)¶
127.0.0.1:5678
(literal IPv4 address with explicit port)¶
[2001:DB8:0:0:8:800:200C:417A]
(literal IPv6 address)¶
[2001:DB8:0:0:8:800:200C:417A]:5678
(literal IPv6 address with explicit port)¶
Rooms hold the same definition under [I-D.ralston-mimi-terminology]: a conceptual place where users send and receive events. All users with sufficient access to the room receive events sent to that room.¶
The different chat types are represented by rooms through state events (Section 3.5.2), which ultimately change how the different algorithms in the room version (Section 3.2.1) behave.¶
Rooms have a single internal "Room ID" to identify them from another room. The ABNF [RFC5234] grammar for a room ID is:¶
room_id = "!" room_id_localpart ":" server_name room_id_localpart = 1*opaque opaque = DIGIT / ALPHA / "-" / "." / "~" / "_"¶
server_name
is inherited from Section 3.1.¶
room_id
MUST NOT exceed 255 characters and MUST be treated as case sensitive.¶
Example: !abc:example.org
.¶
The server_name
for a room ID does NOT indicate the room is "hosted" or served by that
domain. The domain is used as a namespace to prevent another server from maliciously taking
over a room. The server represented by that domain may no longer be participating in the room.¶
The entire room ID after the !
sigil MUST be treated as opaque. No part of the room ID should
be parsed, and cannot be assumed to be human-readable.¶
TODO: We should consider naming this something else.¶
A room version is a set of algorithms which define how the room operates, identified by a single string. Room versions are immutable once specified, as otherwise a change in algorithms could cause a split-brain between participating servers.¶
Room versions prefixed with I.
MUST only be used within the IETF specification process.
Room versions consisting solely of 0-9
and .
MUST only be used by the Matrix protocol.¶
There is no implicit ordering or hierarchy to room versions. Although there is a recommended default room version, some rooms might benefit from features of a different room version.¶
A room version has the following algorithms defined:¶
A server is capable of supporting multiple room versions at a time. The transport API decouples specific details regarding room versions from the wire formats. For example, events are treated as JSON blobs in this document's Server-Server API (Section 13).¶
Room versions are normally specified using a dedicated document. An example of this can be found in the existing Matrix Spec Change process as [MSC3820].¶
Each room version has a "stable" or "unstable" designation. Stable room versions SHOULD be used in production by messaging providers. Unstable room versions might contain bugs or are not yet fully specified and SHOULD NOT be used in production by messaging providers.¶
TODO: Matrix considers a version as stable once accepted through FCP. When would be the process equivalent for the IETF?¶
The ABNF [RFC5234] grammar for a room version is:¶
room_version = 1*128room_version_char room_version_char = DIGIT / %x61-7A ; a-z / "-" / "."¶
Examples:¶
Room versions not formally specified SHOULD be prefixed using reverse domain name notation,
creating a sort of namespace. org.example.my-room-version
is an example of this.¶
As described by [I-D.ralston-mimi-terminology], a user is typically a human which operates a client. Each user has a distinct user ID.¶
The ABNF [RFC5234] grammar for a user ID is:¶
user_id = "@" user_id_localpart ":" server_name user_id_localpart = 1*user_id_char user_id_char = DIGIT / %x61-7A ; a-z / "-" / "." / "=" / "_" / "/" / "+"¶
server_name
is inherited from Section 3.1.¶
user_id
MUST NOT exceed 255 characters and MUST be treated as case sensitive.¶
Examples:¶
user_id_localpart
SHOULD be human-readable and notably MUST NOT contain uppercase letters.¶
server_name
denotes the domain name (Section 3.1) which allocated the ID, or would allocate
the ID if the user doesn't exist yet.¶
Identity systems and messaging providers SHOULD NOT use a phone number in a localpart, as the localpart for a user ID is unchangeable. In these cases, a GUID (scoped to the allocating server) is recommended so the associated human can change their phone number without losing chats.¶
This document does not define how a user ID is acquired. It is expected that an identity specification under MIMI will handle resolving email addresses, phone numbers, names, and other common queries to user IDs.¶
User IDs are sometimes informally referenced as "MXIDs", short for "Matrix User IDs".¶
Each user can have zero or more devices/active clients. These devices are intended to be members of the MLS group and thus have their own key package material associated with them.¶
TODO: Do we need to define grammar and such for device IDs, or is that covered by MLS already?¶
All data exchanged over Linearized Matrix is expressed as an "event". Each client action
(such as sending a message) correlates with exactly one event. All events have a type
to distinguish them, and use reverse domain name notation to namespace custom events
(for example, org.example.appname.eventname
).¶
Event types using m.
as a prefix MUST only be used by the protocol.¶
When events are traversing a transport to another server they are referred to as a Persistent Data Unit or PDU. Structurally, an event and PDU are the same.¶
An event has the following minimum fields:¶
room_id
(string; required) - The room ID for where the event is being sent. This MUST be
a valid room ID (Section 3.2).¶
type
(string; required) - A UTF-8 [RFC3629] string to distinguish different data types
being carried by events. Event types are case sensitive. This MUST NOT exceed 255 characters.¶
state_key
(string; optional) - A UTF-8 [RFC3629] string to further distinguish an event
type from other related events. Only specified on State Events (Section 3.5.2). Can be
empty. This MUST NOT exceed 255 characters.¶
sender
(string; required) - The user ID which is sending this event. This MUST be a valid
user ID (Section 3.3).¶
origin_server_ts
(integer; required) - The milliseconds since the unix epoch for when this
event was created.¶
hub_server
(string; technically optional) - The domain name of the hub server which is
sending this event to the remainder of the room. All events created within Linearized Matrix
MUST have this field set, however events created externally MUST NOT set this field. This
MUST be a valid server name (Section 3.1).¶
content
(object; required) - The event content. The specific schema depends on the event
type. Clients and servers processing an event MUST NOT assume the content
is safe or
accurately represented. Malicious clients and servers are able to send payloads which don't
comply with a given schema, which may cause unexpected behaviour on the receiving side.
For example, a field marked as "required" might be missing.¶
hashes
(object; required) - The content hashes for the event. The lpdu
key within this
object is an object itself, keyed by hash algorithm with value being the encoded hash. Similarly,
outside of lpdu
, hashes
is keyed by hash algorithm with value being the encoded hash.
Events created within Linearized Matrix MUST specify an LPDU hash, however events created
externally MUST NOT set such a hash.¶
signatures
(object; required) - Keyed first by domain name then by key ID, the signatures for
the event.¶
auth_events
(array of strings; required) - The event IDs which prove the sender is able to
send this event in the room. Which specific events are put here are defined by the auth events
selection algorithm (Section 6.4.1).¶
prev_events
(array of strings; required) - The event IDs which precede the event. Events
created within Linearized Matrix MUST only ever have a single event ID here, however events
created externally MAY have one or more referenced event IDs.¶
unsigned
(object; optional) - Additional metadata not covered by the signing algorithm. Like
content
, a receiver MUST NOT trust the values to match any particular schema.¶
Note that an event ID is not specified on the schema. Event IDs are calculated to ensure accuracy
and consistency between servers. To determine the ID for an event, calculate the reference hash
(Section 10.2) then encode it using URL-safe Unpadded Base64 (Section 11)
and prefix that with the event ID sigil, $
. For example, $nKHVqt3iyLA_HEE8lT1yUaaVjjBRR-fAqpN4t7opadc
.¶
The ABNF [RFC5234] for an event ID is:¶
event_id = "$" reference_hash reference_hash = 1*urlsafe_unpadded_base64_char urlsafe_unpadded_base64_char = ALPHA / DIGIT / "-" / "_"¶
If both the sender and receiver are implementing the algorithms correctly, the event ID will be
the same. When different, the receiver will have issues accepting the event (none of the auth_events
will make sense, for example). The sender and receiver will need to review that their implementation
matches the specification in this case.¶
Events are treated as JSON [RFC8259] within the protocol, but can be encoded and represented by any binary-compatible format. Additional overhead may be introduced when converting between formats, however.¶
An example event is:¶
{ "room_id": "!abc:example.org", "type": "m.room.member", "state_key": "@alice:first.example.org", "sender": "@bob:second.example.org", "origin_server_ts": 1681340188825, "hub_server": "first.example.org", "content": { "membership": "invite" }, "hashes": { "lpdu": { "sha256": "<unpadded base64>" }, "sha256": "<unpadded base64>" }, "signatures": { "first.example.org": { "ed25519:1": "<unpadded base64 signature covering whole event>" }, "second.example.org": { "ed25519:1": "<unpadded base64 signature covering LPDU>" } }, "auth_events": ["$first", "$second"], "prev_events": ["$parent"], "unsigned": { "arbitrary": "fields" } }¶
An event/PDU MUST NOT exceed 65536 bytes when formatted using Canonical JSON (Section 8).
Note that this includes all signatures
on the event.¶
Fields have no size limit unless specified above, other than the maximum 65536 bytes for the whole event.¶
The hub server is responsible for ensuring events are linearly added to the room from all participants,
which means participants cannot set fields such as prev_events
on their events. Additionally,
participant servers are not expected to store past conversation history or even "current state" for
the room, further making participants unable to reliably populate auth_events
and prev_events
.¶
To avoid these problems, the participant server MUST NOT populate the following fields on events they are sending to the hub:¶
auth_events
- the participant cannot reliably determine what allows it to send the event.¶
prev_events
- the participant cannot reliably know what event precedes theirs.¶
hashes
(except hashes.lpdu
) - top-level hashes cover the above two fields.¶
The participant server MUST populate the hashes.lpdu
object, covering a content hash
(Section 10.1) of the partial event, giving authenticity to the sender's contents. The
participant server additionally signs this partial event before sending it to the hub.¶
The participant server will receive an echo of the fully-formed event from the hub once appended to the room.¶
State events track metadata for the room, such as name, topic, and members. State is keyed by a
tuple of type
and state_key
, noting that an empty string is a valid state key. State in the
room with the same key-tuple will be overwritten as "current state".¶
State events are otherwise processed like regular events in the room: they're appended to the room history and can be referenced by that room history.¶
"Current state" is the state at the time being considered (which is often the implied HEAD
of
the room). In Linearized Matrix, a simple approach to calculating current state is to iterate
over all events in order, overwriting the key-tuple for state events in an adjacent map. That
map becomes "current state" when the loop is finished.¶
Stripped state event are extremely simplified state events to provide context to an invite or knock. Servers MUST NOT rely on stripped state events being accurate, only using them if the server doesn't have access to the room's real state.¶
When generating stripped state for an invite or knock, the following events SHOULD be included if present in the current room state itself:¶
m.room.create
(Section 3.5.3.1)¶
m.room.name
(TODO: Link)¶
m.room.avatar
(TODO: Link)¶
m.room.topic
(TODO: Link)¶
m.room.join_rules
(Section 3.5.3.2)¶
m.room.canonical_alias
(TODO: Link)¶
m.room.encryption
(TODO: Link)¶
Servers MAY include other event types/state keys. The above set gives users enough context to determine if they'd like to knock/join the room, as features such as the name and avatar are generally key pieces of information for a user.¶
Stripped state events MUST only have sender
, type
, state_key
, and content
from the event
schema (Section 3.5).¶
Example:¶
{ "type": "m.room.create", "sender": "@alice:example.org", "state_key": "", "content": { "room_version": "I.1" } }¶
Linearized Matrix defines the following event types. The section headers are the event type
.¶
m.room.create
The very first event in the room. It MUST NOT have any auth_events
or prev_events
, and the
domain of the sender
MUST be the same as the domain in the room_id
. The state_key
MUST
be an empty string.¶
The content
for a create event MUST have at least a room_version
field to denote what set
of algorithms the room is using.¶
These conditions are checked as part of the event authorization rules (Section 6.4).¶
m.room.join_rules
Defines whether users can join without an invite and other similar conditions. The state_key
MUST be an empty string. Any other state key, including lack thereof, serve no meaning and
are treated as though they were a custom event.¶
The content
for a join rules event MUST have at least a join_rule
field to denote the
join policy for the room. Allowable values are:¶
public
- anyone can join without an invite.¶
knock
- users must receive an invite to join, and can request an invite (knock) too.¶
invite
- users must receive an invite to join.¶
TODO: Describe restricted
(and knock_restricted
) rooms?¶
TODO: What's the default?¶
m.room.member
Defines the membership for a user in the room. If the user does not have a membership event then
they are presumed to be in the leave
state.¶
The state_key
MUST be a non-empty string denotating the user ID the membership is affecting.¶
The content
for a membership event MUST have at least a membership
field to denote the
membership state for the user. Allowable values are:¶
leave
- not participating in the room. If the state_key
and sender
do not match, this was
a kick rather than voluntary leave.¶
join
- participating in the room.¶
knock
- requesting an invite to the room.¶
invite
- invited to participate in the room.¶
ban
- implies kicked/not participating. Cannot be invited or join the room without being
unbanned first (moderator sends a kick, essentially).¶
These conditions are checked as part of the event authorization rules (Section 6.4), as are the rules for moving between membership states.¶
The content
for a membership event MAY additionally have a reason
field containing a human-readable
(and usually human-supplied) description for why the membership change happened. For example, the reason
why a user was kicked/banned or why they are requesting an invite by knocking.¶
m.room.power_levels
Defines what given users can and can't do, as well as which event types they are able to send. The enforcement of these power levels is determined by the event authorization rules (Section 6.4).¶
The state_key
MUST be an empty string.¶
The content
for a power levels event SHOULD have at least the following:¶
ban
(integer) - the level required to ban a user. Defaults to 50
if unspecified.¶
kick
(integer) - the level required to kick a user. Defaults to 50
if unspecified.¶
invite
(integer) - the level required to invite a user. Defaults to 0
if unspecified.¶
redact
(integer) - the level required to redact an event sent by another user. Defaults
to 50
if unspecified.¶
events
(map) - keyed by event type string, the level required to send that event type to
the room. Defaults to an empty map if unspecified.¶
events_default
(integer) - the level required to send events in the room. Overridden by
the events
map. Defaults to 0
if unspecified.¶
state_default
(integer) - the level required to send state events in the room. Overridden
by the events
map. Defaults to 50
if unspecified.¶
users
(map) - keyed by user ID, the level of that user. Defaults to an empty map if
unspecified.¶
users_default
(integer) - the level for users. Overridden by the users
map. Defaults to
0
if unspecified.¶
TODO: Include notifications for at-room here too?¶
Note that if no power levels event is specified in the room then the room creator (sender
of
the m.room.create
state event) has a default power level of 100.¶
These conditions are checked as part of the event authorization rules (Section 6.4).¶
All power levels are calculated with reference to the content
of an m.room.power_levels
state event.¶
To calculate a user's current power level:¶
users
is present, use the power level for the user ID, if present.¶
users
is not present, or the user ID is not present in users
, use users_default
.¶
users_default
is not present, use 0
.¶
To calculate the required power level to do an action:¶
kick
, ban
, invite
, or redact
) is present, use that power level.¶
50
for kick
, ban
, and redact
, 0
for invite
).¶
To calculate the required power level to send an event:¶
m.room.history_visibility
TODO: Describe.¶
TODO: Describe. (when can a server see an event?)¶
TODO: m.room.name
, m.room.topic
, m.room.avatar
, m.room.encryption
, m.room.canonical_alias
¶
TODO: Drop m.room.encryption
and pack it into the create event instead?¶
Room versions are described by Section 3.2.1.¶
As a whole, this document describes the I.1
room version. Future room versions can build
upon this version's principles (or entirely replace them) through dedicated documents.¶
Servers MUST implement support for I.1
, and SHOULD implement other specified room versions
as needed. Servers SHOULD use I.1
when creating new rooms. I.1
shall be considered "stable".¶
Implementation note: Currently I.1
is not a real thing. Use
org.matrix.i-d.ralston-mimi-linearized-matrix.02
when testing against other Linearized Matrix
implementations. This string may be updated later to account for breaking changes.¶
Implementation note: org.matrix.i-d.ralston-mimi-linearized-matrix.00
also exists in the
wild, defining a set of algorithms which exist in a prior version of this document (00 and 01).¶
TODO: Remove implementation notes.¶
The hub server MUST enforce the room version's algorithms upon the room. Participant servers SHOULD enforce the room version's algorithm, but can opt to believe the hub server if they wish.¶
MIMI has a chartered requirement to use Messaging Layer Security (MLS) [I-D.ietf-mls-protocol] [I-D.ietf-mls-architecture] for encryption, and MLS requires that all group members (devices) know of all other devices.¶
Each room has a single MLS Group associated with it, starting with the m.room.create
event.¶
TODO: Details on how key material is stored/shared within the room.¶
TODO: What does m.room.encrypted
(user message) look like here?¶
All servers, including hubs and participants, publish an ed25519 [RFC8032] signing key to be used by other servers when verifying signatures.¶
TODO: Verify RFC reference. We might be using a slightly different ed25519 key today? See https://hdevalence.ca/blog/2020-10-04-its-25519am¶
Each key ID consists of an algorithm name and version. Signing keys MUST use an algorithm
of ed25519
(and therefore MUST be an ed25519 key). The key version MUST be valid under
the following ABNF [RFC5234]:¶
key_version = 1*key_version_char key_version_char = ALPHA / DIGIT / "_"¶
An algorithm and version combined is a "key ID", deliminated by :
as per the following
ABNF [RFC5234]:¶
key_id = key_algorithm ":" key_version key_algorithm = "ed25519"¶
Additional key algorithms may be supported by future documents.¶
To sign an event:¶
To sign an object:¶
signatures
and unsigned
.¶
signatures
using unpadded base64 (Section 11).¶
Note that signatures
is an object with keys being the entity which did the signing and value
being the key ID to encoded signature pair. See Section 3.5 for details on the signatures
structure for events specifically.¶
If the signatures
field is missing, doesn't contain the entity that is expected to have done
the signing (usually a server name), doesn't have a known key ID, or is otherwise structurally invalid
then the signature check fails.¶
If decoding the base64 fails, the check fails.¶
If the object is an event, redact (Section 9) it before continuing.¶
If removing the signatures
and unsigned
properties, canonicalizing the JSON (Section 8),
and verifying the signature fails, the check fails. Note that to verify the signature the server
may need to fetch another server's key first (Section 13.4.1).¶
Otherwise, the check passes.¶
TODO: Which specific signatures are required? If a server has multiple signing keys, possibly a combination of new and old, do we require all or some of them to sign?¶
When signing a JSON object, such as an event, it is important that the bytes be ordered in the same way for everyone. Otherwise, the signatures will never match.¶
To canonicalize a JSON object, use [RFC8785].¶
TODO: Matrix currently doesn't use RFC8785, but it should (or similar).¶
All fields at the top level except the following are stripped from the event:¶
type
¶
room_id
¶
sender
¶
state_key
¶
content
¶
origin_server_ts
¶
hashes
¶
signatures
¶
prev_events
¶
auth_events
¶
hub_server
¶
Additionally, some event types retain specific fields under the event's content
. All other
fields are stripped.¶
An event is covered by two hashes: a content hash and a reference hash. The content hash covers the unredacted event to ensure it was not modified in transit. The reference hash covers the essential fields of the event, including content hashes, and serves as the event's ID.¶
Throughout this document, "unpadded base64" is used to represent binary values as strings. Base64 is
as specified by Section 4 of [RFC4648], and "unpadded base64" simply removes any =
padding from
the resulting string.¶
Implementations SHOULD accept input with or without padding on base64 values, where possible.¶
Section 5 of [RFC4648] describes URL-safe base64. The same changes are adopted here. Namely, the
62nd and 63rd characters are replaced with -
and _
respectively. The unpadded behaviour is as
described above.¶
TODO: This section, if we want a single canonical hub in the room. Some expected problems in this area are: who signs the transfer event? who sends the transfer event? how does a transfer start?¶
TODO: Is this section better placed in the MSC for now?¶
This document specifies a wire transport which uses JSON [RFC8259] over HTTPS [RFC9110]. Servers MUST support a minimum of HTTP/2 [RFC9113] and TLS 1.3 [RFC8446].¶
Servers MUST provide a TLS certificate signed by a known Certificate Authority. Requesting servers are ultimately responsible for the Certificate Authorities they place trust in, however servers SHOULD trust authorities which would commonly be trusted by an operating system or web browser.¶
All HTTP POST
and PUT
endpoints require the sending server to supply a (potentially empty) JSON
object as the request body. Requesting servers SHOULD supply a Content-Type
header of application/json
for such requests.¶
All endpoints which require a server to respond with a JSON object MUST include a Content-Type
header
of application/json
.¶
All JSON data, in requests or responses, MUST be encoded using UTF-8 [RFC3629].¶
All endpoints in this document do not support trailing slashes on them. When such a request is encountered, it MUST be handled as an unknown endpoint (Section 13.2.3). Examples include:¶
https://example.org/_matrix/path
- valid.¶
https://example.org/_matrix/path/
- unknown/invalid.¶
https://example.org//_matrix/path
- unknown/invalid (domain also can't have a trailing slash).¶
https://example.org//_matrix/path/
- doubly unknown/invalid.¶
Servers (both hub and participants) MUST implement all endpoints unless otherwise specified.¶
Most endpoints have a version number as part of the path. This version number is that endpoint's version, allowing for breaking changes to be made to the schema of that endpoint. For clarity, the version number is not representative of an API version.¶
All errors are represented by an error code defined by this document and an accompanied HTTP status code. It is possible for a HTTP status code to map to multiple error codes, and it's possible for an error code to map to multiple HTTP status codes.¶
When a server is returning an error to a caller, it MUST use the most appropriate error response defined
by the endpoint. If no appropriate error response is specified, the server SHOULD use M_UNKNOWN
as the
error code and 500 Internal Server Error
as the HTTP status code.¶
Errors are represented as JSON objects, requiring a Content-Type: application/json
response header:¶
{ "errcode": "M_UNKNOWN", "error": "Something went wrong." }¶
errcode
is required and denotes the error code. error
is an optional human-readable description of
the error. error
can be as precise or vague as the responding server desires - the strings in this
document are suggestions.¶
Some common error codes are:¶
M_UNKNOWN
- An unknown error has occurred.¶
M_FORBIDDEN
- The caller is not permitted to access the resource. For example, trying to join a room
the user does not have an invite for.¶
M_NOT_JSON
- The request did not contain valid JSON. Must be accompanied by a 400 Bad Request
HTTP
status code.¶
M_BAD_JSON
- The request did contain valid JSON, but it was missing required keys or was malformed in
another way. Must be accompanied by a 400 Bad Request
HTTP status code.¶
M_LIMIT_EXCEEDED
- Too many requests have been sent. The caller should wait before trying the request
again.¶
M_TOO_LARGE
- The request was too large for the receiver to handle.¶
If a server receives a request for an unsupported or otherwise unknown endpoint, the server MUST respond
with an HTTP 404 Not Found
status code and M_UNRECOGNIZED
error code. If the request was for a known
endpoint, but wrong HTTP method, a 405 Method Not Allowed
HTTP status code and M_UNRECOGNIZED
error
code (Section 13.2.2).¶
If a server is expecting JSON in the request body but receives something else, it MUST respond with an
HTTP status code of 400 Bad Request
and error code M_NOT_JSON
(Section 13.2.2). If the
request contains JSON, and is for a known endpoint, but otherwise missing required keys or is malformed,
the server MUST respond with an HTTP status code of 400 Bad Request
and error code M_BAD_JSON
(Section 13.2.2). Where possible, error
for M_BAD_JSON
should describe the missing keys
or other parsing error.¶
Where endpoints use HTTP PUT
, it is typical for a "transaction ID" to be specified in the path
parameters. This transaction ID MUST ONLY be used for making requests idempotent - if a server receives
two (or more) requests with the same transaction ID, it MUST return the same response for each and only
process the request body once. It is assumed that requests using the same transaction ID also contain
the same request body between calls.¶
A transaction ID only needs to be unique per-endpoint and per-sending server. A server's transaction IDs do not affect requests made by other servers or made to other endpoints by the same server.¶
Servers SHOULD implement rate limiting semantics to reduce the risk of being overloaded. Endpoints which support being rate limited are annotated in this document.¶
If a rate limit is encountered, the server MUST respond with an HTTP 429 Too Many Requests
status code
and M_LIMIT_EXCEEDED
error code (Section 13.2.2). If applicable, the server should additionally
include a retry_after_ms
integer field on the error response to denote how long the caller should
wait before retrying, in milliseconds.¶
{ "errcode": "M_LIMIT_EXCEEDED", "error": "Too many requests. Try again later.", "retry_after_ms": 10254 }¶
The exact rate limit mechanics are left as an implementation detail. A potential approach may be to prevent repeated requests for the same resource at a high rate and ensuring a remote server does not request more than a defined number of resources at a time.¶
Before making an API request, the caller MUST resolve a server name (Section 3.1) to an IP address and port, suitable for HTTPS [RFC9110] traffic.¶
A server MAY change the IP/port combination used for API endpoints using SRV DNS records [RFC2782].
Servers MAY additionally change which TLS certificate is presented by using .well-known
delegation.¶
.well-known
delegation (step 3 below) is recommended for its ease of configuration over SRV DNS records.¶
The target server MUST present a valid TLS certificate (Section 13.1) for the name described in each
step. Similarly, the requesting server MUST use an HTTP Host
header matching the description in each
step.¶
Server developers should note that many of the DNS requirements for the steps below are typically handled by the software language or library implicitly. It is rare that a DNS A record needs to be resolved manually, for example.¶
Per Section 3.1, a server name consists of <hostname>[:<port>]
. The steps to convert that
server name to an IP address and port are:¶
If <hostname>
is an IP literal, then that IP address is to be used together with the given port
number, or 8448 if no port is given.¶
TLS certificate: <hostname>
(always without port)¶
Host header: <hostname>
or <hostname>:<port>
if a port was specified¶
If <hostname>
is not an IP literal, and an explicit <port>
is present, resolve <hostname>
to
an IP address using CNAME [RFC1034] [RFC2181], AAAA [RFC3596], or A [RFC1035] DNS
records. Requests are made to the resolved IP address and port number.¶
TLS certificate: <hostname>
(always without port)¶
Host header: <hostname>:<port>
¶
If <hostname>
is not an IP literal, a regular (non-Matrix) HTTPS request is made to
https://<hostname>/.well-known/matrix/server
, expecting the schema defined by Section 13.3.1.
If the response is invalid (bad/not JSON, missing properties, non-200 response, etc), skip to Step 4.
If the response is valid, the m.server
property is parsed as <delegated_hostname>[:<delegated_port>]
.¶
If <delegated_hostname>
is an IP literal, then that IP address is to be used together with the
given port number, or 8448 if no port is given.¶
TLS certificate: <delegated_hostname>
(always without port)¶
Host header: <delegated_hostname>
or <delegated_hostname>:<delegated_port>
if a port was specified¶
If <delegated_hostname>
is not an IP literal, and <delegated_port>
is present, resolve
<delegated_hostname>
to an IP address using CNAME, AAAA, or A DNS records. Requests are made to the
resolved IP address and port number.¶
TLS certificate: <delegated_hostname>
(always without port)¶
Host header: <delegated_hostname>:<delegated_port>
¶
If <delegated_hostname>
is not an IP literal and no <delegated_port>
is present, an SRV DNS
record is resolved for _matrix._tcp.<delegated_hostname>
. This may result in another hostname
and port to be resolved using AAAA or A DNS records. Requests are made to the resolved IP address
and port number.¶
TLS certificate: <delegated_hostname>
¶
Host header: <delegated_hostname>
(without port)¶
If no SRV record is found, an IP address is resolved for <delegated_hostname>
is resolved using
CNAME, AAAA, or A DNS records. Requests are made to the resolved IP address with port number 8448.¶
TLS certificate: <delegated_hostname>
¶
Host header: <delegated_hostname>
(without port)¶
If the .well-known
call from Step 3 resulted in an invalid response, an SRV DNS record is resolved
for _matrix._tcp.<hostname>
. This may result in another hostname and port to be resolved using AAAA
or A DNS records. Requests are made to the resolved IP address and port number.¶
TLS certificate: <hostname>
(always without port)¶
Host header: <hostname>
(without port)¶
If the .well-known
call from Step 3 resulted in an invalid response, and the SRV record from Step 4
was not found, and IP address is resolved using CNAME, AAAA, or A DNS records. Requests are made to the
resolved IP address and port 8448.¶
TLS certificate: <hostname>
(always without port)¶
Host header: <hostname>
(without port)¶
We require <[delegated_]hostname>
rather than <srv_hostname>
in Steps 3.3 and 4 for the following reasons:¶
<[delegated_]hostname>
via TLS.¶
Server implementations and owners should additionally note that the target of a SRV record MUST NOT be a CNAME, as per RFC 2782 [RFC2782]:¶
the name MUST NOT be an alias (in the sense of RFC 1034 or RFC 2181)¶
GET /.well-known/matrix/server
Used by the server name resolution approach to determine a delegated hostname for a given server. 30x HTTP redirection MUST be followed, though loops SHOULD be avoided. Normal X.509 certificate validation is applied to this endpoint (not the specific validation required by the server name resolution steps) [RFC5280].¶
This endpoint MAY be implemented by servers (it is optional).¶
Rate-limited: No.¶
Authentication required: No.¶
This HTTP endpoint does not specify any request parameters or body.¶
200 OK
response:¶
{ "m.server": "delegated.example.org:8448" }¶
m.server
is a required response field. Responses SHOULD have a Content-Type
HTTP header of
application/json
, however servers parsing the response should assume that the body is JSON regardless
of Content-Type
header. Failures in parsing the JSON or otherwise invalid data that prevents parsing
MUST NOT result in discovery failure. Instead, the caller is expected to move on to the next step of
the name resolution approach.¶
Cache control headers SHOULD be respected on a 200 OK
response. Callers SHOULD impose a maximum
cache time of 48 hours, regardless of cache control headers. A default of 24 hours SHOULD be used
when no cache control headers are present.¶
Error responses (non-200) SHOULD be cached for no longer than 1 hour. Callers SHOULD exponentially back off (to a defined limit) upon receiving repeated error responses.¶
Most endpoints in this document require authentication to prove which server is making the request. This is done using public key digital signatures.¶
The request method, target, and body are represented as a JSON object, signed, and appended as an HTTP
Authorization
header with an auth scheme of X-Matrix
.¶
The object to be signed is:¶
{ "method": "GET", "uri": "/path/to/endpoint?with_qs=true", "origin": "requesting.server.name.example.org", "destination": "target.server.name.example.org", "content": {"json_request_body": true} }¶
method
is the HTTP request method, capitalized. uri
is the full request path, beginning with the
leading slash and containing the query string (if present). uri
does not contain the https:
scheme
or hostname.¶
TODO: Define an ordering algorithm for the query string (if we need to?).¶
origin
and destination
are the sender and receiver server names (Section 3.1), respectively.¶
content
is the JSON-encoded request body. When a request doesn't contain a body, such as in GET
requests, use an empty JSON object.¶
That object is then signed (Section 7.2) by the requesting server. The resulting signature
is appended as an Authentication
HTTP header on the request:¶
GET /path/to/endpoint?with_qs=true Authorization: X-Matrix origin="requesting.server.name.example.org", destination="target.server.name.example.org", key="ed25519:0", sig="<unpadded base64 encoded signature>" Content-Type: application/json {"json_request_body": true}¶
Linebreaks within Authorization
are for clarity and are non-normative.¶
The format of the Authorization header matches Section 11.4 of [RFC9110]. The header begins with an
authorization scheme of X-Matrix
, followed by one or more spaces, followed by an (unordered)
comma-separated list of parameters written as name=value pairs. The names are case insensitive, though
the values are. The values must be enclosed in quotes if they contain characters which are not allowed
in a token
, as defined by Section 5.6.2 of [RFC9110]. If a value is a valid token
it may not be
enclosed in quotes. Quoted values MAY contain backslash-escaped characters. When parsing the header,
the recipient must unescape the characters.¶
The exact parameters are:¶
origin
- The name of the sending server. MUST match the origin
in the signed JSON.¶
destination
- The name of the receiving server. MUST match the destination
in the signed JSON.¶
key
- The ID, including algorithm name, of the sending server's signing key used to sign the request.¶
signature
- The unpadded base64 (Section 11) encoded signature from step 2.¶
Unknown parameters are ignored and MUST NOT result in authentication errors.¶
A receiving server validates the Authorization header by composing the JSON object represented above
and checking the sender's signature (Section 7.3). Note that to comply with
Section 7.3 the receiver may need to append a signatures
field to the JSON object
manually. All signatures MUST use an unexpired key at the time of the request
(Section 13.4.1.1).¶
A server with multiple signing keys SHOULD include an Authorization
header for each signing key.¶
If an endpoint requires authentication, servers MUST:¶
Authorization
headers.¶
Authorization
header is present.¶
If either fails (lack of headers, or any of the headers fail validation), the request MUST be rejected
with an HTTP 401 Unauthorized
status code and M_FORBIDDEN
error code (Section 13.2.2):¶
{ "errcode": "M_FORBIDDEN", "error": "Signature error on request." }¶
If an endpoint does not require authentication, Authorization
headers are ignored entirely.¶
Responses from a server are authenticated using TLS and do not have additional signing requirements.¶
A server's signing keys are published under /_matrix/key/v2/server
(Section 13.4.1.2) and can
be queried through notary servers in two ways: Section 13.4.1.3 and Section 13.4.1.4.
Notary servers implicitly call /_matrix/key/v2/server
when queried, signing and caching the response
for some time. This allows the target server to offline without affecting their previously sent events.¶
The approach used here is borrowed from the Perspectives Project [PerspectivesProject], modified to cover the server's ed25519 keys and to use JSON instead of XML. The advantage of this system is it allows each server to pick which notaries it trusts, and can contact multiple notaries to corroborate the keys returned by any given notary.¶
Servers SHOULD attempt to contact the target server directly before using a notary server.¶
Note that these endpoints operate outside the context of a room: a server does not need to participate in any shared rooms to be used as a notary by another server, and does not need to use the hub as a notary.¶
A server's keys are only valid for a short time, denoted by valid_until_ts
. Around the valid_until_ts
timestamp, a server would re-fetch the server's keys to discover any changes. In the vast majority of
cases, only valid_until_ts
changes between requests (keys are long-lived, but validated frequently).¶
valid_until_ts
MUST be handled as the lesser of valid_until_ts
and 7 days into the future, preventing
attackers from publishing long-lived keys that are unable to be revoked. Servers SHOULD use a timestamp
approximately 12 hours into the future when responding with their keys.¶
TODO: What does it mean to require events have an origin_server_ts
which is less than that of
valid_until_ts
? Do we reject the event, soft-fail it, or do something else? Do we only do this on the
hub?¶
GET /_matrix/key/v2/server
Retrieves the server's signing keys. The server can have any number of active or inactive keys at a time, but SHOULD have at least 1 active key at all times.¶
Rate-limited: No.¶
Authentication required: No.¶
This HTTP endpoint does not specify any request parameters or body.¶
200 OK
response:¶
{ "server_name": "example.org", "valid_until_ts": 1686776437176, "m.linearized": true, "verify_keys": { "ed25519:0": { "key": "<unpadded base64 encoded public key>" } }, "old_verify_keys": { "ed25519:bad": { "expired_ts": 1586776437176, "key": "<unpadded base64 encoded public key>" } }, "signatures": { "example.org": { "ed25519:0": "<unpadded base64 encoded signature>" } } }¶
server_name
MUST be the name of the server (Section 3.1) which is returning the keys.¶
valid_until_ts
is the integer timestamp (milliseconds since Unix epoch) for when the server's keys
should be re-fetched. See Section 13.4.1.1.¶
m.linearized
is an optional boolean, but SHOULD be set to true
. Semantics for false
and not
being present apply to contexts outside of this document.¶
verify_keys
are the current signing keys for the server, keyed by key ID (Section 7). The
object value for each key ID under verify_keys
is simply the key
, consisting of the unpadded
base64 encoded public key matching that algorithm and version.¶
old_verify_keys
are similar to verify_keys
, but have an additional required expired_ts
property
to denote when the key ceased usage. This overrides valid_until_ts
for the purposes of
Section 13.4.1.1 at an individual key level.¶
TODO: What about events sent with old_verify_keys
?¶
For request authentication (Section 13.4), only keys listed under verify_keys
are honoured.
If another key is referenced by the Authorization
headers, the request fails authentication.¶
Notaries SHOULD cache a 200 OK response for half of its lifetime to avoid serving stale values. Responding servers SHOULD avoid returning responses which expire in less than an hour to avoid repeated requests. Requesting servers SHOULD limit how frequently they query for keys to avoid flooding a server with requests.¶
If the server fails to respond to this request, notaries SHOULD continue to return the last response they received from the server so that the signatures of old events can still be checked, even if that response is no longer considered valid (Section 13.4.1.1).¶
Servers are capable of rotating their keys without populating old_verify_keys
, though this can cause
reliability issues if other servers don't see both keys. Notaries SHOULD cache responses with distinct
key IDs indefinitely. For example, if a server has ed25519:0
and ed25519:1
on its first response,
and a later response returns ed25519:1
and ed25519:2
, the notary should cache both responses. This
gives servers an ability to validate ed25519:0
for old events in a room.¶
GET /_matrix/key/v2/query/:serverName
This is one of two endpoints for querying a server's keys through another server. The notary (receiving)
server will attempt to refresh its cached copy of the target server's keys through /_matrix/key/v2/server
,
falling back to any cached values if needed.¶
Rate-limited: No.¶
Authentication required: No.¶
Path parameters:¶
:serverName
- the target server's name (Section 3.1) to retrieve keys for.¶
Query parameters:¶
minimum_valid_until_ts
(integer; optional) - The time in milliseconds since the Unix epoch the
target server's keys will need to be valid until to be useful to the caller. If not specified the
notary server's current time will be used.¶
Request body: None applicable.¶
200 OK
response:¶
{ "server_keys": [ {/* server key */} ] }¶
server_keys
is the array of keys (see Section 13.4.1.2 response format) for the target server.
If the target server could not be reached and the notary has no cached keys, this array is empty. If
the keys do not meet minimum_valid_until_ts
per Section 13.4.1.1, they are not included.¶
The notary server MUST sign each key returned in server_keys
by at least one of its own signing keys.
The calling server MUST validate all signatures on the objects.¶
POST /_matrix/key/v2/query
A bulk version of /_matrix/key/v2/query/:serverName
(Section 13.4.1.3). The same behaviour
applies to this endpoint.¶
Rate-limited: No.¶
Authentication required: No.¶
Path parameters: None applicable.¶
Query parameters: None applicable.¶
Request body:¶
{ "server_keys": { "example.org": { "ed25519:0": { "minimum_valid_until_ts": 1686783382189 } } } }¶
server_keys
is required and is the search criteria. The object value is first keyed by server name
which maps to another object keyed by Key ID, mapping to the specific criteria. If no key IDs are
given in the request, all of the server's known keys are queried. If no servers are given in the
request, the response MUST contain an empty server_keys
array.¶
minimum_valid_until_ts
holds the same meaning as in Section 13.4.1.3.¶
200 OK
response:¶
Same as Section 13.4.1.3 with the following added detail:¶
Responding servers SHOULD only return signed key objects for the key IDs requested by the caller, however servers MAY respond with more keys than requested. The caller is expected to filter the response if needed.¶
Events accepted into the room by a hub server must be sent to all other servers in that room. Similarly, participant servers need a way to send partial events through the hub server, as mentioned by Section 3.5.1.¶
A single endpoint is used for all rooms on either server, and can contain both fully-formed PDUs (Section 3.5) or Linearized PDUs (partial events; Section 3.5.1) depending on the server's role in the applicable room.¶
A typical event send path will be:¶
PUT /send/:txnId
is shorthand for Section 13.5.1.¶
Hubs which generate events would skip to the point where they create a fully-formed PDU and send it out to all other participants.¶
When a hub is broadcasting events to participant servers, it MUST include the following targets:¶
sender
for a kick or ban m.room.member
(Section 3.5.3.3) event, up
to the point of that kick or ban.¶
PUT /_matrix/federation/v2/send/:txnId
Sends (L)PDUs (Section 3.5, Section 3.5.1) to another server. The sending server MUST wait for a
200 OK
response from the receiver before sending another request with a different :txnId
.¶
Implementation note: Currently this endpoint doesn't actually exist. Use
PUT /_matrix/federation/unstable/org.matrix.i-d.ralston-mimi-linearized-matrix.02/send/:txnId
when testing against other Linearized Matrix implementations. This string may be updated later to
account for breaking changes.¶
TODO: Remove implementation notes.¶
Rate-limited: No.¶
Authentication required: Yes.¶
Path parameters:¶
:txnId
- the transaction ID (Section 13.2.5) for the request.¶
Query parameters: None applicable.¶
Request body:¶
{ "edus": [{/* TODO: Define EDUs */}], "pdus": [ {/* Either an LPDU or PDU */} ] }¶
TODO: Describe EDUs.¶
edus
are the Ephemeral Data Units to send. If no EDUs are being sent, this field MAY be excluded
from the request body. There MUST NOT be more than 100 entries in edus
.¶
pdus
are the events/PDUs (Section 3.5) and LPDUs (Section 3.5.1) to send to the server. Whether
it's an LPDU or PDU depends on the sending server's role in that room: if they are a non-hub server,
it will be an LPDU. There MUST NOT be more than 50 entries in pdus
.¶
Each event in the pdus
array gets processed as such:¶
Identify the room ID for the event. The exact format of the event can differ between room versions,
however currently this would be done by extracting the room_id
property.¶
Server implementation authors should note that these steps can be condensed, but are expanded here for specification purposes. For example, an LPDU's signature can/will fail without ever needing to append the PDU fields first - the server can skip some extra work this way.¶
200 OK
response:¶
{ "failed_pdus": { "$eventid": { "error": "Invalid event format" }, "$eventid": { "error": "@alice:example.org cannot send m.room.power_levels" } } }¶
The receiving server MUST NOT send a 200 OK
response until all events have been processed. Servers
SHOULD NOT block responding to this endpoint on sending accepted events to local clients or other
participant servers, as doing so could lead to a lengthy backlog of events waiting to be sent.¶
Sending servers SHOULD apply/expect a timeout and retry the exact same request with the same transaction
ID until they see a 200 OK
response. If the sending server attempts to send a different transaction
ID from the one already in flight, the receiving server MUST respond with a 400 Bad Request
HTTP
status code and M_BAD_STATE
error code (Section 13.2.2). Receiving servers SHOULD continue
processing requests to this endpoint event after the sender has disconnected/timed out, but SHOULD NOT
process the request multiple times due to the transaction ID (Section 13.2.5).¶
failed_pdus
is an object mapping event ID (Section 3.5) to error string. Event IDs are based upon
the received object, not the final/complete object. For example, if an LPDU is sent, gets its PDU
fields appended, and fails event authorization, then the error would be for the event ID of the LPDU,
not the fully-formed PDU. This is to allow the sender to correlate what they sent with errors.¶
The object for each event ID MUST contain an error
string field, representing the human-readable
reason for an event being rejected.¶
Events which are dropped/ignored or accepted do not appear in failed_pdus
.¶
TODO: Should we also return fully-formed PDUs for the LPDUs we received?¶
When a participant in the room is missing an event, or otherwise needs a new copy of it, it can retrieve that event from the hub server. Similar mechanics apply for getting state events, current state of a room, and backfilling scrollback in a room.¶
All servers are required to implement all endpoints (Section 13.2.1), however only hub servers are guaranteed to have the full history/state for a room. While other participant servers might have history, they SHOULD NOT be contacted due to the high likelihood of a Not Found-style error.¶
GET /_matrix/federation/v2/event/:eventId
Retrieves a single event.¶
Implementation note: Currently this endpoint doesn't actually exist. Use
GET /_matrix/federation/unstable/org.matrix.i-d.ralston-mimi-linearized-matrix.02/event/:eventId
when testing against other Linearized Matrix implementations. This string may be updated later to
account for breaking changes.¶
TODO: Remove implementation notes.¶
Rate-limited: Yes.¶
Authentication required: Yes.¶
Path parameters:¶
:eventId
- the event ID (Section 3.5) to retrieve. Note that event IDs are typically reference
hashes (Section 10.2) of the event itself, which includes the room ID. This makes
event IDs globally unique.¶
Query parameters: None applicable.¶
Request body: None applicable.¶
200 OK
response:¶
{ /* the event */ }¶
The response body is simply the event (Section 3.5) itself, if the requesting server has reasonable visibility of the event (Section 3.5.3.5.1). When the server can see an event but not the contents, the event is served redacted (Section 9) instead.¶
If the event isn't known to the server, or the requesting server has no reason to know that the event
even exists, a 404 Not Found
HTTP status code and M_NOT_FOUND
error code (Section 13.2.2)
is returned.¶
The returned event MUST be checked before being used by the requesting server (Section 6.1). This endpoint MUST NOT return LPDUs (Section 3.5.1), instead treating such events as though they didn't exist.¶
GET /_matrix/federation/v1/state/:roomId
Retrieves a snapshot of the room state (Section 3.5.2) at the given event. This is typically most useful when a participant server prefers to store minimal information about the room, but still needs to offer context to its clients.¶
Rate-limited: Yes.¶
Authentication required: Yes.¶
Path parameters:¶
:roomId
- the room ID (Section 3.2) to retrieve state in.¶
Query parameters:¶
event_id
(string; required) - The event ID (Section 3.5) to retrieve state at.¶
Request body: None applicable.¶
200 OK
response:¶
{ "auth_chain": [ {/* event */} ], "pdus": [ {/* event */} ] }¶
The returned room state is in two parts: the pdus
, consisting of the events which represent "current
state" (Section 3.5.2) prior to considering state changes induced by the event in the original
request, and auth_chain
, consisting of the events which make up the auth_events
(Section 6.4.1)
for the pdus
and the auth_events
of those events, recursively.¶
The auth_chain
will eventually stop recursing when it reaches the m.room.create
event, as it cannot
have any auth_events
.¶
For example, if the requested event ID was an m.room.power_levels
event, the returned state would be
as if the new power levels were not applied.¶
Both auth_chain
and pdus
contain event objects (Section 3.5).¶
If the requesting server does not have reasonable visibility on the room (Section 3.5.3.5.1),
or either the room ID or event ID don't exist, a 404 Not Found
HTTP status code and M_NOT_FOUND
error code (Section 13.2.2) is returned. The same error is returned if the event ID doesn't
exist in the requested room ID.¶
Note that the requesting server will generally always have visibility of the auth_chain
and pdu
events, but may not be able to see their contents. In this case, they are redacted (Section 9)
before being served.¶
The returned events MUST be checked before being used by the requesting server (Section 6.1). This endpoint MUST NOT return LPDUs (Section 3.5.1), instead treating such events as though they didn't exist.¶
GET /_matrix/federation/v1/state_ids/:roomId
This performs the same function as Section 13.6.2 but returns just the event IDs instead.¶
Rate-limited: Yes.¶
Authentication required: Yes.¶
Path parameters:¶
:roomId
- the room ID (Section 3.2) to retrieve state in.¶
Query parameters:¶
event_id
(string; required) - The event ID (Section 3.5) to retrieve state at.¶
Request body: None applicable.¶
200 OK
response:¶
{ "auth_chain_ids": ["$event1", "$event2"], "pdu_ids": ["$event3", "$event4"] }¶
See Section 13.6.2 for behaviour. Note that auth_chain
becomes auth_chain_ids
when using
this endpoint, and pdus
becomes pdu_ids
.¶
GET /_matrix/federation/v2/backfill/:roomId
Retrieves a sliding window history of previous events in a given room.¶
Implementation note: Currently this endpoint doesn't actually exist. Use
GET /_matrix/federation/unstable/org.matrix.i-d.ralston-mimi-linearized-matrix.02/backfill/:roomId
when testing against other Linearized Matrix implementations. This string may be updated later to
account for breaking changes.¶
TODO: Remove implementation notes.¶
Rate-limited: Yes.¶
Authentication required: Yes.¶
Path parameters:¶
:roomId
- the room ID (Section 3.2) to retrieve events from.¶
Query parameters:¶
v
(string; required) - The event ID (Section 3.5) to start backfilling from.¶
limit
(integer; required) - The maximum number of events to return, including v
.¶
Request body: None applicable.¶
200 OK
response:¶
{ "pdus": [ {/* event */} ] }¶
The number of returned pdus
MUST NOT exceed the limit
provided by the caller. limit
SHOULD have
a maximum value imposed by the receiving server. pdus
contains the events (Section 3.5) preceeding
the requested event ID (v
), including v
. pdus
is ordered from oldest to newest.¶
If the requesting server does not have reasonable visibility on the room (Section 3.5.3.5.1),
or either the room ID or event ID don't exist, a 404 Not Found
HTTP status code and M_NOT_FOUND
error code (Section 13.2.2) is returned. The same error is returned if the event ID doesn't
exist in the requested room ID.¶
If the requesting server does have visibility on the returned events, but not their contents, they are redacted (Section 9) before being served.¶
The returned events MUST be checked before being used by the requesting server (Section 6.1). This endpoint MUST NOT return LPDUs (Section 3.5.1), instead treating such events as though they didn't exist.¶
When a server is already participating in a room, it can simply send m.room.member
(Section 3.5.3.3)
events with the /send
API (Section 13.5.1) to other servers/the hub directly. When a server is
not already participating however, it needs to be welcomed in by the hub server.¶
A typical invite flow would be:¶
POST /invite
is shorthand for Section 13.7.2.1. Similarly, GET /make_join
is Section 13.7.3.1
and POST /send_join
is Section 13.7.3.2.¶
If the user decided to reject the invite, the TargetServer would use GET /make_leave
(Section 13.7.2.2.1)
and POST /send_leave
(Section 13.7.2.2.2) instead of make/send_join.¶
When a server is already participating in a room, it can use m.room.member
(Section 3.5.3.3) events
and the /send
API (Section 13.5.1) to directly change membership. When the server is not already
involved in the room, such as when being invited for the first time, the server needs to "make" an event
and "send" it through the hub server to append it to the room.¶
The different processes which use this handshake are:¶
The "make" portion of the endpoints take the shape of GET /_matrix/federation/v1/make_CHANGE/:roomId/:userId
,
where CHANGE
is leave
, join
, or knock
(respective to the list above). This endpoint will
return a partial LPDU (Section 3.5.1) which needs to be turned into a full LPDU and signed before being
sent using POST /_matrix/federation/v3/send_CHANGE/:txnId
.¶
The flow for this handshake appears as such:¶
Note that the send_CHANGE
step re-checks the event against the auth rules: any amount of time
could have passed between the make_CHANGE
and send_CHANGE
calls.¶
TODO: Describe how the external server is meant to find the hub. Invites work by (usually) trying to contact the server which sent the invite, but knocking is a guess.¶
When inviting a user belonging to a server already in the room, senders SHOULD use m.room.member
(Section 3.5.3.3) events and the /send
API (Section 13.5.1). This section's endpoints SHOULD
only be used when the target server is not participating in the room already.¶
Note that being invited does not count as the server "participating" in the room. This can mean that while a server has a user with a pending invite in the room, this section's endpoints are needed to send additional invites to other users on the same server.¶
The full invite sequence is:¶
POST /invite
is shorthand for Section 13.7.2.1.¶
What causes a user to be considered "ineligible" for an invite is left as an implementation detail. See Section 14 and Section 15 for suggestions on handling user-level privacy controls and spam invites.¶
POST /_matrix/federation/v3/invite/:txnId
Sends an invite event to a server. If the sender is a participant server, the receiving server (the hub) will convert the contained LPDU (Section 3.5.1) to a fully-formed event (Section 3.5) before sending that event to the intended server.¶
Implementation note: Currently this endpoint doesn't actually exist. Use
POST /_matrix/federation/unstable/org.matrix.i-d.ralston-mimi-linearized-matrix.02/invite/:txnId
when testing against other Linearized Matrix implementations. This string may be updated later to
account for breaking changes.¶
TODO: Remove implementation notes.¶
Rate-limited: Yes.¶
Authentication required: Yes.¶
Path parameters:¶
:txnId
- the transaction ID (Section 13.2.5) for the request. The event ID (Section 3.5) of the
contained event may be a good option as a readily-available transaction ID.¶
Query parameters: None applicable.¶
Request body:¶
{ "event": {/* the event */}, "invite_room_state": [/* stripped state events */], "room_version": "I.1" }¶
invite_room_state
are the stripped state events (Section 3.5.2.1) for the room's current
state. invite_room_state
MAY be excluded from the request body.¶
room_version
is the room version identifier (Section 3.2.1) the room is currently using. This
will be retrieved from the m.room.create
(Section 3.5.3.1) state event.¶
event
is the event (LPDU or PDU; Section 3.5) representing the invite for the user. It MUST meet
the following criteria, in addition to the requirements of an event:¶
When the hub server receives a request from a participant server, it MUST populate the event fields
before sending the event to the intended recipient. This means running the event through the normal
event authorization steps (Section 6.4). If the invite is not allowed under the auth rules,
the server responds with a 403 Forbidden
HTTP status code and M_FORBIDDEN
error code (Section 13.2.2).¶
The intended recipient of the invite can be identified by the state_key
on the event.¶
If the invite event is valid, the hub server sends its own POST /_matrix/federation/v3/invite/:txnId
request to the target server (if the target server is not itself) with the fully-formed event. The
transaction ID does not need to be the same as the original inbound request.¶
All responses from the target server SHOULD be proxied verbatim to the original requesting server through the hub. The hub SHOULD discard what appears to be excess data before sending a response to the requesting server, such as extra or large fields. If the target server does not respond with JSON, an error response (Section 13.2.2) SHOULD be sent by the hub instead.¶
The target server then ensures it can support the room version. If it can't, it responds with an HTTP
status code of 400 Bad Request
and error code of M_INCOMPATIBLE_ROOM_VERSION
(Section 13.2.2).¶
Then, the target server runs any implementation-specific checks as needed, such as those implied by Section 14 and Section 15, rejecting/erroring the request as needed.¶
Finally, the target server signs the event and returns it to the hub. The hub server appends this signed
event to the room and sends it out to all participants in the room. The signed event is additionally
returned to the originating participant server, though it also receives the event through the /send
API (Section 13.5.1).¶
200 OK
response:¶
{ "pdu": {/* signed fully-formed event */} }¶
Note that by the time a response is received, the event is signed 2-3 times:¶
These signatures are to satisfy the auth rules (Section 6.4).¶
TODO: Do we ever validate the target server's signature? Do we need to?¶
Rejecting an invite is done by making a membership transition of invite
to leave
through the user's
m.room.member
(Section 3.5.3.3) event. The membership event SHOULD be sent directly when it can and
use the "make and send" handshake (Section 13.7.1) described here otherwise.¶
This same approach is additionally used to retract a knock (Section 13.7.4).¶
GET /_matrix/federation/v1/make_leave/:roomId/:userId
Requests an event template from the hub server for a room. The requesting server will have already been checked to ensure it supports the room version as part of the invite process prior to making a call to this endpoint.¶
Rate-limited: Yes.¶
Authentication required: Yes.¶
Path parameters:¶
:roomId
- the room ID (Section 3.2) to get a template for.¶
:userId
- the user ID (Section 3.3) attempting to leave.¶
Query parameters: None applicable.¶
Request body: None applicable.¶
200 OK
response:¶
{ "event": {/* partial LPDU */}, "room_version": "I.1" }¶
The response body's event
MUST be a partial LPDU (Section 3.5.1) with at least the following fields:¶
type
of m.room.member
.¶
state_key
of :userId
from the path parameters.¶
sender
of :userId
from the path parameters.¶
content
of {"membership": "leave"}
.¶
The sending server SHOULD remove all other fields before using the event in a send_leave
(Section 13.7.2.2.2).¶
If the receiving server is not the hub server for the room ID, an HTTP status code of 400 Bad Request
and error code M_WRONG_SERVER
(Section 13.2.2) is returned. If the room ID is not known,
404 Not Found
is used as an HTTP status code and M_NOT_FOUND
as an error code (Section 13.2.2).¶
If the user does not have permission to leave under the auth rules (Section 6.4), a 403 Forbidden
HTTP status code is returned alongside an error code of M_FORBIDDEN
(Section 13.2.2). For example,
if the user does not have a pending invite, is not a member of the room, or is banned.¶
If the sending server does not recognize the returned room_version
, it SHOULD NOT attempt to populate
the template or use the send_leave
(Section 13.7.2.2.2) endpoint.¶
POST /_matrix/federation/v3/send_leave/:txnId
Sends a leave membership event to the room through a hub server.¶
Implementation note: Currently this endpoint doesn't actually exist. Use
POST /_matrix/federation/unstable/org.matrix.i-d.ralston-mimi-linearized-matrix.02/send_leave/:txnId
when testing against other Linearized Matrix implementations. This string may be updated later to
account for breaking changes.¶
TODO: Remove implementation notes.¶
Rate-limited: Yes.¶
Authentication required: Yes.¶
Path parameters:¶
:txnId
- the transaction ID (Section 13.2.5) for the request. The event ID (Section 3.5) of the
contained event may be a good option as a readily-available transaction ID.¶
Query parameters: None applicable.¶
Request body:¶
{ /* LPDU created from make_leave template */ }¶
200 OK
response:¶
{/* deliberately empty object */}¶
The errors responses from /make_leave
(Section 13.7.2.2.1) are copied here. Servers should note
that room state MAY change between a /make_leave
and /send_leave
, potentially in a way which
prevents the user from leaving the room suddenly. For example, the invited user may have been banned
from the room.¶
Joins for users SHOULD be sent directly whenever possible, and otherwise use the "make and send" handshake (Section 13.7.1) approach described here.¶
GET /_matrix/federation/v1/make_join/:roomId/:userId
Requests an event template from the hub server for a room. This is done to ensure the requesting server supports the room's version (Section 3.2.1), as well as hint at the event format needed to participate.¶
Note that this endpoint is extremely similar to /make_leave
(Section 13.7.2.2.1).¶
Rate-limited: Yes.¶
Authentication required: Yes.¶
Path parameters:¶
:roomId
- the room ID (Section 3.2) to get a template for.¶
:userId
- the user ID (Section 3.3) attempting to join.¶
Query parameters:¶
ver
(string; required; repeated) - The room versions (Section 3.2.1) the sending server
supports.¶
Request body: None applicable.¶
200 OK
response:¶
{ /* partial LPDU */ }¶
The response body MUST be a partial LPDU (Section 3.5.1) with at least the following fields:¶
type
of m.room.member
.¶
state_key
of :userId
from the path parameters.¶
sender
of :userId
from the path parameters.¶
content
of {"membership": "join"}
.¶
The sending server SHOULD remove all other fields before using the event in a send_join
(Section 13.7.3.2).¶
If the receiving server is not the hub server for the room ID, an HTTP status code of 400 Bad Request
and error code M_WRONG_SERVER
(Section 13.2.2) is returned. If the room ID is not known,
404 Not Found
is used as an HTTP status code and M_NOT_FOUND
as an error code (Section 13.2.2).¶
If the user does not have permission to join under the auth rules (Section 6.4), a 403 Forbidden
HTTP status code is returned alongside an error code of M_FORBIDDEN
(Section 13.2.2).¶
If the room version is not one of the ver
strings the sender supplied, a 400 Bad Request
HTTP status
code is returned alongside M_INCOMPATIBLE_ROOM_VERSION
error code (Section 13.2.2).¶
POST /_matrix/federation/v3/send_join/:txnId
Sends a join membership event to the room through a hub server.¶
Note that this endpoint is extremely similar to /send_leave
(Section 13.7.2.2.2).¶
Implementation note: Currently this endpoint doesn't actually exist. Use
POST /_matrix/federation/unstable/org.matrix.i-d.ralston-mimi-linearized-matrix.02/send_join/:txnId
when testing against other Linearized Matrix implementations. This string may be updated later to
account for breaking changes.¶
TODO: Remove implementation notes.¶
Rate-limited: Yes.¶
Authentication required: Yes.¶
Path parameters:¶
:txnId
- the transaction ID (Section 13.2.5) for the request. The event ID (Section 3.5) of the
contained event may be a good option as a readily-available transaction ID.¶
Query parameters: None applicable.¶
TODO: Incorporate faster joins work.¶
Request body:¶
{ /* LPDU created from make_join template */ }¶
200 OK
response:¶
{ "state": [/* events */], "auth_chain": [/* events */], "event": {/* fully-formed event */} }¶
state
is the current room state, consisting of the events which represent "current state" (Section 3.5.2)
prior to considering the membership state change. auth_chain
consists of the events which make up
the auth_events
(Section 6.4.1) for the state
events, and the auth_events
of those events,
recursively. event
will be the fully-formed PDU (Section 3.5) that is sent by the hub to all other
participants in the room.¶
The errors responses from /make_join
(Section 13.7.3.1) are copied here (with the exception
of M_INCOMPATIBLE_ROOM_VERSION
, as the server already checked for support). Servers should note
that room state MAY change between a /make_join
and /send_join
, potentially in a way which
prevents the user from joining the room suddenly.¶
To knock on a room is to request an invite to that room. It is not a join, nor is it an invite itself.
"Approving" the knock is done by inviting the user, which is typically only allowed by moderators in
these rooms. "Denying" the knock is done through kicking (sending a leave
membership) or banning the
user. If the user is kicked, they may re-send their knock.¶
Senders should note the reason
field on m.room.member
events (Section 3.5.3.3) to provide context
for their knock.¶
To retract a knock, the sending server uses the same APIs as rejecting an invite (Section 13.7.2.2).¶
Where possible, knocks from users SHOULD be sent directly, otherwise using the "make and send" handshake (Section 13.7.1) approach described here.¶
GET /_matrix/federation/v1/make_knock/:roomId/:userId
Requests an event template from the hub server for a room. This is done to ensure the requesting server supports the room's version (Section 3.2.1), as well as hint at the event format needed to participate.¶
Note that this endpoint is almost exactly the same as /make_join
(Section 13.7.3.1).¶
TODO: It's so similar to make_join that we should probably just combine the two endpoints.¶
Rate-limited: Yes.¶
Authentication required: Yes.¶
Path parameters:¶
:roomId
- the room ID (Section 3.2) to get a template for.¶
:userId
- the user ID (Section 3.3) attempting to knock.¶
Query parameters:¶
ver
(string; required; repeated) - The room versions (Section 3.2.1) the sending server
supports.¶
Request body: None applicable.¶
200 OK
response:¶
{ /* partial LPDU */ }¶
The response body MUST be a partial LPDU (Section 3.5.1) with at least the following fields:¶
type
of m.room.member
.¶
state_key
of :userId
from the path parameters.¶
sender
of :userId
from the path parameters.¶
content
of {"membership": "knock"}
.¶
The sending server SHOULD remove all other fields before using the event in a send_knock
(Section 13.7.4.2).¶
If the receiving server is not the hub server for the room ID, an HTTP status code of 400 Bad Request
and error code M_WRONG_SERVER
(Section 13.2.2) is returned. If the room ID is not known,
404 Not Found
is used as an HTTP status code and M_NOT_FOUND
as an error code (Section 13.2.2).¶
If the user does not have permission to knock under the auth rules (Section 6.4), a 403 Forbidden
HTTP status code is returned alongside an error code of M_FORBIDDEN
(Section 13.2.2).¶
If the room version is not one of the ver
strings the sender supplied, a 400 Bad Request
HTTP status
code is returned alongside M_INCOMPATIBLE_ROOM_VERSION
error code (Section 13.2.2).¶
POST /_matrix/federation/v3/send_knock/:txnId
Sends a knock membership event to the room through a hub server.¶
Implementation note: Currently this endpoint doesn't actually exist. Use
POST /_matrix/federation/unstable/org.matrix.i-d.ralston-mimi-linearized-matrix.02/send_knock/:txnId
when testing against other Linearized Matrix implementations. This string may be updated later to
account for breaking changes.¶
TODO: Remove implementation notes.¶
Rate-limited: Yes.¶
Authentication required: Yes.¶
Path parameters:¶
:txnId
- the transaction ID (Section 13.2.5) for the request. The event ID (Section 3.5) of the
contained event may be a good option as a readily-available transaction ID.¶
Query parameters: None applicable.¶
Request body:¶
{ /* LPDU created from make_knock template */ }¶
200 OK
response:¶
{ "stripped_state": [ /* stripped state events */ ] }¶
stripped_state
are the stripped state events (Section 3.5.2.1) for the room.¶
The errors responses from /make_knock
(Section 13.7.4.1) are copied here (with the exception
of M_INCOMPATIBLE_ROOM_VERSION
, as the server already checked for support). Servers should note
that room state MAY change between a /make_knock
and /send_knock
, potentially in a way which
prevents the user from knocking upon the room suddenly.¶
The content repository, sometimes called the "media repo", is where user-generated content is stored for referencing within an encrypted message.¶
TODO: Complete this section. We want auth/event linking from MSC3911 and MSC3916.¶
TODO: Spell out that content is images, videos, files, etc.¶
TODO: This section.¶
Topics:¶
Notably/deliberately missing APIs are:¶
get_missing_events
- this is used by DAG servers only¶
/_matrix/federation/v1/version
in here? It's used by federation testers, but not
really anything else.¶
TODO: Fully complete this section.¶
Messaging providers may have user-level settings to prevent unexpected or unwarranted invites, such
as automatically blocking invites from non-contacts. This setting can be upheld by returning an error
on POST /_matrix/federation/v3/invite/:txnId
(Section 13.7.2.1), and by having the server (optionally)
auto-decline any invites received directly through PUT /_matrix/federation/v2/send/:txnId
(Section 13.5.1).
See Section 13.7.2.2 for more information on rejecting invites.¶
TODO: Fully complete this section.¶
Servers MAY temporarily or permanently block a room entirely by using the room ID. Typically, when a
room becomes blocked, all local users will be removed from the room using m.room.member
events with
membership
of leave
(Section 3.5.3.3). Then, any time the server receives a request for that
room ID it can reject it with an error response (Section 13.2.2).¶
Blocking a room does not block it from all servers, but does prevent users on a server from accessing the content within. This is primarily useful to remove a server from rooms where abusive/illegal content is shared.¶
TODO: Expand upon this section.¶
With the combined use of MLS and server-side enforcement, the server theoretically has an ability to add a malicious device to the MLS group and receive decryptable messages. Authenticity of devices needs to be established to ensure a user's devices are actually a user's devices.¶
TODO: Should we bring Matrix's cross-signing here?¶
Servers retain the ability to control/puppet their own users due to no strong cryptographic link between the sending device and the event which gets emitted.¶
The m.*
namespace likely needs formal registration in some capacity.¶
The I.*
namespace likely needs formal registration in some capacity.¶
Port 8448 may need formal registration.¶
The SRV service name matrix
may need re-registering, or a new service name assigned.¶
The .well-known/matrix
namespace is already registered for use by The Matrix.org Foundation C.I.C.¶
Thank you to the Matrix Spec Core Team (SCT), and in particular Richard van der Hoff, for exploring how Matrix rooms could be represented as a linear structure, leading to this document.¶