# Blackbird Bot API

Reference for building bots for Blackbird. Online: https://blackbirdtalk.com/bots

Base address: `https://api.blackbirdtalk.com/bot/v1/` · Auth: `Authorization: Bearer <token>` · JSON in and out.

## 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](#telegram).

|   |   |
|---|---|
| Base address | `https://api.blackbirdtalk.com/bot/v1/` |
| Auth | `Authorization: Bearer <token>` on every request (`bearer` in any case). Not in the address: `/bot<token>/...` answers `401`, unless the owner turned on Telegram libraries |
| Format | JSON in, JSON out. Form-encoded bodies are accepted too. |
| Method names | Case-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.

|   | Install | Needs |
|---|---|---|
| Python | `pip install blackbirdtalk` | Python 3.11+ |
| JavaScript / TypeScript | `npm install @blackbirdtalk/bot` | Node.js 22+, Deno, Bun or Workers |

Python:

```python
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()
```

JavaScript:

```javascript
import { Bot, Button } from "@blackbirdtalk/bot";

const bot = new Bot(); // reads BLACKBIRD_TOKEN

bot.command("start", (msg) => msg.answer("Welcome!", {
  buttons: [[new Button("Pay ₹49", { callback: "pay_49", style: "main", icon: "card" })]],
}));

bot.button("pay_49", (tap) => tap.answer("Payment started"));

bot.start();
```

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](https://pypi.org/project/blackbirdtalk/) and [@blackbirdtalk/bot on npm](https://www.npmjs.com/package/@blackbirdtalk/bot). 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`.

```bash
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](#libraries)):

Python:

```python
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"],
            })
```

Node.js:

```node-js
const TOKEN = "bb_..."; // from Bot Studio
const API = "https://api.blackbirdtalk.com/bot/v1/";
const H = { "Authorization": "Bearer " + TOKEN, "Content-Type": "application/json" };

