Duvet DOCS
GitHub

Room Messages

Endpoints for interacting with messages, typing indicators, and reactions within rooms.

Webhook messages: Messages sent via webhooks have author_id: null and webhook_id set. The user field in responses contains a synthetic user built from the webhook’s stored identity (webhook_author), with is_bot: true. Reply previews also resolve webhook author data automatically.

Get Message History

GET /api/rooms/{roomId}/messages

Retrieve paginated message history for a room.

Query Parameters

FieldTypeDescription
count ?integerNumber of messages to return
before ?uuidReturn messages before this message ID
after ?uuidReturn messages after this message ID
order ?stringSort order (e.g. "asc" or "desc")

Returns: Array of MessageResponse objects.

Get Message

GET /api/rooms/{roomId}/messages/{messageId}

Retrieve a specific message in a room, including attachments and reactions.

Returns: MessageResponse object.

Get Message Thread

GET /api/rooms/{roomId}/messages/{messageId}/thread

Retrieve the threaded replies for a specific room message. Each message includes a user object and webhook_id if applicable.

Each thread message includes a user object. For webhook messages (author_id is null), the user is built from the stored webhook_author data with is_bot=true. Thread messages also include ref_message_id for resolving reply chains within the thread.

The thread stays in the room it's read through: it only follows replies between messages of that room.

Returns: Array of ThreadMessageResponse objects.

Errors: 404 when the message isn't one of this room's.

Create Message

POST /api/rooms/{roomId}/messages

Send a new message to a room. Supports both JSON and multipart/form-data for file attachments. At least one of content, files, embeds, or sticker_attachment_id is required.

JSON Body

FieldTypeDescription
content ?string?Message text content (max 5000 characters)
nonce stringClient-generated nonce for deduplication
type integerMessage type: 0 (a message) or 1 (a voice note); 2, a pin notice, is the API's only, and anything else is a 400
message_reference ?MessageReferenceThe message this one replies to
embeds ?Embed[]Array of embed objects
sticker_attachment_id ?stringThe attachment_id of one of this room's space's stickers, to send it

Message Reference Object

FieldTypeDescription
type ?integerReference type: 0 (reply), the only one
message_id ?stringReplied message ID
room_id ?stringRoom of the replied message: this room (the default)
space_id ?stringIgnored: the reply stores this room's space
fail_if_not_exists ?booleanWhether to fail if the referenced message does not exist

Multipart Body

When sending files, use multipart/form-data:

FieldTypeDescription
content ?stringMessage text content
nonce stringClient-generated nonce
type ?stringMessage type (as string): 0 or 1, as in the JSON body
message_reference ?stringMessage reference as JSON string
files ?file[]Attachment files (not available for bots)
sticker_attachment_id ?stringAs in the JSON body

A message is sent with all its files or not at all: if one fails to upload, the message and the files already uploaded are deleted and the request fails.

Returns: MessageResponse object.

Errors: 400 with invalid message_reference: ... for a type other than 0 or an ID that isn't a UUID; 400 with invalid sticker_attachment_id when it isn't a UUID, or that sticker can't be sent here when it isn't the attachment_id of one of this space's stickers; 403 with replied message must be in the same room; 404 with referenced message not found, unless fail_if_not_exists is false (then the reply is sent without the reference); 413 for a file over your upload limit or the storage bucket's, or with no room left for it.

Update Message

PATCH /api/rooms/{roomId}/messages/{messageId}

Update the content of a message. Only its author can edit it, and only while they can still see the room: someone who left the space can't.

JSON Body

FieldTypeDescription
content stringNew message content (max 5000 characters)

Returns: Message object.

Errors: 400 for a pin notice, which can't be edited.

Delete Message

DELETE /api/rooms/{roomId}/messages/{messageId}

Delete a message from a room. Authors can delete their own messages; users with ManageMessages permission can delete others' messages, and pin notices (type 2), which their authors can't.

Returns: 204 No Content


Pinned Messages

A room has 250 pinned messages at most. Pinning a message posts the room’s pin notice for it, and pins and unpins reach members as MESSAGE_PIN_UPDATE.

Get Pinned Messages

GET /api/rooms/{roomId}/pins

List the room's pinned messages, the last pinned first. Requires ViewChannels.

Returns: Array of MessageResponse objects.

Pin Message

PUT /api/rooms/{roomId}/pins/{messageId}

Pin a message of the room, which posts the room's pin notice for it. Requires PinMessages. Pinning a pinned message changes nothing and posts no second notice.

Returns: 204 No Content

Errors: 400 for a pin notice, or when the room has 250 pinned messages already; 403 without PinMessages; 404 for a message that isn't in the room.

Unpin Message

DELETE /api/rooms/{roomId}/pins/{messageId}

Unpin a message of the room. Requires PinMessages. Unpinning a message that isn't pinned changes nothing.

Returns: 204 No Content

Errors: 403 without PinMessages; 404 for a message that isn't in the room.


Typing & Read State

Send Typing Indicator

POST /api/rooms/{roomId}/typing

Notify the room that the authenticated user is typing. Broadcasts a TYPING event via the gateway. Each user's notices go out at most once every 2 seconds per room: extra ones are still answered 204, and dropped.

Returns: 204 No Content

Send Recording Indicator

POST /api/rooms/{roomId}/recording

Notify the room that the authenticated user is recording a voice message. Broadcasts a RECORDING event via the gateway. It takes the same permissions as typing, and has its own limit: at most once every 2 seconds per room, extra ones answered 204 and dropped.

Returns: 204 No Content

Acknowledge Room

POST /api/rooms/{roomId}/ack

Mark the room as read for the authenticated user. Human-only. The read state only moves forward: an older message ID than the one stored leaves it as it is, so acks arriving out of order are harmless. A message_id that isn't a UUID is rejected (400).

JSON Body

FieldTypeDescription
message_id uuidNewest message read in the room

Returns: 204 No Content


Reactions

Add Reaction

PUT /api/rooms/{roomId}/messages/{messageId}/reactions

Add an emoji reaction to a room message.

JSON Body

FieldTypeDescription
emoji_name stringUnicode emoji character (e.g. "") or custom emoji UUID string
emoji_id ?uuidCustom emoji attachment ID (for custom emojis only)

Returns: 204 No Content

Errors: 404 when the message isn't one of this room's.

Remove Reaction

DELETE /api/rooms/{roomId}/messages/{messageId}/reactions/{emojiName}

Remove your emoji reaction from a room message. The emojiName path parameter is the Unicode character or custom emoji UUID.

Returns: 204 No Content

Errors: 404 when the message isn't one of this room's.