Developers

Bot API reference

Everything you need to build a Blackbird bot: set-up, every method with examples, the objects your bot receives, and the rules.

The download is the whole reference in one Markdown file, for your notes or your tools.

Introduction

Blackbird bots are programs that chat. You create a bot in the app, get its token, and your code talks to the Bot API over HTTPS with JSON. People find the bot by its @username, press Start, and chat with it like with anyone else. Admins can add bots to groups.

The API follows the shape of Telegram's Bot API, so code written for Telegram needs few changes: the base address and the token header. Telegram libraries put the token in the address, which Blackbird refuses unless the bot's owner turns on Bot Studio › Settings › Telegram libraries; then they work with only the base address changed. See Coming from Telegram.

Base addresshttps://api.blackbirdtalk.com/bot/v1/
AuthAuthorization: Bearer <token> on every request (bearer in any case). Not in the address: /bot<token>/... answers 401, unless the owner turned on Telegram libraries
FormatJSON in, JSON out. Form-encoded bodies are accepted too.
Method namesCase-insensitive: sendMessage, sendmessage and SENDMESSAGE are the same.

Official libraries

The fastest way to build a bot. Both libraries cover every method on this page, check your buttons against the same rules the server uses before sending, retry safely (a message is never sent twice), handle each chat in order, and come with a fake server for tests. They are generated from the same API description as this page, so a new method reaches them with the next deploy.

InstallNeeds
Pythonpip install blackbirdtalkPython 3.11+
JavaScript / TypeScriptnpm install @blackbirdtalk/botNode.js 22+, Deno, Bun or Workers
from blackbirdtalk import Bot, Button

bot = Bot()  # reads BLACKBIRD_TOKEN

@bot.command("start")
async def start(msg):
    await msg.answer("Welcome!", buttons=[[
        Button("Pay ₹49", callback="pay_49", style="main", icon="card")]])

@bot.button("pay_49")
async def pay(tap):
    await tap.answer("Payment started")

bot.run()

Each library prints one line a day when a newer version is out. New fields from the server show up on objects even before you update, and a method or parameter newer than your library can still be passed. Their guides: blackbirdtalk on PyPI and @blackbirdtalk/bot on npm. Start a project with blackbirdtalk new mybot (Python) or npx @blackbirdtalk/bot new mybot, and check a token with blackbirdtalk check.

The rest of this page is the HTTP API itself, for other languages and for the details. Each method shows its library name.

Quick start

  1. In the app open Settings › Bot Studio › New bot. Pick a name and an @username ending in bot. Your account must be at least 7 days old; you can own up to 20 bots.
  2. Copy the token from the bot's Overview tab. You can copy it again at any time, or Replace it if it leaks: the old one stops working at once (401) and the bot's webhook is removed, so set it again with the new token.
  3. Call getMe to check the token, then read updates with getUpdates and answer with sendMessage.
curl -H "Authorization: Bearer $TOKEN" https://api.blackbirdtalk.com/bot/v1/getMe

A complete echo bot with plain HTTP (with a library it is a few lines, see Official libraries):

import requests

TOKEN = "bb_..."  # from Bot Studio
API = "https://api.blackbirdtalk.com/bot/v1/"
H = {"Authorization": "Bearer " + TOKEN}

offset = 0
while True:
    r = requests.post(API + "getUpdates", headers=H,
                      json={"offset": offset, "timeout": 25}, timeout=40)
    for u in r.json()["result"]:
        offset = u["update_id"] + 1
        m = u.get("message")
        if m and m.get("text"):
            requests.post(API + "sendMessage", headers=H, json={
                "chat_id": m["chat"]["id"],
                "text": "You said: " + m["text"],
            })

Making requests

Send every method as POST (or GET) to /bot/v1/<method>. Parameters go in a JSON body, a form-encoded body, or the query string. reply_markup may be an object or a JSON string.

Every answer has ok. On success the value is in result; on failure there is an error_code and a human-readable description.

{"ok": true, "result": {"message_id": 5502, "date": 1793870000, "chat": {...}}}
{"ok": false, "error_code": 400, "description": "Bad Request: text must be 1 to 4096 characters"}

The token is not accepted in the URL, where proxies and logs would keep it, unless the bot's owner turned on Telegram libraries (see Coming from Telegram).

Errors

CodeMeaningWhat to do
400A parameter is missing or wrong.Read description; it names the parameter.
401The token is missing or not valid (maybe replaced in Bot Studio).Copy the current token from Bot Studio.
403Not allowed: the person has not pressed Start, paused the bot, is not on its list (Who can use it), or the group did not give the bot that power.Don't retry; the person or admin decides.
404Unknown method.Check the name against this page.
409getUpdates while a webhook is set, or another program started polling with the same token (the older call ends: "terminated by other getUpdates request").Run one copy of your bot per token, and read updates one way: remove the webhook, or stop polling.
429Too many messages or calls.Wait parameters.retry_after seconds (also in the Retry-After header).
500Something failed on Blackbird's side.Retry later with the same client_id.

