API reference
Uploads
Attachments and images: ask for a slot, send the bytes to its URL, finalize, then use the upload in a message or a profile. Files are served from a separate media host or the storage bucket, never the app's origin.
Every path is relative to https://api.palloo.app/v1.
Send a bot token as Authorization: Bearer palloo_bot_... unless the operation says otherwise; see Authentication.
Try it
Send real requests to https://api.palloo.app/v1 from this page. Open Try it under an operation, fill in its fields and send it.
A bot token is a password.
Anyone who has it can act as your bot. Use a test bot in a test server, never a production bot. The token stays in this tab's memory only: it is not saved, and it goes to the API and nowhere else. Reloading or leaving the page forgets it.
Starts with palloo_bot_. People's tokens are refused here.
On this page
Create attachment slots
/channels/{id}/attachments
Bot token: Call it with a bot token (people can call it too).
Ask for upload slots for up to 10 files to attach to a message. Requires Send Messages and Attach Files; bots included.
Step 1 of the upload flow. For each file, the response gives upload_url,
upload_method and upload_headers. Send the file's bytes to upload_url with exactly
those headers and nothing else (no Authorization): with S3 storage the URL is a
presigned URL of the bucket, which checks the size and content type it was signed for;
with disk storage it is PUT /uploads/{id}/content on this server. Then call
POST /uploads/{id}/finalize for each file and send the message with
attachments: [{upload_id}] within 24 hours. Slots expire after an hour.
Limits: max_upload_bytes per file and daily_upload_bytes in any 24 hours (both in
GET /instance), and at most 50 unfinished uploads per account.
Required permissions
Requires Send Messages and Attach Files; bots included.
Try it: POST /channels/{id}/attachments
Sends the bot token from the top of this page.
Prefilled from the example above. Replace the placeholder values; unknown fields are refused.
Request
POST https://api.palloo.app/v1/channels/{id}/attachments
curl command
Response
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id Required | path | string |
Channel ID. |
Request body
application/json:
CreateAttachmentSlotsRequest
(required)
| Field | Type | Description |
|---|---|---|
| filesRequired | array of UploadFile | at least 1 items; at most 10 items |
{
"files": [
{
"filename": "string",
"size": 1,
"content_type": "string"
}
]
}
Responses
- 201
-
One slot per file, in request order.
Body: UploadSlotList
Cache-Controlheader.Example response (201) { "attachments": [ { "id": "01J9ZQ3V5K8M2N4P6R8T0W2Y4A", "filename": "string", "size": 0, "content_type": "string", "upload_url": "string", "upload_method": "PUT", "upload_headers": { "key": "string" }, "upload_expires_at": "2026-10-15T09:00:00Z", "expires_at": "2026-10-15T09:00:00Z" } ] } - 400
-
invalid_body,invalid_upload(a file without a name, a size or a valid content type) ortoo_many_files.Body: Error
- 401
-
unauthenticated: the bearer token is missing, invalid or expired.Body: Error
WWW-Authenticateheader. - 403
-
missing_permission(Send Messages or Attach Files, named inmissing_permissions),timed_out(withtimeout_until),dm_not_allowedin a DM, oruploads_restricted: malware scanning blocked three of this account's files in 24 hours, so its uploads are paused for a day (Retry-Aftersays how long).Body: Error
Retry-Afterheader. - 404
-
channel_not_found: no such channel, or the caller is not a member of its community or cannot view the channel.Body: Error
- 409
-
channel_not_text,blocking_user, orupload_limit_reached(50 unfinished uploads).Body: Error
- 413
-
file_too_large: a file is larger thanmedia.max_upload_bytes; the message says the limit.Body: Error
- 429
-
rate_limited(30 slot requests at once, then 1 every 2 seconds) orupload_quota_exceeded(the files declared in the last 24 hours would passmedia.daily_upload_bytes);Retry-Aftersays when to try again.Body: Error
Retry-Afterheader. - 500
-
internal: an unexpected server error. Details are only in server logs.Body: Error
- 503
-
storage_unavailable: file storage could not be reached; try again shortly.Body: Error
Retry-Afterheader.
Create upload slot
/uploads
Bot token: Call it with a bot token (people can call it too).
Ask for an upload slot for an avatar, a profile banner, or a community icon or banner.
Works like attachment slots. avatar and banner are for the caller (people and bots); community_icon and community_banner need community_id and Manage Community there. The file must be a PNG, JPEG, GIF or WebP image. After finalizing, set it with PATCH /users/@me or PATCH /communities/{id}.
Try it: POST /uploads
Sends the bot token from the top of this page.
Prefilled from the example above. Replace the placeholder values; unknown fields are refused.
Request
POST https://api.palloo.app/v1/uploads
curl command
Response
Request body
application/json:
CreateUploadRequest
(required)
| Field | Type | Description |
|---|---|---|
| purposeRequired | string | The One of |
| community_id | ID | Required for the community purposes |
| filenameRequired | string | |
| sizeRequired | integer | at least 1 |
| content_typeRequired | string |
{
"purpose": "avatar",
"community_id": "01J9ZQ3V5K8M2N4P6R8T0W2Y4A",
"filename": "string",
"size": 1,
"content_type": "string"
}
Responses
- 201
-
The slot.
Body: UploadSlot
Cache-Controlheader.Example response (201) { "id": "01J9ZQ3V5K8M2N4P6R8T0W2Y4A", "filename": "string", "size": 0, "content_type": "string", "upload_url": "string", "upload_method": "PUT", "upload_headers": { "key": "string" }, "upload_expires_at": "2026-10-15T09:00:00Z", "expires_at": "2026-10-15T09:00:00Z" } - 400
-
invalid_body,invalid_upload(unknown purpose, orcommunity_idmissing or extra) orunsupported_type(not an image).Body: Error
- 401
-
unauthenticated: the bearer token is missing, invalid or expired.Body: Error
WWW-Authenticateheader. - 403
-
missing_permission, oruploads_restricted: malware scanning blocked three of this account's files in 24 hours, so its uploads are paused for a day (Retry-After).Body: Error
Retry-Afterheader. - 404
-
community_not_found: no such community, or the caller is not a member. The two cases are indistinguishable on purpose.Body: Error
- 409
-
upload_limit_reached: 50 unfinished uploads.Body: Error
- 413
-
file_too_large: a file is larger thanmedia.max_upload_bytes; the message says the limit.Body: Error
- 429
-
rate_limited(30 slot requests at once, then 1 every 2 seconds) orupload_quota_exceeded(the files declared in the last 24 hours would passmedia.daily_upload_bytes);Retry-Aftersays when to try again.Body: Error
Retry-Afterheader. - 500
-
internal: an unexpected server error. Details are only in server logs.Body: Error
- 503
-
storage_unavailable: file storage could not be reached; try again shortly.Body: Error
Retry-Afterheader.
Receive upload
/uploads/{id}/content
No token: Needs no token.
The upload target of a slot with disk storage. Authenticated by the slot's X-Upload-Token header.
Only reached through a slot's upload_url and upload_headers; with S3 storage files go straight to the bucket instead. Content-Type and Content-Length must be the ones the slot was created for. A slot takes one upload.
Try it: not available. This operation takes a file, and Try it sends JSON only; use curl instead.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id Required | path | string |
Upload ID. |
| X-Upload-Token Required | header | string |
Request body
*/*:
string (binary)
(required)
Responses
- 204
-
Received. Finalize it next.
- 400
-
upload_size_mismatchorcontent_type_mismatch: the body is not what the slot was created for.Body: Error
- 404
-
upload_not_found: no such slot, or the token is wrong. These are indistinguishable.Body: Error
- 409
-
upload_already_received.Body: Error
- 410
-
upload_expired: the slot or the finished upload expired; start again.Body: Error
- 429
-
rate_limited: too many attempts. Every operation a bot calls is also limited per bot (50 at once, then 50 per second), so any authenticated operation can answer this to a bot. Changes to a community's structure (creating or changing the community, channels, roles, member roles, overwrites, invites, kicks, bans, timeouts, report updates and actions, and bots) are also limited per caller (30 at once, then 1 per second) and per community (60 at once, then 1 per second), and creating communities per caller (10 at once, then 1 every 5 minutes).Body: Error
Retry-Afterheader. - 500
-
internal: an unexpected server error. Details are only in server logs.Body: Error
- 503
-
storage_unavailable: file storage could not be reached; try again shortly.Body: Error
Retry-Afterheader.
Finalize upload
/uploads/{id}/finalize
Bot token: Call it with a bot token (people can call it too).
Check and process an uploaded file. Safe to call again; concurrent calls get the same result.
The server reads the file, decides its type from its content (never from the declared type), and for images decodes them, applies the EXIF orientation, strips all metadata by re-encoding, makes a thumbnail and a placeholder, and checks them against the instance's abuse hash lists. WebP images are stored as JPEG or PNG. Only the caller's own uploads can be finalized; anyone else's is 404.
Try it: POST /uploads/{id}/finalize
Sends the bot token from the top of this page.
Request
POST https://api.palloo.app/v1/uploads/{id}/finalize
curl command
Response
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id Required | path | string |
Upload ID. |
Responses
- 200
-
The finished upload.
Body: Upload
Example response (200) { "id": "01J9ZQ3V5K8M2N4P6R8T0W2Y4A", "purpose": "attachment", "state": "ready", "filename": "string", "size": 0, "content_type": "string", "channel_id": "string", "community_id": "string", "width": 0, "height": 0, "placeholder": "string", "animated": false, "expires_at": "2026-10-15T09:00:00Z" } - 400
-
upload_size_mismatch,content_type_mismatch(the declared type was an image, video or audio type and the content is not),unsupported_type(a profile image that is not an image),invalid_image(the content is a PNG, JPEG, GIF or WebP image that cannot be decoded; it is never stored as a plain file),image_too_large(over 40 million pixels, more than 1000 frames, more than 192 MiB once decoded such as a 16-bit PNG over 25 million pixels or a large progressive JPEG, more than 100 JPEG scans, an image file over 50 MiB, an animated avatar or icon over 1024 pixels on a side, or an animated banner over 2048 pixels on a side),upload_rejected,upload_malware(malware scanning is on and the file was blocked as malware; it is not stored, and staff see the detection in the audit log), or for stickerssticker_too_large,sticker_explicitorsticker_too_slow(decoding took longer than the 2 second limit, which can happen when the server is busy; a new upload of the same file may succeed).Body: Error
- 401
-
unauthenticated: the bearer token is missing, invalid or expired.Body: Error
WWW-Authenticateheader. - 403
-
terms_acceptance_required(accept the current terms withPOST /users/@me/terms, then retry),uploads_restricted(malware scanning blocked three of this account's files in 24 hours, so its uploads are paused for a day), oruploads_paused(five of this account's files were refused withfile_too_large_to_scanbecause they hit the scanner's limits within an hour, so its files other than images are not scanned or accepted until the hour is over). The upload stays unfinished;Retry-Aftersays when to finalize it again.Body: Error
Retry-Afterheader. - 404
-
upload_not_found.Body: Error
- 409
-
upload_incomplete(the bytes have not arrived yet; finalize again after the upload) orupload_changed(the bytes were replaced while the server processed them; upload into a new slot).Body: Error
- 410
-
upload_expired: the slot or the finished upload expired; start again.Body: Error
- 413
-
file_too_large_to_scan: malware scanning is on and the file could not be scanned whole: it is larger than the scanner accepts and the instance refuses those (PALLOO_CLAMAV_OVERSIZE=reject), or it hit one of the scanner's limits (archive depth, file count, expanded size or scan time) and the instance refuses those (PALLOO_CLAMAV_LIMITS=reject, the default). It is not retried; send a different file.Body: Error
- 429
-
rate_limited: too many finalize calls, or this address recently had uploads refused withupload_rejectedorupload_malware(the EICAR test file does not count).Body: Error
Retry-Afterheader. - 500
-
internal: an unexpected server error. Details are only in server logs.Body: Error
- 503
-
storage_unavailable,scan_unavailable(a scanner is down; the upload is held and checked again in the background, so finalize again later and it may already be ready), orupload_processing(still being processed after 20 seconds; processing continues on the server whether or not the client waits, so finalize again afterRetry-After).Body: Error
Retry-Afterheader.