Spaces

[Added in v1.2]

Often used to group rooms of similar subject matter (such as a public “Official matrix.org rooms” space or personal “Work stuff” space), spaces are a way to organise rooms while being represented as rooms themselves.

A space is defined by the m.space room type, making it known as a “space-room”. The space’s name, topic, avatar, aliases, etc are all defined through the existing relevant state events within the space-room.

Sending normal m.room.message events within the space-room is discouraged - clients are not generally expected to have a way to render the timeline of the room. As such, space-rooms should be created with m.room.power_levels which prohibit normal events by setting events_default to a suitably high number. In the default power level structure, this would be 100. Clients might wish to go a step further and explicitly ignore notification counts on space-rooms.

Membership of a space is defined and controlled by the existing mechanisms which govern a room: m.room.member, m.room.history_visibility, and m.room.join_rules. Public spaces are encouraged to have a similar setup to public rooms: world_readable history visibility, published canonical alias, and suitably public join_rule. Invites, including third-party invites, still work just as they do in normal rooms as well.

All other aspects of regular rooms are additionally carried over, such as the ability to set arbitrary state events, hold room account data, etc. Spaces are just rooms with extra functionality on top.

Managing rooms/spaces included in a space

Spaces form a hierarchy of rooms which clients can use to structure their room list into a tree-like view. The parent/child relationship can be defined in two ways: with m.space.child state events in the space-room, or with m.space.parent state events in the child room.

In most cases, both the child and parent relationship should be defined to aid discovery of the space and its rooms. When only a m.space.child is used, the space is effectively a curated list of rooms which the rooms themselves might not be aware of. When only a m.space.parent is used, the rooms are “secretly” added to spaces with the effect of not being advertised directly by the space.

m.space.child relationship

When using this approach, the state events get sent into the space-room which is the parent to the room. The state_key for the event is the child room’s ID.

For example, to achieve the following:

#space:example.org
    #general:example.org (!abcdefg:example.org)
    !private:example.org

the state of #space:example.org would consist of:

Unimportant fields trimmed for brevity.

{
    "type": "m.space.child",
    "state_key": "!abcdefg:example.org",
    "content": {
        "via": ["example.org"]
    }
}
{
    "type": "m.space.child",
    "state_key": "!private:example.org",
    "content": {
        "via": ["example.org"]
    }
}

No state events in the child rooms themselves would be required (though they can also be present). This allows for users to define personal/private spaces to organise their own rooms without needing explicit permission from the room moderators/admins.

Child rooms can be removed from a space by omitting the via key of content on the relevant state event, such as through redaction or otherwise clearing the content.

m.space.child


Defines the relationship of a child room to a space-room. Has no effect in rooms which are not spaces.

Event type: State event
State key The child room ID being described.

Content

Name Type Description
order string

Optional string to define ordering among space children. These are lexicographically compared against other children’s order, if present.

Must consist of ASCII characters within the range \x20 (space) and \x7E (~), inclusive. Must not exceed 50 characters.

order values with the wrong type, or otherwise invalid contents, are to be treated as though the order key was not provided.

See Ordering for information on how the ordering works.

suggested boolean Optional (default false) flag to denote whether the child is “suggested” or of interest to members of the space. This is primarily intended as a rendering hint for clients to display the room differently, such as eagerly rendering them in the room list.
via [string]

A list of servers to try and join through. See also: Routing.

When not present or invalid, the child room is not considered to be part of the space.

Examples

{
  "content": {
    "order": "lexicographically_compare_me",
    "suggested": true,
    "via": [
      "example.org",
      "other.example.org"
    ]
  },
  "event_id": "$143273582443PhrSn:example.org",
  "origin_server_ts": 1432735824653,
  "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
  "sender": "@example:example.org",
  "state_key": "!roomid:example.org",
  "type": "m.space.child",
  "unsigned": {
    "age": 1234
  }
}
Ordering

When the client is displaying the children of a space, the children should be ordered using the algorithm below. In some cases, like a traditional left side room list, the client may override the ordering to provide better user experience. A theoretical space summary view would however show the children ordered.

Taking the set of space children, first order the children with a valid order key lexicographically by Unicode code-points such that \x20 (space) is sorted before \x7E (~). Then, take the remaining children and order them by the origin_server_ts of their m.space.child event in ascending numeric order, placing them after the children with a valid order key in the resulting set.