Getting updates

An update is something that happened for your bot: a message, a button tap, a group event. Read them in one of two ways, never both:

  • Polling with getUpdates: simplest, works from any computer, no public address needed.
  • Webhook with setWebhook: Blackbird POSTs each update to your HTTPS address.

One program per token. A token has one open getUpdates call at a time: when a second program starts polling, the first one's call ends with 409, as on Telegram. A new token (Replace in Bot Studio) or giving the bot away ends the old token's call at once with 401 and removes the webhook (someone holding a leaked token could have pointed it at their own server). Updates still waiting are kept for the new token.

Updates nobody collects are dropped after 24 hours (a bot whose code is down keeps its newest 20,000). Each update has an update_id and one of these fields:

FieldWhen
messageSomeone sent the bot a message (in its chat, or addressed to it in a group).
callback (also callback_query)Someone tapped one of the bot's message buttons.
my_membershipThe bot was added to or removed from a group.
memberPeople joined or left a group the bot is in.
join_requestSomeone asked to join a group where the bot can approve requests.
access_requestSomeone asked to use the bot (Who can use it: Chosen people). Has from.
accessThe owner changed Who can use it in Bot Studio. Has audience, message, allowed.
pollNew counts for one of the bot's polls, after a vote or when it closes.
poll_answerSomeone voted in a public poll: poll_id, user, option_ids (empty: vote taken back).

Webhooks

Each POST to your address carries the secret you set as the X-Blackbird-Bot-Secret header. Check it, so nobody else can feed your bot. Answer with any 2xx within 10 seconds; anything else is retried with growing gaps, and Bot Studio shows the last error. If your server is gone, remove the webhook in Bot Studio › Settings › Webhook. One webhook per bot: a new setWebhook replaces the old address, and ends any open getUpdates call.

Chat ids

You don't look up chat ids, you receive them: every message and callback has chat.id. Keep it if you want to write to that person or group later. Ids are numbers that mean something only to your bot.

  • A person's chat id is positive; a group's is negative.
  • Your bot can write to a person only after they pressed Start, and never while they have paused it.
  • To find your own chat id: send your bot any message, then read message.chat.id from getUpdates.
  • A group's id is also shown to its admins in Control center › Bots.

Who can use your bot

Each bot has one access setting: Everyone, Only me (owner) or Chosen people (chosen). It can be changed in two places, and both change the same setting: the owner in Bot Studio › Settings › Who can use it, and your code with setAccess. A change made in Bot Studio is sent to your code as an access update, so the two never disagree.

Blackbird enforces the setting on its servers, whatever your code does:

  • People who are not allowed can't find the bot in search, write to it or add it to a group. Their messages never reach your bot.
  • If your code writes to someone who is not allowed, it gets 403 with the reason.
  • Someone who opens the bot's link sees its chat with its name, photo and your access message (for example "For class 10 students only") in place of the message box. With Chosen people they can also tap Request access.
  • A request reaches your code as an access_request update, appears in Bot Studio, and shows as a line in the owner's own chat with the bot. Approve it from any of them: approveAccess in code, or one tap in Bot Studio or the chat. The person then sees a line in their chat with the bot, yes or no. These lines come from Blackbird: your code never receives them as messages.
  • The owner and chosen people can add a private bot to their groups; there it answers that group.
// Only people on the list, with a message for everyone else
{"audience": "chosen", "message": "For class 10 students only. Tap Request access.", "allow": [262527487]}

Buttons and keyboards

Pass reply_markup with one of four shapes. No emoji needed: pick an icon and the app draws it.

