Bot Send (HTTP API)
The Bot Send API lets an external service post messages into any chat the bot has been added to, using a single bearer token. Messages appear under the bot’s identity (name, avatar), not the human who created the bot.
At a glance
Section titled “At a glance”| Direction | Bot → chat |
| Auth | Authorization: Bearer vifbot_<token> |
| Transport | POST over HTTPS (application/json) |
| Typical use case | AI agents, assistants, automations that need a persistent identity across multiple chats |
When to use this
Section titled “When to use this”Pick a bot when the sender needs a reusable identity — one token can post into any chat the bot has been added to, and messages render with the bot’s name and avatar. If you just need to post an alert into a single chat without an identity, an Incoming Webhook is simpler. If your bot also needs to receive messages and react, pair this with Bot Receive.
For users
Section titled “For users”import { Steps } from ‘@astrojs/starlight/components’;
- Open the DonutChat app → Bots → Create bot.
- Enter a name, a unique username, and an optional description. Tap Create.
- DonutChat shows the bot’s token (starts with
vifbot_). Copy it — the token is shown only once. - Add the bot to a chat: open the chat → Bots → Add bot → pick your bot.
- Repeat step 4 for every chat the bot should be able to post into.
import { Aside } from ‘@astrojs/starlight/components’;
For developers
Section titled “For developers”Endpoint
Section titled “Endpoint”POST https://api.donutchat.com/bots/v1/messagesRequest headers
Section titled “Request headers”| Header | Value |
|---|---|
Authorization | Bearer vifbot_<token> |
Content-Type | application/json |
Request body
Section titled “Request body”{ "chat_id": 1234, "content": "Standup starts in 5 minutes.", "username": "Standup Bot", "avatar_url": "https://example.com/bot-avatar.png"}| Field | Type | Required | Notes |
|---|---|---|---|
chat_id | int64 | yes | ID of the target chat. The bot must already be a member. |
content | string | yes | Message body. Max 4000 UTF-8 bytes (multi-byte characters like CJK or emoji count as multiple bytes). |
username | string | no | Per-message override of the bot’s display name. |
avatar_url | string | no | Per-message override of the bot’s avatar URL. |
Samples
Section titled “Samples”import { Tabs, TabItem } from ‘@astrojs/starlight/components’;
curl -X POST \ "https://api.donutchat.com/bots/v1/messages" \ -H "Authorization: Bearer vifbot_YOUR_TOKEN_HERE" \ -H "Content-Type: application/json" \ -d '{ "chat_id": 1234, "content": "Standup starts in 5 minutes." }'const TOKEN = process.env.VIFBOT_TOKEN;
const response = await fetch('https://api.donutchat.com/bots/v1/messages', { method: 'POST', headers: { 'Authorization': `Bearer ${TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ chat_id: 1234, content: 'Standup starts in 5 minutes.', }),});
if (!response.ok) { const err = await response.json(); throw new Error(`${err.error}: ${err.message}`);}
const { message_id, chat_id } = await response.json();console.log('Posted message', message_id, 'to chat', chat_id);Response
Section titled “Response”Success — HTTP 200:
{ "ok": true, "message_id": 98765, "chat_id": 1234}Error — HTTP 4xx or 5xx:
{ "ok": false, "error": "<code>", "message": "<human-readable>"}Error codes
Section titled “Error codes”| HTTP status | error code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_json | Body was not valid JSON | Send a well-formed JSON body |
| 400 | missing_chat_id | chat_id is missing or not positive | Provide a valid int64 chat_id |
| 400 | missing_content | content field is empty or missing | Provide a non-empty content string |
| 400 | content_too_long | content exceeds 4000 bytes | Split or shorten the message |
| 401 | unauthorized | Token is missing, invalid, or the bot has been disabled | Confirm the Authorization header shape; re-enable the bot in the app if it was deactivated; regenerate the token if compromised |
| 403 | not_in_chat | Bot is not a member of the target chat | Add the bot to the chat first |
| 405 | method_not_allowed | Used a method other than POST | Use POST |
| 500 | send_failed | Internal error while delivering the message | Retry with exponential backoff |
Limits and gotchas
Section titled “Limits and gotchas”- Message size:
contentis capped at 4000 UTF-8 bytes. Multi-byte characters (CJK, emoji) can hit the cap below 4000 visible characters; if you send rich content, size onBuffer.byteLength(s, 'utf8')before posting. - Identity override precedence: per-request
username/avatar_url→ bot’s configured defaults. Requests with neither field use the bot’s defaults. - Trigger mode does not gate manual sends. A bot configured with
trigger_mode: manualormentionstill accepts directPOST /bots/v1/messagescalls. Trigger mode only governs auto-replies driven by Bot Receive events. - Token format: bot tokens start with
vifbot_. Anything else is not a valid bot token. - Per-chat membership is required every time. Creating a bot does not auto-add it to any chat.
Related
Section titled “Related”- Incoming Webhooks — simplest one-way post into a specific chat, no identity.
- Bot Receive — receive real-time chat events so your bot can react.