In cases where the order values are the same, the children are ordered by their timestamps. If the timestamps are the same, the children are ordered lexicographically by their room IDs (state keys) in ascending order.

Noting the careful use of ASCII spaces here, the following demonstrates a set of space children being ordered appropriately:

Unimportant fields trimmed for brevity.

[
    {
        "type": "m.space.child",
        "state_key": "!b:example.org",
        "origin_server_ts": 1640341000000,
        "content": {
            "order": " ",
            "via": ["example.org"]
        }
    },
    {
        "type": "m.space.child",
        "state_key": "!a:example.org",
        "origin_server_ts": 1640141000000,
        "content": {
            "order": "aaaa",
            "via": ["example.org"]
        }
    },
    {
        "type": "m.space.child",
        "state_key": "!c:example.org",
        "origin_server_ts": 1640841000000,
        "content": {
            "order": "first",
            "via": ["example.org"]
        }
    },
    {
        "type": "m.space.child",
        "state_key": "!e:example.org",
        "origin_server_ts": 1640641000000,
        "content": {
            "via": ["example.org"]
        }
    },
    {
        "type": "m.space.child",
        "state_key": "!d:example.org",
        "origin_server_ts": 1640741000000,
        "content": {
            "via": ["example.org"]
        }
    }
]
  1. !b:example.org is first because \x20 is before aaaa lexically.
  2. !a:example.org is next because aaaa is before first lexically.
  3. !c:example.org is next because first is the last order value.
  4. !e:example.org is next because the event timestamp is smallest.
  5. !d:example.org is last because the event timestamp is largest.
m.space.parent relationships

Rooms can additionally claim to be part of a space by populating their own state with a parent event. Similar to child events within spaces, the parent event’s state_key is the room ID of the parent space, and they have a similar via list within their content to denote both whether or not the link is valid and which servers might be possible to join through.

To avoid situations where a room falsely claims it is part of a given space, m.space.parent events should be ignored unless one of the following is true:

m.space.parent events can additionally include a canonical boolean key in their content to denote that the parent space is the main/primary space for the room. This can be used to, for example, have the client find other rooms by peeking into that space and suggesting them to the user. Only one canonical parent should exist, though this is not enforced. To tiebreak, use the lowest room ID sorted lexicographically by Unicode code-points.

m.space.parent


Defines the relationship of a room to a parent space-room.

Event type: State event
State key The parent room ID.

Content

Name Type Description
canonical boolean

Optional (default false) flag to denote this parent is the primary parent for the room.

When multiple canonical parents are found, the lowest parent when ordering by room ID lexicographically by Unicode code-points should be used.

via [string]

A list of servers to try and join through. See also: Routing.

When not present or invalid, the room is not considered to be part of the parent space.

Examples

{
  "content": {
    "canonical": true,
    "via": [
      "example.org",
      "other.example.org"
    ]
  },
  "event_id": "$143273582443PhrSn:example.org",
  "origin_server_ts": 1432735824653,
  "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
  "sender": "@example:example.org",
  "state_key": "!parent_roomid:example.org",
  "type": "m.space.parent",
  "unsigned": {
    "age": 1234
  }
}

Discovering rooms within spaces

Often the client will want to assist the user in exploring what rooms/spaces are part of a space. This can be done with crawling m.space.child state events in the client and peeking into the rooms to get information like the room name, though this is impractical for most cases.

Instead, a hierarchy API is provided to walk the space tree and discover the rooms with their aesthetic details.

The GET /hierarchy API works in a depth-first manner: when it encounters another space as a child it recurses into that space before returning non-space children.

GET /_matrix/client/v1/rooms/{roomId}/hierarchy


Added in v1.2

Paginates over the space tree in a depth-first manner to locate child rooms of a given space.

Where a child room is unknown to the local server, federation is used to fill in the details. The servers listed in the via array should be contacted to attempt to fill in missing rooms.

Only m.space.child state events of the room are considered. Invalid child rooms and parent events are not covered by this endpoint.

Rate-limited: Yes
Requires authentication: Yes

Request

Request parameters

path parameters
Name Type Description
roomId string Required: The room ID of the space to get a hierarchy for.
query parameters
Name Type Description
from string A pagination token from a previous result. If specified, max_depth and suggested_only cannot be changed from the first request.
limit integer

