Developer overview
Connect your own services to DonutChat over HTTPS and JSON. Post alerts into a chat, or build a bot that people can talk to.
What you can build
Section titled “What you can build”- Notifications: post build results, monitoring alerts or scheduled reports into a chat.
- Bots: give your service its own name and picture. A bot posts in every chat it’s added to, receives what people send it, and replies in threads.
- AI agents: stream an answer into a thread as it’s written, show progress while the agent works, and let people answer with buttons.
Choose an integration
Section titled “Choose an integration”| Integration | Direction | What it does | Credential |
|---|---|---|---|
| Incoming webhook | Your service to one chat | Posts plain-text messages. No bot needed. | Secret URL |
| Bot HTTP API | Your service to the bot’s chats | Posts messages and thread replies as the bot, updates a reply while it’s written, shows typing, and downloads files people share. | Bot token |
| Event stream | The bot’s chats to your service | Sends people’s messages and thread replies, reactions, and notices when the bot is added to or removed from a chat, over a WebSocket. | Bot token |
| Event webhooks | The bot’s chats to your service | Sends people’s messages, thread replies and reactions to your HTTPS endpoint. | Signing secret |
| Interactive cards | Inside a bot’s thread replies | Shows facts and up to 4 buttons; selecting a button posts a reply that mentions the bot. | Bot token |
An incoming webhook is the quickest way to post into one chat. Use a bot when your service needs one identity across several chats, needs to hear from people, or answers in threads.
You set up both in the DonutChat apps. In a group, the owner and managers create incoming webhooks in Chat info on iPhone, Android or the web app; in a feed chat, only the owner can. The desktop app can’t manage webhooks. You create bots in My Bots in any app (desktop app: Settings > App > My bots).
Base URLs
Section titled “Base URLs”| API | URL |
|---|---|
| Bot HTTP API | https://api.donutchat.com/bots/v1/ |
| Event stream | wss://api.donutchat.com/bots/v1/stream |
| Incoming webhooks | The URL you copy from the app, such as https://donutchat.com/webhooks/v1/{webhook_id}/{token} |
Bot API endpoints
Section titled “Bot API endpoints”| Endpoint | Purpose |
|---|---|
POST /bots/v1/messages | Post a message in a chat |
GET /bots/v1/chats | List the chats the bot belongs to |
POST /bots/v1/typing | Show or clear a typing indicator |
POST /bots/v1/thread-replies | Reply in a thread |
POST /bots/v1/thread-replies/stream | Update a reply while it’s written, then finish it |
POST /bots/v1/thread-replies/activity | Report progress while a reply is written |
GET /bots/v1/stream | Receive events over a WebSocket |
GET /bots/v1/chats/{chat_id}/messages/…/attachments/{attachment_id}/content | Download a file someone shared |
The APIs can’t create bots, add them to chats or change their trigger modes; you do that in the apps. They also can’t edit or delete posted messages (a bot can only update its own thread replies), add reactions, upload files, or read chat history or member lists.
Conventions
Section titled “Conventions”Requests and responses
Section titled “Requests and responses”- Send request bodies as JSON with
Content-Type: application/json. - Bot requests need an
Authorization: Bearerheader with the bot’s token. See Authentication. - A successful request returns HTTP 200 and a JSON object with
"ok": true. (File downloads return the file itself, and the event stream upgrades to a WebSocket.) - An error returns a JSON object with an
errorcode for your code to check and amessagefor people:
{ "ok": false, "error": "not_in_chat", "message": "Bot is not a member of this chat"}A few responses aren’t JSON:
- A request with the wrong HTTP method gets 405 with an empty body and an
Allowheader that names the right method. - A URL that doesn’t match any endpoint gets 404 with the text
404 page not found. - While DonutChat is briefly unavailable, for example during maintenance, requests can get 503 with the text
server is not ready. Retry with backoff.
Endpoints ignore fields they don’t recognize, except POST /bots/v1/thread-replies/activity, which rejects them. No endpoint takes an idempotency key, so retrying a request that timed out can post the same message twice.
Call the APIs from your server. The HTTP endpoints don’t send CORS headers, so cross-origin calls from a browser fail, and a token in browser code is visible to anyone who opens the page.
Text limits are in UTF-8 bytes
Section titled “Text limits are in UTF-8 bytes”Message and reply text can be up to 80,000 UTF-8 bytes. Limits on sender names and typing status text are in bytes too. DonutChat counts the bytes of the text itself, not of its escaped JSON form. An English letter, digit or space is 1 byte. Accented letters and characters from other scripts take 2 to 4 bytes each. An emoji usually takes 4 bytes, and combined emoji such as flags take 8 bytes or more.
To check a length, use Buffer.byteLength(text, 'utf8') in Node.js or len(text.encode('utf-8')) in Python.
IDs are 64-bit integers
Section titled “IDs are 64-bit integers”Chat, message and reply IDs are 64-bit integers. Send them as JSON numbers: a quoted ID such as "chat_id": "1234" is rejected with 400 invalid_json (invalid_activity on POST /bots/v1/thread-replies/activity).
Chat, message and reply IDs can be larger than JavaScript’s Number.MAX_SAFE_INTEGER (2^53 − 1), and chat IDs usually are. JSON.parse() and response.json() round such numbers without any error, so requests that use the rounded ID fail or point at the wrong message:
JSON.parse('{"chat_id": 9007199254740993}').chat_id; // 9007199254740992In Node.js 22 or later, read large integers as BigInt and write them back as plain JSON numbers:
const parseJSON = (text) => JSON.parse(text, (key, value, context) => Number.isInteger(value) && !Number.isSafeInteger(value) ? BigInt(context.source) : value);
const toJSON = (value) => JSON.stringify(value, (key, v) => (typeof v === 'bigint' ? JSON.rawJSON(v.toString()) : v));
const event = parseJSON('{"chat_id": 9007199254740993, "message_id": 5678}');event.chat_id; // 9007199254740993ntoJSON({ chat_id: event.chat_id, content: 'Hi' }); // '{"chat_id":9007199254740993,"content":"Hi"}'Small IDs stay ordinary numbers, so compare IDs with String(a) === String(b) and use String(id) to print them or use them as map keys. Python’s json module and 64-bit integer types in other languages keep IDs exact without extra work.
Get started
Section titled “Get started”Guides: Connect a Hermes agent, Build and deploy notifications and Progress and stopping. Reference: Errors and Limits.