Skip to content

Chat API

Endpoints for direct chats, group conversations, messages, attachments, polls, and live thread snapshots.

Permissions

  • social:chat.read for conversations, messages, attachments, and live snapshots
  • social:chat.write for 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 to 1..200

POST /v1/social/chat/conversations

Creates or loads a conversation for the given participant set.

Body:

json
{
  "kind": "direct",
  "participantIds": ["uuid"]
}

Rules:

  • kind=direct requires exactly one other participant besides the caller.
  • kind=group accepts 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 to 1..120
  • beforeMs: optional pagination cursor
  • includeArchived: optional, defaults to true

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:

json
{
  "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:

json
{
  "optionIds": ["poll-option-id"]
}

POST /v1/social/chat/conversations/{conversationId}/attachments

Uploads an attachment and returns a reusable attachment payload.

Body:

json
{
  "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:

  • interval
  • limit
  • beforeMs
  • includeArchived

Auth:

  • Authorization: Bearer <token> header, or
  • Sec-WebSocket-Protocol: uebliche-bearer.<base64url-token> for WebSocket clients that cannot set custom headers.

Payloads

Conversation payload

json
{
  "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

json
{
  "id": "objectId",
  "fileName": "screenshot.png",
  "contentType": "image/png",
  "sizeBytes": 123456,
  "url": "/v1/social/chat/conversations/<conversationId>/attachments/<attachmentId>"
}

Poll payload

json
{
  "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

json
{
  "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:

json
{
  "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
}