{"buttons":  [[{"text": "Pay 49", "style": "main", "icon": "card", "callback": "pay_49", "confirm": "Pay 49?"}]]}
{"keyboard": [[{"text": "Today"}, {"text": "Tomorrow"}], [{"text": "Share my location", "request": "location"}]],
 "one_time": true, "placeholder": "Pick a day"}
{"remove_keyboard": true}
{"force_reply": true, "placeholder": "Your name"}
Button fieldDescription
text1-64 characters.
callbackMessage buttons. Your bot gets a callback update with this data (1-64 bytes).
urlMessage buttons. An https:// address (http, javascript:, intent:, file: and the like are refused); the app shows the domain before opening it.
copyMessage buttons. Copies this text on tap.
requestKeyboard buttons. location or contact; shared only after the person agrees.
stylemain (Telegram's primary works too), normal, success, warning, danger (danger always asks first).
color#RRGGBB. The app keeps the text readable on it.
iconOne of: play pause chart bell help info check alert trash link keyboard card star close user location copy settings search calendar clock gift heart home list lock mail phone refresh send share download upload plus minus edit back next cart ticket.
sizepill, normal or full.
confirmAsk this question before acting.
disabledtrue greys the button out.

Up to 8 rows, 8 buttons a row, 48 in all. A keyboard button with no request sends its text as a message. Telegram's inline_keyboard and keyboard shapes are accepted too.

Bots in groups

An admin adds a bot in Control center › Bots if the owner allowed groups (Bot Studio › Settings). A bot is never a member: it sees only messages meant for it, which are a /command, a message with its @username, or a reply to one of its messages. In end-to-end encrypted groups the phone sends the bot a readable copy of just those messages.

What else it may do depends on the powers the admin gave it:

PowerMethods
Pin messagespinMessage, unpinMessage
Delete messagesdeleteMessage on other people's messages
Remove membersremoveMember
Approve join requestsapproveJoin, declineJoin, and join_request updates

Formatting

Send parse_mode with HTML, MarkdownV2 or Markdown (as on Telegram), or the formatting itself as entities (caption_entities for a file). The app shows bold, *italic*, underline, strikethrough, spoilers (tap to reveal), code and code blocks (tap to copy; a block shows its language and a Copy line), quotes and links. An expandable quote (<blockquote expandable>, or **> ... || in MarkdownV2) folds to three lines with Show more. A link must be http or https; the app shows its address before opening it. Hidden text stays hidden in the chat list, in notifications and in reply quotes.

{"chat_id": 262527487, "parse_mode": "HTML",
 "text": "<b>Order #4521</b> confirmed. <a href=\"https://shop.example.com/t/4521\">Track it</a>"}

Dates, times and live countdowns use Telegram's date_time: <tg-time unix="1794167173" format="r">2 hours</tg-time> (MarkdownV2: !2 hours). Format r is a live countdown that ticks in the bubble (02:14:33, 3 days 4 hours, then Ended); the bot sends nothing more. Telegram's other letters (w weekday, d/D short/long date, t/T short/long time, for example wDt) show the moment in each person's own language and time zone. A tap shows the full date. Older apps show the words the bot wrote.

The answer has the words without the marks and entities with their place (offsets in UTF-16 units). Unlike Telegram, a mark that is never closed is shown as written instead of failing the call. Older apps show the words without the formatting.

Limits

LimitValue
Messages30 a second per bot; 60 a minute into one private chat; 20 a minute into one group (edits and files count)
API calls60 a second per bot, any method
TypingOne sendChatAction per chat every 3 seconds is shown; extra calls are answered ok
Text1-4096 characters, counted as Unicode characters: an emoji is one (Telegram counts UTF-16 units, where most emoji are two, so up to twice as many emoji fit here)
File captionUp to 1024 characters, counted the same way
FilesThe app's size limits (up to 500 MB). Not empty. A file_size you send must be the exact size
Albums2-10 photos and videos (sendAlbum); at most 500 MB in one request
FormattingUp to 100 entities a message; their offsets and lengths count UTF-16 units, as on Telegram
CommandsUp to 100; names 1-32 of a-z 0-9 _; descriptions 1-256 characters
Message edits and deletesWithin 48 hours
Unread updatesKept 24 hours
  • Bot chats are not end-to-end encrypted: your server reads them, and the app says so. Never ask for passwords or one-time codes.
  • People can report a bot. Many reports in a week switch it off until Blackbird reviews it.

Methods

All methods, in the order you usually meet them.

getMe

Checks the token and returns the bot.

Library: bot.get_me() in Python, bot.getMe() in JavaScript.

No parameters.

Returns The bot: id, username, name, about, can_join_groups, audience (see Who can use your bot).

curl -X POST https://api.blackbirdtalk.com/bot/v1/getMe \
  -H "Authorization: Bearer $TOKEN"

getUpdates

Returns new updates, waiting up to timeout seconds when there are none.

Library: bot.get_updates() in Python, bot.getUpdates() in JavaScript.

ParameterTypeRequiredDescription
offsetIntegerNoThe last update_id you handled + 1. Confirms everything before it. Negative (-1): keep only the last update and forget older ones.
limitIntegerNo1-100. Default 100.
timeoutIntegerNoSeconds to wait for an update, 0-25. Use 25.

Returns A list of updates.

curl -X POST https://api.blackbirdtalk.com/bot/v1/getUpdates \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"offset": 812, "timeout": 25}'

setWebhook

Sends updates to your HTTPS address instead of getUpdates.

Library: bot.set_webhook() in Python, bot.setWebhook() in JavaScript.

ParameterTypeRequiredDescription
urlStringYesA public https address.
secret_tokenStringNo1-256 of A-Z a-z 0-9 _ -, sent back as X-Blackbird-Bot-Secret.
drop_pending_updatesBooleanNoThrow away updates waiting now.

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/setWebhook \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/hook", "secret_token": "s3cr3t"}'

deleteWebhook

Goes back to getUpdates.

Library: bot.delete_webhook() in Python, bot.deleteWebhook() in JavaScript.

ParameterTypeRequiredDescription
drop_pending_updatesBooleanNoThrow away updates waiting now.

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/deleteWebhook \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'

getWebhookInfo

The webhook and its health.

Library: bot.get_webhook_info() in Python, bot.getWebhookInfo() in JavaScript.

No parameters.

Returns url, pending_update_count, and last_error_date, last_error_message after a failure.

curl -X POST https://api.blackbirdtalk.com/bot/v1/getWebhookInfo \
  -H "Authorization: Bearer $TOKEN"

sendMessage

Sends a text message.

Library: bot.send_message() in Python, bot.sendMessage() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYesFrom an update's chat.id.
textStringYes1-4096 characters.
reply_to_message_idIntegerNoAnswer a message in the same chat.
reply_markupObjectNoButtons or a keyboard. See Buttons.
client_idStringNoUp to 64 characters. Retrying with the same id never sends twice.
parse_modeStringNoHTML, Markdown or MarkdownV2. See Formatting.
entitiesArrayNoInstead of parse_mode: Telegram's entities (offsets in UTF-16 units).

Returns The sent message, with entities when it is formatted.

curl -X POST https://api.blackbirdtalk.com/bot/v1/sendMessage \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 262527487, "text": "<b>Hello!</b>", "parse_mode": "HTML"}'

copyMessage

Sends a copy of a message the bot can read (in its own chats; never an end-to-end encrypted one), without a link to it.

Library: bot.copy_message() in Python, bot.copyMessage() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYesWhere the copy goes.
from_chat_idIntegerYesThe chat the message is in: a person's chat with the bot, or a group it is in.
message_idIntegerYes
captionStringNoA new caption for a photo, video or file. Leave it out to keep the caption.
parse_modeStringNoFormatting of the new caption.
caption_entitiesArrayNoInstead of parse_mode.
reply_to_message_idIntegerNo
reply_markupObjectNoButtons on the copy. The original's buttons are not copied.

Returns {"message_id": ...} of the copy.

curl -X POST https://api.blackbirdtalk.com/bot/v1/copyMessage \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 262527487, "from_chat_id": 262527487, "message_id": 5502}'

forwardMessage

Forwards a message the bot can read; it shows as "Forwarded", as a person's forward does.

Library: bot.forward_message() in Python, bot.forwardMessage() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYesWhere it goes.
from_chat_idIntegerYesThe chat the message is in.
message_idIntegerYes

Returns The forwarded message, with forwarded: true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/forwardMessage \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 262527487, "from_chat_id": 262527487, "message_id": 5502}'

