The Matrix API reference
Every endpoint, id shape, error code and table of the Matrix Client-Server API a site serves — the reference; the guide is under Building a site.
The guide is The community as a Matrix homeserver. This page says exactly what is there. Modules: spaces and api; with either off nothing answers.
Endpoints
All under the site's own host. A client that reads /.well-known/matrix/client finds the same address.
| Method | Path | What |
|---|---|---|
| GET | /_matrix/client/versions |
the specification versions spoken |
| GET | /_matrix/client/v3/login |
the flows — m.login.password and nothing else |
| POST | /_matrix/client/v3/login |
the site's own email and password; answers a token of ours |
| POST | /_matrix/client/v3/logout |
ends our row and the auth server's session |
| POST | /_matrix/client/v3/logout/all |
the same |
| POST | /_matrix/client/v3/register |
refused, with where to sign up instead |
| GET | /_matrix/client/v3/capabilities |
and what is absent, each with its reason |
| GET | /_matrix/client/v3/account/whoami |
the person and the device |
| GET | /_matrix/client/v3/devices |
this account's Matrix clients |
| GET | /_matrix/client/v3/sync?since= |
the rooms, the timeline, the state |
| GET | /_matrix/client/v3/joined_rooms |
the rooms and Spaces that are theirs |
| GET | /_matrix/client/v3/rooms/{id}/messages?from=&dir=&limit= |
paging, both directions |
| PUT | /_matrix/client/v3/rooms/{id}/send/{type}/{txn} |
a message, a thread reply, an edit |
| PUT | /_matrix/client/v3/rooms/{id}/redact/{event}/{txn} |
a withdrawal |
| GET | /_matrix/client/v3/rooms/{id}/state |
the room's state events |
| PUT | /_matrix/client/v3/rooms/{id}/state/{type}/{key} |
refused — see below |
| GET | /_matrix/client/v3/rooms/{id}/members |
the space's members |
| GET | /_matrix/client/v3/rooms/{id}/joined_members |
the same, as a map |
| PUT | /_matrix/client/v3/rooms/{id}/typing/{user} |
the typing stamp |
| POST | /_matrix/client/v3/rooms/{id}/receipt/{type}/{event} |
where this client has read to |
| POST | /_matrix/client/v3/rooms/{id}/read_markers |
the same |
| GET | /_matrix/client/v3/profile/{user} |
the display name |
| GET | /_matrix/client/v1/rooms/{id}/hierarchy |
a Space and the channels in it, so a client draws the site's arrangement |
| GET | /_matrix/client/v1/rooms/{id}/relations/{event}[/{type}] |
a thread's replies |
| GET | /_matrix/client/v3/pushrules/ |
an empty ruleset: this site notifies its own way |
| POST | /_matrix/client/v3/user/{id}/filter |
accepted, never applied — a sync here is already only theirs |
| GET | /_matrix/client/v3/user/{id}/filter/{id} |
the empty filter back |
| PUT | /_matrix/client/v3/user/{id}/account_data/{type} |
taken and dropped: a reader's settings are the site's |
| POST | /_matrix/client/v3/keys/upload |
the device's public keys and its one-time keys, with the real counts back |
| POST | /_matrix/client/v3/keys/query |
the devices and cross-signing keys of people sharing a room |
| POST | /_matrix/client/v3/keys/claim |
a one-time key, claimable exactly once; the fallback when they run out |
| POST | /_matrix/client/v3/keys/device_signing/upload |
the three cross-signing keys, stored and served |
| POST | /_matrix/client/v3/keys/signatures/upload |
signatures merged in, and only the caller's own |
| PUT | /_matrix/client/v3/sendToDevice/{type}/{txn} |
queued per device, delivered in the next sync |
| GET|POST|PUT|DELETE | /_matrix/client/v3/room_keys/* |
the key backup: version, store, fetch, and delete a version — M_NOT_FOUND until there is one |
| GET | /_matrix/client/v3/thirdparty/protocols |
empty: no bridges |
| GET | /_matrix/media/v3/config, /_matrix/client/v1/media/config |
the largest file taken |
| POST | /_matrix/media/v3/upload |
into the site's own bucket; 10 MB |
| GET | /_matrix/media/v3/download/{server}/{id} |
signed as the person, so a room they cannot read refuses |
| GET | /_matrix/client/v1/media/download/{server}/{id} |
the same, fetched with the token as a current client does |
Identifiers
A uuid with its hyphens removed, behind one letter that says what it names, at the site's host. Total and reversible, with no table of correspondences to go out of step.
| Thing | Shape |
|---|---|
| a channel | !c<32 hex>:host |
| a space | !s<32 hex>:host |
| a person | @<32 hex>:host |
| a message | $m<32 hex> — no host, as room version 4 onward |
The sync token is base64 of channel:seq pairs — space_messages.seq, which the timeline is already indexed by.
State a client may not set
A PUT to any of these is refused by name, because each is the site's decision and a client that could set one would be a second, weaker way to make it.
m.room.power_levels · m.room.name · m.room.topic · m.room.avatar · m.room.join_rules · m.room.history_visibility · m.room.encryption · m.space.child
Power levels are derived from space_grants: 0 a member, 25 voice · file · invite · mention-channel, 50 moderate · announce, 100 an administrator.
Error codes
Every refusal carries Matrix's errcode and an error sentence naming the reason — never a bare code.
| Code | When |
|---|---|
M_MISSING_TOKEN |
no access token on the request |
M_UNKNOWN_TOKEN |
not ours, signed out, or the account ended the session |
M_FORBIDDEN |
wrong password; not in the room; read-only state; registration; encryption; a key of the wrong kind or without the grant |
M_LIMIT_EXCEEDED |
past the sign-in cap, with retry_after_ms |
M_NOT_FOUND |
no room with that id here |
M_TOO_LARGE |
a file past the limit |
M_BAD_JSON |
a body this endpoint cannot read |
M_UNRECOGNIZED |
an endpoint this server does not implement |
Tables
| Table | What | Policies |
|---|---|---|
matrix_sessions |
a client's token hash, its device, and the auth server's refresh token | RLS on, no policies — only the service role reaches it |
matrix_read |
where a Matrix client has read to, per room | the person's own |
matrix_login_attempts |
the sign-in cap's counter | the service role's |
matrix_session_claim(hash) takes the row for update, so two requests cannot spend one refresh token at once.
Keys
A key of kind matrix, made under Settings → API, is for a script or a bridge: it acts as the key's maker and reaches what it is granted. A key of any other kind is M_FORBIDDEN. No key reaches the coaching chat or the support desk.
What a client insists on, and what it is told
Several endpoints above look optional and are not: a client will not finish starting up, or will not draw a room, until each of them answers. They were found by pointing a real client at a running site rather than by reading the specification, and each answer is a true statement about this site rather than a stub:
- Push rules — an empty ruleset. A client will not begin syncing without one, and this site notifies through its own channels, which know about memberships and the desk and work when no Matrix client is open.
- A filter — accepted, never applied. A filter asks for less than everything; a sync here is already only what that person may see, so there is nothing to cut.
- Device and cross-signing keys — a real key store, where the
space-encryptionmodule is on. A channel whose file saysencrypted: trueis served withm.room.encryptionin its state, and a client seeing that seals every message it sends there — so a client that could not store or fetch a key could not send at all. What is stored is public keys and opaque blobs, never a secret. With the module off these answer as they did before there was any such thing: taken, kept by nobody, counts of zero, which a client reads as "no encrypted rooms" and which is then true. - Key backup — the reader's own sessions, sealed under a recovery key the site has never seen.
M_NOT_FOUNDuntil there is a backup, which is a client's ordinary first day, rather than "not implemented", which reads as a server too old to ask. A version can be deleted (Element calls it "delete backup"); the row is marked rather than removed, since the sessions in it may be the only copy of keys somebody still wants. - Sending and reading in a sealed room — a client sends
m.room.encryptedand it is stored as the row's ciphertext; a sealed row is served back asm.room.encryptedand not as a message with no words. The two refusals are the other way round from everywhere else: a ciphertext into an open room is refused, and so is a plain message into a sealed one — the second would be words in a timeline everybody else is sealing, with nothing about the room to show it. - To-device messages — how a room key reaches another device at all. A batch is deleted on the NEXT sync rather than the one that delivered it, which is Matrix's own contract: a client that dies mid-sync sees its keys again instead of losing them for good. The sender on each is a Matrix id: handed the account's own uuid instead, a client cannot match the message to the sender it claims, drops the room key, and reads "the sender's device has not sent us the keys for this message" for ever — while the site's own client, which translates the id itself, reads the room perfectly.
- An unimplemented endpoint —
M_UNRECOGNIZEDwith 404, never 401. A 401 says the endpoint exists and the token is wrong, which is the opposite of the truth, and a client probing for an optional feature cannot tell the two apart. - No
well_knownon the login answer. A client moves its base address to whatever that field says, and this surface cannot state it correctly — it sees the address the request arrived on inside the stack, not the one the browser used, and behind a site's own rewrite neither is right. Saying nothing leaves the client where it already is, which is correct everywhere.
Pointing Element at a site
Element Web works against a site: sign in with the email and password of the site's own account,
and the Spaces, channels, people, threads and withdrawals are the site's own. Two things to know.
Element offers to set up encryption keys on first sign-in. Where the site seals no channel there is
nothing to set up, so say Later — and a deployment that would rather not be asked at all can set
"io.element.e2ee": { "force_disable": true } in Element's config.json. Where a channel IS sealed,
set the keys up: Element will not send into an encrypted room without them, and the recovery key it
shows is the one thing the site cannot show again, because the site never had it. And a site's channels
appear inside its Space, not under Home, because that is where they belong.
A picture does not yet draw in Element. The event is well formed — mimetype, size, width, height — and the bytes are served correctly to anything that asks with the token, over both the old address and the authenticated one. Element makes no request for it and renders an error in its place; the cause has not been found, and advertising the specification version that moved media behind the token changed nothing. Pictures work in the site's own client. This is the one part of this surface that a real client does not yet show.
Not implemented, on purpose
Federation, end-to-end encryption, presence, VoIP, and slash commands — each refused with a reason rather than left to fail. The guide says why.