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. Returns400if the referenced message belongs to a different channel.attachment_ids— list of attachment UUIDs previously uploaded viaPOST /channels/{channel_id}/attachments. Links them to this message.
Behavior:
- requires membership
- validates
reply_to_idbelongs to the same channel (if provided) - links attachment records by setting their
message_id - publishes
message.newon Redis channel topic (includesreply_tosnippet andattachments) - if channel is federated, relays
message.relayto remote servers
PATCH /channels/messages/{message_id}
Edits own message only.
Request:
{ "content": "<updated-payload>" }Behavior:
- sets
edited_at - publishes
message.editevent
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
filefield - 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.