Status: ✅ Current (v3.4.0) Last Updated: 2026-03-07
- Implementation Reference
- Design Principles
- Data Types (Go)
- Response Envelope
- Commands & Events
- Room Commands
- 1. Create Room -
communication.room.create - 7. Get Room Details -
communication.room.get - 32. Get Room As User -
communication.room.get.as_user - 8. Update Room -
communication.room.update - 9. Delete Room -
communication.room.delete - 12. List All Rooms (Admin) -
communication.room.list
- 1. Create Room -
- Message Commands
- 2. Send Message -
communication.message.send - 5. Delete Message -
communication.message.delete - 13. Get Message Details -
communication.message.get
- 2. Send Message -
- Reaction Commands
- 3. Add Reaction -
communication.reaction.add - 4. Remove Reaction -
communication.reaction.remove - 14. Get Reaction Details -
communication.reaction.get
- 3. Add Reaction -
- Room Membership Commands
- 6. Batch Room Membership (Add) -
communication.room.member.batch.add - 10. Batch Room Membership (Remove) -
communication.room.member.batch.remove - 28. Get Room Members -
communication.room.members.get
- 6. Batch Room Membership (Add) -
- Actor Commands
- 11. Sync Actor Profile -
communication.actor.sync - 11a. Create Actor Session -
communication.actor.session.create
- 11. Sync Actor Profile -
- Space Commands
- 15. Create Space -
communication.space.create - 16. Update Space -
communication.space.update - 17. Delete Space -
communication.space.delete - 21. Get Space Details -
communication.space.get - 22. List All Spaces (Admin) -
communication.space.list
- 15. Create Space -
- Space Membership Commands
- 19. Batch Space Membership (Add) -
communication.space.member.batch.add - 20. Batch Space Membership (Remove) -
communication.space.member.batch.remove
- 19. Batch Space Membership (Add) -
- Hierarchy Commands
- 18. Set Parent (Hierarchy) -
communication.hierarchy.set_parent
- 18. Set Parent (Hierarchy) -
- Thread Commands
- 29. Get Thread Messages -
communication.thread.messages.get
- 29. Get Thread Messages -
- Read Receipt Commands
- 30. Mark Message Read -
communication.message.read - 31. Get Unread Counts -
communication.room.unread_counts.get
- 30. Mark Message Read -
- Room Commands
- Outgoing Events
- 23. Message Received -
communication.message.received - 24. DM Requested -
communication.room.dm.requested - 25. Reaction Added -
communication.reaction.added - 26. Reaction Removed -
communication.reaction.removed - 27. Room Member Left -
communication.room.member.left - 27a. Read Receipt Updated -
communication.room.receipt.updated - 27b. Message Edited -
communication.message.edited - 27c. Message Redacted -
communication.message.redacted - 27d. Room Created -
communication.room.created - 27e. Room Member Updated -
communication.room.member.updated - 27f. Room Updated -
communication.room.updated
- 23. Message Received -
- HTTP Endpoints
- Error Handling Strategy
- TS Library Export
- Go DTOs:
pkg/dto/- Source of truth for all data structures - TypeScript Library:
lib/src/dto/generated.ts- Auto-generated from Go - Event Types:
lib/src/matrix.adapter.event.type.ts- Enum of all 37 topics - Topic Constants:
internal/infrastructure/queue/topics.go- Single source of truth
This document defines the communication protocol between the Alkemio Server and the Matrix Adapter service. The protocol is designed to be transport-agnostic but is currently implemented over RabbitMQ.
- Alkemio-Native: All identifiers exchanged are Alkemio UUIDs (v4 or v7). The Adapter is responsible for mapping these to Matrix internal IDs.
- Opaque Transport: The Server does not know about Matrix Room IDs (
!abc:matrix.org) or Event IDs ($abc...). - Structured Responses: Every command returns a structured response indicating success, failure, or partial success.
- Idempotency: Operations should be idempotent where possible, relying on the deterministic Alkemio UUIDs.
The following types are used throughout the specification.
import (
"time"
"github.com/google/uuid"
)
// AlkemioRoomID is a UUID v4 or v7 identifying a room in Alkemio.
type AlkemioRoomID uuid.UUID
// AlkemioContextID is a UUID v4 or v7 identifying a context (Space) in Alkemio.
// Currently maps to the Authorization Policy ID.
type AlkemioContextID uuid.UUID
// AlkemioActorID is a UUID v4 or v7 identifying an actor (user or bot) in Alkemio.
type AlkemioActorID uuid.UUID
// MessageID is an opaque string identifier for a message (mapped to Matrix Event ID).
type MessageID string
// ReactionID is an opaque string identifier for a reaction.
type ReactionID stringAll responses from the Adapter MUST follow this structure.
type ErrorCode string
const (
ErrCodeInvalidParam ErrorCode = "INVALID_PARAM"
ErrCodeRoomNotFound ErrorCode = "ROOM_NOT_FOUND"
ErrCodeSpaceNotFound ErrorCode = "SPACE_NOT_FOUND"
ErrCodeActorNotFound ErrorCode = "ACTOR_NOT_FOUND"
ErrCodeMessageNotFound ErrorCode = "MESSAGE_NOT_FOUND"
ErrCodeReactionNotFound ErrorCode = "REACTION_NOT_FOUND"
ErrCodeMatrixError ErrorCode = "MATRIX_ERROR"
ErrCodeInternalError ErrorCode = "INTERNAL_ERROR"
ErrCodeNotAllowed ErrorCode = "NOT_ALLOWED"
)
// ErrorResponse contains structured error information.
type ErrorResponse struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"` // Optional technical details
}// BaseResponse is the standard response structure for all command responses.
// Operations that return only status use this directly.
// Operations with additional data embed this struct.
type BaseResponse struct {
Success bool `json:"success"`
Error *ErrorResponse `json:"error,omitempty"`
}Creates a new communication channel. The Server provides the ID.
- Event Subject:
communication.room.create
type RoomType string
const (
RoomTypeCommunity RoomType = "community"
RoomTypeDirect RoomType = "direct"
)
type CreateRoomRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
Type RoomType `json:"type"`
Name string `json:"name,omitempty"` // Ignored for 'direct'
InitialMembers []AlkemioActorID `json:"initial_members,omitempty"`
Topic string `json:"topic,omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
ParentContextID *AlkemioContextID `json:"parent_context_id,omitempty"` // Optional parent Space
JoinRule JoinRule `json:"join_rule,omitempty"` // Defaults to 'invite'
}
type JoinRule string
const (
JoinRulePublic JoinRule = "public"
JoinRuleInvite JoinRule = "invite"
JoinRuleRestricted JoinRule = "restricted" // Requires ParentContextID
)Operations that only confirm success/failure return BaseResponse directly:
// Returns BaseResponse - no additional data needed on success,
// as Server already knows the ID.Adapter Actions:
- Check if mapping for
AlkemioRoomIDalready exists. If so, return Success (Idempotency). - If
Type == direct:- Ensure exactly 2
InitialMembers. - Check if a Matrix DM exists for these users. If yes, map it.
- If no, create a new Matrix DM.
- Ensure exactly 2
- If
Type == community:- Create a new Matrix Room.
- Invite/Join
InitialMembers.
- Store
AlkemioRoomID <-> MatrixRoomIDmapping.
Sends a text message to a room.
- Event Subject:
communication.message.send
type SendMessageRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
SenderActorID AlkemioActorID `json:"sender_actor_id"`
Content string `json:"content"` // Markdown supported
ParentMessageID *MessageID `json:"parent_message_id,omitempty"` // For threads/replies
}type SendMessageResponse struct {
BaseResponse
MessageID MessageID `json:"message_id"`
Timestamp time.Time `json:"timestamp"` // UTC time recorded by Matrix
}Adapter Actions:
- Resolve
SenderActorIDto Matrix User. - Resolve
AlkemioRoomIDto Matrix Room ID. - Send event to Matrix.
- Return the generated Matrix Event ID as
MessageID.
Adds an emoji reaction to a message.
- Event Subject:
communication.reaction.add
type AddReactionRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
MessageID MessageID `json:"message_id"`
SenderActorID AlkemioActorID `json:"sender_actor_id"`
Emoji string `json:"emoji"`
}type AddReactionResponse struct {
BaseResponse
ReactionID ReactionID `json:"reaction_id"`
}Removes a previously added reaction.
- Event Subject:
communication.reaction.remove
type RemoveReactionRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
ReactionID ReactionID `json:"reaction_id"` // ID returned from AddReaction
SenderActorID AlkemioActorID `json:"sender_actor_id"`
}Returns BaseResponse directly.
Redacts/deletes a message.
- Event Subject:
communication.message.delete
type DeleteMessageRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
MessageID MessageID `json:"message_id"`
SenderActorID AlkemioActorID `json:"sender_actor_id"`
Reason string `json:"reason,omitempty"`
}Returns BaseResponse directly.
Adds a single actor to multiple rooms (e.g., joining a Space and all its sub-rooms). This operation supports partial success.
- Event Subject:
communication.room.member.batch.add
type BatchAddMemberRequest struct {
ActorID AlkemioActorID `json:"actor_id"`
AlkemioRoomIDs []AlkemioRoomID `json:"alkemio_room_ids"`
}type BatchAddMemberResponse struct {
BaseResponse
// Results is a map of AlkemioRoomID (string representation) to the operation result.
// Only populated if BaseResponse.Success is true (meaning the batch was processed).
// If BaseResponse.Success is false, the entire batch failed (e.g. Actor not found).
Results map[string]BaseResponse `json:"results,omitempty"`
}Adapter Actions:
- Validate Actor exists. If not, return top-level Error.
- Iterate through
AlkemioRoomIDs. - For each room:
- Resolve Matrix Room ID.
- Attempt invite/join.
- Record success/failure in
Results.
- Return
Success: true(even if some rooms failed) with the detailedResultsmap.
Retrieves current state of a room.
- Event Subject:
communication.room.get
type GetRoomRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
}type MessageDto struct {
ID MessageID `json:"id"`
Content string `json:"content"`
SenderActorID AlkemioActorID `json:"sender_actor_id"`
Timestamp time.Time `json:"timestamp"`
Reactions []ReactionDto `json:"reactions"`
ThreadID *MessageID `json:"thread_id,omitempty"`
}
type ReactionDto struct {
ID ReactionID `json:"id"`
Emoji string `json:"emoji"`
SenderActorID AlkemioActorID `json:"sender_actor_id"`
Timestamp time.Time `json:"timestamp"`
}
type GetRoomResponse struct {
BaseResponse
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
DisplayName string `json:"display_name"`
AvatarURL string `json:"avatar_url,omitempty"`
MemberActorIDs []AlkemioActorID `json:"member_actor_ids"`
Messages []MessageDto `json:"messages"`
}Adapter Actions:
- Resolve Matrix Room ID.
- Fetch state (members, recent messages).
- Map Matrix User IDs back to
AlkemioActorID(using internal mapping or lookup). - Return sanitized DTOs.
Updates room metadata (Name, Topic, Visibility).
- Event Subject:
communication.room.update
type UpdateRoomRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
Name *string `json:"name,omitempty"`
Topic *string `json:"topic,omitempty"`
AvatarURL *string `json:"avatar_url,omitempty"`
IsPublic *bool `json:"is_public,omitempty"` // Maps to WorldReadable/GuestAccess
JoinRule *JoinRule `json:"join_rule,omitempty"`
}Returns BaseResponse directly.
Archives or deletes a room.
- Event Subject:
communication.room.delete
type DeleteRoomRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
Reason string `json:"reason,omitempty"`
}Returns BaseResponse directly.
Removes a single actor from multiple rooms.
- Event Subject:
communication.room.member.batch.remove
type BatchRemoveMemberRequest struct {
ActorID AlkemioActorID `json:"actor_id"`
AlkemioRoomIDs []AlkemioRoomID `json:"alkemio_room_ids"`
Reason string `json:"reason,omitempty"`
}type BatchRemoveMemberResponse struct {
BaseResponse
Results map[string]BaseResponse `json:"results,omitempty"`
}Ensures an actor exists in Matrix and updates their profile (Display Name, Avatar).
Replaces tryRegisterNewUser.
- Event Subject:
communication.actor.sync
type SyncActorRequest struct {
ActorID AlkemioActorID `json:"actor_id"`
DisplayName string `json:"display_name"`
AvatarURL string `json:"avatar_url,omitempty"`
}Returns BaseResponse directly.
Mints a Matrix access token for one actor by logging in as them with the Adapter's application-service token (MSC2778). The ghost is registered first, so an actor who has never been synced is handled. A fresh device is created on every call.
- Event Subject:
communication.actor.session.create
type CreateActorSessionRequest struct {
ActorID AlkemioActorID `json:"actor_id"`
}The actor is the authenticated caller on the Server side; no other subject can be named.
type CreateActorSessionResponse struct {
BaseResponse
UserID string `json:"user_id"`
DeviceID string `json:"device_id"`
AccessToken string `json:"access_token"`
}Security: access_token is a live Matrix credential. It must never be logged, cached in a
shared store, or returned to anyone but the actor it was minted for.
Retrieves a list of all rooms known to the Adapter. Used for auditing and finding orphaned rooms.
- Event Subject:
communication.room.list
type ListRoomsRequest struct {
Cursor string `json:"cursor,omitempty"`
}type ListRoomsResponse struct {
BaseResponse
AlkemioRoomIDs []AlkemioRoomID `json:"alkemio_room_ids"`
NextCursor string `json:"next_cursor,omitempty"`
}Retrieves details of a specific message, including its sender and reactions.
- Event Subject:
communication.message.get
type GetMessageRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
MessageID MessageID `json:"message_id"`
}type GetMessageResponse struct {
BaseResponse
Message MessageDto `json:"message"`
}Retrieves details of a specific reaction, primarily to identify the sender.
- Event Subject:
communication.reaction.get
type GetReactionRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
ReactionID ReactionID `json:"reaction_id"`
}type GetReactionResponse struct {
BaseResponse
Reaction ReactionDto `json:"reaction"`
}
---
### 15. Create Space
Creates a new Matrix Space (Context).
* **Event Subject**: `communication.space.create`
#### Request Payload
```go
type CreateSpaceRequest struct {
AlkemioContextID AlkemioContextID `json:"alkemio_context_id"`
Name string `json:"name"`
Topic string `json:"topic,omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
ParentContextID *AlkemioContextID `json:"parent_context_id,omitempty"`
InitialMembers []AlkemioActorID `json:"initial_members,omitempty"`
JoinRule JoinRule `json:"join_rule,omitempty"`
}Returns BaseResponse directly.
Adapter Actions:
- Check if mapping for
AlkemioContextIDexists. - Create Matrix Room with
type="m.space". - If
ParentContextIDis provided, add the new space as a child of the parent space. - Store
AlkemioContextID <-> MatrixRoomIDmapping.
Updates space metadata.
- Event Subject:
communication.space.update
type UpdateSpaceRequest struct {
AlkemioContextID AlkemioContextID `json:"alkemio_context_id"`
Name *string `json:"name,omitempty"`
Topic *string `json:"topic,omitempty"`
AvatarURL *string `json:"avatar_url,omitempty"`
JoinRule *JoinRule `json:"join_rule,omitempty"`
}Returns BaseResponse directly.
Deletes/Archives a space.
- Event Subject:
communication.space.delete
type DeleteSpaceRequest struct {
AlkemioContextID AlkemioContextID `json:"alkemio_context_id"`
Reason string `json:"reason,omitempty"`
}Returns BaseResponse directly.
Sets or changes the parent of a Room or Space. This manages the m.space.child state events.
- Event Subject:
communication.hierarchy.set_parent
type SetParentRequest struct {
// ChildID is the AlkemioRoomID (for rooms) or AlkemioContextID (for subspaces) to add as child.
ChildID string `json:"child_id"`
IsSpace bool `json:"is_space"`
ParentContextID AlkemioContextID `json:"parent_context_id"`
Order string `json:"order,omitempty"`
Suggested bool `json:"suggested,omitempty"`
}Returns BaseResponse directly.
Adapter Actions:
- Parse
ChildIDas UUID and resolve to Matrix Room ID (usingIsSpaceto determine alias pattern). - Resolve Parent Space from
ParentContextID. - Send
m.space.childevent in Parent Space pointing to Child withOrderandSuggested. - Send
m.space.parentevent in Child pointing to Parent (canonical).
Adds a single actor to multiple spaces.
- Event Subject:
communication.space.member.batch.add
type BatchAddSpaceMemberRequest struct {
ActorID AlkemioActorID `json:"actor_id"`
AlkemioContextIDs []AlkemioContextID `json:"alkemio_context_ids"`
}type BatchAddSpaceMemberResponse struct {
BaseResponse
Results map[string]BaseResponse `json:"results,omitempty"`
}Removes a single actor from multiple spaces.
- Event Subject:
communication.space.member.batch.remove
type BatchRemoveSpaceMemberRequest struct {
ActorID AlkemioActorID `json:"actor_id"`
AlkemioContextIDs []AlkemioContextID `json:"alkemio_context_ids"`
Reason string `json:"reason,omitempty"`
}type BatchRemoveSpaceMemberResponse struct {
BaseResponse
Results map[string]BaseResponse `json:"results,omitempty"`
}Retrieves details of a space, including its children (hierarchy).
- Event Subject:
communication.space.get
type GetSpaceRequest struct {
AlkemioContextID AlkemioContextID `json:"alkemio_context_id"`
}type SpaceChildDto struct {
// ChildID is the AlkemioRoomID (for rooms) or AlkemioContextID (for subspaces).
ChildID string `json:"child_id"`
IsSpace bool `json:"is_space"`
Order string `json:"order,omitempty"`
Suggested bool `json:"suggested,omitempty"`
}
type GetSpaceResponse struct {
BaseResponse
AlkemioContextID AlkemioContextID `json:"alkemio_context_id"`
DisplayName string `json:"display_name"`
Topic string `json:"topic,omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
JoinRule JoinRule `json:"join_rule"`
MemberActorIDs []AlkemioActorID `json:"member_actor_ids"`
Children []SpaceChildDto `json:"children"`
ParentContextID *AlkemioContextID `json:"parent_context_id,omitempty"`
}Retrieves a list of all spaces known to the Adapter.
- Event Subject:
communication.space.list
type ListSpacesRequest struct {
Cursor string `json:"cursor,omitempty"`
}type ListSpacesResponse struct {
BaseResponse
AlkemioContextIDs []AlkemioContextID `json:"alkemio_context_ids"`
NextCursor string `json:"next_cursor,omitempty"`
}Events emitted by the Adapter to notify Alkemio Server of external actions.
Published when the Adapter receives a message in a room from a user.
- Event Subject:
communication.message.received
// Message represents a Matrix message event (used in outgoing events).
type Message struct {
ID string `json:"id"`
Message string `json:"message"`
ThreadID *MessageID `json:"threadID,omitempty"`
Sender string `json:"sender"`
Timestamp int64 `json:"timestamp"`
Reactions []Reaction `json:"reactions"`
}
// Reaction represents a reaction to a message (used in outgoing events).
type Reaction struct {
ID string `json:"id"`
Emoji string `json:"emoji"`
Sender string `json:"sender"`
Timestamp int64 `json:"timestamp"`
MessageID string `json:"messageId"`
}
type MessageReceivedPayload struct {
RoomID string `json:"roomId"`
RoomName string `json:"roomName"`
Message Message `json:"message"`
ActorID string `json:"actorID"`
CommunityID *string `json:"communityId,omitempty"`
}Published when a Synapse spam checker module requests approval for a DM room creation. This event is triggered via the Adapter's webhook endpoint.
- Event Subject:
communication.room.dm.requested
type DMRequestedEvent struct {
InitiatorActorID string `json:"initiator_actor_id"` // Alkemio UUID of initiating user
TargetActorID string `json:"target_actor_id"` // Alkemio UUID of target user
}Source: HTTP webhook from Synapse module at POST /_matrix/app/alkemio/dm-request
Server Response: If approved, Server sends communication.room.create with type: "direct" and both actors in initial_members.
Published when a user adds a reaction to a message.
- Event Subject:
communication.reaction.added
type ReactionAddedEvent struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
MessageID MessageID `json:"message_id"`
ReactionID ReactionID `json:"reaction_id"`
Emoji string `json:"emoji"`
SenderActorID AlkemioActorID `json:"sender_actor_id"`
Timestamp int64 `json:"timestamp"` // Unix milliseconds
}Published when a user removes a reaction from a message.
- Event Subject:
communication.reaction.removed
type ReactionRemovedEvent struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
MessageID MessageID `json:"message_id"`
ReactionID ReactionID `json:"reaction_id"`
Emoji string `json:"emoji"` // May be empty if original reaction unavailable
SenderActorID AlkemioActorID `json:"sender_actor_id"`
Timestamp int64 `json:"timestamp"` // Unix milliseconds
}Deprecated: This event is no longer emitted by the adapter. Use
RoomMemberUpdatedEvent(27e) withmembership=leaveinstead, which covers all membership transitions including leave/kick/ban. The DTO and topic constant remain in the codebase for backward compatibility but no events are published to this topic.
- Event Subject:
communication.room.member.left
type RoomMemberLeftEvent struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
ActorID AlkemioActorID `json:"actor_id"`
Reason string `json:"reason,omitempty"`
Timestamp int64 `json:"timestamp"` // Unix milliseconds
}Published when a user's read receipt is updated (message marked as read).
- Event Subject:
communication.room.receipt.updated
type ReadReceiptUpdatedEvent struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
ActorID AlkemioActorID `json:"actor_id"`
EventID MessageID `json:"event_id"` // Event ID that was marked as read
ThreadID *MessageID `json:"thread_id,omitempty"` // Thread root event ID (if thread-level)
Timestamp int64 `json:"timestamp"` // Unix milliseconds
}Published when a message is edited (via Matrix m.replace relation).
- Event Subject:
communication.message.edited
type MessageEditedEvent struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
SenderActorID AlkemioActorID `json:"sender_actor_id"`
OriginalMessageID MessageID `json:"original_message_id"` // Event ID of original message
NewMessageID MessageID `json:"new_message_id"` // Event ID of the edit event
NewContent string `json:"new_content"`
ThreadID *MessageID `json:"thread_id,omitempty"` // Thread root event ID (if in thread)
Timestamp int64 `json:"timestamp"` // Unix milliseconds
}Published when a message is redacted (deleted).
- Event Subject:
communication.message.redacted
type MessageRedactedEvent struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
RedactorActorID AlkemioActorID `json:"redactor_actor_id"`
RedactedMessageID MessageID `json:"redacted_message_id"` // Event ID of the redacted message
RedactionMessageID MessageID `json:"redaction_message_id"` // Event ID of the redaction event
Reason string `json:"reason,omitempty"`
ThreadID *MessageID `json:"thread_id,omitempty"` // Thread root event ID (if in thread)
Timestamp int64 `json:"timestamp"` // Unix milliseconds
}Published when a room is created in Matrix.
- Event Subject:
communication.room.created
type RoomCreatedEvent struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
CreatorActorID AlkemioActorID `json:"creator_actor_id"`
RoomType string `json:"room_type"` // "room", "space"
Name string `json:"name,omitempty"`
Topic string `json:"topic,omitempty"`
Timestamp int64 `json:"timestamp"` // Unix milliseconds
}Published when a user's membership status changes (join, invite, leave, ban, knock). This is the single source of truth for all membership transitions — RoomMemberLeftEvent (27) is deprecated.
- Event Subject:
communication.room.member.updated
type RoomMemberUpdatedEvent struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
MemberActorID AlkemioActorID `json:"member_actor_id"` // Actor whose membership changed
SenderActorID AlkemioActorID `json:"sender_actor_id"` // Actor who performed the action
Membership string `json:"membership"` // join, leave, invite, ban, knock
Timestamp int64 `json:"timestamp"` // Unix milliseconds
}Published when a room's properties change in Matrix (m.room.name, m.room.avatar, m.room.topic state events). Each state event produces one event with only the changed property populated.
- Event Subject:
communication.room.updated
type RoomUpdatedEvent struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
DisplayName *string `json:"display_name,omitempty"`
AvatarURL *string `json:"avatar_url,omitempty"`
Topic *string `json:"topic,omitempty"`
Timestamp int64 `json:"timestamp"` // Unix milliseconds
}Pointer semantics:
nil(omitted in JSON) — property was not changed by this state event.- Non-nil empty string (
"") — property was explicitly cleared (e.g., avatar removed). - Non-nil non-empty string — property was set to that value.
Note: This event fires for all state changes, including those triggered by the adapter itself (e.g., via communication.room.update). Room state events intentionally bypass the bot-sender filter so the server receives confirmation of changes it requested.
Retrieves the list of joined members in a room.
- Event Subject:
communication.room.members.get
type GetRoomMembersRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
}type GetRoomMembersResponse struct {
BaseResponse
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
MemberActorIDs []AlkemioActorID `json:"member_actor_ids"`
}Retrieves all messages in a thread, including the thread root message.
- Event Subject:
communication.thread.messages.get
type GetThreadMessagesRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
ThreadID MessageID `json:"thread_id"`
}type GetThreadMessagesResponse struct {
BaseResponse
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
ThreadID MessageID `json:"thread_id"`
Messages []MessageDto `json:"messages"` // Root message first, then replies
}Marks a message as read for a user. Supports both room-level and thread-level read receipts.
- Event Subject:
communication.message.read
type MarkMessageReadRequest struct {
ActorID AlkemioActorID `json:"actor_id"`
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
MessageID MessageID `json:"message_id"` // Event ID to mark as read
ThreadID *MessageID `json:"thread_id,omitempty"` // Optional: for thread-specific receipts
}Returns BaseResponse directly.
Adapter Actions:
- Resolve
ActorIDto Matrix User. - Resolve
AlkemioRoomIDto Matrix Room ID. - Send
m.readreceipt (room-level) orm.read.threadreceipt (ifThreadIDprovided). - Matrix homeserver handles multiple client synchronization.
Retrieves unread message counts for a user in a room, optionally including thread-level counts.
- Event Subject:
communication.room.unread_counts.get
type GetUnreadCountsRequest struct {
ActorID AlkemioActorID `json:"actor_id"`
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
ThreadIDs []MessageID `json:"thread_ids,omitempty"` // Optional: specific threads to query
}type GetUnreadCountsResponse struct {
BaseResponse
RoomUnreadCount int `json:"room_unread_count"`
ThreadUnreadCounts map[string]int `json:"thread_unread_counts,omitempty"` // Map[ThreadID]Count
}Adapter Actions:
- Resolve
ActorIDto Matrix User. - Resolve
AlkemioRoomIDto Matrix Room ID. - Query room state for unread counts.
- If
ThreadIDsprovided, also query thread-level unread counts.
Retrieves room details from a specific user's perspective, including read state information for each message. This enables efficient UI rendering with read/unread indicators without additional API calls.
- Event Subject:
communication.room.get.as_user
type GetRoomAsUserRequest struct {
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
ActorID AlkemioActorID `json:"actor_id"`
}// MessageWithReadStateDto extends MessageDto with read receipt info.
type MessageWithReadStateDto struct {
MessageDto
IsRead bool `json:"is_read"` // Has the requesting user read this message?
}
type GetRoomAsUserResponse struct {
BaseResponse
AlkemioRoomID AlkemioRoomID `json:"alkemio_room_id"`
DisplayName string `json:"display_name"`
AvatarURL string `json:"avatar_url,omitempty"`
MemberActorIDs []AlkemioActorID `json:"member_actor_ids"`
Messages []MessageWithReadStateDto `json:"messages"`
LastReadEventID *MessageID `json:"last_read_event_id,omitempty"`
UnreadCount int `json:"unread_count"`
}Adapter Actions:
- Resolve
AlkemioRoomIDto Matrix Room ID. - Fetch room details (name, members, messages).
- Resolve
ActorIDto Matrix User ID. - Query unread count for the user via Matrix
/syncAPI. - Mark messages as read/unread based on the unread count (last N messages are unread).
- Return enriched response with
is_readflag per message.
Note: Read state uses the "high water mark" model - when a user reads message N, all messages before N are also considered read.
- Method:
GET - Path:
/health - Response:
{"status": "ok"}
Receives DM creation requests from the Synapse spam checker module.
- Method:
POST - Path:
/_matrix/app/alkemio/dm-request - Authentication: Bearer token (HS token from AppService registration)
Authorization: Bearer <hs_token>
Content-Type: application/json
type DMWebhookPayload struct {
Inviter string `json:"inviter"` // Matrix user ID: @uuid:domain
Invitee string `json:"invitee"` // Matrix user ID: @uuid:domain
}Success (200 OK):
{"status": "accepted"}Errors:
401 Unauthorized: Invalid or missing Bearer token400 Bad Request: Missing required fields or invalid user ID format500 Internal Server Error: Failed to publish event to RabbitMQ
Adapter Actions:
- Validate Bearer token matches configured HS token
- Extract Alkemio Actor UUIDs from Matrix user IDs
- Publish
DMRequestedEventtocommunication.room.dm.requestedtopic - Return 200 OK (fire-and-forget)
The Adapter must distinguish between:
-
Transient Errors (Network issues, Matrix homeserver down):
- Adapter should retry internally if possible.
- If retries fail, return
ErrCodeMatrixErrororErrCodeInternalError. - Server may retry the RMQ message based on policy.
-
Permanent Errors (Invalid ID, Malformed Data):
- Return
ErrCodeInvalidParamorErrCodeRoomNotFound. - Server should not retry.
- Return
-
Business Logic Errors (User not allowed):
- Return
ErrCodeNotAllowed. - Server handles as a domain exception.
- Return
The Go service should generate/export a TypeScript definition file matching these structures for the Node.js server to consume.
// Example generated TS
export interface BaseResponse {
success: boolean;
error?: ErrorResponse;
}
export interface ErrorResponse {
code: ErrorCode;
message: string;
details?: string;
}
// ... etc