editMessage

Changes the text or buttons of one of your bot's messages.

Telegram name: editMessageText, editMessageReplyMarkup, editMessageCaption

Library: bot.edit_message() in Python, bot.editMessage() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYes
message_idIntegerYesYour bot's message, at most 48 hours old.
textStringNoNew text, 1-4096 characters.
reply_markupObjectNoNew buttons; {} removes them. Leave it out to keep them.
parse_modeStringNoFormatting of the new text (new text without it is plain).
entitiesArrayNoInstead of parse_mode.

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/editMessage \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 262527487, "message_id": 5502, "text": "Updated"}'

deleteMessage

Deletes a message: your bot's own, or in a group anyone's if it has the delete power.

Library: bot.delete_message() in Python, bot.deleteMessage() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYes
message_idIntegerYesAt most 48 hours old.

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/deleteMessage \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 262527487, "message_id": 5502}'

sendChatAction

Shows "typing…" for a few seconds.

Library: bot.send_chat_action() in Python, bot.sendChatAction() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYes
actionStringYestyping. Telegram's other actions (upload_photo, record_voice, ...) show as typing too.

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/sendChatAction \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 262527487, "action": "typing"}'

answerCallback

Answers a button tap. Answer every tap within 60 seconds, even with nothing.

Telegram name: answerCallbackQuery

Library: bot.answer_callback() in Python, bot.answerCallback() in JavaScript.

ParameterTypeRequiredDescription
callback_idStringYesThe callback.id from the update.
textStringNoUp to 200 characters, shown as a short note.
alertBooleanNoShow text in a dialog instead.

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/answerCallback \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"callback_id": "cb_7f3a", "text": "Saved"}'

setCommands

Sets the list people see when they type /. The same list as Bot Studio › Commands.

Telegram name: setMyCommands, deleteMyCommands

Library: bot.set_commands() in Python, bot.setCommands() in JavaScript.

