Relay · Channels
Channels, membership, and permissions
Relay provides a small transport permission model. It isolates tenants, requires membership for conversation writes, and leaves business authorization to the host.
The current read policy is broader than the write policy. Treat the matrix below as the public behavior, not as a private-channel RBAC system.
Create a channel
Any authenticated tenant user can create a channel:
POST /api/channels
Authorization: Bearer <jwt>
Content-Type: application/json
{
"metadata": {
"name": "support"
}
} Relay returns 201 Created and automatically creates a membership for the user who created the channel.
Channel metadata is host-defined JSON with a maximum encoded size of 4096 bytes. Relay stores it but does not assign business meaning to its keys.
Current permission matrix
| Operation | Current requirement |
|---|---|
| List channels in the tenant | Any authenticated user in that tenant |
| Get a channel | Any authenticated user in that tenant |
| List channel members | Any authenticated user in that tenant |
| Join the WebSocket topic | Channel member |
| Send a message | Channel member and open channel |
| List messages | Non-members currently receive an empty list, not 403 |
| Add a member | Existing channel member |
| Remove a membership | The user may remove only their own membership |
| Close a channel | Channel member |
| Change channel metadata | Channel agent member or tenant admin |
| Mute notification events | The current member only |
The three tenant-wide read operations are not filtered by channel membership. Do not treat channel existence, metadata, or member lists as confidential between users who hold tokens for the same tenant.
The empty-list behavior for non-member message reads comes from the current policy filter. It is not a strong authorization signal and may be tightened in a future contract.
Add a member
An existing member can add another host user by user_id:
POST /api/channels/{channel_id}/members
Authorization: Bearer <jwt>
Content-Type: application/json
{
"user_id": "user-uuid"
} A new membership returns 201 Created. Adding the same user twice returns a 422 validation response because (channel_id, user_id) is unique.
The endpoint does not evaluate host roles, teams, subscriptions, or case ownership. The host must decide whether a user should be added before making this request.
Leave a channel
The delete endpoint accepts a membership ID, not a user ID:
DELETE /api/channels/{channel_id}/members/{member_id}
Authorization: Bearer <jwt> A user can delete only their own membership. Success returns 204 No Content. Attempting to remove another user’s membership returns 403; an unknown membership returns 404.
There is no member or moderator endpoint for removing another member. Tenant-wide user offboarding exists on the admin surface, but it is not channel moderation.
Close a channel
Any member can close an open channel:
POST /api/channels/{channel_id}/close
Authorization: Bearer <jwt> Close is idempotent. Repeating the request returns the already closed channel without changing the original closed_at value.
A closed channel keeps its history but rejects new messages with a 422 validation response. The current public API has no reopen or channel delete endpoint.
Change metadata
Ordinary user members cannot change channel metadata. A channel member whose kind is agent, or a tenant admin, can use either operation:
PATCH /api/channels/{channel_id}/metadata
PUT /api/channels/{channel_id}/metadata PATCH performs a shallow merge of top-level keys. Nested maps are replaced, not deep-merged. PUT replaces the complete metadata map. Invalid or oversized metadata returns 422.
Admin requests must identify the tenant explicitly. Agent requests derive the tenant from the signed token and still require channel membership.
Mute notification events
The current member can mute or unmute Relay-generated notification webhook events:
POST /api/channels/{channel_id}/notification_mute
DELETE /api/channels/{channel_id}/notification_mute Mute changes member.muted_at. It does not block message reads, writes, or WebSocket fan-out. Relay does not decide whether the host sends email, mobile push, or an in-product badge.
Product boundary
Relay enforces tenant isolation and coarse conversation membership. The host owns business roles and the decision to create a channel, add a member, or expose a Relay token.
Private channel discovery, owner or moderator roles, removing other members, and reopening a channel are not present in the current tenant API. Products that need those guarantees require additional Relay contract work; naming a host role in channel metadata does not create authorization.
Next: upload and bind attachments.