Skip to content

API - Message Routes

Source of truth: d3chat/backend/app/routers/messages.py

Prefix: /api/v1/channels

All routes below require Bearer auth.

GET /channels/{channel_id}/messages

Returns paginated messages.

Query params:

  • before (datetime, optional)
  • limit (default 50, max 100)

Response:

{
"messages": [
{
"id": "<uuid>",
"channel_id": "<uuid>",
"sender_id": "<uuid>",
"sender_device_id": "<uuid>",
"content": "<encrypted-or-plaintext-payload>",
"content_type": "text",
"protocol_version": 1,
"edited_at": null,
"created_at": "...",
"reply_to_id": "<uuid|null>",
"reply_to": {
"id": "<uuid>",
"sender_id": "<uuid|null>",
"content": "<original-message-content>",
"content_type": "text",
"protocol_version": 1
},
"attachments": [
{
"id": "<uuid>",
"filename": "photo.jpg",
"content_type": "image/jpeg",
"size_bytes": 204800,
"url": "/api/v1/attachments/<uuid>/download",
"thumbnail_url": "/api/v1/attachments/<uuid>/thumbnail",
"width": 1920,
"height": 1080
}
]
}
],
"has_more": true
}

POST /channels/{channel_id}/messages

Creates message in channel.

Request:

{
"content": "<ciphertext-or-message>",
"content_type": "text",
"protocol_version": 1,
"sender_device_id": "<uuid>",
"reply_to_id": "<uuid|null>",
"attachment_ids": ["<uuid>", "..."]
}

Optional fields:

  • reply_to_id — UUID of a message in the same channel to reply to. Returns 400 if the referenced message belongs to a different channel.
  • attachment_ids — list of attachment UUIDs previously uploaded via POST /channels/{channel_id}/attachments. Links them to this message.

Behavior:

  • requires membership
  • validates reply_to_id belongs to the same channel (if provided)
  • links attachment records by setting their message_id
  • publishes message.new on Redis channel topic (includes reply_to snippet and attachments)
  • if channel is federated, relays message.relay to remote servers

PATCH /channels/messages/{message_id}

Edits own message only.

Request:

{ "content": "<updated-payload>" }

Behavior:

  • sets edited_at
  • publishes message.edit event

DELETE /channels/messages/{message_id}

Deletes own message only and publishes message.delete.

Returns 204.

Attachments

Source of truth: d3chat/backend/app/routers/attachments.py

POST /channels/{channel_id}/attachments

Uploads a file attachment to a channel. Returns the attachment metadata for use with message creation.

  • Max file size: 10 MB (configurable via MAX_FILE_SIZE_BYTES)
  • Requires channel membership
  • Multipart form data with file field
  • Images (JPEG, PNG, GIF, WebP) get automatic thumbnail generation (200x200 WebP)

Response:

{
"id": "<uuid>",
"filename": "photo.jpg",
"content_type": "image/jpeg",
"size_bytes": 204800,
"url": "/api/v1/attachments/<uuid>/download",
"thumbnail_url": "/api/v1/attachments/<uuid>/thumbnail",
"width": 1920,
"height": 1080
}

Non-image files omit thumbnail_url, width, and height.

GET /attachments/{attachment_id}/download

Downloads the original file. Requires authentication via query parameter:

/api/v1/attachments/<uuid>/download?token=<jwt>

Query param auth is used instead of Bearer header because <img> and <a> tags cannot send custom headers.

GET /attachments/{attachment_id}/thumbnail

Downloads the thumbnail (images only). Same auth model as download:

/api/v1/attachments/<uuid>/thumbnail?token=<jwt>

Returns 404 if no thumbnail exists for the attachment.