ParameterTypeRequiredDescription
commandsArrayYes[{"command": "quiz", "description": "Start a quiz"}], up to 100.

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/setCommands \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"commands": [{"command": "quiz", "description": "Start a quiz"}]}'

getCommands

Returns the command list.

Telegram name: getMyCommands

Library: bot.get_commands() in Python, bot.getCommands() in JavaScript.

No parameters.

Returns A list of {command, description}.

curl -X POST https://api.blackbirdtalk.com/bot/v1/getCommands \
  -H "Authorization: Bearer $TOKEN"

setName

Changes the bot's name, the same one as Bot Studio › Name.

Telegram name: setMyName

Library: bot.set_name() in Python, bot.setName() in JavaScript.

ParameterTypeRequiredDescription
nameStringYes1-64 characters.
language_codeStringNoLeave out: a bot has one name for every language, so any value is refused.

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/setName \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Quiz Master"}'

getName

Returns the bot's name.

Telegram name: getMyName

Library: bot.get_name() in Python, bot.getName() in JavaScript.

No parameters.

Returns {name}.

curl -X POST https://api.blackbirdtalk.com/bot/v1/getName \
  -H "Authorization: Bearer $TOKEN"

setDescription

Changes the description people see before they start the bot.

Telegram name: setMyDescription

Library: bot.set_description() in Python, bot.setDescription() in JavaScript.

ParameterTypeRequiredDescription
descriptionStringNoUp to 512 characters. Empty clears it.
language_codeStringNoLeave out (one description for every language).

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/setDescription \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"description": "Daily quizzes for class 10. Tap Start."}'

getDescription

Returns the description.

Telegram name: getMyDescription

Library: bot.get_description() in Python, bot.getDescription() in JavaScript.

No parameters.

Returns {description}.

curl -X POST https://api.blackbirdtalk.com/bot/v1/getDescription \
  -H "Authorization: Bearer $TOKEN"

setAbout

Changes the short line on the bot's profile and in search.

Telegram name: setMyShortDescription

Library: bot.set_about() in Python, bot.setAbout() in JavaScript.

ParameterTypeRequiredDescription
aboutStringNoUp to 120 characters. Empty clears it. Telegram's short_description works too.
language_codeStringNoLeave out (one line for every language).

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/setAbout \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"about": "Quizzes every day"}'

getAbout

Returns the short line.

Telegram name: getMyShortDescription

Library: bot.get_about() in Python, bot.getAbout() in JavaScript.

No parameters.

Returns {about, short_description} (the same text twice).

curl -X POST https://api.blackbirdtalk.com/bot/v1/getAbout \
  -H "Authorization: Bearer $TOKEN"

sendPoll

Sends a poll or a quiz on the app's own poll card. People vote with one tap and see the result at once. The bot gets a poll update with the counts after each vote and, for a public poll, poll_answer (who chose what; an empty option_ids is a vote taken back).

Library: bot.send_poll() in Python, bot.sendPoll() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYes
questionStringYes1-300 characters.
optionsArrayYes2-12 answers, 1-100 characters each: strings, or Telegram's {"text": ...}.
is_anonymousBooleanNoDefault true: nobody, the bot included, learns who chose what. false: the bot gets poll_answer and voters see who voted.
typeStringNoregular (default) or quiz.
allows_multiple_answersBooleanNoRegular polls only.
correct_option_idIntegerNoQuiz: the right answer, from 0. Each person sees it after answering; an answer is final.
correct_option_idsArrayNoInstead of correct_option_id: Telegram's newer list, with exactly one answer.
explanationStringNoQuiz: up to 200 characters, shown with the right answer.
open_periodIntegerNoClose by itself after 5 seconds to 7 days (Telegram stops at 10 minutes).
close_dateIntegerNoInstead of open_period: Unix time to close.
reply_to_message_idIntegerNoAnswer a message in the same chat.
reply_markupObjectNoButtons under the poll.
client_idStringNoRetrying with the same id never sends twice.

Returns The sent message, with poll: id (the message id as a string), question, options (text, voter_count), total_voter_count, is_closed, is_anonymous, type, allows_multiple_answers, and for a quiz correct_option_id and explanation.

curl -X POST https://api.blackbirdtalk.com/bot/v1/sendPoll \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 262527487, "question": "Which day suits you?", "options": ["Saturday", "Sunday"], "is_anonymous": false}'

stopPoll

Closes one of your bot's polls now. Everyone sees the final result.

Library: bot.stop_poll() in Python, bot.stopPoll() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYes
message_idIntegerYesThe poll's message.

Returns The final poll.

curl -X POST https://api.blackbirdtalk.com/bot/v1/stopPoll \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 262527487, "message_id": 9041}'

sendDocument

Sends a file. The app shows it by its type: a photo as a photo, a video as a video.

Telegram name: sendPhoto, sendVideo, sendAudio, sendVoice, sendAnimation, sendVideoNote

