Public reference for people and agents building an embedded Matrix client against this homeserver. It describes what this build implements. An endpoint that is absent here is not implemented, even if the Matrix specification defines it.
matrix.gdna.aihttps://matrix.gdna.ai@localpart:matrix.gdna.aiSend Client-Server requests to https://matrix.gdna.ai. GET /.well-known/matrix/client advertises https://matrix.gdna.ai as m.homeserver.base_url. On the deployed homeservers the public host and the server name are the same, so those two URLs match.
Same-origin Markdown for this page is /docs/index.md. The agent index is /docs/llms.txt. No access token is required for any /docs page. /docs.md redirects to /docs/index.md.
A client should learn the base URL from discovery, then ask which specification versions this process speaks, before it tries to log in.
GET /.well-known/matrix/client is unauthenticated and returns:
{"m.homeserver": {"base_url": "https://matrix.gdna.ai"}}
GET /.well-known/matrix/server is unauthenticated and returns:
{"m.server": "matrix.gdna.ai:443"}
Federation traffic for this deployment is also accepted on port 8448. The well-known delegation above names port 443. Both listeners forward to this process. Do not hard-code 8448 in a client: follow m.server.
GET /_matrix/client/versions is unauthenticated. It is the first call a Client-Server client makes. This build advertises r0.6.1 and v1.1 through v1.11, and an empty unstable_features object. No unstable MSC is implemented.
v1.11 is advertised so clients will use authenticated media (/_matrix/client/v1/media/...). The federation half of v1.11 media — multipart/mixed fetches under /_matrix/federation/v1/media/ — is not supported. Remote media is still fetched over the legacy download path.
GET /_matrix/key/v2/server returns this server's federation signing keys. GET /health returns the plain text ok and is the load-balancer health check.
GET /metrics is a Prometheus scrape. It is not a Client-Server endpoint. With no METRICS_TOKEN configured it answers 404 and does not describe itself. With a token configured, the scraper must send Authorization: Bearer <METRICS_TOKEN>.
Embedded clients log in with SSO. The identity provider is Keycloak (OIDC) at auth.gdna.io. This deployment's issuer for the default realm is https://auth.gdna.io/realms/gdna. Additional tenants carry their own issuer. The homeserver redirects the browser to that realm; the client never talks to Keycloak directly except by following the redirect.
Password login (m.login.password) exists for accounts that have a password hash. Production registration is disabled, so new accounts are created by SSO (or the admin CLI), not by POST /register. Prefer SSO.
GET https://matrix.gdna.ai/_matrix/client/v3/login lists flows. When OIDC is configured it includes:
m.login.passwordm.login.sso, with one identity_providers entry per active tenant (id is the tenant slug) and any brokered upstream provider (this deployment advertises google)m.login.token, which is how the SSO dance finishesThe unsuffixed GET /_matrix/client/v3/login/sso/redirect works only when exactly one tenant is active. These deployments also run the system sentinel tenant, so that URL returns 400 M_MISSING_PARAM. Always call the suffixed form with an id from identity_providers.
GET https://matrix.gdna.ai/_matrix/client/v3/login/sso/redirect/{idpId}?redirectUrl={urlencoded callback}.redirectUrl is required. It must be same-origin with this homeserver, or on the operator allow-list SSO_CLIENT_REDIRECT_ALLOWLIST. The homeserver checks it before the flow starts and again before it attaches the login token. A rejected URL is 400 M_INVALID_PARAM. These deployments allow https://matrix.gdna.io, https://matrix.gdna.ai, https://aeos.gdna.io, https://aeosdev.gdna.io, https://chat.gdna.io, and https://chat.gdna.ai. An embedded client on any other origin has to be added to that list or the login token will not be delivered.https://matrix.gdna.ai/_matrix/client/v3/login/sso/callback. A brokered id such as google adds Keycloak's kc_idp_hint so the realm skips its own chooser.redirectUrl?loginToken=.... The login token is single-use and lives 60 seconds. It is a credential: do not log it or put it in a Referer.POST https://matrix.gdna.ai/_matrix/client/v3/login
Content-Type: application/json
{"type":"m.login.token","token":"<loginToken>"}
The JSON response is user_id, access_token, and device_id. Optional fields you may send on that POST: device_id, initial_device_display_name.
Send the access token as Authorization: Bearer <token>. The query-parameter form (?access_token=) is not supported.
Tokens last 24 hours (DEFAULT_TOKEN_LIFETIME_MS). The login response does not include refresh_token. POST /_matrix/client/v3/refresh and m.login.refresh are not supported. When the token expires, run SSO again.
A missing or unknown bearer token is 401 with errcode M_UNKNOWN and error "unauthorized". It is not M_MISSING_TOKEN or M_UNKNOWN_TOKEN. Treat 401 as "log in again". There is nothing to refresh.
user_id is @localpart:matrix.gdna.ai. SSO derives the localpart from the Keycloak preferred_username (lowercased; @ and spaces become -; other characters outside the Matrix localpart grammar are dropped). The account is keyed on the OIDC (issuer, sub) pair. A preferred username that is already taken does not adopt that account.
POST https://matrix.gdna.ai/_matrix/client/v3/logout with the bearer token and an empty JSON object revokes that token and deletes the device (and its published device keys). The response may include sso_logout_url, the Keycloak end-session URL. Open it if you also need the browser's IdP session to end. A client that ignores the field is still logged out of Matrix.
POST https://matrix.gdna.ai/_matrix/client/v3/logout/all revokes every device for the user, including the caller. It is not UIAA-gated.
POST /_matrix/client/v3/account/deactivate deactivates the authenticated account.
DELETE /_matrix/client/v3/devices/{deviceId} is not supported. Logout is how the current device is removed. GET /devices and GET/PUT /devices/{deviceId} exist (list, fetch, rename).
GET https://matrix.gdna.ai/_matrix/client/v3/sync requires an access token.
| Query | Meaning |
|---|---|
since | Omit for the initial sync. Thereafter send the previous response's next_batch. |
timeout | Milliseconds to long-poll when an incremental sync has nothing new. Clamped to 30000. 0 or omitted returns immediately. |
types | Optional comma-separated event types to keep. This is the only filter /sync applies. |
The initial sync (since absent) does not long-poll. It returns the joined rooms and their current state and a timeline slice.
An incremental sync with timeout waits until a joined room has new timeline events, a to-device event arrives, or a device-list change is recorded — or until the timeout. Wakeups are local to the write and are also delivered across tasks through Postgres LISTEN/NOTIFY.
The response always has next_batch and rooms (join, and leave when the user has left a room). It also includes to_device, device_lists, account_data (including m.direct), and presence when those sections are non-empty. Timeline events for a joined room are rooms.join[roomId].timeline.events. Ephemeral events (typing, receipts) are under rooms.join[roomId].ephemeral, not the timeline.
POST /user/{userId}/filter and GET /user/{userId}/filter/{filterId} store a filter. /sync does not read a filter query parameter or a filter id. Sending one is ignored. Use types if you need to narrow event types.
Sliding sync is not supported.
Receiving is /sync, then GET /_matrix/client/v3/rooms/{roomId}/messages for history, GET /_matrix/client/v3/rooms/{roomId}/context/{eventId} to jump to an event, and the /relations endpoints for reactions, edits, and threads.
Relations are under /v1/, not /v3/. The specification added them after the v3 rename. Three concrete routes exist:
GET /_matrix/client/v1/rooms/{roomId}/relations/{eventId}GET /_matrix/client/v1/rooms/{roomId}/relations/{eventId}/{relType}GET /_matrix/client/v1/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}Sending a message:
PUT https://matrix.gdna.ai/_matrix/client/v3/rooms/{roomId}/send/m.room.message/{txnId}
Authorization: Bearer <token>
Content-Type: application/json
{"msgtype":"m.text","body":"hello"}
txnId is chosen by the client. Repeating the same txnId from the same device returns the original event_id and does not create a second event. The success body is {"event_id":"$..."}.
The caller must already be joined to the room. State events use PUT /_matrix/client/v3/rooms/{roomId}/state/{eventType} (empty state key) or PUT .../state/{eventType}/{stateKey}. The empty-state-key form is also registered with a trailing slash, because clients send both.
Redaction is PUT /_matrix/client/v3/rooms/{roomId}/redact/{eventId}/{txnId} and is power-level enforced.
GET /_matrix/client/v3/rooms/{roomId}/messages pages history. from is a prior batch token; direction is dir=b (backwards) or dir=f.
Create a room with POST https://matrix.gdna.ai/_matrix/client/v3/createRoom. The body accepts name, topic, and preset. preset: "public_chat" makes the room publicly joinable. Any other preset, including omitting it, creates a private room. Private rooms are given join_rule: restricted against the caller's tenant Space. trusted_private_chat auto-power-level behaviour is not supported. The response is {"room_id":"!..."}.
Join a room this server already knows with POST /_matrix/client/v3/rooms/{roomId}/join. Join by id or alias with POST /_matrix/client/v3/join/{roomIdOrAlias}. Leave with POST /_matrix/client/v3/rooms/{roomId}/leave. Invite with POST /_matrix/client/v3/rooms/{roomId}/invite and body {"user_id":"@someone:matrix.gdna.ai"}. An invite into an isolated room across tenants is refused unless a live sharing grant exists.
Membership reads: GET /rooms/{roomId}/members, GET /rooms/{roomId}/joined_members, GET /joined_rooms.
Aliases: GET /rooms/{roomId}/aliases, and GET, PUT, DELETE /_matrix/client/v3/directory/room/{roomAlias}.
Directory: GET and POST /_matrix/client/v3/publicRooms are both authenticated, including the GET the specification marks as optionally public. GET and PUT /_matrix/client/v3/directory/list/room/{roomId} publish or unpublish a room.
Moderation, all authenticated and power-level enforced: POST /rooms/{roomId}/kick, /ban, /unban, /forget, and POST /rooms/{roomId}/report/{eventId}.
GET /_matrix/client/v1/rooms/{roomId}/hierarchy is not supported. Spaces can be created and linked with m.space.child and m.space.parent state events; a client walks those events through /state.
Knock, third-party invites, and room upgrading are not supported.
The content repository is supported when MEDIA_STORE_PATH is set. These deployments set it. If it is unset, the media routes still exist and answer 404 M_UNRECOGNIZED ("the content repository is not configured").
GET /_matrix/media/v3/config (and the authenticated GET /_matrix/client/v1/media/config) returns m.upload.size. The default cap is 50 MiB (MEDIA_MAX_UPLOAD_BYTES). There is also a per-user byte quota.
Upload with POST /_matrix/media/v3/upload (raw body, Content-Type of the file, optional ?filename=). The response is {"content_uri":"mxc://matrix.gdna.ai/<media id>"}. To reserve an id first, POST /_matrix/media/v1/create, then PUT /_matrix/media/v3/upload/{serverName}/{mediaId}. Too many unfinished reservations is 429 M_LIMIT_EXCEEDED. Overwriting an existing object is M_CANNOT_OVERWRITE_MEDIA. A download of a reservation that has not been uploaded yet is M_NOT_YET_UPLOADED.
Legacy download (unauthenticated through v1.10; the media id is the secret):
GET /_matrix/media/v3/download/{serverName}/{mediaId}GET /_matrix/media/v3/download/{serverName}/{mediaId}/{fileName}GET /_matrix/media/v3/thumbnail/{serverName}/{mediaId}Authenticated equivalents, which require a bearer token:
GET /_matrix/client/v1/media/download/{serverName}/{mediaId}GET /_matrix/client/v1/media/download/{serverName}/{mediaId}/{fileName}GET /_matrix/client/v1/media/thumbnail/{serverName}/{mediaId}Downloads are served with a sandbox Content-Security-Policy (default-src 'none'; script-src 'none'), X-Content-Type-Options: nosniff, and Content-Disposition: attachment for types that are not safe to render inline. That policy applies to media responses, not to this documentation page.
GET /_matrix/media/v3/preview_url is routed and always returns M_UNRECOGNIZED. URL previews are not supported.
Remote homeserver media is fetched and cached when MEDIA_REMOTE_CACHE_BYTES is non-zero (these deployments set 10 GiB). Federation multipart/mixed media is not supported.
The server stores and forwards keys. It does not encrypt or decrypt room contents. Olm and Megolm sessions live in the client.
Device keys:
POST /_matrix/client/v3/keys/upload — upload this device's device keys and one-time keysPOST /_matrix/client/v3/keys/query — query device keys, including cross-signing keys (master, self-signing, user-signing) when they have been uploadedPOST /_matrix/client/v3/keys/claim — claim one-time keysCross-signing:
POST /_matrix/client/v3/keys/device_signing/uploadPOST /_matrix/client/v3/keys/signatures/uploadThe first cross-signing upload is not gated on user-interactive authentication. These accounts have no local password, and a UIAA gate would make cross-signing unreachable. Replacing an existing cross-signing identity is forbidden (M_FORBIDDEN) unless the caller is allowed to replace it. See api::client::cross_signing.
To-device (key gossip, including encrypted to-device events): PUT /_matrix/client/v3/sendToDevice/{eventType}/{txnId}. Delivered events show up on the next /sync under to_device. The transaction id makes a retry idempotent.
Server-side key backup (room key escrow):
POST and GET /_matrix/client/v3/room_keys/versionGET, PUT, and DELETE /_matrix/client/v3/room_keys/version/{version}PUT, GET, and DELETE /_matrix/client/v3/room_keys/keysPUT, GET, and DELETE /_matrix/client/v3/room_keys/keys/{roomId}PUT, GET, and DELETE /_matrix/client/v3/room_keys/keys/{roomId}/{sessionId}A backup request that names the wrong version is M_WRONG_ROOM_KEYS_VERSION.
Dehydrated devices and secret storage are not supported. There is no /keys/changes endpoint; device-list changes arrive on /sync in device_lists. SAS and QR verification are to-device messages the client drives itself. This server has no separate verification route.
Federated key exchange, used by other homeservers rather than by your client:
POST /_matrix/federation/v1/user/keys/queryPOST /_matrix/federation/v1/user/keys/claimdevice_id on login is the device these keys are bound to. POST /logout deletes that device and its keys. There is no DELETE /devices/{deviceId} route.
Dehydrated devices, secret storage (/unstable/org.matrix.msc3814 and the stable secret-storage endpoints), and the key backup /_matrix/client/v3/room_keys/version/{version} room-key endpoints beyond the five routes above are what this server ships. The following are not supported: SAS verification as a server endpoint (verification is to-device traffic the client drives itself), /keys/changes as a separate poll (device-list changes arrive on /sync in device_lists), and QR-code verification shortcuts.
Every row is a route this binary registers. Paths use the router's :parameter names. Optional segments are separate rows, not brackets. Authentication is a user access token (Authorization: Bearer) unless the row says otherwise.
GET /metrics with no METRICS_TOKEN configured answers an empty 404 on purpose. The route is still registered.
| Method | Path | Auth | Notes |
|---|---|---|---|
GET | /health | no | Plain text ok. |
GET | /_matrix/client/versions | no | Supported spec versions. |
GET | /.well-known/matrix/server | no | Federation delegation, port 443. |
GET | /.well-known/matrix/client | no | m.homeserver.base_url. |
GET | /_matrix/key/v2/server | no | This server's signing keys. |
GET | /metrics | metrics token | Empty 404 when METRICS_TOKEN is unset. |
POST | /_matrix/client/v3/register | no | Rate-limited. Production mode is disabled. |
GET | /_matrix/client/v3/login | no | Lists flows, including m.login.sso when OIDC is configured. |
POST | /_matrix/client/v3/login | no | m.login.password or m.login.token. Rate-limited. |
GET | /_matrix/client/v3/login/sso/redirect | no | Only when exactly one tenant is active. Otherwise M_MISSING_PARAM. Rate-limited. |
GET | /_matrix/client/v3/login/sso/redirect/:idp_id | no | Tenant slug or brokered IdP id. Rate-limited. |
GET | /_matrix/client/v3/login/sso/callback | no | OIDC redirect URI. Not called by the embedded client directly. |
POST | /_matrix/client/v3/logout | access token | Revokes the token and deletes the device. May return sso_logout_url. |
POST | /_matrix/client/v3/logout/all | access token | Revokes every device. |
POST | /_matrix/client/v3/account/deactivate | access token | Deactivates the account. |
GET | /_matrix/client/v3/sync | access token | Initial sync, or long-poll. Timeout clamped to 30000 ms. |
POST | /_matrix/client/v3/createRoom | access token | name, topic, preset. Private rooms are restricted to the tenant Space. |
POST | /_matrix/client/v3/rooms/:room_id/join | access token | Local join, or federation join when the room is remote. |
POST | /_matrix/client/v3/join/:room_id_or_alias | access token | Join by room id or alias. |
POST | /_matrix/client/v3/rooms/:room_id/leave | access token | |
POST | /_matrix/client/v3/rooms/:room_id/invite | access token | Body user_id. |
GET | /_matrix/client/v3/rooms/:room_id/members | access token | |
GET | /_matrix/client/v3/rooms/:room_id/joined_members | access token | |
GET | /_matrix/client/v3/rooms/:room_id/state | access token | Full state. |
GET | /_matrix/client/v3/rooms/:room_id/state/:event_type | access token | Empty state key. |
PUT | /_matrix/client/v3/rooms/:room_id/state/:event_type | access token | Empty state key. |
GET | /_matrix/client/v3/rooms/:room_id/state/:event_type/ | access token | Same handler as the no-slash empty state key. |
PUT | /_matrix/client/v3/rooms/:room_id/state/:event_type/ | access token | Same handler as the no-slash empty state key. |
GET | /_matrix/client/v3/rooms/:room_id/state/:event_type/:state_key | access token | |
PUT | /_matrix/client/v3/rooms/:room_id/state/:event_type/:state_key | access token | |
PUT | /_matrix/client/v3/rooms/:room_id/send/:event_type/:txn_id | access token | Idempotent per device and txn_id. |
GET | /_matrix/client/v3/rooms/:room_id/messages | access token | from, to, dir (b default), limit (max 100). |
GET | /_matrix/client/v3/rooms/:room_id/context/:event_id | access token | Permalink / jump-to-event. |
GET | /_matrix/client/v1/rooms/:room_id/relations/:event_id | access token | All relations. v1, not v3. |
GET | /_matrix/client/v1/rooms/:room_id/relations/:event_id/:rel_type | access token | |
GET | /_matrix/client/v1/rooms/:room_id/relations/:event_id/:rel_type/:event_type | access token | |
GET | /_matrix/client/v1/register/m.login.registration_token/validity | no | Rate-limited probe. |
GET | /_matrix/client/v3/voip/turnServer | access token | TURN credentials. Anonymous issuance is refused. |
GET | /_matrix/client/v3/account/whoami | access token | |
GET | /_matrix/client/v3/capabilities | access token | |
GET | /_matrix/client/v3/pushrules/ | access token | Read-only. The trailing slash is the registered path. |
GET | /_matrix/client/v3/joined_rooms | access token | |
POST | /_matrix/client/v3/user/:user_id/filter | access token | Stored, but /sync does not apply filter ids. |
GET | /_matrix/client/v3/user/:user_id/filter/:filter_id | access token | |
GET | /_matrix/client/v3/user/:user_id/account_data/:event_type | access token | Includes m.direct. |
PUT | /_matrix/client/v3/user/:user_id/account_data/:event_type | access token | |
GET | /_matrix/client/v3/user/:user_id/rooms/:room_id/account_data/:event_type | access token | |
PUT | /_matrix/client/v3/user/:user_id/rooms/:room_id/account_data/:event_type | access token | |
GET | /_matrix/client/v3/profile/:user_id | no | Unauthenticated. A user id on another server is M_FORBIDDEN; federation profile queries are not supported. |
GET | /_matrix/client/v3/profile/:user_id/displayname | no | Unauthenticated read. |
PUT | /_matrix/client/v3/profile/:user_id/displayname | access token | |
GET | /_matrix/client/v3/profile/:user_id/avatar_url | no | Unauthenticated read. |
PUT | /_matrix/client/v3/profile/:user_id/avatar_url | access token | |
GET | /_matrix/client/v3/devices | access token | |
GET | /_matrix/client/v3/devices/:device_id | access token | No DELETE route. |
PUT | /_matrix/client/v3/devices/:device_id | access token | Rename. |
POST | /_matrix/client/v3/rooms/:room_id/kick | access token | |
POST | /_matrix/client/v3/rooms/:room_id/ban | access token | |
POST | /_matrix/client/v3/rooms/:room_id/unban | access token | |
POST | /_matrix/client/v3/rooms/:room_id/forget | access token | |
POST | /_matrix/client/v3/rooms/:room_id/report/:event_id | access token | |
PUT | /_matrix/client/v3/rooms/:room_id/redact/:event_id/:txn_id | access token | |
PUT | /_matrix/client/v3/rooms/:room_id/typing/:user_id | access token | Ephemeral, not a timeline event. |
POST | /_matrix/client/v3/rooms/:room_id/receipt/:receipt_type/:event_id | access token | |
POST | /_matrix/client/v3/rooms/:room_id/read_markers | access token | |
GET | /_matrix/client/v3/presence/:user_id/status | access token | |
PUT | /_matrix/client/v3/presence/:user_id/status | access token | |
GET | /_matrix/client/v3/rooms/:room_id/aliases | access token | |
GET | /_matrix/client/v3/directory/room/:room_alias | no | Alias lookup. Spec allows this unauthenticated. |
PUT | /_matrix/client/v3/directory/room/:room_alias | access token | |
DELETE | /_matrix/client/v3/directory/room/:room_alias | access token | |
GET | /_matrix/client/v3/publicRooms | access token | Authenticated even though the spec marks it optional. |
POST | /_matrix/client/v3/publicRooms | access token | |
GET | /_matrix/client/v3/directory/list/room/:room_id | access token | |
PUT | /_matrix/client/v3/directory/list/room/:room_id | access token | |
POST | /_matrix/client/v3/user_directory/search | access token | Results limited to users who share a room with the caller. |
GET | /_matrix/client/v3/pushers | access token | |
POST | /_matrix/client/v3/pushers/set | access token | Gateway URL must be on the operator allow-list. |
POST | /_matrix/client/v3/keys/upload | access token | Device and one-time keys. |
POST | /_matrix/client/v3/keys/query | access token | Includes cross-signing keys when present. |
POST | /_matrix/client/v3/keys/claim | access token | |
POST | /_matrix/client/v3/keys/device_signing/upload | access token | First upload is not UIAA-gated. |
POST | /_matrix/client/v3/keys/signatures/upload | access token | |
PUT | /_matrix/client/v3/sendToDevice/:event_type/:txn_id | access token | |
POST | /_matrix/client/v3/room_keys/version | access token | Create a backup version. |
GET | /_matrix/client/v3/room_keys/version | access token | Latest version. |
GET | /_matrix/client/v3/room_keys/version/:version | access token | |
PUT | /_matrix/client/v3/room_keys/version/:version | access token | |
DELETE | /_matrix/client/v3/room_keys/version/:version | access token | |
PUT | /_matrix/client/v3/room_keys/keys | access token | |
GET | /_matrix/client/v3/room_keys/keys | access token | |
DELETE | /_matrix/client/v3/room_keys/keys | access token | |
PUT | /_matrix/client/v3/room_keys/keys/:room_id | access token | |
GET | /_matrix/client/v3/room_keys/keys/:room_id | access token | |
DELETE | /_matrix/client/v3/room_keys/keys/:room_id | access token | |
PUT | /_matrix/client/v3/room_keys/keys/:room_id/:session_id | access token | |
GET | /_matrix/client/v3/room_keys/keys/:room_id/:session_id | access token | |
DELETE | /_matrix/client/v3/room_keys/keys/:room_id/:session_id | access token | |
GET | /_matrix/media/v3/config | no | m.upload.size. |
POST | /_matrix/media/v3/upload | access token | Returns mxc:// content URI. |
POST | /_matrix/media/v1/create | access token | Reserve an id, then PUT the bytes. |
PUT | /_matrix/media/v3/upload/:server_name/:media_id | access token | |
GET | /_matrix/media/v3/download/:server_name/:media_id | no | Unauthenticated through v1.10. The id is the secret. |
GET | /_matrix/media/v3/download/:server_name/:media_id/:file_name | no | |
GET | /_matrix/media/v3/thumbnail/:server_name/:media_id | no | |
GET | /_matrix/media/v3/preview_url | no | Always M_UNRECOGNIZED. URL previews are not supported. |
GET | /_matrix/client/v1/media/config | access token | Authenticated media config. |
GET | /_matrix/client/v1/media/download/:server_name/:media_id | access token | |
GET | /_matrix/client/v1/media/download/:server_name/:media_id/:file_name | access token | |
GET | /_matrix/client/v1/media/thumbnail/:server_name/:media_id | access token | |
GET | /_matrix/federation/v1/version | no | Unauthenticated. Body is {"server":{"name":"gdna-matrix","version":"<crate version>"}}. Federation testers treat a 404 here as the server being down. |
PUT | /_matrix/federation/v1/send/:txn_id | federation | Transaction push. |
GET | /_matrix/federation/v1/make_join/:room_id/:user_id | federation | |
PUT | /_matrix/federation/v2/send_join/:room_id/:event_id | federation | |
PUT | /_matrix/federation/v1/invite/:room_id/:event_id | federation | |
PUT | /_matrix/federation/v2/invite/:room_id/:event_id | federation | |
GET | /_matrix/federation/v1/make_leave/:room_id/:user_id | federation | |
PUT | /_matrix/federation/v2/send_leave/:room_id/:event_id | federation | |
GET | /_matrix/federation/v1/state/:room_id | federation | |
GET | /_matrix/federation/v1/backfill/:room_id | federation | |
POST | /_matrix/federation/v1/user/keys/query | federation | |
POST | /_matrix/federation/v1/user/keys/claim | federation |
Limits apply to unauthenticated, credential-adjacent routes. They are per process. Each of these deployments runs one task and does not autoscale, so the numbers below are the deployment's limits. Set RATE_LIMIT_DISABLED=1 only to turn them off; the server binary enables them.
The limiter keys on the TCP peer address. X-Forwarded-For is ignored unless TRUST_FORWARDED_FOR=1. These deployments set that, because the load balancer is the only path to the task and overwrites the header. A client that can reach the process directly must not be trusted to set it.
| Route family | Burst | Sustained rate |
|---|---|---|
POST /_matrix/client/v3/login | 5 | 1 per 12 seconds |
POST /_matrix/client/v3/register | 3 | 1 per 30 seconds |
GET /_matrix/client/v1/register/m.login.registration_token/validity | 10 | 1 per 6 seconds |
GET /_matrix/client/v3/login/sso/redirect and .../redirect/{idpId} | 10 | 1 per 3 seconds |
Over the limit the response is 429:
{"errcode":"M_LIMIT_EXCEEDED","error":"Too many requests","retry_after_ms":1000}
retry_after_ms is at least 1. Wait that long. The shared load balancer also has a WAF rate-based rule of 2000 requests per IP on paths under /_matrix/client/, evaluated on the WAF's default window, which blocks rather than returning M_LIMIT_EXCEEDED. /docs is not under that path.
/sync is not rate-limited by this process. Its timeout is clamped to 30 seconds so one client cannot hold a connection longer than that.
Matrix errors are JSON: {"errcode":"M_...","error":"..."}. Handlers that return a plain AppError (no specific errcode) are wrapped as M_UNKNOWN with a fixed message:
| Status | error string | When |
|---|---|---|
| 400 | the handler's message | AppError::BadRequest |
| 401 | unauthorized | missing or unknown access token |
| 403 | forbidden | password login failed, or a forbidden action that did not set its own errcode |
| 404 | not found | AppError::NotFound |
| 409 | the handler's message | conflict |
| 500 | internal server error | database or internal failure; the detail is logged, not returned |
| 503 | temporarily unable to verify; retry | a dependency such as the OIDC provider was unreachable |
A client that only treats M_MISSING_TOKEN / M_UNKNOWN_TOKEN as "session expired" will not notice a dead access token. Branch on HTTP 401.
Errcodes this server sets explicitly, and that an embedded client will actually see:
| Code | When |
|---|---|
M_LIMIT_EXCEEDED | rate limit, or too many unfinished media reservations |
M_MISSING_PARAM | a required SSO or login field was absent; bare SSO redirect when more than one tenant is active |
M_INVALID_PARAM | malformed id, bad redirectUrl, bad profile or state value |
M_FORBIDDEN | SSO token rejected, cross-signing replacement refused, profile change for someone else |
M_NOT_FOUND | unknown IdP, unknown alias, SSO not configured, unknown backup version |
M_USER_DEACTIVATED | password or SSO login for a deactivated account, after the credential checked out |
M_USER_IN_USE | registration localpart taken |
M_UNAUTHORIZED | UIAA stage failed (registration) |
M_UNRECOGNIZED | URL preview, content repository disabled, unknown UIAA type, push gateway not configured |
M_EXCLUSIVE | application-service user_id outside the bot's namespace |
M_TOO_LARGE | upload over the size cap |
M_NOT_JSON / M_BAD_JSON | account-data or filter body |
M_ROOM_IN_USE | alias already points at a room |
M_BAD_STATE | moderation or membership rejected by room state |
M_WRONG_ROOM_KEYS_VERSION | key backup version mismatch |
M_CANNOT_OVERWRITE_MEDIA | upload onto an id that already has bytes |
M_NOT_YET_UPLOADED | download of a reserved id that has no bytes yet |
M_UNABLE_TO_AUTHORISE_JOIN | federation join the local server will not authorise |
M_INCOMPATIBLE_ROOM_VERSION | federation invite for a room version this server refuses |
Password login uses the same 403 M_UNKNOWN for "no such user" and "wrong password". M_USER_DEACTIVATED is returned only after the password verifies.
matrix.gdna.ai is one homeserver. matrix.gdna.io and matrix.gdna.ai are two deployments of this same binary, each with its own server name, database, and signing key. They are not two tasks of one server.
The code default is federation disabled (FEDERATION_MODE unset, disabled, or unrecognised). Nothing is sent or accepted until an operator opts in. These two deployments set FEDERATION_MODE=allowlist:
matrix.gdna.io allows only matrix.gdna.aimatrix.gdna.ai allows only matrix.gdna.ioAny other server name is refused, inbound and outbound, after the caller has authenticated. The policy does not unsend events that were already shared. An allow-list that is empty allows nobody.
Clients do not call the federation endpoints. They join, invite, and leave through the Client-Server routes, and this process speaks Server-Server to the peer when the room has a member there. Joining a room that lives on the other homeserver uses POST /_matrix/client/v3/join/{roomIdOrAlias} or POST /rooms/{roomId}/join; this server then runs the federation join against the peer.
Server-Server routes this build implements are listed in the endpoint reference. GET /_matrix/federation/v1/version is the exception: it is unauthenticated and returns {"server":{"name":"gdna-matrix","version":"<crate version>"}}. A 404 on that path is what federation testers report as the server being down. Every other federation route requires a signed Authorization: X-Matrix header, not a user access token. Federation listens on the well-known port from GET /.well-known/matrix/server (matrix.gdna.ai:443) and, on this deployment, also on 8448.
Not supported on the federation side: multipart/mixed media (/_matrix/federation/v1/media/...), /_matrix/federation/v1/query directory queries beyond what the client directory already does locally, and open federation with the public Matrix network. A room on matrix.org cannot be joined from these servers.
Replace nothing in the URLs below: this page already substituted https://matrix.gdna.ai and matrix.gdna.ai. The examples assume an embedded web client whose origin is on the SSO redirect allow-list (see Authentication).
Discover, then start SSO. {idpId} comes from GET /login (identity_providers[].id), not from a guess. The callback below must be URL-encoded and allow-listed.
BASE=https://matrix.gdna.ai
curl -sS "$BASE/.well-known/matrix/client"
curl -sS "$BASE/_matrix/client/versions"
curl -sS "$BASE/_matrix/client/v3/login"
Open this in a browser (the redirectUrl is your app, not the homeserver):
https://matrix.gdna.ai/_matrix/client/v3/login/sso/redirect/{idpId}?redirectUrl=https%3A%2F%2Fchat.gdna.io%2Fcallback
The browser returns to your callback with ?loginToken=. Exchange it within 60 seconds. The response has access_token, user_id, and device_id, and no refresh_token.
curl -sS -X POST "$BASE/_matrix/client/v3/login" \
-H 'content-type: application/json' \
-d '{"type":"m.login.token","token":"LOGIN_TOKEN"}'
Initial sync (does not long-poll), create a room, send a message, then wait for the next event:
TOKEN=ACCESS_TOKEN
curl -sS "$BASE/_matrix/client/v3/sync" -H "authorization: Bearer $TOKEN"
ROOM=$(curl -sS -X POST "$BASE/_matrix/client/v3/createRoom" \
-H "authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{"name":"embedded"}' | jq -r .room_id)
TXN=$(uuidgen | tr '[:upper:]' '[:lower:]')
curl -sS -X PUT "$BASE/_matrix/client/v3/rooms/$ROOM/send/m.room.message/$TXN" \
-H "authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{"msgtype":"m.text","body":"hello"}'
NEXT=$(curl -sS "$BASE/_matrix/client/v3/sync" \
-H "authorization: Bearer $TOKEN" | jq -r .next_batch)
curl -sS -G "$BASE/_matrix/client/v3/sync" \
-H "authorization: Bearer $TOKEN" \
--data-urlencode "since=$NEXT" \
--data-urlencode "timeout=30000"
timeout larger than 30000 is clamped. A 401 with errcode M_UNKNOWN means the access token is missing or dead. Log in with SSO again. Refresh is not supported.
Logout revokes the token and deletes the device:
curl -sS -X POST "$BASE/_matrix/client/v3/logout" \
-H "authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{}'
import * as sdk from "matrix-js-sdk";
const baseUrl = "https://matrix.gdna.ai";
// 1. SSO. idpId is identity_providers[].id from GET /_matrix/client/v3/login.
// redirectUrl must be on this server's allow-list.
const idpId = "default";
const redirectUrl = window.location.origin + "/callback";
window.location.assign(
`${baseUrl}/_matrix/client/v3/login/sso/redirect/${encodeURIComponent(idpId)}` +
`?redirectUrl=${encodeURIComponent(redirectUrl)}`,
);
// 2. On the callback page, exchange loginToken. There is no refresh token.
const loginToken = new URLSearchParams(window.location.search).get("loginToken");
if (!loginToken) throw new Error("missing loginToken");
const session = await sdk.createClient({ baseUrl }).login("m.login.token", {
token: loginToken,
initial_device_display_name: "embedded",
});
const client = sdk.createClient({
baseUrl,
accessToken: session.access_token,
userId: session.user_id,
deviceId: session.device_id,
});
// 3. Initial sync, then live events. toStartOfTimeline is pagination, not new traffic.
client.on(
"Room.timeline",
(event: sdk.MatrixEvent, room: sdk.Room | undefined, toStartOfTimeline: boolean) => {
if (toStartOfTimeline || !room) return;
if (event.getType() !== "m.room.message") return;
if (event.getContent().msgtype !== "m.text") return;
console.log(room.roomId, event.getContent().body);
},
);
await client.startClient({ initialSyncLimit: 20 });
// 4. Send. txnId is generated inside sendTextMessage.
const roomId = (await client.createRoom({ name: "embedded" })).room_id;
await client.sendTextMessage(roomId, "hello from an embedded client");
client.createRoom calls POST /_matrix/client/v3/createRoom. sendTextMessage calls PUT /rooms/{roomId}/send/m.room.message/{txnId}. startClient drives GET /sync. When a request fails with HTTP 401, call the SSO redirect again; do not call a refresh API. This server does not implement one.
User ids in events are @localpart:matrix.gdna.ai.
These are absent from the router, or present only as an explicit refusal. Do not call them and retry.
refresh_token field on POST /login. No /_matrix/client/v3/refresh. No m.login.refresh. Tokens last 24 hours; run SSO again.DELETE /_matrix/client/v3/devices/{deviceId}. Logout deletes the calling device. Listing and renaming devices is supported.Authorization: Bearer./sync/v4, and MSC4186./sync filter ids. Filter CRUD exists. /sync honours since, timeout (max 30000 ms), and types only.GET /_matrix/client/v1/rooms/{roomId}/hierarchy). Walk m.space.child / m.space.parent via /state.GET /_matrix/media/v3/preview_url is registered and always returns M_UNRECOGNIZED.GET /_matrix/client/v3/pushrules/ is read-only. There is no PUT.GET and POST /_matrix/client/v3/pushers exist, and the gateway URL is checked against PUSH_GATEWAY_ALLOWLIST. These deployments set that list empty, so setting a pusher is refused (M_UNRECOGNIZED when push is not configured).REGISTRATION_MODE=disabled). POST /register exists in the binary and is closed in production./search), notifications (/notifications).trusted_private_chat auto-power-levels on createRoom.multipart/mixed under /_matrix/federation/v1/media/.m.login.token refresh. Guest registration is not implemented. m.login.token is only the SSO login-token exchange./peregrine, which is an operator UI gated by the SSO session cookie. It is not part of the client-server surface and is not documented as a public client API.