Contextual Chat

The workflow for entering organization, cluster, or location chat contexts through PixService bootstrap.

Contextual chat uses PixService authorization to enter Matrix spaces for an organization, cluster, or location. Always start through a PixService bootstrap endpoint; an existing Matrix room membership is not proof of current PixService access.

Bootstrap a context

GET /v1/chat/orgs/{organizationId}/bootstrap
GET /v1/chat/clusters/{clusterId}/bootstrap
GET /v1/chat/locations/{locationId}/bootstrap
Authorization: Bearer <token>

The current credential must pass one final, tier-capped decision:

  • organization chat requires IntegratedChatUse=Allow and OrganizationView=Allow;
  • cluster chat requires IntegratedChatUse=Allow and ClusterView=Read from the same principal;
  • location chat requires IntegratedChatUse=Allow, ClusterView=Read, and LocationView=Read from the same principal.

Organization membership alone is not enough. The active entitlement version can disable chat without hiding clusters, locations, or bundles. Personal access tokens are also limited by their live organization scopes and capability ceilings. A denied request creates no chat context, room membership, or Matrix login token.

Missing and inaccessible contexts use the same opaque 404 Not Found response. This applies to bootstrap, concrete channel-layout replacement, and organization template reads or writes, so a denial does not reveal context names, channel counts/layouts, or the capability that failed.

The successful response supplies the Matrix homeserver URL, Matrix user ID, short-lived JWT login token, whether membership was ensured, and the current space/channel identifiers. Use the login token with Matrix login type org.matrix.login.jwt, then begin Matrix sync against the returned homeserver.

For a chat notification that carries a channel room ID, use POST /v1/chat/rooms/resolve with { "matrixRoomId": "!channel:server" }. It returns the currently authorized context and matching active channel without requiring a map lookup or full chat directory. Unknown or revoked destinations return the same 404. This lookup does not create Matrix rooms, mark messages read, or check whether a targeted message still exists.

Discover conversations

The account catalog finds currently authorized conversations without location IDs from the map or a Matrix history download. It defaults to locations and returns up to 50 items. The maximum page size is 100. During the staged rollout, a deployment that has not enabled the catalog returns 503 chat_unavailable; continue using the existing contextual chat bootstrap and location directory until the capability is available.

GET /v1/chat/conversations?type=location&limit=50
Authorization: Bearer <token>

Use type=all, organization, or cluster for other context types. query searches conversation and breadcrumb names without case sensitivity. Each item has a stable conversationId, display name, breadcrumb, resource IDs, current context and channel identifiers, provisioning state, and pin position. A resource that is eligible for chat but has no Matrix context yet appears as pending. A nullable unread count or activity timestamp is unavailable; do not display it as zero. Once Matrix reconciliation is enabled, a user-session page may report unreadNotificationCount, highlightCount, and summaryFreshness: "fresh" for a context whose every channel has a recent verified Matrix count. Missing, stale, or changed room mappings keep the counts null. These are Matrix notification counts, not a count of all unread messages. A scoped PAT receives no account-wide count state. A confirmed new message can supply lastActivityEventId, lastActivityAtUtc, lastActivitySenderId, and lastActivityKind (message or encrypted). Message text is not stored in this catalog.

If hasMore is true, pass the opaque nextCursor with the same type, query, and credential on the next request. A cursor is valid for 30 minutes and is single use. A malformed or mismatched cursor returns 400 chat_catalog_cursor_invalid; a consumed, expired, or pin-revision-stale cursor returns 409 chat_catalog_reset_required. Restart at page one after a reset and merge entries by conversationId. Access is rechecked on each page.

Matching pins lead in stored order and count toward the page limit. Existing chats without a catalog-observed visible message start in stable name and ID order. A later message moves an unpinned chat up after Matrix sync confirms that user can see it. Reading, edits, redactions, and delayed pushes do not change activity order. Initial historical messages do not establish order.

GET /v1/chat/summary reads every currently authorized conversation for a user session, including pages the client has not opened. It reports authorizedConversationCount, pendingConversationCount, changeSequence, snapshotAtUtc, and freshness. When every active room has a recent Matrix count, unreadConversationCount, unreadNotificationCount, and highlightCount contain account totals. If any room is missing or stale, all three totals are null. unreadNotificationCount counts Matrix notifications, not every unread message. Scoped PATs cannot read this account summary.

Save chat pins across devices

Pinned conversations are account preferences. They do not grant access, join a Matrix room, change unread state, or mute notifications. Pin preference routes require a user session; personal access tokens cannot read or replace the account's pin set. A pin references a stable conversation ID such as location-5f920a386ce54d1f91b309c37761de28, cluster-5f920a386ce54d1f91b309c37761de28, or org-5f920a386ce54d1f91b309c37761de28.

GET /v1/chat/pins
Authorization: Bearer <user-session-token>

The response contains revision and ordered pins with conversationId and zero-based position. It also returns a quoted revision in ETag, for example "3". Only currently accessible pins appear in the response.

PUT /v1/chat/conversations/{conversationId}/pin
DELETE /v1/chat/conversations/{conversationId}/pin
PUT /v1/chat/pins/order
Authorization: Bearer <user-session-token>
If-Match: "3"

Pinning appends after existing pins; repeating an existing pin leaves its position and revision unchanged. At most five distinct conversations can be pinned. Reordering takes { "conversationIds": ["location-...", "org-..."] } with exactly the current pins once each; it cannot add or remove pins. An uncertain successful write can be retried: if the desired state is already current, the server returns it without another revision change. A stale change returns 412 chat_pin_revision_conflict; refresh GET /v1/chat/pins and retry. A sixth distinct pin returns 409 chat_pin_limit_reached. Missing If-Match returns 428 chat_pin_revision_required. Unknown or inaccessible conversation IDs remain opaque with 404.

Each real pin change writes a durable, revisioned account invalidation. A connected user-session client may receive ChatAccountChanged on the existing notification hub with { "kind": "pins_changed", "pinRevision": 4 }. Treat it as a hint and fetch authoritative state. To catch up after reconnect:

GET /v1/chat/conversations/changes?since={changeSequence}&limit=100
Authorization: Bearer <user-session-token>

Process items in sequence, follow nextSequence while hasMore is true, and persist the sequence only after handling that page. Each item currently contains sequence, kind, pinRevision, and createdAtUtc. A 409 chat_changes_reset_required response means the cursor is outside the retained journal; discard cached chat-list state and restart from a fresh snapshot. Use the account catalog page's changeSequence as the snapshot watermark.

Change channel layouts

Chat access does not grant layout-management access. Full channel replacement uses these additional final capabilities:

  • organization layouts and cluster/location templates: OrganizationSettingsWrite=Allow;
  • one concrete cluster layout: ClusterUpdate=Allow;
  • one concrete location layout: LocationUpdate=Allow.

Channels omitted from a replacement request are archived or removed according to that endpoint's contract. Re-read bootstrap after a successful change to refresh room identifiers and provisioning state.

Handle permission changes

Treat PixService as the authority. Do not cache a Matrix invite, joined-room list, or earlier bootstrap response as an authorization decision. A permission revocation, entitlement transition, suspension, or expiry can remove desired Matrix membership while spatial and bundle access remains otherwise unchanged. Re-run bootstrap when navigation context or credentials change, and handle a denied or not-found response without attempting direct room access through the PixService client flow.

On this page