Skip to main content

ChatSDK REST API Reference

Base URL: http://localhost:5501

Authentication

All API requests require two headers:

HeaderDescription
X-API-KeyYour application API key
AuthorizationBearer <JWT_TOKEN> (for user-specific endpoints)

Token Generation

POST /tokens

Generate JWT tokens for a user. Call this from your backend after authenticating the user.

Headers:

  • X-API-Key: Required

Request Body:

{
"userId": "user-123",
"name": "Alice Johnson",
"email": "alice@example.com",
"image": "https://example.com/avatar.jpg",
"custom": { "role": "admin" }
}
FieldTypeRequiredDescription
userIdstringYesUnique user identifier from your system
namestringNoDisplay name
emailstringNoUser's email address
imagestringNoAvatar URL
customobjectNoCustom metadata

Response: 200 OK

{
"token": "eyJhbGciOiJIUzI1NiJ9...",
"wsToken": "eyJhbGciOiJIUzI1NiJ9...",
"user": {
"id": "user-123",
"name": "Alice Johnson",
"email": "alice@example.com",
"image": "https://example.com/avatar.jpg"
},
"expiresIn": 86400
}

Channels

GET /api/channels

List channels for the authenticated user.

Query Parameters:

ParameterTypeDefaultDescription
limitnumber50Max results (max: 100)
offsetnumber0Pagination offset
typestring-Filter by type: messaging, group, team, livestream

Response:

{
"channels": [
{
"id": "uuid",
"cid": "group:general",
"name": "General",
"type": "group",
"image": "https://...",
"memberCount": 5,
"unreadCount": 3,
"lastMessage": {...},
"createdAt": "2024-01-01T00:00:00Z"
}
]
}

POST /api/channels

Create a new channel.

Request Body:

{
"name": "Project Alpha",
"type": "group",
"description": "Discussion for Project Alpha",
"image": "https://example.com/channel.jpg",
"memberIds": ["user-2", "user-3"],
"workspaceId": "workspace-uuid"
}

Channel Types:

TypeDescription
messagingDirect message (1:1) - requires exactly 1 memberIds
groupGroup chat
teamTeam channel
livestreamLivestream/broadcast channel
publicPublic channel - anyone can join
privatePrivate channel - invite only

Response: 201 Created

{
"id": "channel-uuid",
"cid": "group:project-alpha",
"name": "Project Alpha",
"type": "group",
...
}

GET /api/channels/:channelId

Get channel details.

DELETE /api/channels/:channelId

Delete a channel (owner only).

GET /api/channels/unread-count

Get total unread message count across all channels for the authenticated user.

Response:

{
"count": 15
}

POST /api/channels/:channelId/read

Mark a channel as read for the authenticated user.

Request Body (optional):

{
"messageId": "message-uuid"
}

Response:

{
"success": true,
"lastReadSeq": 42
}

Side Effects:

  • Sets unreadCount to 0 for this channel
  • Emits read.updated WebSocket event to channel members
  • Emits channel.unread_changed WebSocket event to user
  • Emits channel.total_unread_changed WebSocket event to user

Read Receipts

POST /api/channels/:channelId/receipts

Mark messages as read up to a specific message.

Request Body:

{
"messageId": "message-uuid"
}

Response:

{
"success": true,
"lastReadMessageId": "message-uuid"
}

GET /api/channels/:channelId/receipts/messages/:messageId/receipts

Get read receipts for a specific message.

Response:

{
"readBy": [
{
"userId": "user-123",
"name": "Alice Johnson",
"image": "https://...",
"readAt": "2024-01-15T12:00:00Z"
}
],
"readCount": 5,
"totalMembers": 10
}

POST /api/channels/:channelId/receipts/query

Batch query read receipts for multiple messages.

Request Body:

{
"messageIds": ["msg-1", "msg-2", "msg-3"]
}

Response:

{
"receipts": {
"msg-1": { "readCount": 5, "readers": [...] },
"msg-2": { "readCount": 3, "readers": [...] },
"msg-3": { "readCount": 0, "readers": [] }
}
}

GET /api/channels/:channelId/receipts/read-status

Get read status for all channel members.

Response:

{
"members": [
{
"userId": "user-123",
"name": "Alice Johnson",
"image": "https://...",
"lastReadMessageId": "msg-456",
"lastReadSeq": 42
}
]
}

Messages

GET /api/channels/:channelId/messages

Get messages in a channel.

Query Parameters:

ParameterTypeDefaultDescription
limitnumber100Max messages (max: 200)
beforestring-Message ID for cursor pagination (older)
afterstring-Message ID for cursor pagination (newer)
since_seqnumber0Sequence number for sync

Response:

{
"messages": [
{
"id": "message-uuid",
"channelId": "channel-uuid",
"userId": "user-1",
"text": "Hello @user-2!",
"seq": 42,
"user": {
"id": "user-1",
"name": "Alice Johnson",
"image": "https://..."
},
"reactions": [
{"type": "👍", "count": 2, "own": true, "users": [...]}
],
"mentions": ["user-2"],
"attachments": [],
"createdAt": "2024-01-01T12:00:00Z",
"updatedAt": "2024-01-01T12:00:00Z"
}
],
"maxSeq": 100,
"hasMore": true
}

POST /api/channels/:channelId/messages

Send a message.

Request Body:

{
"text": "Hello @user-2! Check this out.",
"attachments": [
{
"type": "image",
"url": "https://example.com/image.jpg",
"name": "photo.jpg"
}
],
"parentId": "parent-message-uuid"
}

Mentions: Use @username syntax in message text. The API automatically:

  • Extracts mentions (supports hyphens: @user-2)
  • Stores them in the database
  • Returns them in the mentions array
  • Sets mention flags for push notifications

Response: 201 Created

{
"id": "new-message-uuid",
"text": "Hello @user-2! Check this out.",
"mentions": ["user-2"],
"reactions": [],
...
}

Link previews are automatically generated when messages contain URLs. The system extracts OpenGraph metadata and creates rich previews for shared links.

How It Works

  1. User sends a message containing a URL
  2. Message is saved and returned immediately
  3. Background job (Inngest) fetches URL metadata
  4. Message is updated with linkPreviews data
  5. Real-time event broadcasts the update

Supported Platforms

PlatformFeatures
YouTubeVideo embed, thumbnail, title, channel name
VimeoVideo embed, thumbnail, title
Any websiteOpenGraph title, description, image
{
"id": "message-uuid",
"text": "Check out this video: https://youtube.com/watch?v=abc123",
"linkPreviews": [
{
"url": "https://youtube.com/watch?v=abc123",
"title": "Amazing Video Title",
"description": "Video description here...",
"image": "https://img.youtube.com/vi/abc123/maxresdefault.jpg",
"siteName": "YouTube",
"type": "video",
"videoId": "abc123",
"embedUrl": "https://www.youtube.com/embed/abc123"
}
],
...
}
FieldTypeDescription
urlstringOriginal URL from message
titlestringPage/video title
descriptionstringMeta description
imagestringPreview image URL
siteNamestringWebsite name (e.g., "YouTube")
typestringContent type: website, video, article
videoIdstringVideo ID (YouTube/Vimeo only)
embedUrlstringEmbeddable video URL

React Component

import { LinkPreview } from '@chatsdk/react';

function Message({ message }) {
return (
<div>
<p>{message.text}</p>
{message.linkPreviews?.map((preview, i) => (
<LinkPreview key={i} preview={preview} />
))}
</div>
);
}

Configuration

Link previews require Inngest for background processing:

# .env.production
INNGEST_EVENT_KEY=your-inngest-key
INNGEST_SIGNING_KEY=your-signing-key