Library: bot.send_document() in Python, bot.sendDocument() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYes
fileFileNoMultipart upload; put this part last. Telegram's names (document, photo, video, audio, voice) work too.
file_idStringNoInstead of file: a file your bot was sent or sent before (also accepted in photo, document, ...). A web link is not fetched.
captionStringNoUp to 1024 characters.
file_sizeIntegerNoThe size in bytes, with file, so a too-big file is refused before upload.
reply_to_message_idIntegerNoAnswer a message in the same chat.
reply_markupObjectNoButtons under the file (in a multipart upload, as a JSON string).
parse_modeStringNoFormatting of the caption.
caption_entitiesArrayNoInstead of parse_mode.

Returns The sent message, with file.

curl -X POST https://api.blackbirdtalk.com/bot/v1/sendDocument \
  -H "Authorization: Bearer $TOKEN" \
  -F chat_id=262527487 -F caption="Your report" \
  -F file=@report.pdf

sendAlbum

Sends 2-10 photos and videos as one album (or 2-10 documents, or audio files, one after another).

Telegram name: sendMediaGroup

Library: bot.send_album() in Python, bot.sendAlbum() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYes
mediaArrayYes2-10 of {type, media, caption, parse_mode, caption_entities}: type is photo, video, document or audio; media is attach://<name> for an upload or a file_id. Photos and videos may be mixed; documents only with documents, audio only with audio.
filesFileNoMultipart: each upload in its own field, named as in attach://<name>.
reply_to_message_idIntegerNoThe first item answers this message.

Returns The sent messages; an album's carry the same media_group_id. An album shows one caption, the first one given (captions on later photos are not shown).

curl -X POST https://api.blackbirdtalk.com/bot/v1/sendAlbum \
  -H "Authorization: Bearer $TOKEN"

editMedia

Puts a new photo, video or file (and caption) on one of your bot's file messages, in place.

Telegram name: editMessageMedia

Library: bot.edit_media() in Python, bot.editMedia() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYes
message_idIntegerYesYour bot's photo, video or file message, at most 48 hours old.
mediaObjectYes{type, media, caption, parse_mode, caption_entities}, as in sendAlbum. No caption removes it.
filesFileNoMultipart: the upload, named as in attach://<name>.
reply_markupObjectNoNew buttons; leave it out to keep them.

Returns The changed message. The old file is deleted; a photo in an album stays in it.

curl -X POST https://api.blackbirdtalk.com/bot/v1/editMedia \
  -H "Authorization: Bearer $TOKEN"

getFile

Describes a file.

Library: bot.get_file() in Python, bot.getFile() in JavaScript.

ParameterTypeRequiredDescription
file_idStringYesFrom a message's file.

Returns file_id, file_name, mime_type, file_size, file_path. Download with GET /bot/v1/file/<file_id> and the same header.

curl -X POST https://api.blackbirdtalk.com/bot/v1/getFile \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"file_id": "48213"}'

getChat

Describes a person's chat or a group.

Library: bot.get_chat() in Python, bot.getChat() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYes

Returns The chat. In a group also my_powers.

curl -X POST https://api.blackbirdtalk.com/bot/v1/getChat \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": -1048213}'

getChatMember

Someone's place in a group: creator (the owner), administrator with their powers (and Telegram's can_* fields), member, or left. Ask with the bot's own id to see what the group lets it do.

Library: bot.get_chat_member() in Python, bot.getChatMember() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYesA group, or a person's chat.
user_idIntegerYesFrom from.id.

Returns {status, user, powers, custom_title}. For someone not in the group only their id comes back, never their name.

curl -X POST https://api.blackbirdtalk.com/bot/v1/getChatMember \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": -1048213, "user_id": 262527487}'

getChatAdmins

The group's owner and admins.

Telegram name: getChatAdministrators

Library: bot.get_chat_admins() in Python, bot.getChatAdmins() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYesA group.

Returns A list like getChatMember's.

curl -X POST https://api.blackbirdtalk.com/bot/v1/getChatAdmins \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": -1048213}'

getMemberCount

How many people are in the group now (2 for a private chat).

Telegram name: getChatMemberCount, getChatMembersCount

Library: bot.get_member_count() in Python, bot.getMemberCount() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYes

Returns A number.

curl -X POST https://api.blackbirdtalk.com/bot/v1/getMemberCount \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": -1048213}'

pinMessage

Pins a message in a group. Needs the pin power.

Telegram name: pinChatMessage

Library: bot.pin_message() in Python, bot.pinMessage() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYesA group.
message_idIntegerYes

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/pinMessage \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": -1048213, "message_id": 5502}'

unpinMessage

Unpins it.

Telegram name: unpinChatMessage

