Attachments
Events tell your bot which files people attached to a message or thread reply, and the bot API lets you download each file. A bot can also attach files to its own thread replies when its owner allows it: see Attach files. Bots can’t attach files to top-level messages.
Attachment fields
Section titled “Attachment fields”message.new and thread.reply.new events always include an attachments array, which is [] when there are no files. Events don’t contain file URLs.
{ "event_id": "evt_1793957400_3f9a1c2b7d4e", "type": "message.new", "timestamp": "2026-11-06T09:30:00Z", "data": { "message_id": 5678, "chat_id": 1234, "chat_name": "Release team", "chat_type": "group", "sender": { "id": 42, "kind": "user", "name": "Alice Kim", "username": "alice" }, "text": "@releasebot what does this error mean?", "content_type": "TX", "message_type": "image", "attachments": [ { "id": 2468, "file_type": "image", "mime_type": "image/png", "original_filename": "build-error.png", "file_size": 284113, "width": 1170, "height": 2532 } ], "mentions_bot": true, "thread_reply_count": 0 }}| Field | Type | Description |
|---|---|---|
id | integer | The attachment’s ID. Use it in the download URL. |
file_type | string | image, video, audio, document or other. |
mime_type | string | The file’s MIME type. |
original_filename | string | The file’s name when it was shared. |
file_size | integer | Size in bytes. |
width, height | integer | Dimensions in pixels. |
duration_ms | integer | Length in milliseconds. |
Only id and file_type are always present. The other fields are left out when they’re empty or zero.
Download a file
Section titled “Download a file”For a file on a top-level message, use chat_id, message_id and the attachment’s id from the message.new event:
https://api.donutchat.com/bots/v1/chats/{chat_id}/messages/{message_id}/attachments/{attachment_id}/content For a file on a thread reply, use chat_id, parent_message_id, reply_id and the attachment’s id from the thread.reply.new event:
https://api.donutchat.com/bots/v1/chats/{chat_id}/messages/{parent_message_id}/replies/{reply_id}/attachments/{attachment_id}/content The parent and linked_messages objects in an event carry their own attachments. Download those with the same two endpoints, using the object’s message_id (and its reply_id when it has one) instead of the event’s.
A download works when:
- The request has your bot token in an
Authorization: Bearerheader. - The bot is an active member of the chat.
- The message or reply still exists and belongs to that chat (and, for a reply, to that parent message).
- The attachment belongs to that message or reply and is still available.
- The file is at most 10 MiB (10,485,760 bytes).
curl -fsS -o build-error.png \ -H "Authorization: Bearer donutbot_YOUR_TOKEN_HERE" \ https://api.donutchat.com/bots/v1/chats/1234/messages/5678/attachments/2468/content// Node.js 22 or later. `type` and `data` come from an event parsed so that// 64-bit IDs stay exact (see the Event stream example).const API = 'https://api.donutchat.com/bots/v1';const TOKEN = process.env.DONUT_BOT_TOKEN;
async function downloadAttachment(type, data, attachment) { const source = type === 'thread.reply.new' ? `messages/${data.parent_message_id}/replies/${data.reply_id}` : `messages/${data.message_id}`; const url = `${API}/chats/${data.chat_id}/${source}/attachments/${attachment.id}/content`;
const res = await fetch(url, { headers: { Authorization: `Bearer ${TOKEN}` } }); if (!res.ok) throw new Error(`Download failed: HTTP ${res.status} ${await res.text()}`); return { mimeType: res.headers.get('content-type'), bytes: Buffer.from(await res.arrayBuffer()), };}A successful download returns 200 with the file’s bytes as the body and these headers:
| Header | Value |
|---|---|
Content-Type | The file’s MIME type |
Content-Length | The file’s size in bytes |
Content-Disposition | attachment |
Cache-Control | private, no-store |
X-Content-Type-Options | nosniff |
Treat original_filename as text from a chat member: don’t use it as a file path without cleaning it up.
Errors
Section titled “Errors”Errors return JSON in the form {"ok": false, "error": "<code>", "message": "<text>"}.
| HTTP | error | Cause |
|---|---|---|
| 400 | invalid_id | An ID in the path isn’t a positive 64-bit integer. |
| 401 | unauthorized | The token is missing or invalid, or the bot is paused. |
| 403 | attachment_unavailable | The bot isn’t an active member of the chat. |
| 404 | attachment_unavailable | The message, reply or attachment doesn’t exist, was deleted or has expired, or the IDs don’t belong together. For files on top-level messages, a bot that isn’t in the chat also gets 404. |
| 413 | attachment_unavailable | The file is larger than 10 MiB. |
| 429 | busy | Too many downloads are in progress. Wait a moment and try again. |
| 503 | unavailable | Downloads are temporarily unavailable. Try again later. |
A request with a method other than GET gets 405 with an empty body.