# Or use Inngest Dev Server locally
INNGEST_DEV=true

If Inngest is not configured, messages will be sent without link previews.


PATCH /api/channels/:channelId/messages/:messageId

Edit a message (owner only).

Request Body:

{
"text": "Updated message text"
}

DELETE /api/channels/:channelId/messages/:messageId

Delete a message (owner only, within 15 minutes).


Reactions

POST /api/channels/:channelId/messages/:messageId/reactions

Add a reaction to a message.

Request Body:

{
"emoji": "👍"
}

DELETE /api/channels/:channelId/messages/:messageId/reactions/:emoji

Remove a reaction from a message.

DELETE /api/channels/abc/messages/xyz/reactions/👍

Pin Messages

POST /api/channels/:channelId/messages/:messageId/pin

Pin a message to the channel.

Response: 200 OK

{
"success": true
}

DELETE /api/channels/:channelId/messages/:messageId/pin

Unpin a message.

Response: 200 OK

{
"success": true
}

GET /api/channels/:channelId/pins

Get all pinned messages in a channel.

Response:

{
"messages": [
{
"id": "message-uuid",
"text": "Important announcement!",
"pinnedBy": "user-1",
"pinnedAt": "2024-01-01T12:00:00Z",
...
}
]
}

Users

GET /api/users

List users in your app.

Query Parameters:

ParameterTypeDefaultDescription
limitnumber50Max results (max: 100)
offsetnumber0Pagination offset
qstring-Search by name

Response:

{
"users": [
{
"id": "user-1",
"name": "Alice Johnson",
"image": "https://...",
"lastActiveAt": "2024-01-01T12:00:00Z",
"online": true
}
]
}

GET /api/users/:userId

Get user details.

POST /api/users/sync

Bulk sync users from your system to ChatSDK. Use this to ensure ChatSDK has the latest user data.

Request Body:

{
"users": [
{
"id": "user-1",
"name": "Alice Johnson",
"image": "https://example.com/alice.jpg",
"email": "alice@example.com",
"custom": { "role": "admin", "department": "Engineering" }
},
{
"id": "user-2",
"name": "Bob Smith",
"image": "https://example.com/bob.jpg"
}
]
}

Response: 200 OK

{
"synced": 2,
"created": 1,
"updated": 1,
"errors": []
}

PUT /api/users/:userId

Create or update a single user. Useful for real-time sync when a user updates their profile.

Request Body:

{
"name": "Alice Johnson",
"image": "https://example.com/alice.jpg",
"email": "alice@example.com",
"custom": { "role": "admin" }
}

Response: 200 OK

{
"id": "user-1",
"name": "Alice Johnson",
"image": "https://example.com/alice.jpg",
"email": "alice@example.com",
"custom": { "role": "admin", "email": "alice@example.com" }
}

DELETE /api/users/:userId

Delete a user and their associated data. Use this to clean up seed/test users.

Response: 200 OK

{
"success": true,
"deletedUserId": "user-1"
}

POST /api/users/bulk-delete

Delete multiple users at once. Useful for cleaning up seed data.

Request Body:

{
"userIds": ["user-1", "user-2", "user-3"]
}

Response: 200 OK

{
"success": true,
"deleted": ["user-1", "user-2", "user-3"],
"count": 3
}

Devices & Push Notifications

ChatSDK uses Novu for push notifications. See Push Notifications Guide for setup.

POST /api/devices

Register a device for push notifications.

Request Body:

{
"platform": "ios",
"token": "device-push-token",
"provider": "apns",
"deviceId": "unique-device-id",
"appVersion": "1.0.0"
}
FieldTypeRequiredDescription
platformstringYesios, android, or expo
tokenstringYesPush token from device
providerstringYesfcm, apns, or expo
deviceIdstringNoUnique device identifier
appVersionstringNoApp version for targeting

Response: 201 Created

{
"id": "device-uuid",
"platform": "ios",
"provider": "apns",
"createdAt": "2024-01-01T00:00:00Z"
}

