# WWChat Bot API — complete reference for LLMs You are reading the machine-readable reference for the **WWChat Bot API**. Use it to write a working bot for the WWChat messenger. The API is **Telegram-Bot-API–compatible** in spirit: methods, update types, inline keyboards, webhooks and star payments map closely to Telegram, so Telegram bot knowledge transfers directly. Differences from Telegram are called out explicitly below. - Human docs (same content, formatted): https://wwchat.org/docs/bot-api - This file (always current): https://wwchat.org/botapi.txt - Bot API version: **v1.3** (2026-06-30) - **Mini Apps** (web apps launched from a bot button — Telegram-Web-App-style, with a JS SDK and signed `initData`): separate reference at **https://wwchat.org/miniapps.txt** — fetch it too if the task involves an in-chat web app. Summary in the "Mini Apps" section below. ## Mental model - A **bot** is an account with a token. It talks to the platform over HTTPS. - You receive **updates** (messages, button presses, payments, reactions) either by **long polling** (`getUpdates`) or by **webhook** (`setWebhook`). Use exactly one. - You act by calling **methods** (`sendMessage`, `sendPhoto`, …). - All IDs (users, chats, messages, files) are **UUID strings**, not integers. - `chat_id` accepts a chat **UUID** or an **@username** (for public groups/channels). ## Base URL & authentication ``` https://api.wwchat.org/bot/v1/{token}/{method} ``` The token authenticates the bot and is part of the URL path (no header needed). Token format: `{user_id}:{random_string}`, e.g. `550e8400-e29b-41d4-a716-446655440000:aB3dE5fG7hJ9kL1mN3pQ5rS7tU9vW1xY3zA5bC7dE9fG1`. ## Creating a bot Via the system bot **@BotMama** inside WWChat: 1. Open a chat with @BotMama. 2. Send `/newbot`, then the bot name, then a username ending in `bot`. 3. Receive the token. @BotMama commands: `/newbot`, `/mybots`, `/editbot`, `/deletebot`, `/token`. ## Response format Every method returns JSON: ```json { "ok": true, "result": { } } ``` On error: ```json { "ok": false, "error_code": 400, "description": "Error message" } ``` ## Receiving updates ### getUpdates (long polling) `GET /bot/v1/{token}/getUpdates` | param | type | description | |---|---|---| | offset | Integer | id of the first update to return | | limit | Integer | max number, 1–100 | | timeout | Integer | long-poll timeout, max 60 s | | allowed_updates | Array | filter update types | After processing, call again with `offset = last_update_id + 1` to acknowledge. While a webhook is set, `getUpdates` returns `409 Conflict`. ### Webhooks - `POST /bot/v1/{token}/setWebhook` — params: `url` (HTTPS, required), `secret_token`, `max_connections` (1–100), `allowed_updates` (array). - `POST /bot/v1/{token}/deleteWebhook` - `GET /bot/v1/{token}/getWebhookInfo` The platform POSTs each update to your `url`: ``` POST https://yourbot.example.com/webhook Content-Type: application/json X-WWChat-Bot-Api-Secret-Token: my_secret_123 { "update_id": 123456789, "message": { } } ``` Return HTTP 200. After 10 consecutive failures the webhook is auto-disabled. ## Methods ### getMe `GET /bot/v1/{token}/getMe` → bot info (`id`, `username`, `is_bot`, `can_join_groups`, `can_read_group_messages`, `can_join_channels`). ### sendMessage `POST /bot/v1/{token}/sendMessage` | param | type | required | description | |---|---|---|---| | chat_id | String | yes | chat UUID or @username | | text | String | yes | message text (≤4096 chars) | | parse_mode | String | no | `HTML` or `Markdown` | | reply_to_message_id | String | no | message UUID to reply to | | disable_notification | Boolean | no | silent message | | reply_markup | ReplyMarkup | no | inline / reply keyboard or removal | Inline keyboard example: ```json { "chat_id": "550e8400-e29b-41d4-a716-446655440000", "text": "Choose an action:", "reply_markup": { "inline_keyboard": [ [ {"text": "Button 1", "callback_data": "btn1"}, {"text": "Button 2", "callback_data": "btn2"} ], [ {"text": "Open website", "url": "https://example.com"} ] ] } } ``` ### editMessageText `POST /bot/v1/{token}/editMessageText` — params: `chat_id`, `message_id`, `text`, optional `reply_markup`. Only the bot's own messages can be edited. ### answerCallbackQuery `POST /bot/v1/{token}/answerCallbackQuery` — params: `callback_query_id` (required), `text` (≤200 chars), `show_alert` (bool), `cache_time`. Always call this after an inline button press, even if you show nothing. ### getChat `GET|POST /bot/v1/{token}/getChat` — param: `chat_id`. Returns chat info. For a private chat it includes the peer's `language` (`"ru"`/`"en"`) — handy for localizing replies. For groups/channels: `title`, `members_count`, `is_public`. ### getMessageReactions `GET|POST /bot/v1/{token}/getMessageReactions` — params: `chat_id`, `message_ids` (comma-separated UUIDs in GET, array in POST, ≤100). Returns current reactions per message broken down by user. Reconcile/backfill for missed realtime `message_reaction` updates. Bot must be a chat member. ## Files ### uploadFile `POST /bot/v1/{token}/uploadFile` (multipart/form-data, field `file`, ≤50 MB) → `{ id, file_name, file_size, mime_type, media_type }`. The returned `id` is the `file_id` you pass to send* methods. ### sendDocument / sendPhoto / sendVoice `POST /bot/v1/{token}/sendDocument` — params: `chat_id`, `document` (file_id), `caption`, `parse_mode`, `reply_markup`. `sendPhoto` — same with `photo` (file_id). `sendVoice` — `voice` (file_id), optional `duration` (seconds). ### getFile `GET /bot/v1/{token}/getFile?file_id={file_id}` — get metadata for a file you received in an update (e.g. `message.voice.file_id`): ```json { "ok": true, "result": { "file_id": "abc-...-uuid", "file_size": 23456, "file_path": "voice/abc-...-uuid.oga" } } ``` `file_path` is a `/.` path (`voice` / `photos` / `videos` / `documents`). Append it to the download URL below. ### Downloading a file (receive voice / photos / documents) `GET /bot/v1/{token}/file/{file_path}` Download the **bytes** of a media file the bot received. Authentication is by the **bot token in the path** — no user token needed. - `{file_path}` is the value from `getFile` (`voice/.oga`). A **bare `file_id`** straight from an update is also accepted, e.g. `/file/{file_id}` — so calling `getFile` first is optional. - Supports HTTP `Range` (streaming of large files). - Response: a binary stream with the file's `Content-Type`. Errors: `404` not found, `403` the bot has no access (it may only download files sent in chats it belongs to, or files it owns). ## Star payments Bots can charge users in **stars** (currency code `XTR`; amounts in updates are in units where 100 units = 1 star). Flow: 1. Bot calls `sendInvoice` → user sees a message with a "Pay" button. 2. User confirms → bot receives a `pre_checkout_query` update. 3. Bot calls `answerPreCheckoutQuery` (`ok: true/false`) within **10 seconds**. 4. On `ok: true`, stars move from user to bot; bot receives a `message` with `successful_payment`. ### sendInvoice `POST /bot/v1/{token}/sendInvoice` — params: `chat_id`, `title` (≤255), `description` (≤1000), `amount` (Float, stars, >0), `payload` (≤512, hidden from user), optional `photo_url`. ### answerPreCheckoutQuery `POST /bot/v1/{token}/answerPreCheckoutQuery` — params: `pre_checkout_query_id`, `ok` (Boolean), `error_message` (when `ok:false`). ### getStarBalance `GET|POST /bot/v1/{token}/getStarBalance` → `{ balance, pending }`. ## Text formatting `parse_mode` is supported by `sendMessage`, `editMessageText`, `sendDocument`, `sendPhoto`. - `HTML`: ``, ``, ``, ``, ``, ``, ``, `
`, ``.
- `Markdown`: `**bold**`, `*italic*`, `` `code` ``, `~~strike~~`, `[link](https://…)`.

## Object types

### Update
Exactly **one** of these fields is set per update:
`message`, `callback_query`, `channel_post`, `my_chat_member`, `pre_checkout_query`,
`message_reaction`. Plus `update_id` (Integer).

### Message
```json
{
  "message_id": "uuid",
  "from": { "id": "uuid", "username": "john", "is_bot": false },
  "chat": { "id": "uuid", "type": "private" },
  "date": 1705123456,
  "text": "Hello",
  "entities": [],
  "forwarded_from": { "id": "uuid", "username": "..." },
  "reply_to_message": {},
  "reply_markup": {},
  "photo": [ { "file_id": "uuid", "width": 0, "height": 0 } ],
  "document": { "file_id": "uuid", "file_name": "...", "mime_type": "..." },
  "voice": { "file_id": "uuid", "duration": 5, "mime_type": "audio/ogg" },
  "location": { "latitude": 55.751244, "longitude": 37.618423, "name": "...", "address": "..." },
  "successful_payment": {}
}
```
`location` is present when the user shares a geo-position (message type `location`), e.g. after tapping a `request_location` button; `latitude`/`longitude` are required, `name`/`address` optional, and `text` is empty on such updates.

To receive media, take the `file_id` (for photos, use the last item of `photo[]`) and
download it via `GET /bot/v1/{token}/file/{file_id}`.

### CallbackQuery
`{ "id": "...", "from": {…}, "message": {…}, "chat_instance": "uuid", "data": "btn1" }`

### PreCheckoutQuery
`{ "id": "uuid", "from": {…}, "currency": "XTR", "total_amount": 1000, "invoice_payload": "…" }`

### SuccessfulPayment (inside `message.successful_payment`)
`{ "currency": "XTR", "total_amount": 1000, "invoice_payload": "…", "provider_payment_charge_id": "uuid" }`

### MyChatMember
Bot added/removed from a group/channel:
`{ "chat": {…}, "from": {…}, "old_chat_member": { "status": "left" }, "new_chat_member": { "status": "member" } }`.
Statuses: `member`, `left`, `kicked`.

### MessageReaction
Delivered to all bot members of a chat when a user adds/removes/changes a reaction.
`old_reaction`/`new_reaction` are that user's full reaction set before/after:
`{ "chat": {…}, "message_id": "uuid", "user": {…}, "date": 1705123456, "old_reaction": ["😀"], "new_reaction": ["🔥"] }`
(empty `new_reaction` = removed). Queued durably — not lost if the bot was offline.

### Keyboards (reply_markup is one of these)
- **InlineKeyboardMarkup**: `{ "inline_keyboard": [[ InlineKeyboardButton ]] }` (under the message).
  InlineKeyboardButton fields: `text` (required), `callback_data` (≤64 bytes), `url`,
  `pay` (Boolean, invoice button).
- **ReplyKeyboardMarkup**: custom keyboard over the input field:
  `{ "keyboard": [[ {"text":"Yes"} ]], "resize_keyboard": true, "one_time_keyboard": true, "is_persistent": false, "input_field_placeholder": "…" }`.
  KeyboardButton fields: `text` (required, also sent on tap), `request_contact`,
  `request_location` (not both). Limits: ≤25 rows, ≤12 buttons/row, ≤300 total, text ≤256 bytes.
- **ReplyKeyboardRemove**: `{ "remove_keyboard": true }`.

**Requesting a location:** send a reply keyboard with `request_location: true` on a button (usually one button + `one_time_keyboard: true`). On tap a supporting client sends a `location` message; the bot then receives a `message` update with the `location` object filled in (`text` empty). Clients that don't render reply keyboards don't show the button, so the request is silently ignored by older clients — no version gating needed.

### User / Chat
User: `id`, `username`, `is_bot`, `is_deleted`. Chat: `id`, `type`
(`private`/`group`/`channel`), `title`, `username`.

## Bots in groups and channels

- Add a bot to a group/channel → it receives `my_chat_member`.
- Group messages arrive as `message` (chat.type `group`); channel posts as
  `channel_post` (chat.type `channel`).
- Send with `chat_id` = `@group_or_channel_username` or its UUID. Sending to a channel
  creates a full post. `sendDocument`/`sendPhoto`/`sendVoice` work too.

## Mini Apps (web apps inside chats)

WWChat supports **Mini Apps** — web apps you host that open in an embedded webview from a
bot button (Telegram-Web-App-compatible: a JS SDK `window.WWChat.WebApp`, signed `initData`
that identifies the user, theming, buttons, and posting results back to the chat). Entry
points from the Bot API:

- Attach a `web_app` button in `sendMessage`:
  `{ "text": "Open app", "web_app": { "url": "https://yourapp.example.com" } }` inside `inline_keyboard`.
- Or set a persistent menu button: `POST setChatMenuButton` with
  `{ "menu_button": { "type": "web_app", "text": "Open app", "web_app": { "url": "https://..." } } }`.
- The Mini App posts a result back into the chat via `POST answerWebAppQuery`
  (`{ "web_app_query_id": "", "text": "..." }`).
- Your backend validates `initData` (HMAC with the bot token) before trusting the user.

**Full Mini Apps reference (SDK methods, initData fields + validation code, theming, full
example):** https://wwchat.org/miniapps.txt

## Example: echo bot (Python, long polling)

```python
import requests, time

TOKEN = "550e8400-...:aB3dE5fG7h..."
BASE = f"https://api.wwchat.org/bot/v1/{TOKEN}"

def send(chat_id, text):
    requests.post(f"{BASE}/sendMessage", json={"chat_id": chat_id, "text": text})

offset = None
while True:
    params = {"timeout": 30}
    if offset:
        params["offset"] = offset
    r = requests.get(f"{BASE}/getUpdates", params=params).json()
    if not r.get("ok"):
        time.sleep(5); continue
    for u in r["result"]:
        offset = u["update_id"] + 1
        msg = u.get("message")
        if not msg:
            continue
        chat_id = msg["chat"]["id"]
        text = msg.get("text", "")
        if text.startswith("/start"):
            send(chat_id, "Hello! I'm a WWChat bot.")
        else:
            send(chat_id, f"You wrote: {text}")
```

## Example: receive a voice message and download it (Python)

```python
# inside the update loop, when msg has a voice:
voice = msg.get("voice")
if voice:
    file_id = voice["file_id"]
    # Download straight by file_id (getFile is optional):
    audio = requests.get(f"{BASE}/file/{file_id}")   # bytes, Content-Type audio/ogg
    with open("incoming.oga", "wb") as f:
        f.write(audio.content)
    send(msg["chat"]["id"], "Got your voice message!")
```

## Limits

- Rate: 30 requests/sec per bot; per-chat send (`sendMessage`/`sendDocument`/`sendPhoto`/
  `sendVoice`/`sendInvoice`) 1/sec and 20/min per chat. Exceeding → `429` with `Retry-After: 1`.
- Message text ≤4096 chars. Inline keyboard ≤25 rows, ≤8 buttons/row, ≤100 total,
  `callback_data` ≤64 bytes.
- Long-poll timeout ≤60 s; updates ≤100/request; file upload ≤50 MB.
- Webhook URL HTTPS only.
- Up to 20 bots per user (including deleted); a bot username is reserved forever.