Chat API
Endpoints for direct chats, group conversations, messages, attachments, polls, and live thread snapshots.
Permissions
social:chat.readfor conversations, messages, attachments, and live snapshotssocial:chat.writefor conversation creation, message send, attachment upload, and poll voting
If a permission node is missing in the database, the route currently allows access by default.
Endpoints
GET /v1/social/chat/conversations
Lists the caller's conversations.
Query parameters:
limit: optional, clamped to1..200
POST /v1/social/chat/conversations
Creates or loads a conversation for the given participant set.
Body:
{
"kind": "direct",
"participantIds": ["uuid"]
}Rules:
kind=directrequires exactly one other participant besides the caller.kind=groupaccepts multiple participants.- The caller is always injected server-side; clients must only send the peer ids.
GET /v1/social/chat/conversations/{conversationId}/messages
Returns a message snapshot for one conversation.
Query parameters:
limit: optional, clamped to1..120beforeMs: optional pagination cursorincludeArchived: optional, defaults totrue
includeArchived=true merges current live messages with older archived history for legacy conversations.
POST /v1/social/chat/conversations/{conversationId}/messages
Creates a new message in the conversation.
Body:
{
"content": "Hello world",
"attachmentIds": ["objectId"],
"mentions": [
{
"kind": "user",
"id": "uuid",
"label": "@Player"
}
],
"poll": {
"question": "Which map?",
"options": [
{ "label": "Hub" },
{ "label": "Spawn" }
],
"multiple": false,
"expiresAtMs": 1770000000000
}
}At least one of content, attachmentIds, or poll is required.
POST /v1/social/chat/conversations/{conversationId}/messages/{messageId}/poll/vote
Votes on an existing poll message.
Body:
{
"optionIds": ["poll-option-id"]
}POST /v1/social/chat/conversations/{conversationId}/attachments
Uploads an attachment and returns a reusable attachment payload.
Body:
{
"fileName": "screenshot.png",
"contentType": "image/png",
"dataBase64": "iVBORw0KGgoAAAANSUhEUgAA..."
}Attachments are limited server-side and are only readable by conversation participants.
GET /v1/social/chat/conversations/{conversationId}/attachments/{attachmentId}
Streams attachment bytes back to an authenticated participant.
Auth:
Authorization: Bearer <token>header.
Bearer tokens in query strings are rejected by default. Native clients that cannot attach headers to rendered media should fetch attachments through their authenticated bridge and render the resulting local/cache URL.
GET /v1/social/chat/conversations/{conversationId}/live
WebSocket endpoint that emits a full ChatMessageListPayload snapshot immediately and then only sends a new snapshot when the thread contents change.
Optional query parameters:
intervallimitbeforeMsincludeArchived
Auth:
Authorization: Bearer <token>header, orSec-WebSocket-Protocol: uebliche-bearer.<base64url-token>for WebSocket clients that cannot set custom headers.
Payloads
Conversation payload
{
"id": "objectId",
"kind": "group",
"participants": [
{
"id": "uuid",
"name": "Player",
"online": true
}
],
"createdAt": "2026-02-20T18:00:00Z",
"updatedAt": "2026-02-20T18:03:00Z",
"lastMessagePreview": "Hello world"
}Attachment payload
{
"id": "objectId",
"fileName": "screenshot.png",
"contentType": "image/png",
"sizeBytes": 123456,
"url": "/v1/social/chat/conversations/<conversationId>/attachments/<attachmentId>"
}Poll payload
{
"question": "Which map?",
"multiple": false,
"closed": false,
"totalVotes": 3,
"options": [
{
"id": "option-1",
"label": "Hub",
"votes": 2,
"selected": true
},
{
"id": "option-2",
"label": "Spawn",
"votes": 1,
"selected": false
}
],
"expiresAt": "2026-02-20T20:00:00Z"
}Message payload
{
"id": "objectId",
"conversationId": "objectId",
"senderId": "uuid",
"senderName": "Player",
"content": "Hello <@user:uuid>",
"mentions": [
{
"kind": "user",
"id": "uuid",
"token": "<@user:uuid>",
"label": "@Player"
}
],
"attachments": [],
"poll": null,
"createdAt": "2026-02-20T18:03:00Z",
"createdAtMs": 1770000000000,
"source": "live"
}Message snapshot payload
GET /v1/social/chat/conversations/{conversationId}/messages and the live endpoint both return:
{
"conversation": {
"id": "objectId",
"kind": "direct",
"participants": [],
"createdAt": "2026-02-20T18:00:00Z",
"updatedAt": "2026-02-20T18:03:00Z",
"lastMessagePreview": "Hello world"
},
"items": [],
"nextBeforeMs": 1769999999999,
"hasMore": false
}