Library: bot.unpin_message() in Python, bot.unpinMessage() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYesA group.
message_idIntegerYes

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/unpinMessage \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": -1048213, "message_id": 5502}'

removeMember

Removes someone from a group. Needs the remove power.

Telegram name: banChatMember, kickChatMember

Library: bot.remove_member() in Python, bot.removeMember() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYesA group.
user_idIntegerYesFrom from.id.
banBooleanNoAlso stop them from coming back.

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/removeMember \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": -1048213, "user_id": 262527487}'

approveJoin

Lets someone in who asked to join. Needs the approve power.

Telegram name: approveChatJoinRequest

Library: bot.approve_join() in Python, bot.approveJoin() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYesA group.
user_idIntegerYes

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/approveJoin \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": -1048213, "user_id": 262527487}'

declineJoin

Turns the request down.

Telegram name: declineChatJoinRequest

Library: bot.decline_join() in Python, bot.declineJoin() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYesA group.
user_idIntegerYes

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/declineJoin \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": -1048213, "user_id": 262527487}'

getAccess

Who can use the bot: the setting, the people on the list, and waiting requests. The same thing Bot Studio shows.

Library: bot.get_access() in Python, bot.getAccess() in JavaScript.

No parameters.

Returns audience (everyone, owner, chosen), message, allowed and requests (lists of users).

curl -X POST https://api.blackbirdtalk.com/bot/v1/getAccess \
  -H "Authorization: Bearer $TOKEN"

setAccess

Changes who can use the bot. Send only what you want to change.

Library: bot.set_access() in Python, bot.setAccess() in JavaScript.

ParameterTypeRequiredDescription
audienceStringNoeveryone, owner (only the owner) or chosen (the list).
messageStringNoUp to 300 characters, shown to people who may not use the bot.
allowArray of IntegerNoUser ids (from.id) to add to the list. Up to 200 people in all.
removeArray of IntegerNoUser ids to take off the list.

Returns The new state, as getAccess.

curl -X POST https://api.blackbirdtalk.com/bot/v1/setAccess \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"audience": "chosen", "message": "For class 10 students only."}'

approveAccess

Lets in someone who asked (an access_request update).

Library: bot.approve_access() in Python, bot.approveAccess() in JavaScript.

ParameterTypeRequiredDescription
user_idIntegerYesThe request's from.id.

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/approveAccess \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"user_id": 262527487}'

declineAccess

Turns a request down.

Library: bot.decline_access() in Python, bot.declineAccess() in JavaScript.

ParameterTypeRequiredDescription
user_idIntegerYesThe request's from.id.

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/declineAccess \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"user_id": 262527487}'

leaveGroup

The bot leaves a group.

Telegram name: leaveChat

Library: bot.leave_group() in Python, bot.leaveGroup() in JavaScript.

ParameterTypeRequiredDescription
chat_idIntegerYesA group.

Returns true.

curl -X POST https://api.blackbirdtalk.com/bot/v1/leaveGroup \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": -1048213}'

Objects

What updates and results are made of.

Update

FieldTypeDescription
update_idIntegerIncreasing number.
messageMessageOptional.
callbackCallbackOptional. Also as callback_query.
my_membershipObjectOptional. chat, status (added, removed), by, date.
memberObjectOptional. chat, event (joined, left, removed), users, by, date.
join_requestObjectOptional. chat, from, answer, date.
access_requestObjectOptional. from, date.
accessObjectOptional. audience, message, allowed, changed_in, date.
pollObjectOptional. The poll, as sendPoll returns it.
poll_answerObjectOptional. poll_id, user, option_ids.

Message