GET /api/devices/preferences

Get notification preferences for the current user.

Response:

{
"preferences": {
"pushEnabled": true,
"newMessages": true,
"mentions": true,
"reactions": false,
"channelInvites": true,
"threadReplies": true,
"quietHoursEnabled": false,
"quietHoursStart": "22:00",
"quietHoursEnd": "07:00"
},
"novuPreferences": {
"channels": [...],
"workflows": [...]
}
}

PATCH /api/devices/preferences

Update notification preferences.

Request Body:

{
"pushEnabled": true,
"mentions": true,
"reactions": false,
"quietHoursEnabled": true,
"quietHoursStart": "22:00",
"quietHoursEnd": "07:00"
}

DELETE /api/devices/:deviceId

Unregister a device from push notifications.


Workspaces

GET /api/workspaces

List workspaces the user belongs to.

Response:

{
"workspaces": [
{
"id": "workspace-uuid",
"name": "Acme Corp",
"type": "team",
"memberCount": 25,
"channelCount": 8,
"role": "admin",
"isDefault": true
}
]
}

POST /api/workspaces

Create a new workspace.

Request Body:

{
"name": "Project Team",
"type": "team",
"image": "https://example.com/workspace.jpg"
}

POST /api/workspaces/:workspaceId/invite

Create an invite link.

Request Body:

{
"maxUses": 10,
"expiresInDays": 7,
"role": "member"
}

Response:

{
"token": "abc123...",
"inviteUrl": "https://app.example.com/invite/abc123...",
"expiresAt": "2024-01-08T00:00:00Z"
}

Channel Member Settings

PATCH /api/channels/:channelId/settings

Update channel settings for the current user.

Request Body:

{
"starred": true,
"muted": false
}

Response:

{
"success": true,
"starred": true,
"muted": false
}

Typing Indicators

POST /api/channels/:channelId/typing

Send typing indicator (broadcasts via WebSocket).


Error Responses

All errors follow this format:

{
"error": {
"message": "Error description",
"code": "ERROR_CODE"
}
}

Common Error Codes:

CodeHTTP StatusDescription
UNAUTHORIZED401Missing or invalid API key
INVALID_TOKEN401Invalid or expired JWT token
FORBIDDEN403Not authorized for this action
NOT_FOUND404Resource not found
VALIDATION_ERROR400Invalid request body
INTERNAL_ERROR500Server error

Rate Limits

EndpointLimit
Token generation100/min per IP
Message send60/min per user
All other endpoints1000/min per API key

WebSocket Connection

Connect to Centrifugo for real-time events:

import { Centrifuge } from 'centrifuge';

const client = new Centrifuge('ws://localhost:8001/connection/websocket', {
token: wsToken, // From /tokens response
});

// Subscribe to a channel
const sub = client.subscribe(`chat:${appId}:${channelId}`);

sub.on('publication', (ctx) => {
console.log('Event:', ctx.data);
// { type: 'message.new', payload: { message: {...} } }
});

client.connect();

Event Types:

Channel Events (published to chat:{appId}:{channelId}):

  • message.new - New message
  • message.updated - Message edited
  • message.deleted - Message deleted
  • reaction.added - Reaction added
  • reaction.removed - Reaction removed
  • typing.start - User started typing
  • typing.stop - User stopped typing
  • channel.member_joined - User joined channel
  • channel.member_left - User left channel
  • read.updated - User read position updated
  • read_receipt - User read a specific message

User Events (published to user:{appId}:{userId}):

  • channel.unread_changed - Unread count changed for a channel
    { "type": "channel.unread_changed", "payload": { "channelId": "...", "count": 5 } }
  • channel.total_unread_changed - Total unread count changed
    { "type": "channel.total_unread_changed", "payload": { "count": 15 } }
  • presence.online - User came online
  • presence.offline - User went offline