Skip to content

HTTP & uploads

The HTTP server and WebSocket share the configured port. CORS and WebSocket origins are currently open; authentication and authorization happen at the application layer.

Method Path Purpose
GET /, /info, /user/{username} Rendered public pages
GET /server-assets/{name} Resolved server icon/banner
GET /emojis/{name} Custom emoji content
GET /stickers/{name} Custom sticker content
POST /attachments/upload Attachment upload
GET /attachments/{id} Attachment download/redirect
GET /webhooks[/path…] Webhook information/verification
POST /webhooks[/path…] Receive a webhook payload
  1. Read handshake.val.attachments. If enabled is false, do not show uploads.
  2. Enforce max_size, MIME allowed_types, account quota, requested expiry, and configured upload rate client-side for fast feedback. Do not rely on the handshake’s hard-coded uploads_per_minute: 10 when an operator may have changed attachments.uploads_per_minute.
  3. Obtain a Rotur validator for the supplied validator_key, then include both values in the upload body. This is separate from the WebSocket connection’s authentication state.
  4. Use the returned attachment reference in message_new.attachments.
  5. The message handler resolves references to trusted attachment objects before persistence.

The server accepts local or S3-compatible storage. Although compression settings are defined, the current upload path stores the decoded bytes without resizing or recompressing them. A deleted message triggers deletion of its referenced attachments.

The handshake reports uploads.uploads_per_minute: 10 as a fixed value. Actual upload enforcement reads attachments.uploads_per_minute, so clients should treat an upload rejection as authoritative when an operator has configured a different limit.

JSON uploads use file (base64 or an exact data:<mime_type>;base64,… URI), name, mime_type, validator_key, validator, and optional expires_in_days. Multipart uploads use the same names with a file part named file. File names must be 1–255 characters with no CR/LF. expires_in_days must be positive; it is capped by free_tier_max_expiration_days (default seven). When that cap is -1, stored uploads have no expiry. Every current upload response has permanent: false.

Server icon and banner values are resolved through the asset backend at startup. Custom emoji and sticker records point at their content endpoint. Clients should use the URLs returned by server objects instead of deriving filesystem paths.