FieldTypeDescription
message_idInteger
dateIntegerUnix time.
chatChat
fromUser
textStringOptional.
commandStringOptional. quiz for "/quiz physics".
argsStringOptional. physics.
entitiesArrayOptional. With a command: [{"type": "bot_command", "offset": 0, "length": 5}], as on Telegram.
contactContactOptional. Someone shared their contact with a request: contact button.
locationObjectOptional. latitude, longitude, shared with a request: location button.
kindStringOptional. For a file: image, video, voice, doc (from the file's type, so a PNG sent with sendDocument is image and shows as a photo).
captionStringOptional. A file's caption.
fileFileOptional.
photo, document, video, voiceObjectOptional. The same file under Telegram's name for its kind (photo is a list with one size: file_id, width, height, file_size).
reply_to_message_idIntegerOptional.

Chat

FieldTypeDescription
idIntegerPositive for a person, negative for a group.
typeStringprivate or group.
nameStringA person's name (also first_name).
usernameStringA person's username.
titleStringA group's name.
member_countIntegerA group's size.

User

FieldTypeDescription
idInteger
is_botBoolean
nameStringAlso first_name.
usernameString

Callback

FieldTypeDescription
idStringPass to answerCallback.
fromUser
messageMessageThe message with the button: message_id, date, chat, from, text, and its buttons as reply_markup (inline_keyboard).
dataStringThe button's callback.

Contact

FieldTypeDescription
idIntegerThe person's id (also user_id).
nameStringAlso first_name.
usernameString
is_botBoolean
phone_numberStringAlways empty: Blackbird never gives a bot a phone number or e-mail.

File

FieldTypeDescription
file_idString
file_unique_idStringThe same for every copy of one file (sent again by file_id); use it to spot a file you have seen.
file_nameString
mime_typeStringDecided by Blackbird from the file's bytes, not from the upload's label.
file_sizeIntegerBytes.

Coming from Telegram

Code written for Telegram's Bot API works with these differences. Where both names are listed, either one works.

TelegramBlackbird
Base addresshttps://api.telegram.org/bot<token>/https://api.blackbirdtalk.com/bot/v1/ + header Authorization: Bearer <token>. The token is not taken from the address by default (/bot<token>/... answers 401 JSON), because addresses end up in logs. Telegram libraries (python-telegram-bot, aiogram, Telegraf, node-telegram-bot-api) can change only the base address: turn on Bot Studio › Settings › Telegram libraries and set it to https://api.blackbirdtalk.com/bot (files: https://api.blackbirdtalk.com/file/bot). Turn it on only for such code; if the token may have leaked into a log, press Replace token. The official libraries need nothing
Token123456:ABC...bb_123456_ABC...; Telegram's shape 123456:ABC... (the same token, bb_ dropped and the first _ made :) is accepted too, for libraries that check it (aiogram)
Button tapscallback_querycallback (also sent as callback_query)
Answer a tapanswerCallbackQuery, callback_query_id, show_alertanswerCallback, callback_id, alert (both accepted)
EditeditMessageText, editMessageReplyMarkupeditMessage (both accepted)
CommandssetMyCommands, getMyCommandssetCommands, getCommands (both accepted)
Name and textssetMyName, setMyDescription, setMyShortDescription and the get onessetName, setDescription, setAbout and the get ones (both accepted). One text for every language: a language_code is refused
MembersgetChatMember, getChatAdministrators, getChatMemberCountThe same, with getChatAdmins and getMemberCount as Blackbird's names. Someone outside the group comes back as left with only their id
Incoming commandsentities with bot_commandSame entities, plus command and args ready to use
Shared contactcontact with phone_number, first_name, user_iduser_id, first_name (and name, username); phone_number is always empty
Message buttonsinline_keyboard with callback_data, url, copy_textbuttons with callback, url, copy (both accepted)
Button coloursstyle: primary, success, dangerSame names (primary is main), plus warning and normal
Keyboardkeyboard, one_time_keyboard, input_field_placeholderkeyboard, one_time, placeholder (both accepted)
Person's namefirst_name, last_namename (also sent as first_name)
GroupsbanChatMember, pinChatMessage, approveChatJoinRequest, leaveChatremoveMember, pinMessage, approveJoin, leaveGroup (both accepted)
Formattingparse_mode (HTML, Markdown, MarkdownV2), entitiesSame, shown in the app (links: http and https only). A mark that is never closed is shown as written instead of a 400
Dates and timesdate_time (<tg-time>, tg://time)Same; format r is a live countdown that ticks in the app
AlbumssendMediaGroup with attach:// filesSame (sendAlbum); one caption per album, the first one given
Change a fileeditMessageMediaSame (editMedia)
Copy and forwardcopyMessage, forwardMessageSame, for messages the bot can read; a forward shows "Forwarded"
FilessendPhoto, sendDocument, ... with the file in photo, document, ...Same names accepted (also file); a web link is not fetched, upload the file. The type comes from the file's bytes, never its label: a photo sent with sendDocument shows as a photo, sendPhoto with something that is not a picture is refused (400), an SVG is always a document, and a file that is not the video or sound it claims arrives as a document
Repliesreply_parameters or reply_to_message_idBoth accepted
Chat actionstyping, upload_photo, record_voice, ...All accepted, all shown as typing
Webhook secretX-Telegram-Bot-Api-Secret-TokenSent too, beside X-Blackbird-Bot-Secret
Files people sendphoto (sizes), document, video, voiceSame fields (one photo size, with its width and height), plus file and kind
Sent message's buttonsreply_markup.inline_keyboardSame, beside buttons; a keyboard is not echoed, as on Telegram
PollssendPoll, stopPoll, poll and poll_answer updatesSame, on the app's poll card; a poll can stay open up to 7 days (open_period), not only 10 minutes
Not availableinline mode, payments, stickers, games, mini apps; sendLocation, sendContact, logOut, closeAnswer 404 unknown method. The photo is set in Bot Studio

Questions or a bug in the API: buildsevaa@gmail.com