let offset = 0;
for (;;) {
  const r = await fetch(API + "getUpdates", { method: "POST", headers: H,
    body: JSON.stringify({ offset, timeout: 25 }) });
  for (const u of (await r.json()).result) {
    offset = u.update_id + 1;
    if (u.message?.text) {
      await fetch(API + "sendMessage", { method: "POST", headers: H,
        body: JSON.stringify({ chat_id: u.message.chat.id, text: "You said: " + u.message.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`.

```json
{"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](#telegram)).

## Errors

| Code | Meaning | What to do |
|---|---|---|
| 400 | A parameter is missing or wrong. | Read `description`; it names the parameter. |
| 401 | The token is missing or not valid (maybe replaced in Bot Studio). | Copy the current token from Bot Studio. |
| 403 | Not 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. |
| 404 | Unknown method. | Check the name against this page. |
| 409 | `getUpdates` 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. |
| 429 | Too many messages or calls. | Wait `parameters.retry_after` seconds (also in the `Retry-After` header). |
| 500 | Something 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:

| Field | When |
|---|---|
| `message` | Someone 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_membership` | The bot was added to or removed from a group. |
| `member` | People joined or left a group the bot is in. |
| `join_request` | Someone asked to join a group where the bot can approve requests. |
| `access_request` | Someone asked to use the bot (Who can use it: Chosen people). Has `from`. |
| `access` | The owner changed Who can use it in Bot Studio. Has `audience`, `message`, `allowed`. |
| `poll` | New counts for one of the bot's polls, after a vote or when it closes. |
| `poll_answer` | Someone 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.

```json
// 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.

```json
{"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 field | Description |
|---|---|
| `text` | 1-64 characters. |
| `callback` | Message buttons. Your bot gets a `callback` update with this data (1-64 bytes). |
| `url` | Message buttons. An `https://` address (`http`, `javascript:`, `intent:`, `file:` and the like are refused); the app shows the domain before opening it. |
| `copy` | Message buttons. Copies this text on tap. |
| `request` | Keyboard buttons. `location` or `contact`; shared only after the person agrees. |
| `style` | `main` (Telegram's `primary` works too), `normal`, `success`, `warning`, `danger` (danger always asks first). |
| `color` | `#RRGGBB`. The app keeps the text readable on it. |
| `icon` | One 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. |
| `size` | `pill`, `normal` or `full`. |
| `confirm` | Ask this question before acting. |
| `disabled` | `true` 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.

## Start links

`https://blackbirdtalk.com/u/yourbot?start=promo` opens your bot in the app. When the person taps Start, you get `/start promo` (also split into `command` and `args`). The payload is 1-64 of `A-Z a-z 0-9 _ -`. Use it to know where someone came from: a shop page, a QR code on a poster.

## 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:

| Power | Methods |
|---|---|
| Pin messages | `pinMessage`, `unpinMessage` |
| Delete messages | `deleteMessage` on other people's messages |
| Remove members | `removeMember` |
| Approve join requests | `approveJoin`, `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.

```json
{"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](tg://time?unix=1794167173&format=r)`). 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

| Limit | Value |
|---|---|
| Messages | 30 a second per bot; 60 a minute into one private chat; 20 a minute into one group (edits and files count) |
| API calls | 60 a second per bot, any method |
| Typing | One `sendChatAction` per chat every 3 seconds is shown; extra calls are answered ok |
| Text | 1-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 caption | Up to 1024 characters, counted the same way |
| Files | The app's size limits (up to 500 MB). Not empty. A `file_size` you send must be the exact size |
| Albums | 2-10 photos and videos (`sendAlbum`); at most 500 MB in one request |
| Formatting | Up to 100 entities a message; their offsets and lengths count UTF-16 units, as on Telegram |
| Commands | Up to 100; names 1-32 of `a-z 0-9 _`; descriptions 1-256 characters |
| Message edits and deletes | Within 48 hours |
| Unread updates | Kept 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

### getMe

Checks the token and returns the bot.

Library: `bot.get_me()` in Python, `bot.getMe()` in JavaScript.

Returns: The bot: `id`, `username`, `name`, `about`, `can_join_groups`, `audience` (see [Who can use your bot](#who-can-use)).

### getUpdates

Returns new updates, waiting up to `timeout` seconds when there are none.

Library: `bot.get_updates()` in Python, `bot.getUpdates()` in JavaScript.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `offset` | Integer | No | The last `update_id` you handled + 1. Confirms everything before it. Negative (-1): keep only the last update and forget older ones. |
| `limit` | Integer | No | 1-100. Default 100. |
| `timeout` | Integer | No | Seconds to wait for an update, 0-25. Use 25. |

Returns: A list of updates.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `url` | String | Yes | A public `https` address. |
| `secret_token` | String | No | 1-256 of `A-Z a-z 0-9 _ -`, sent back as `X-Blackbird-Bot-Secret`. |
| `drop_pending_updates` | Boolean | No | Throw away updates waiting now. |

Returns: `true`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `drop_pending_updates` | Boolean | No | Throw away updates waiting now. |

Returns: `true`.

```bash
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.

Returns: `url`, `pending_update_count`, and `last_error_date`, `last_error_message` after a failure.

### sendMessage

Sends a text message.

Library: `bot.send_message()` in Python, `bot.sendMessage()` in JavaScript.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes | From an update's `chat.id`. |
| `text` | String | Yes | 1-4096 characters. |
| `reply_to_message_id` | Integer | No | Answer a message in the same chat. |
| `reply_markup` | Object | No | Buttons or a keyboard. See [Buttons](#buttons). |
| `client_id` | String | No | Up to 64 characters. Retrying with the same id never sends twice. |
| `parse_mode` | String | No | `HTML`, `Markdown` or `MarkdownV2`. See [Formatting](#formatting). |
| `entities` | Array | No | Instead of `parse_mode`: Telegram's entities (offsets in UTF-16 units). |

Returns: The sent message, with `entities` when it is formatted.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes | Where the copy goes. |
| `from_chat_id` | Integer | Yes | The chat the message is in: a person's chat with the bot, or a group it is in. |
| `message_id` | Integer | Yes |  |
| `caption` | String | No | A new caption for a photo, video or file. Leave it out to keep the caption. |
| `parse_mode` | String | No | Formatting of the new caption. |
| `caption_entities` | Array | No | Instead of `parse_mode`. |
| `reply_to_message_id` | Integer | No |  |
| `reply_markup` | Object | No | Buttons on the copy. The original's buttons are not copied. |

Returns: `{"message_id": ...}` of the copy.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes | Where it goes. |
| `from_chat_id` | Integer | Yes | The chat the message is in. |
| `message_id` | Integer | Yes |  |

Returns: The forwarded message, with `forwarded: true`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes |  |
| `message_id` | Integer | Yes | Your bot's message, at most 48 hours old. |
| `text` | String | No | New text, 1-4096 characters. |
| `reply_markup` | Object | No | New buttons; `{}` removes them. Leave it out to keep them. |
| `parse_mode` | String | No | Formatting of the new text (new text without it is plain). |
| `entities` | Array | No | Instead of `parse_mode`. |

Returns: `true`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes |  |
| `message_id` | Integer | Yes | At most 48 hours old. |

Returns: `true`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes |  |
| `action` | String | Yes | `typing`. Telegram's other actions (`upload_photo`, `record_voice`, ...) show as typing too. |

Returns: `true`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `callback_id` | String | Yes | The `callback.id` from the update. |
| `text` | String | No | Up to 200 characters, shown as a short note. |
| `alert` | Boolean | No | Show `text` in a dialog instead. |

Returns: `true`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `commands` | Array | Yes | `[{"command": "quiz", "description": "Start a quiz"}]`, up to 100. |

Returns: `true`.

```bash
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.

Returns: A list of `{command, description}`.

### 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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `name` | String | Yes | 1-64 characters. |
| `language_code` | String | No | Leave out: a bot has one name for every language, so any value is refused. |

Returns: `true`.

```bash
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.

Returns: `{name}`.

### setDescription

Changes the description people see before they start the bot.

Telegram name: setMyDescription

Library: `bot.set_description()` in Python, `bot.setDescription()` in JavaScript.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `description` | String | No | Up to 512 characters. Empty clears it. |
| `language_code` | String | No | Leave out (one description for every language). |

Returns: `true`.

```bash
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.

Returns: `{description}`.

### 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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `about` | String | No | Up to 120 characters. Empty clears it. Telegram's `short_description` works too. |
| `language_code` | String | No | Leave out (one line for every language). |

Returns: `true`.

```bash
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.

Returns: `{about, short_description}` (the same text twice).

### 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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes |  |
| `question` | String | Yes | 1-300 characters. |
| `options` | Array | Yes | 2-12 answers, 1-100 characters each: strings, or Telegram's `{"text": ...}`. |
| `is_anonymous` | Boolean | No | Default `true`: nobody, the bot included, learns who chose what. `false`: the bot gets `poll_answer` and voters see who voted. |
| `type` | String | No | `regular` (default) or `quiz`. |
| `allows_multiple_answers` | Boolean | No | Regular polls only. |
| `correct_option_id` | Integer | No | Quiz: the right answer, from 0. Each person sees it after answering; an answer is final. |
| `correct_option_ids` | Array | No | Instead of `correct_option_id`: Telegram's newer list, with exactly one answer. |
| `explanation` | String | No | Quiz: up to 200 characters, shown with the right answer. |
| `open_period` | Integer | No | Close by itself after 5 seconds to 7 days (Telegram stops at 10 minutes). |
| `close_date` | Integer | No | Instead of `open_period`: Unix time to close. |
| `reply_to_message_id` | Integer | No | Answer a message in the same chat. |
| `reply_markup` | Object | No | Buttons under the poll. |
| `client_id` | String | No | Retrying 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`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes |  |
| `message_id` | Integer | Yes | The poll's message. |

Returns: The final poll.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes |  |
| `file` | File | No | Multipart upload; put this part last. Telegram's names (`document`, `photo`, `video`, `audio`, `voice`) work too. |
| `file_id` | String | No | Instead of `file`: a file your bot was sent or sent before (also accepted in `photo`, `document`, ...). A web link is not fetched. |
| `caption` | String | No | Up to 1024 characters. |
| `file_size` | Integer | No | The size in bytes, with `file`, so a too-big file is refused before upload. |
| `reply_to_message_id` | Integer | No | Answer a message in the same chat. |
| `reply_markup` | Object | No | Buttons under the file (in a multipart upload, as a JSON string). |
| `parse_mode` | String | No | Formatting of the caption. |
| `caption_entities` | Array | No | Instead of `parse_mode`. |

Returns: The sent message, with `file`.

### 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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes |  |
| `media` | Array | Yes | 2-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. |
| `files` | File | No | Multipart: each upload in its own field, named as in `attach://<name>`. |
| `reply_to_message_id` | Integer | No | The 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).

### 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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes |  |
| `message_id` | Integer | Yes | Your bot's photo, video or file message, at most 48 hours old. |
| `media` | Object | Yes | `{type, media, caption, parse_mode, caption_entities}`, as in `sendAlbum`. No caption removes it. |
| `files` | File | No | Multipart: the upload, named as in `attach://<name>`. |
| `reply_markup` | Object | No | New buttons; leave it out to keep them. |

Returns: The changed message. The old file is deleted; a photo in an album stays in it.

### getFile

Describes a file.

Library: `bot.get_file()` in Python, `bot.getFile()` in JavaScript.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `file_id` | String | Yes | From 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.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes |  |

Returns: The chat. In a group also `my_powers`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes | A group, or a person's chat. |
| `user_id` | Integer | Yes | From `from.id`. |

Returns: `{status, user, powers, custom_title}`. For someone not in the group only their id comes back, never their name.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes | A group. |

Returns: A list like `getChatMember`'s.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes |  |

Returns: A number.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes | A group. |
| `message_id` | Integer | Yes |  |

Returns: `true`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes | A group. |
| `message_id` | Integer | Yes |  |

Returns: `true`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes | A group. |
| `user_id` | Integer | Yes | From `from.id`. |
| `ban` | Boolean | No | Also stop them from coming back. |

Returns: `true`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes | A group. |
| `user_id` | Integer | Yes |  |

Returns: `true`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes | A group. |
| `user_id` | Integer | Yes |  |

Returns: `true`.

```bash
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.

Returns: `audience` (`everyone`, `owner`, `chosen`), `message`, `allowed` and `requests` (lists of users).

### setAccess

Changes who can use the bot. Send only what you want to change.

Library: `bot.set_access()` in Python, `bot.setAccess()` in JavaScript.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `audience` | String | No | `everyone`, `owner` (only the owner) or `chosen` (the list). |
| `message` | String | No | Up to 300 characters, shown to people who may not use the bot. |
| `allow` | Array of Integer | No | User ids (`from.id`) to add to the list. Up to 200 people in all. |
| `remove` | Array of Integer | No | User ids to take off the list. |

Returns: The new state, as `getAccess`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `user_id` | Integer | Yes | The request's `from.id`. |

Returns: `true`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `user_id` | Integer | Yes | The request's `from.id`. |

Returns: `true`.

```bash
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.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `chat_id` | Integer | Yes | A group. |

Returns: `true`.

```bash
curl -X POST https://api.blackbirdtalk.com/bot/v1/leaveGroup \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": -1048213}'
```

## Objects

### Update

| Field | Type | Description |
|---|---|---|
| `update_id` | Integer | Increasing number. |
| `message` | Message | Optional. |
| `callback` | Callback | Optional. Also as `callback_query`. |
| `my_membership` | Object | Optional. `chat`, `status` (added, removed), `by`, `date`. |
| `member` | Object | Optional. `chat`, `event` (joined, left, removed), `users`, `by`, `date`. |
| `join_request` | Object | Optional. `chat`, `from`, `answer`, `date`. |
| `access_request` | Object | Optional. `from`, `date`. |
| `access` | Object | Optional. `audience`, `message`, `allowed`, `changed_in`, `date`. |
| `poll` | Object | Optional. The poll, as `sendPoll` returns it. |
| `poll_answer` | Object | Optional. `poll_id`, `user`, `option_ids`. |

### Message

| Field | Type | Description |
|---|---|---|
| `message_id` | Integer |  |
| `date` | Integer | Unix time. |
| `chat` | Chat |  |
| `from` | User |  |
| `text` | String | Optional. |
| `command` | String | Optional. `quiz` for "/quiz physics". |
| `args` | String | Optional. `physics`. |
| `entities` | Array | Optional. With a command: `[{"type": "bot_command", "offset": 0, "length": 5}]`, as on Telegram. |
| `contact` | Contact | Optional. Someone shared their contact with a `request: contact` button. |
| `location` | Object | Optional. `latitude`, `longitude`, shared with a `request: location` button. |
| `kind` | String | Optional. 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). |
| `caption` | String | Optional. A file's caption. |
| `file` | File | Optional. |
| `photo, document, video, voice` | Object | Optional. 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_id` | Integer | Optional. |

### Chat

| Field | Type | Description |
|---|---|---|
| `id` | Integer | Positive for a person, negative for a group. |
| `type` | String | `private` or `group`. |
| `name` | String | A person's name (also `first_name`). |
| `username` | String | A person's username. |
| `title` | String | A group's name. |
| `member_count` | Integer | A group's size. |

### User

| Field | Type | Description |
|---|---|---|
| `id` | Integer |  |
| `is_bot` | Boolean |  |
| `name` | String | Also `first_name`. |
| `username` | String |  |

### Callback

| Field | Type | Description |
|---|---|---|
| `id` | String | Pass to `answerCallback`. |
| `from` | User |  |
| `message` | Message | The message with the button: `message_id`, `date`, `chat`, `from`, `text`, and its buttons as `reply_markup` (`inline_keyboard`). |
| `data` | String | The button's `callback`. |

### Contact

| Field | Type | Description |
|---|---|---|
| `id` | Integer | The person's id (also `user_id`). |
| `name` | String | Also `first_name`. |
| `username` | String |  |
| `is_bot` | Boolean |  |
| `phone_number` | String | Always empty: Blackbird never gives a bot a phone number or e-mail. |

### File

| Field | Type | Description |
|---|---|---|
| `file_id` | String |  |
| `file_unique_id` | String | The same for every copy of one file (sent again by `file_id`); use it to spot a file you have seen. |
| `file_name` | String |  |
| `mime_type` | String | Decided by Blackbird from the file's bytes, not from the upload's label. |
| `file_size` | Integer | Bytes. |

## Coming from Telegram

|   | Telegram | Blackbird |
|---|---|---|
| Base address | `https://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 |
| Token | `123456: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 taps | `callback_query` | `callback` (also sent as `callback_query`) |
| Answer a tap | `answerCallbackQuery`, `callback_query_id`, `show_alert` | `answerCallback`, `callback_id`, `alert` (both accepted) |
| Edit | `editMessageText`, `editMessageReplyMarkup` | `editMessage` (both accepted) |
| Commands | `setMyCommands`, `getMyCommands` | `setCommands`, `getCommands` (both accepted) |
| Name and texts | `setMyName`, `setMyDescription`, `setMyShortDescription` and the `get` ones | `setName`, `setDescription`, `setAbout` and the `get` ones (both accepted). One text for every language: a `language_code` is refused |
| Members | `getChatMember`, `getChatAdministrators`, `getChatMemberCount` | The same, with `getChatAdmins` and `getMemberCount` as Blackbird's names. Someone outside the group comes back as `left` with only their id |
| Incoming commands | `entities` with `bot_command` | Same `entities`, plus `command` and `args` ready to use |
| Shared contact | `contact` with `phone_number`, `first_name`, `user_id` | `user_id`, `first_name` (and `name`, `username`); `phone_number` is always empty |
| Message buttons | `inline_keyboard` with `callback_data`, `url`, `copy_text` | `buttons` with `callback`, `url`, `copy` (both accepted) |
| Button colours | `style`: `primary`, `success`, `danger` | Same names (`primary` is `main`), plus `warning` and `normal` |
| Keyboard | `keyboard`, `one_time_keyboard`, `input_field_placeholder` | `keyboard`, `one_time`, `placeholder` (both accepted) |
| Person's name | `first_name`, `last_name` | `name` (also sent as `first_name`) |
| Groups | `banChatMember`, `pinChatMessage`, `approveChatJoinRequest`, `leaveChat` | `removeMember`, `pinMessage`, `approveJoin`, `leaveGroup` (both accepted) |
| Formatting | `parse_mode` (HTML, Markdown, MarkdownV2), `entities` | Same, 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 times | `date_time` (`<tg-time>`, `tg://time`) | Same; format `r` is a live countdown that ticks in the app |
| Albums | `sendMediaGroup` with `attach://` files | Same (`sendAlbum`); one caption per album, the first one given |
| Change a file | `editMessageMedia` | Same (`editMedia`) |
| Copy and forward | `copyMessage`, `forwardMessage` | Same, for messages the bot can read; a forward shows "Forwarded" |
| Files | `sendPhoto`, `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 |
| Replies | `reply_parameters` or `reply_to_message_id` | Both accepted |
| Chat actions | `typing`, `upload_photo`, `record_voice`, ... | All accepted, all shown as typing |
| Webhook secret | `X-Telegram-Bot-Api-Secret-Token` | Sent too, beside `X-Blackbird-Bot-Secret` |
| Files people send | `photo` (sizes), `document`, `video`, `voice` | Same fields (one photo size, with its width and height), plus `file` and `kind` |
| Sent message's buttons | `reply_markup.inline_keyboard` | Same, beside `buttons`; a keyboard is not echoed, as on Telegram |
| Polls | `sendPoll`, `stopPoll`, `poll` and `poll_answer` updates | Same, on the app's poll card; a poll can stay open up to 7 days (`open_period`), not only 10 minutes |
| Not available | inline mode, payments, stickers, games, mini apps; `sendLocation`, `sendContact`, `logOut`, `close` | Answer `404 unknown method`. The photo is set in Bot Studio |
