Skip to content

Direct Messages API

Older endpoint notes

Rendered from ProjectInfo/api-docs/api/dm.md on docs/encryption-modes-spec. These notes predate the REST API reference, which is canonical wherever the two overlap. They are published because they still cover endpoints the reference does not yet document — check shapes against the reference and the spec errata.

DM messages are true E2EE — server stores ciphertext blobs only, never decrypts.
See e2ee.md for the full encryption protocol.

All endpoints require Authorization: Bearer <token>.


POST /api/dm

Open or find an existing 1:1 DM channel. Idempotent.

Request:

{ "user_id": "uuid" }

Response 201 (new) or 200 (existing): DMChannel
Errors: 400 (self-DM)


GET /api/dm

List all DM channels the authenticated user belongs to, ordered by most recently active.

Response 200: Array of DMChannel


GET /api/dm/{channelID}

Fetch a single DM channel.

Response 200: DMChannel
Errors: 403 (not a member)


DELETE /api/dm/{channelID}

Leave a DM channel. Channel + messages are retained for other participant.

Response 204


POST /api/dm/{channelID}/messages

Send an E2EE DM message.

Request:

{
  "ciphertext": "base64",
  "ratchet_header": "base64",
  "x3dh_header": "base64 (first message in a session only, omit otherwise)",
  "device_ciphertexts": [
    { "device_id": "string", "ciphertext": "base64" }
  ],
  "reply_to_id": "uuid (optional)"
}

  • ciphertext — Double Ratchet encrypted message payload
  • ratchet_header — current ratchet public key + chain/message indices
  • x3dh_header — only on first message (includes ephemeral_key, signed_prekey_id, one_time_prekey_id, sender_device_id)
  • device_ciphertexts — one entry per recipient device AND per sender's other devices

Response 201: DMMessage
Errors: 400 (empty ciphertext), 403 (not a member)

Real-time: fires MESSAGE_CREATE on channel.<channelID>.MESSAGE_CREATE.


GET /api/dm/{channelID}/messages?limit=50&before={messageID}

Fetch DM message history. Each message includes all device_ciphertexts — client finds its own device_id to decrypt.

Query params: - limit: 1–100, default 50 - before: cursor UUID (oldest message from previous page)

Response 200: Array of DMMessage, newest first.


DELETE /api/dm/{channelID}/messages/{messageID}

Soft-delete a DM message. Only the original sender.

Response 204
Errors: 403 (not sender), 404 (not found)

Real-time: fires MESSAGE_DELETE on the channel subject.