Skip to main content
The chat module is the server-side client for Sublay’s messaging: direct and group conversations, messages (with GIFs, mentions, threads, quotes, and file attachments), membership, reactions, read state, and reporting.

Acting on behalf of a user

Your service key has no implicit session user, so every chat call takes the user it acts as. Most functions take that user as userId; the few where userId already names a target take the actor as actingUserId instead (createDirectConversation, addMember, removeMember, changeMemberRole).
Membership is enforced against the acting user (resolve-then-check). Sublay resolves the named user first, then runs the normal conversation checks against that user. So you can only act as a user who is genuinely a member of the conversation, and admin-only actions (delete/update conversation, add/remove members, change roles) require that user to be a group admin. A service key cannot post into a conversation as a user who isn’t in it.
Naming a user other than the caller’s own is gated to service/master keys. With a service key, omitting the acting user returns 400 chat/missing-user-id.

Conversations

listConversations

Lists the acting user’s conversations, newest activity first. Uses cursor (keyset) pagination — pass the last item’s lastMessageAt as cursor and its createdAt as cursorCreatedAt to get the next page.
const { conversations, hasMore } = await sublay.chat.listConversations({
  userId: "usr_abc123",
  types: "direct,group",
  limit: 20,
});
userId
string
required
The user whose conversations are listed.
types
string
Comma-separated filter: any of direct, group, space.
cursor
string
Keyset cursor — the lastMessageAt of the last item from the previous page.
cursorCreatedAt
string
Tie-breaker cursor — the createdAt of the last item from the previous page.
limit
number
Page size, 1–50. Defaults to 20.
ReturnsPromise<{ conversations: ConversationPreview[]; hasMore: boolean }> Each ConversationPreview includes otherMembers — up to 5 active members other than the acting user (id, name, username, avatar) for direct/group conversations, so a DM/group can render the counterparty without a separate members fetch. Capped at 5 (use memberCount for the group total); empty for space conversations.

createDirectConversation

Creates (or returns the existing) one-to-one conversation between the acting user and a target user.
const conversation = await sublay.chat.createDirectConversation({
  actingUserId: "usr_abc123",
  userId: "usr_def456",
});
userId
string
required
The other participant (the target).
actingUserId
string
required
The user initiating the DM.
ReturnsPromise<Conversation>

createGroupConversation

Creates a group conversation; the acting user becomes its admin.
const conversation = await sublay.chat.createGroupConversation({
  userId: "usr_abc123",
  name: "Project Falcon",
  memberIds: ["usr_def456", "usr_ghi789"],
});
userId
string
required
The acting user (becomes the group admin).
name
string
Group name.
description
string
Group description.
memberIds
string[]
Initial members to add.
metadata
object
Custom key-value data.
ReturnsPromise<Conversation>

getConversation

Fetches a single conversation the acting user is a member of.
const conversation = await sublay.chat.getConversation({
  conversationId: "cnv_abc123",
  userId: "usr_abc123",
});
conversationId
string
required
userId
string
required
The acting user (must be a member).
ReturnsPromise<Conversation>

updateConversation

Updates a group conversation. Requires the acting user to be a group admin.
const conversation = await sublay.chat.updateConversation({
  conversationId: "cnv_abc123",
  userId: "usr_abc123",
  name: "Project Falcon (Q3)",
  postingPermission: "admins",
});
conversationId
string
required
userId
string
required
The acting user (must be a group admin).
name
string
description
string
avatarFileId
string | null
File id for the avatar, or null to clear it.
postingPermission
"members" | "admins"
Space conversations only.
ReturnsPromise<Conversation>

deleteConversation

Deletes a group conversation. Requires the acting user to be a group admin.
await sublay.chat.deleteConversation({
  conversationId: "cnv_abc123",
  userId: "usr_abc123",
});
conversationId
string
required
userId
string
required
The acting user (must be a group admin).
ReturnsPromise<void>

getUnreadCount

Returns the acting user’s unread totals across all conversations.
const { totalUnread, unreadConversationCount } = await sublay.chat.getUnreadCount({
  userId: "usr_abc123",
});
userId
string
required
ReturnsPromise<{ totalUnread: number; unreadConversationCount: number }>

Members

listMembers

const { data, pagination } = await sublay.chat.listMembers({
  conversationId: "cnv_abc123",
  userId: "usr_abc123",
  limit: 50,
});
conversationId
string
required
userId
string
required
The acting user (must be a member).
page
number
Defaults to 1.
limit
number
1–100, defaults to 50.
role
"admin" | "member"
Filter by role.
ReturnsPromise<PaginatedResponse<ConversationMember>>

addMember

