Skip to content

REST API Reference

All endpoints are prefixed with the chat-service base URL (e.g. https://api.example.com/chat/).

Every request requires a valid JWT token in the Authorization: Bearer <token> header. The token must contain userId and tenantId claims.

Response Format

Success responses return the resource directly (object or array).

Error responses follow a consistent envelope:

json
{
  "success": false,
  "error": {
    "code": "CHAT_CHANNEL_NOT_FOUND",
    "message": "Channel not found",
    "details": { "channelId": "abc-123" }
  },
  "requestId": "req_xyz",
  "statusCode": 404,
  "timestamp": "2026-04-03T12:00:00.000Z",
  "path": "/channels/abc-123"
}

See Error Codes Reference for all possible error codes.

Guards Reference

GuardDescription
ChatAuthGuardValidates JWT, extracts userId and tenantId. Applied to all endpoints.
ChannelMemberGuardVerifies the caller is a member of the target channel.
ChannelOperatorGuardVerifies the caller has the OPERATOR role in the channel.
ChannelNotFrozenGuardRejects the request if the channel is frozen.
UserNotMutedGuardRejects the request if the caller is muted in the channel.
UserNotBannedGuardRejects the request if the caller is banned from the channel.

Quick Reference Table

Channels (36 endpoints)

#MethodPathDescriptionGuards
1GET/channelsList user's channelsAuth
2GET/channels/unread-countGet total unread countAuth
3GET/channels/:idGet channel detailsAuth, Member
4POST/channels/directCreate direct channelAuth
5POST/channels/groupCreate group channelAuth
6PATCH/channels/:idUpdate channelAuth, Member, Operator
7DELETE/channels/:idDelete channelAuth, Member, Operator
8POST/channels/:id/leaveLeave channelAuth, Member
9GET/channels/:id/membersList membersAuth, Member
10POST/channels/:id/members/inviteInvite membersAuth, Member, Operator
11DELETE/channels/:id/members/:userIdRemove a memberAuth, Member, Operator
12GET/channels/:id/operatorsList operatorsAuth, Member
13POST/channels/:id/operatorsAdd operatorsAuth, Member, Operator
14DELETE/channels/:id/operatorsRemove operatorsAuth, Member, Operator
15POST/channels/:id/readMark channel as readAuth, Member
16PUT/channels/:id/push-triggerSet push notification preferenceAuth, Member
17GET/channels/:id/push-triggerGet push notification preferenceAuth, Member
18PUT/channels/:id/count-preferenceSet unread count preferenceAuth, Member
19GET/channels/:id/count-preferenceGet unread count preferenceAuth, Member
20POST/channels/:id/freezeFreeze channelAuth, Member, Operator
21POST/channels/:id/unfreezeUnfreeze channelAuth, Member, Operator
22POST/channels/:id/muteMute channel for selfAuth, Member
23POST/channels/:id/unmuteUnmute channel for selfAuth, Member
24POST/channels/:id/members/:userId/muteMute a userAuth, Member, Operator
25POST/channels/:id/members/:userId/unmuteUnmute a userAuth, Member, Operator
26GET/channels/:id/muted-usersList muted usersAuth, Member, Operator
27POST/channels/:id/members/:userId/banBan a userAuth, Member, Operator
28POST/channels/:id/members/:userId/unbanUnban a userAuth, Member, Operator
29GET/channels/:id/banned-usersList banned usersAuth, Member, Operator
30POST/channels/:id/hideHide channelAuth, Member
31POST/channels/:id/unhideUnhide channelAuth, Member
32POST/channels/:id/reset-historyReset message historyAuth, Member
33GET/channels/:id/metadataGet channel metadataAuth, Member
34PUT/channels/:id/metadataSet channel metadataAuth, Member
35DELETE/channels/:id/metadata/:keyDelete metadata keyAuth, Member
36POST/channels/:id/messages/:messageId/pinPin a messageAuth, Member
37DELETE/channels/:id/messages/:messageId/pinUnpin a messageAuth, Member
38GET/channels/:id/pinned-messagesList pinned messagesAuth, Member
39GET/channels/:id/shared-filesList shared filesAuth, Member
40POST/channels/:id/reportReport channelAuth, Member

Messages (13 endpoints)

#MethodPathDescriptionGuards
41GET/channels/:id/messagesList messagesAuth, Member
42GET/channels/:id/messages/:messageIdGet single messageAuth, Member
43POST/channels/:id/messagesSend a text messageAuth, Member, NotFrozen, NotMuted, NotBanned
44PATCH/channels/:id/messages/:messageIdEdit a messageAuth, Member
45DELETE/channels/:id/messages/:messageIdDelete a messageAuth, Member
46GET/channels/:id/messages/:messageId/threadGet thread repliesAuth, Member
47POST/channels/:id/messages/:messageId/forwardForward a messageAuth, Member
48POST/messages/searchSearch messagesAuth
49POST/channels/:id/messages/:messageId/reactionsAdd a reactionAuth, Member
50DELETE/channels/:id/messages/:messageId/reactions/:keyRemove a reactionAuth, Member

Polls (3 endpoints)

#MethodPathDescriptionGuards
51POST/channels/:channelId/pollsCreate a pollAuth, Member
52POST/channels/:channelId/polls/:pollId/voteVote on a pollAuth, Member
53GET/channels/:channelId/polls/:pollIdGet poll detailsAuth, Member

Users (5 endpoints)

#MethodPathDescriptionGuards
54GET/users/searchSearch usersAuth
55GET/users/blockedList blocked usersAuth
56GET/users/:userIdGet user profileAuth
57POST/users/blockBlock a userAuth
58POST/users/unblockUnblock a userAuth

Scheduled Messages (5 endpoints)

#MethodPathDescriptionGuards
59GET/channels/:channelId/scheduled-messagesList scheduled messagesAuth, Member
60POST/channels/:channelId/scheduled-messagesCreate scheduled messageAuth, Member
61PATCH/channels/:channelId/scheduled-messages/:scheduledIdUpdate scheduled messageAuth, Member
62DELETE/channels/:channelId/scheduled-messages/:scheduledIdCancel scheduled messageAuth, Member
63POST/channels/:channelId/scheduled-messages/:scheduledId/send-nowSend scheduled message immediatelyAuth, Member

Detailed Endpoint Documentation

Channels

GET /channels

List all channels the authenticated user is a member of.

Query Parameters:

ParameterTypeDefaultDescription
limitinteger20Results per page (1-100)
includeEmptybooleanfalseInclude channels with no messages
orderstringlatest_last_messageSort order: latest_last_message or chronological
searchstring-Filter channels by name

Response:

json
{
  "channels": [
    {
      "id": "ch_abc123",
      "tenantId": "tenant_1",
      "type": "GROUP",
      "name": "Team Chat",
      "coverUrl": "https://...",
      "customType": null,
      "isFrozen": false,
      "metadata": {},
      "lastMessageAt": "2026-04-03T10:30:00.000Z",
      "memberCount": 5,
      "createdById": "user_1",
      "createdAt": "2026-01-15T08:00:00.000Z",
      "updatedAt": "2026-04-03T10:30:00.000Z",
      "unreadCount": 3,
      "lastMessage": {
        "id": "msg_xyz",
        "text": "Hello team!",
        "senderId": "user_2",
        "createdAt": "2026-04-03T10:30:00.000Z"
      }
    }
  ]
}

GET /channels/unread-count

Get the total unread message count across all channels.

Response:

json
{
  "totalUnreadCount": 12
}

GET /channels/:id

Get full details of a single channel.

Path Parameters:

ParameterTypeDescription
idstring (CUID)Channel ID

Response:

json
{
  "id": "ch_abc123",
  "tenantId": "tenant_1",
  "type": "GROUP",
  "name": "Team Chat",
  "coverUrl": null,
  "customType": null,
  "isFrozen": false,
  "metadata": {},
  "lastMessageAt": "2026-04-03T10:30:00.000Z",
  "memberCount": 5,
  "createdById": "user_1",
  "createdAt": "2026-01-15T08:00:00.000Z",
  "updatedAt": "2026-04-03T10:30:00.000Z",
  "members": [
    {
      "userId": "user_1",
      "role": "OPERATOR",
      "joinedAt": "2026-01-15T08:00:00.000Z"
    }
  ]
}

POST /channels/direct

Create a direct (1-to-1) channel with another user. If a direct channel already exists between both users, it is returned instead.

Request Body:

json
{
  "userId": "user_target_id"
}
FieldTypeRequiredDescription
userIdstringYesThe other user's ID

Response: Channel object (same as GET /channels/:id).


POST /channels/group

Create a group channel with multiple members.

Request Body:

json
{
  "userIds": ["user_2", "user_3", "user_4"],
  "name": "Project Alpha",
  "coverUrl": "https://example.com/cover.jpg"
}
FieldTypeRequiredDescription
userIdsstring[]YesUser IDs to invite (min 1)
namestringNoChannel display name
coverUrlstringNoChannel cover image URL

Response: Channel object.


PATCH /channels/:id

Update a group channel's properties. Requires operator role.

Request Body:

json
{
  "name": "New Channel Name",
  "coverUrl": "https://example.com/new-cover.jpg",
  "customType": "project"
}
FieldTypeRequiredDescription
namestringNoNew channel name
coverUrlstringNoNew cover image URL
customTypestringNoCustom type tag

Response: Updated channel object.


DELETE /channels/:id

Permanently delete a channel and all its messages. Requires operator role.

Response: 204 No Content


POST /channels/:id/leave

Leave a channel. If the last member leaves, the channel is soft-deleted.

Response:

json
{
  "success": true
}

GET /channels/:id/members

List all active members of a channel.

Response:

json
{
  "members": [
    {
      "id": "cm_abc",
      "userId": "user_1",
      "role": "OPERATOR",
      "isMuted": false,
      "isBanned": false,
      "joinedAt": "2026-01-15T08:00:00.000Z"
    }
  ]
}

POST /channels/:id/members/invite

Invite users to a group channel. Requires operator role.

Request Body:

json
{
  "userIds": ["user_5", "user_6"]
}
FieldTypeRequiredDescription
userIdsstring[]YesUser IDs to invite (min 1)

Errors: CHAT_CHANNEL_MEMBER_LIMIT (413) if adding would exceed 100 members.


DELETE /channels/:id/members/:userId

Remove a user from the channel. Requires operator role.

Response:

json
{
  "success": true
}

GET /channels/:id/operators

List all operators in a channel.

Response:

json
{
  "operators": [
    { "userId": "user_1", "role": "OPERATOR" }
  ]
}

POST /channels/:id/operators

Promote members to operator role. Requires operator role.

Request Body:

json
{
  "userIds": ["user_2", "user_3"]
}

DELETE /channels/:id/operators

Demote operators back to member role. Requires operator role.

Request Body:

json
{
  "userIds": ["user_2"]
}

POST /channels/:id/read

Mark the channel as read for the authenticated user. Updates lastReadAt and lastReadMessageId.

Response:

json
{
  "success": true
}

Emits chat:read:receipt:updated via WebSocket.


PUT /channels/:id/push-trigger

Set the push notification preference for this channel.

Request Body:

json
{
  "option": "mention_only"
}
FieldTypeRequiredValues
optionstringYesall, mention_only, off

GET /channels/:id/push-trigger

Get the current push notification preference.

Response:

json
{
  "pushTrigger": "ALL"
}

PUT /channels/:id/count-preference

Set the unread count preference.

Request Body:

json
{
  "preference": "unread_message_count_only"
}
FieldTypeRequiredValues
preferencestringYesall, unread_message_count_only, off

GET /channels/:id/count-preference

Get the current unread count preference.

Response:

json
{
  "countPreference": "ALL"
}

POST /channels/:id/freeze

Freeze a channel, preventing new messages. Requires operator role.

Emits chat:channel:frozen via WebSocket.


POST /channels/:id/unfreeze

Unfreeze a channel. Requires operator role.

Emits chat:channel:unfrozen via WebSocket.


POST /channels/:id/mute

Mute a channel for the current user (suppresses notifications).

Emits chat:channel:muted via WebSocket.


POST /channels/:id/unmute

Unmute a channel for the current user.

Emits chat:channel:unmuted via WebSocket.


POST /channels/:id/members/:userId/mute

Mute a specific user in the channel. Requires operator role.

Request Body:

json
{
  "seconds": 3600
}
FieldTypeRequiredDescription
secondsintegerNoDuration in seconds (0 = indefinite)

Emits chat:user:muted via WebSocket.


POST /channels/:id/members/:userId/unmute

Unmute a user in the channel. Requires operator role.

Emits chat:user:unmuted via WebSocket.


GET /channels/:id/muted-users

List all muted users in the channel. Requires operator role.


POST /channels/:id/members/:userId/ban

Ban a user from the channel. Requires operator role.

Request Body:

json
{
  "description": "Repeated spam",
  "seconds": 86400
}
FieldTypeRequiredDescription
descriptionstringNoReason for banning
secondsintegerNoDuration in seconds (0 = permanent)

Emits chat:user:banned via WebSocket.


POST /channels/:id/members/:userId/unban

Unban a user from the channel. Requires operator role.

Emits chat:user:unbanned via WebSocket.


GET /channels/:id/banned-users

List all banned users in the channel. Requires operator role.


POST /channels/:id/hide

Hide a channel from the user's channel list.

Request Body:

json
{
  "hidePreviousMessages": false
}
FieldTypeRequiredDefaultDescription
hidePreviousMessagesbooleanNofalseAlso hide messages sent before hiding

Emits chat:channel:hidden via WebSocket.


POST /channels/:id/unhide

Unhide a previously hidden channel.


POST /channels/:id/reset-history

Reset the message history for the current user. Messages before this point become invisible to the user.


GET /channels/:id/metadata

Get the channel's custom metadata key-value store.

Response:

json
{
  "metadata": {
    "department": "engineering",
    "priority": "high"
  }
}

PUT /channels/:id/metadata

Replace the entire channel metadata.

Request Body:

json
{
  "metadata": {
    "department": "marketing",
    "region": "EU"
  }
}

Emits chat:metadata:changed via WebSocket.


DELETE /channels/:id/metadata/:key

Delete a single key from channel metadata.

Path Parameters:

ParameterTypeDescription
keystringMetadata key to delete

POST /channels/:id/messages/:messageId/pin

Pin a message in the channel. Maximum 5 pinned messages per channel.

Emits chat:pinned:message:updated via WebSocket.

Errors: CHAT_CHANNEL_PIN_LIMIT (413) if limit reached.


DELETE /channels/:id/messages/:messageId/pin

Unpin a message.

Emits chat:pinned:message:updated via WebSocket.


GET /channels/:id/pinned-messages

List all pinned message IDs in the channel.

Response:

json
{
  "pinnedMessages": [
    {
      "messageId": "msg_abc",
      "pinnedById": "user_1",
      "createdAt": "2026-04-03T09:00:00.000Z"
    }
  ]
}

GET /channels/:id/shared-files

List all file messages shared in the channel.

Query Parameters:

ParameterTypeDefaultDescription
limitnumber-Max number of files to return

Response:

json
{
  "files": [
    {
      "id": "msg_file1",
      "fileUrl": "https://...",
      "fileName": "report.pdf",
      "fileSize": 1048576,
      "mimeType": "application/pdf",
      "senderId": "user_1",
      "createdAt": "2026-04-01T14:00:00.000Z"
    }
  ]
}

POST /channels/:id/report

Report a channel for moderation.

Request Body:

json
{
  "category": "spam",
  "description": "This channel is sending unsolicited promotional content."
}
FieldTypeRequiredValues
categorystringYesspam, harassment, inappropriate, other
descriptionstringNoAdditional details

Messages

GET /channels/:id/messages

List messages in a channel with cursor-based pagination.

Query Parameters:

ParameterTypeDefaultDescription
limitinteger30Results per page (1-100)
beforeISO 8601 date-Fetch messages before this timestamp
afterISO 8601 date-Fetch messages after this timestamp
includeReactionsbooleantrueInclude reaction data
includeThreadInfobooleantrueInclude thread reply counts

Response:

json
{
  "messages": [
    {
      "id": "msg_abc",
      "channelId": "ch_123",
      "senderId": "user_1",
      "type": "TEXT",
      "text": "Hello everyone!",
      "mentionedUserIds": [],
      "isEdited": false,
      "isForwarded": false,
      "parentMessageId": null,
      "reactions": [
        { "key": "thumbsup", "userIds": ["user_2"] }
      ],
      "createdAt": "2026-04-03T10:30:00.000Z",
      "updatedAt": "2026-04-03T10:30:00.000Z"
    }
  ]
}

GET /channels/:id/messages/:messageId

Get a single message by ID.


POST /channels/:id/messages

Send a text message to a channel. The channel must not be frozen, and the user must not be muted or banned.

Request Body:

json
{
  "text": "Hello team!",
  "mentionedUserIds": ["user_2", "user_3"],
  "parentMessageId": "msg_parent_id",
  "metadata": { "customKey": "value" },
  "linkMetadata": {
    "url": "https://example.com",
    "title": "Example",
    "description": "An example link",
    "imageUrl": "https://example.com/og.png",
    "siteName": "Example Site"
  }
}
FieldTypeRequiredDescription
textstringYesMessage text (max 5000 chars)
mentionedUserIdsstring[]NoUser IDs mentioned in the message
parentMessageIdstringNoParent message ID for threading
metadataobjectNoCustom metadata
linkMetadataobjectNoLink preview data

Emits chat:message:received and optionally chat:mention:received via WebSocket.

Errors: CHAT_MESSAGE_TOO_LONG (400), CHAT_CHANNEL_FROZEN (403), CHAT_USER_MUTED (403), CHAT_USER_BANNED (403).


PATCH /channels/:id/messages/:messageId

Edit a message. Only the message sender can edit their own messages.

Request Body:

json
{
  "text": "Updated message text"
}
FieldTypeRequiredDescription
textstringYesNew message text (max 5000 chars)

Emits chat:message:updated via WebSocket.

Errors: CHAT_MESSAGE_NOT_OWNER (403).


DELETE /channels/:id/messages/:messageId

Soft-delete a message. Only the message sender can delete their own messages (operators may also delete).

Emits chat:message:deleted via WebSocket.


GET /channels/:id/messages/:messageId/thread

Get all threaded replies to a parent message.

Response:

json
{
  "messages": [
    {
      "id": "msg_reply1",
      "parentMessageId": "msg_parent",
      "text": "This is a reply",
      "senderId": "user_2",
      "createdAt": "2026-04-03T10:35:00.000Z"
    }
  ]
}

POST /channels/:id/messages/:messageId/forward

Forward a message to another channel.

Request Body:

json
{
  "targetChannelId": "ch_target_456"
}
FieldTypeRequiredDescription
targetChannelIdstringYesDestination channel ID

The forwarded message will have isForwarded: true and forwardedFromId set to the original message ID.


POST /messages/search

Search messages across all channels the user has access to.

Request Body:

json
{
  "keyword": "quarterly report",
  "channelId": "ch_optional_filter",
  "limit": 20,
  "order": "timestamp",
  "exactMatch": false,
  "timestampFrom": "2026-01-01T00:00:00.000Z",
  "timestampTo": "2026-04-03T23:59:59.000Z"
}
FieldTypeRequiredDefaultDescription
keywordstringYes-Search term
channelIdstringNo-Restrict to a specific channel
limitintegerNo20Results per page (1-100)
orderstringNotimestampSort: score or timestamp
exactMatchbooleanNofalseExact phrase matching
timestampFromISO 8601 dateNo-Start of time range
timestampToISO 8601 dateNo-End of time range

POST /channels/:id/messages/:messageId/reactions

Add an emoji reaction to a message.

Request Body:

json
{
  "key": "thumbsup"
}
FieldTypeRequiredDescription
keystringYesReaction emoji key (e.g. thumbsup, heart)

Emits chat:reaction:updated via WebSocket.


DELETE /channels/:id/messages/:messageId/reactions/:key

Remove your reaction from a message.

Path Parameters:

ParameterTypeDescription
keystringReaction key to remove

Emits chat:reaction:updated via WebSocket.


Polls

POST /channels/:channelId/polls

Create a poll in a channel. This also sends a message of type POLL to the channel.

Request Body:

json
{
  "title": "Where should we have the team lunch?",
  "options": ["Italian", "Japanese", "Mexican"],
  "allowMultipleVotes": false,
  "allowUserSuggestion": true,
  "closeAt": "2026-04-04T18:00:00.000Z"
}
FieldTypeRequiredDefaultDescription
titlestringYes-Poll question
optionsstring[]Yes-Answer options (2-10)
allowMultipleVotesbooleanNofalseAllow voting on multiple options
allowUserSuggestionbooleanNofalseAllow users to add options
closeAtISO 8601 dateNo-Auto-close timestamp

Errors: CHAT_POLL_OPTION_LIMIT (400) if more than 10 options.


POST /channels/:channelId/polls/:pollId/vote

Vote on one or more poll options.

Request Body:

json
{
  "optionIds": ["opt_abc", "opt_def"]
}
FieldTypeRequiredDescription
optionIdsstring[]YesOption IDs to vote for (min 1)

Emits chat:poll:voted via WebSocket.

Errors: CHAT_POLL_CLOSED (410), CHAT_POLL_ALREADY_VOTED (409).


GET /channels/:channelId/polls/:pollId

Get poll details including options and vote counts.

Response:

json
{
  "id": "poll_abc",
  "channelId": "ch_123",
  "title": "Where should we have the team lunch?",
  "allowMultipleVotes": false,
  "allowUserSuggestion": true,
  "closeAt": "2026-04-04T18:00:00.000Z",
  "status": "OPEN",
  "voterCount": 8,
  "createdById": "user_1",
  "options": [
    { "id": "opt_1", "text": "Italian", "voteCount": 5, "position": 0 },
    { "id": "opt_2", "text": "Japanese", "voteCount": 2, "position": 1 },
    { "id": "opt_3", "text": "Mexican", "voteCount": 1, "position": 2 }
  ],
  "createdAt": "2026-04-03T09:00:00.000Z"
}

Users

Search users within the same tenant.

Query Parameters:

ParameterTypeDefaultDescription
keywordstring(required)Search term
limitinteger20Max results (1-50)

GET /users/blocked

List all users blocked by the authenticated user.


GET /users/:userId

Get a user's profile.


POST /users/block

Block a user. Blocked users cannot send direct messages.

Request Body:

json
{
  "userId": "user_to_block"
}

Errors: CHAT_USER_ALREADY_BLOCKED (409).


POST /users/unblock

Unblock a previously blocked user.

Request Body:

json
{
  "userId": "user_to_unblock"
}

Scheduled Messages

GET /channels/:channelId/scheduled-messages

List all pending scheduled messages for the current user in a channel.


POST /channels/:channelId/scheduled-messages

Schedule a message to be sent at a future time.

Request Body:

json
{
  "text": "Good morning team! Don't forget the standup at 10am.",
  "scheduledAt": "2026-04-04T08:00:00.000Z",
  "mentionedUserIds": ["user_2"],
  "metadata": {}
}
FieldTypeRequiredDescription
textstringYesMessage text (max 5000 chars)
scheduledAtISO 8601 dateYesWhen to send (must be in the future)
mentionedUserIdsstring[]NoUser IDs to mention
metadataobjectNoCustom metadata

Errors: CHAT_SCHEDULED_INVALID_TIME (400) if scheduledAt is in the past.


PATCH /channels/:channelId/scheduled-messages/:scheduledId

Update a pending scheduled message.

Request Body:

json
{
  "text": "Updated reminder text",
  "scheduledAt": "2026-04-04T09:00:00.000Z"
}
FieldTypeRequiredDescription
textstringNoUpdated text (max 5000 chars)
scheduledAtISO 8601 dateNoUpdated send time

Errors: CHAT_SCHEDULED_ALREADY_SENT (410) if already sent.


DELETE /channels/:channelId/scheduled-messages/:scheduledId

Cancel a pending scheduled message (sets status to CANCELED).

Errors: CHAT_SCHEDULED_ALREADY_SENT (410).


POST /channels/:channelId/scheduled-messages/:scheduledId/send-now

Send a pending scheduled message immediately instead of waiting for the scheduled time.

Errors: CHAT_SCHEDULED_ALREADY_SENT (410).

MIT License