Uploads and files
These endpoints are plain HTTP, not Connect. Paths are relative to the server's base URL.
| Endpoint | Auth | What |
|---|---|---|
POST /upload |
Bearer | Upload one file. |
GET /files/{id}/{name}?exp=...&sig=... |
signed URL | Download a file. |
GET /emoji |
Bearer | List custom emoji. |
GET /emoji/{id} |
none | One emoji image. |
POST /emoji |
Bearer, moderator or higher | Add emoji, zips too. |
GET /emoji.zip |
Bearer | Export every emoji. |
GET /invite/{code}, GET /i/{code} |
none | Redirect to the join screen. |
GET /healthz |
none | Answers ok. |
Upload a file
Send multipart/form-data with one part named file, and the same Bearer token as for calls. Files are up to 25 MB.
curl https://chat.example.com/upload \
-H 'Authorization: Bearer ...' \
-F [email protected]
The reply is the Attachment as protojson. size is a string, because it is an int64:
{"id":"01a1...","name":"cat.png","contentType":"image/png","size":"14211",
"url":"/files/01a1.../cat.png?exp=1792000800&sig=...","width":640,"height":480}
Then send the id in SendMessage.attachment_ids. A message takes up to 10, and only your own unused uploads. An upload not sent in a message within a day is deleted.
Errors are {"error": "..."} with status 400, 401, 403 or 413.
Images for settings
Banners, community icons, channel headers and per-community profile pictures are uploads too. Upload the image, then pass its id:
| Image | Call |
|---|---|
| Community banner | UpdateServerSettings{banner_id} |
| Community icon | UpdateServerSettings{icon_id} |
| Channel header | UpdateChannel{header_image_id} |
| Profile picture in this community | UpdateProfile{avatar_url} with the upload's url |
The upload must be your own and unattached, PNG, JPEG, GIF, WebP or AVIF, and at most 8 MB. "" removes the image.
Download a file
Use Attachment.url exactly as the server returns it. It is signed: HMAC-SHA256 over the id and the expiry, with the key in <data>/file-key.
- The server signs URLs only for members who may see the file, and for the uploader of an unattached upload.
- No header is needed, so
<img src>works. - A URL is valid for one to two hours.
expis rounded up to the hour, so the URL stays the same for an hour and the browser can cache it. The response isCache-Control: private, immutablewith amax-agethat ends when the URL expires, so no shared cache keeps a file. - A missing, wrong or expired signature returns 403. Get fresh URLs by listing the messages again.
- Removing a member does not revoke URLs they already hold.
The server sniffs the content type, AVIF and HEIC too. width and height are set for PNG, JPEG, GIF, WebP, BMP, AVIF and HEIC. Images, audio and video are served inline. Everything else is a download, with nosniff and a sandbox CSP.
Custom emoji
Messages and reactions use a custom emoji as :name:. Names are 1 to 62 of a-z, 0-9, - and _, unique on the server. Unknown names stay plain text.
List
GET /emoji returns ListEmojiResponse as protojson, sorted by name. It is gzipped when asked, with a weak ETag and Cache-Control: private, no-cache. Send If-None-Match to get 304 when nothing changed. A browser fetch with cache: "no-cache" does this by itself.
After any change every member gets the emoji_changed event. Fetch the list again then.
Images
GET /emoji/{id} is the url from the list. Ids are never reused, so the image is public, immutable and cached for a year. Anyone with the URL can load it.
Add
POST /emoji takes multipart/form-data with any number of file parts, 64 MB in total. Moderator or higher.
- Each part is a PNG, GIF (animated is fine) or WebP of at most 256 KB and 512x512, or a .zip of them.
- Folders inside a zip are fine.
__MACOSXand dotfiles are skipped. - The name comes from the file name: lowercased, and other characters become
_. - A taken name is skipped, never replaced.
The reply is UploadEmojiResponse, with one result per image: added, exists, or rejected with a reason.
Export and delete
GET /emoji.zip returns every emoji as name.ext. It uploads as is to another server. Delete one with the DeleteEmoji call, moderator or higher.
Invite links
GET /invite/{code} and GET /i/{code} redirect a browser to the web client's join screen: {web}/#/join?url={base}&invite={code}. The web client is set with -web-url.
Tools and agents can read every call and type from schema.json.
View this page as Markdown · Glossary · API schema.json · openapi.json