Adds a member to a group. Requires the acting caller to be a group admin.
await sublay.chat.addMember({
  conversationId: "cnv_abc123",
  userId: "usr_new789",      // member to add
  actingUserId: "usr_abc123", // the admin performing the action
});
conversationId
string
required
userId
string
required
The member to add (the target).
actingUserId
string
required
The caller (must be a group admin).
ReturnsPromise<ConversationMember>

removeMember

Removes a member from a group. Requires the acting caller to be a group admin.
await sublay.chat.removeMember({
  conversationId: "cnv_abc123",
  userId: "usr_new789",
  actingUserId: "usr_abc123",
});
conversationId
string
required
userId
string
required
The member to remove (the target).
actingUserId
string
required
The caller (must be a group admin).
ReturnsPromise<void>

changeMemberRole

Promotes or demotes a member. Requires the acting caller to be a group admin.
await sublay.chat.changeMemberRole({
  conversationId: "cnv_abc123",
  userId: "usr_new789",
  role: "admin",
  actingUserId: "usr_abc123",
});
conversationId
string
required
userId
string
required
The member whose role changes (the target).
role
"admin" | "member"
required
actingUserId
string
required
The caller (must be a group admin).
ReturnsPromise<ConversationMember>

leaveConversation

await sublay.chat.leaveConversation({
  conversationId: "cnv_abc123",
  userId: "usr_abc123",
});
conversationId
string
required
userId
string
required
The user leaving.
ReturnsPromise<void>

Messages

listMessages

Lists messages in a conversation. Uses cursor pagination — pass before (an ISO timestamp) to page backwards, or after to page forwards (before/after are mutually exclusive).
const { messages, hasMore, oldestCreatedAt } = await sublay.chat.listMessages({
  conversationId: "cnv_abc123",
  userId: "usr_abc123",
  limit: 50,
});

// Next (older) page:
const older = await sublay.chat.listMessages({
  conversationId: "cnv_abc123",
  userId: "usr_abc123",
  before: oldestCreatedAt!,
});

// Only messages that have thread replies.
const withReplies = await sublay.chat.listMessages({
  conversationId: "cnv_abc123",
  userId: "usr_abc123",
  filters: { hasReplies: true },
});
conversationId
string
required
userId
string
required
The acting user (must be a member).
parentId
string
Restrict to replies of this message (thread view).
before
string
ISO timestamp — messages created before this. Mutually exclusive with after.
after
string
ISO timestamp — messages created after this. Mutually exclusive with before.
limit
number
1–100, defaults to 50.
sort
"asc" | "desc"
include
string
Comma-separated associations to populate, e.g. "files".
filters
MessageFilters
Optional filters. filters.hasReplies (boolean): when true, returns only messages that have thread replies (threadReplyCount > 0); when false, only messages with none. Filters by thread replies, not quotings. Threads are one level deep, so hasReplies: true together with parentId always returns an empty list — the response’s notice field explains why.
ReturnsPromise<{ messages: ChatMessage[]; hasMore: boolean; oldestCreatedAt: string | null; newestCreatedAt: string | null }>

sendMessage

Sends a message as the acting user. A message must have content, a GIF, or at least one file. When files are attached the request is sent as multipart/form-data; otherwise it’s a JSON body.
// Text + mention
const message = await sublay.chat.sendMessage({
  conversationId: "cnv_abc123",
  userId: "usr_abc123",
  content: "Welcome aboard!",
  mentions: [{ type: "user", id: "usr_def456", username: "dana" }],
});

// With a file attachment
import { readFile } from "node:fs/promises";
const buffer = await readFile("./diagram.png");

await sublay.chat.sendMessage({
  conversationId: "cnv_abc123",
  userId: "usr_abc123",
  content: "See attached",
  files: [{ file: new Uint8Array(buffer), filename: "diagram.png", mimeType: "image/png" }],
});
conversationId
string
required
userId
string
required
The message author (must be a member).
content
string
Up to 4000 characters.
gif
GifData | null
An attached GIF object.
mentions
Mention[]
User/space mentions.
parentMessageId
string
Reply within a thread.
quotedMessageId
string
Quote another message.
metadata
object
Custom key-value data.
localId
string
Client-generated id echoed back on the created message (not stored).
files
ChatMessageFile[]
Attachments: { file: Uint8Array | Blob; filename?: string; mimeType?: string }[], up to 10.
ReturnsPromise<ChatMessage>

getMessage

const message = await sublay.chat.getMessage({
  conversationId: "cnv_abc123",
  messageId: "msg_xyz",
  userId: "usr_abc123",
});
conversationId
string
required
messageId
string
required
userId
string
required
The acting user (must be a member).
ReturnsPromise<ChatMessage>

editMessage

