HTTP reference for the surface your Birdie VM calls when it delivers messages back through @birdieagentbot. If you just want to chat with your assistant on Telegram, you don’t need this page.
Every paying Birdie user runs their own Hetzner VM with OpenClaw and the birdie-channel plugin installed. The plugin calls this API over HTTPS to send replies, media, reactions, edits, and inline keyboards back to your Telegram chat.
https://birdie6.com/birdie-apiX-Birdie-API-Key header, one per userchat_idOn first payment, the router provisions your VM and writes BIRDIE_API_KEY into /etc/default/openclaw on it. If you want to use the API from your own code on that VM, read it from there.
curl https://birdie6.com/birdie-api/sendMessage \
-H "Content-Type: application/json" \
-H "X-Birdie-API-Key: birdie_xxxxxxxxxxxxxxxx" \
-d '{"chat_id":"123456789","text":"Hello from my VM."}'
200 OK with { "ok": true, "result": { ... } }. Errors return { "error": "..." } with a 400/401/403/500 status.
Every request body carries chat_id. The router rejects with 403 Chat ID mismatch if it doesn’t match the owner of the API key. There is no way to send to another user’s chat — this is deliberate.
Media fields (photo, video, document, …) accept any of three forms:
https://…) — Telegram fetches it directly.file_id from a previous send or inbound message.b64:<base64 payload>, plus optional <field>_filename and <field>_content_type hints.{
"chat_id": "123456789",
"photo": "b64:iVBORw0KGgoAAAANSUhEUg...",
"photo_filename": "chart.png",
"photo_content_type": "image/png",
"caption": "Today’s numbers"
}
Any extra fields in the body (parse_mode, reply_markup, reply_to_message_id, disable_web_page_preview, …) are forwarded to the Bot API unchanged. See the Telegram Bot API reference for the full list.
Send a text message.
| Field | Type | Notes |
|---|---|---|
chat_id | string | Required. Your chat ID. |
text | string | Required. |
parse_mode | string | Optional. Markdown, MarkdownV2, or HTML. |
reply_markup | object | Optional inline keyboard. |
Send a photo. photo is a URL, file_id, or b64: payload.
Send any file. Use for non-media attachments or anything over 10 MB.
Send an audio track (MP3/M4A). performer, title, duration optional.
Send a voice note (OGG/Opus). Renders as a waveform in Telegram.
Send a video (MP4). duration, width, height, supports_streaming optional.
Send a GIF/MP4 animation without sound.
Send a round video note. Must be square MP4, typically ≤ 1 min.
Send a WebP/TGS sticker by file_id or URL.
Required: latitude, longitude. Optional: live_period.
Required: latitude, longitude, title, address.
Required: phone_number, first_name. Optional: last_name.
Send a dice/emoji animation. Optional emoji: 🎲 🎯 🏀 ⚽ 🎳 🎰.
Required: question, options (array of 2–10 strings).
Album of 2–10 items. media is an array of {type, media, caption?}.
Show typing/uploading indicators. action: typing, upload_photo, record_voice, …
Respond to an inline-keyboard button press. Requires callback_query_id; optional text, show_alert, url.
Required: text, plus either chat_id+message_id or inline_message_id.
Same targeting rules. Required: caption.
Swap the media of a previously sent message. Required: media object.
Change or remove an inline keyboard. Required: reply_markup.
Required: chat_id, message_id.
Required: chat_id, from_chat_id, message_id. Both chat IDs must match the API key owner.
Same fields as forwardMessage. Copies without the “forwarded from” header.
| Status | Meaning |
|---|---|
401 | Missing or unrecognised X-Birdie-API-Key. |
403 | chat_id doesn’t belong to the key’s owner. |
400 | Malformed body or missing required field. |
429 | Rate limit exceeded. Response carries a Retry-After header (seconds) and {"retry_after": N} in the body. |
500 | Telegram rejected the call or the router hit an internal error. The error field carries the underlying message. |
Each API key gets a token bucket: 60 requests per minute with a burst of 20. That’s enough for normal chat, a morning briefing, a cron digest, or a small media group. Beyond that you’ll start getting 429s — back off for the number of seconds in Retry-After and try again.
On top of that, Telegram’s own Bot API limits still apply (roughly 1 msg/sec per chat, 20/min for group bursts). You also share the @birdieagentbot identity with every other Birdie user — be kind.