Optional limit for the maximum number of rooms to include per response. Must be an integer greater than zero.

Servers should apply a default value, and impose a maximum value to avoid resource exhaustion.

max_depth integer

Optional limit for how far to go into the space. Must be a non-negative integer.

When reached, no further child rooms will be returned.

Servers should apply a default value, and impose a maximum value to avoid resource exhaustion.

suggested_only boolean Optional (default false) flag to indicate whether or not the server should only consider suggested rooms. Suggested rooms are annotated in their m.space.child event contents.

Responses

Status Description
200 A portion of the space tree, starting at the provided room ID.
400

The request was invalid in some way. A meaningful errcode and description error text will be returned. Example reasons for rejection are:

  • The from token is unknown to the server.
  • suggested_only or max_depth changed during pagination.
403

The user cannot view or peek on the room. A meaningful errcode and description error text will be returned. Example reasons for rejection are:

  • The room is not set up for peeking.
  • The user has been banned from the room.
  • The room does not exist.
429 This request was rate-limited.

200 response

Name Type Description
next_batch string A token to supply to from to keep paginating the responses. Not present when there are no further results.
rooms [ChildRoomsChunk] Required: The rooms for the current page, with the current filters.
ChildRoomsChunk
Name Type Description
avatar_url string The URL for the room’s avatar, if one is set.
canonical_alias string The canonical alias of the room, if any.
children_state [StrippedChildStateEvent] Required:

The m.space.child events of the space-room, represented as Stripped State Events with an added origin_server_ts key.

If the room is not a space-room, this should be empty.

guest_can_join boolean Required: Whether guest users may join the room and participate in it. If they can, they will be subject to ordinary power level rules like any other user.
join_rule string The room’s join rule. When not present, the room is assumed to be public.
name string The name of the room, if any.
num_joined_members integer Required: The number of members joined to the room.
room_id string Required: The ID of the room.
room_type string The type of room (from m.room.create), if any.

Added in v1.4

topic string The topic of the room, if any.
world_readable boolean Required: Whether the room may be viewed by guest users without joining.
StrippedChildStateEvent
Name Type Description
content EventContent Required: The content for the event.
origin_server_ts integer Required: The origin_server_ts for the event.
sender string Required: The sender for the event.
state_key string Required: The state_key for the event.
type string Required: The type for the event.
{
  "next_batch": "next_batch_token",
  "rooms": [
    {
      "avatar_url": "mxc://example.org/abcdef",
      "canonical_alias": "#general:example.org",
      "children_state": [
        {
          "content": {
            "via": [
              "example.org"
            ]
          },
          "origin_server_ts": 1629413349153,
          "sender": "@alice:example.org",
          "state_key": "!a:example.org",
          "type": "m.space.child"
        }
      ],
      "guest_can_join": false,
      "join_rule": "public",
      "name": "The First Space",
      "num_joined_members": 42,
      "room_id": "!space:example.org",
      "room_type": "m.space",
      "topic": "No other spaces were created first, ever",
      "world_readable": true
    }
  ]
}

400 response

Error
Name Type Description
errcode string Required: An error code.
error string A human-readable error message.
{
  "errcode": "M_INVALID_PARAM",
  "error": "suggested_only and max_depth cannot change on paginated requests"
}

403 response

Error
Name Type Description
errcode string Required: An error code.
error string A human-readable error message.
{
  "errcode": "M_FORBIDDEN",
  "error": "You are not allowed to view this room."
}

429 response

RateLimitError
Name Type Description
errcode string Required: The M_LIMIT_EXCEEDED error code
error string A human-readable error message.
retry_after_ms integer The amount of time in milliseconds the client should wait before trying the request again.
{
  "errcode": "M_LIMIT_EXCEEDED",
  "error": "Too many requests",
  "retry_after_ms": 2000
}
Server behaviour

In the case where the server does not have access to the state of a child room, it can request the information over federation with the GET /hierarchy API. The response to this endpoint should be cached for a period of time. The response might additionally contain information about rooms the requesting user is already a member of, or that the server is aware of - the local data should be used instead of the remote server’s data.

Note that the response to the client endpoint is contextual based on the user. Servers are encouraged to cache the data for a period of time, though permission checks may need to be performed to ensure the response is accurate for that user.