Edits a message. Requires the acting user to be the message author.
const message = await sublay.chat.editMessage({
  conversationId: "cnv_abc123",
  messageId: "msg_xyz",
  userId: "usr_abc123",
  content: "Edited text",
});
conversationId
string
required
messageId
string
required
userId
string
required
The acting user (must be the message author).
content
string
gif
string | null
A GIF URL, or null to clear it.
mentions
Mention[]
metadata
object | null
ReturnsPromise<ChatMessage>

deleteMessage

Soft-deletes a message. Requires the acting user to be the author or a group admin.
await sublay.chat.deleteMessage({
  conversationId: "cnv_abc123",
  messageId: "msg_xyz",
  userId: "usr_abc123",
});
conversationId
string
required
messageId
string
required
userId
string
required
The acting user (author or group admin).
ReturnsPromise<void>

reportMessage

Reports a message for moderation.
await sublay.chat.reportMessage({
  conversationId: "cnv_abc123",
  messageId: "msg_xyz",
  userId: "usr_abc123",
  reason: "harassment",
  details: "Targeted insults",
});
conversationId
string
required
messageId
string
required
userId
string
required
The reporting user (must be a member).
reason
string
required
Up to 200 characters.
details
string
Up to 1000 characters.
ReturnsPromise<{ message: string; code: string }>

Reactions

toggleReaction

Adds or removes the acting user’s emoji reaction on a message.
const { delta, reactionCounts } = await sublay.chat.toggleReaction({
  conversationId: "cnv_abc123",
  messageId: "msg_xyz",
  emoji: "👍",
  userId: "usr_abc123",
});
// delta === 1 when the reaction was added, -1 when it was removed
conversationId
string
required
messageId
string
required
emoji
string
required
userId
string
required
The reacting user (must be a member).
ReturnsPromise<{ reactionCounts: Record<string, number>; userReactions: string[]; delta: 1 | -1 }> (delta is 1 if the reaction was added, -1 if removed).

listReactions

Lists the users who reacted with a given emoji.
const { data, pagination } = await sublay.chat.listReactions({
  conversationId: "cnv_abc123",
  messageId: "msg_xyz",
  emoji: "👍",
  userId: "usr_abc123",
});
conversationId
string
required
messageId
string
required
emoji
string
required
The emoji to list reactors for.
userId
string
required
The acting user (must be a member).
page
number
Defaults to 1.
limit
number
1–100, defaults to 50.
ReturnsPromise<{ data: MessageReaction[]; pagination: { page; limit; total; hasMore } }>

Read state

markAsRead

Marks the conversation read up to a given message.
await sublay.chat.markAsRead({
  conversationId: "cnv_abc123",
  messageId: "msg_xyz",
  userId: "usr_abc123",
});
conversationId
string
required
messageId
string
required
The message up to which the conversation is marked read.
userId
string
required
The acting user (must be a member).
ReturnsPromise<void>

Space conversations

Each space has a single built-in conversation. These three live on client.spaces (they’re /spaces/... routes), not on client.chat.

spaces.getSpaceConversation

Fetches-or-creates a space’s conversation and joins the acting user to it. The acting user must be a non-banned member of the space.
const conversation = await sublay.spaces.getSpaceConversation({
  spaceId: "spc_abc123",
  userId: "usr_abc123",
});
spaceId
string
required
userId
string
required
The acting user (must be a space member).
ReturnsPromise<Conversation>

spaces.moderateSpaceChatMessage

Removes a message in a space conversation.
Unlike the conversation-scoped chat calls, the two space-chat moderation routes are service-key god-mode — your service key passes the space-moderator check automatically. actingUserId here is attribution only (who moderated); omit it for a backend/system action.
await sublay.spaces.moderateSpaceChatMessage({
  spaceId: "spc_abc123",
  messageId: "msg_xyz",
  moderationStatus: "removed",
  moderationReason: "Off-topic",
  actingUserId: "usr_mod123",
});
spaceId
string
required
messageId
string
required
moderationStatus
"removed"
required
moderationReason
string
actingUserId
string
Acting moderator, for attribution.
ReturnsPromise<{ message: string; moderationStatus: string }>

spaces.handleSpaceChatReport

Resolves a report against a space-conversation message: remove the message, ban the user, and/or dismiss.
await sublay.spaces.handleSpaceChatReport({
  spaceId: "spc_abc123",
  reportId: "rpt_abc123",
  actions: ["remove-message", "ban-user"],
  messageId: "msg_xyz",
  userId: "usr_offender",
  reason: "Repeated harassment",
  actingUserId: "usr_mod123",
});
spaceId
string
required
reportId
string
required
actions
("remove-message" | "ban-user" | "dismiss")[]
required
dismiss cannot be combined with the other actions.
messageId
string
Required when actions include remove-message.
userId
string
The user to ban (target); required when actions include ban-user.
reason
string
summary
string
Stored as the report’s resolution note.
actingUserId
string
Acting moderator, for attribution.
ReturnsPromise<{ message: string; code: string }>