Buko Docs

Buko Bot API for AI Agents

This is the canonical single-page Buko Bot API reference.

It is written for AI agents and automation systems first, but humans can read it too. If you are an AI coding agent, read this entire page first, then build the connector.

Use this page when a human gives you a Buko bot token and asks you to connect an agent to Buko. Do not rely on older split-page Bot API docs; this page is the source of truth.

Status

Bot API is available for official and approved bots. Public self-service bot creation is not open yet.

Goal

Build a service that:

  1. Authenticates with a Buko bot token.
  2. Receives user messages from Buko.
  3. Runs your own agent logic outside Buko.
  4. Sends replies back through the bot.

Buko is only the messaging channel. Your service owns the intelligence, business logic, tools, memory, and external integrations.

Production endpoints

PurposeURL
REST API basehttps://ims.buko.app
Bot Gateway WebSocketwss://ims.buko.app/bot/ws
Canonical Bot API dochttps://buko.app/dev-docs/bot-api/

All REST methods use HTTPS. All Bot Gateway connections use WSS.

Core concepts

ConceptMeaning
Bot accountA Buko identity with kind = bot, display name, handle, avatar, and owner.
Official botA Buko-operated bot with an explicit official marker in clients. Official bot tokens may be encrypted in Buko's token vault for operations.
Managed agentA Buko official bot consumed by Buko's own bot-agent runner. External official bots keep this off and consume updates themselves.
Bot tokenSecret token used by your agent. It starts with bot_.
ChatA Buko space_id. It can be a private bot DM or a group.
UpdateAn event delivered to a bot, such as message, edited_message, or my_chat_member.
Message IDThe message seq inside one chat, serialized as a string.

Non-negotiable rules

Bot token

The bot token starts with bot_.

Store it as an environment variable:

export BUKO_BOT_TOKEN="bot_xxx"

Verify it:

curl -sS https://ims.buko.app/bot/getMe \
  -X POST \
  -H "Authorization: Bot $BUKO_BOT_TOKEN" \
  -H "Content-Type: application/json"

Successful response:

{
  "ok": true,
  "result": {
    "id": "u_bot_xxx",
    "is_bot": true,
    "display_name": "Example Bot",
    "handle": "example_bot",
    "status": "active",
    "verified": false,
    "official": false,
    "quota_tier": "free",
    "gateway_connection_limit": 1,
    "capabilities": {
      "edit_delete_messages": false,
      "interactions": false
    }
  }
}

Identifier contract

FieldMeaningType
chat_idBuko space_idstring
message_idmessage sequence inside one chatstring
reply_to_message_idmessage sequence to reply tostring
update_idmonotonically increasing update id for this botstring
from.idper-bot scoped user idstring
from.display_namesender display namestring

message_id may look numeric, but treat it as an opaque string.

from.id is stable for this bot, but it is not Buko's raw internal user id. Different bots cannot use it to correlate the same person.

Use from.display_name for display only. Buko does not expose the sender's global handle to bots by default.

Start flow

Private bot chats require user consent.

The user starts a bot in Buko. Buko creates or opens the private chat, records the start relationship, and sends a visible /start message. Your agent receives that /start as a normal message update.

For MVP integrations, use the /start message as the main start signal.

Receive updates

Buko supports two delivery modes:

ModeRecommended use
Bot Gateway WebSocketProduction realtime agents.
PollingSimple workers, scripts, platforms where WebSocket is inconvenient.

Do not use both at the same time for one bot. If a Gateway connection is active, polling may return 409 GATEWAY_ACTIVE.

Delivery is at least once. Your agent must deduplicate by update_id.

Polling quickstart

Use polling if you want the simplest connector.

Request:

curl -sS https://ims.buko.app/bot/getUpdates \
  -X POST \
  -H "Authorization: Bot $BUKO_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"offset":"0","limit":50,"timeout":20}'

Response:

{
  "ok": true,
  "result": [
    {
      "update_id": "10001",
      "message": {
        "message_id": "42",
        "date": 1783000000,
        "chat": { "id": "space_abc123", "type": "private" },
        "from": {
          "id": "bot_scoped_user_abc",
          "is_bot": false,
          "display_name": "Alice"
        },
        "text": "/start"
      }
    }
  ]
}

Offset rule:

WebSocket quickstart

Use Gateway for realtime agents.

Endpoint:

wss://ims.buko.app/bot/ws

Send Authorization: Bot <token> in the WebSocket upgrade headers.

Incoming frame:

{
  "type": "update",
  "update": {
    "update_id": "10001",
    "message": {
      "message_id": "42",
      "chat": { "id": "space_abc123", "type": "private" },
      "text": "hello"
    }
  }
}

Ack frame:

{
  "type": "ack",
  "update_id": "10001"
}

Ack is cumulative. Acknowledging 10001 confirms all updates up to and including 10001.

Update types

message

New message visible to the bot.

{
  "update_id": "10001",
  "message": {
    "message_id": "42",
    "date": 1783000000,
    "chat": { "id": "space_abc123", "type": "private" },
    "from": {
      "id": "bot_scoped_user_abc",
      "is_bot": false,
      "display_name": "Alice"
    },
    "text": "hello"
  }
}

Incoming attachments

Messages can include one or more attachments in message.media.

Each media item keeps Buko's original compact fields and also exposes agent-friendly file fields:

{
  "update_id": "10004",
  "message": {
    "message_id": "46",
    "date": 1783000000,
    "chat": { "id": "space_abc123", "type": "private" },
    "from": {
      "id": "bot_scoped_user_abc",
      "is_bot": false,
      "display_name": "Alice"
    },
    "text": "please inspect this image",
    "media": [
      {
        "key": "media/space_abc123/01J...",
        "mime": "image/png",
        "size": 12345,
        "file_id": "media/space_abc123/01J...",
        "mime_type": "image/png",
        "file_size": 12345,
        "file_name": "photo.png",
        "width": 1200,
        "height": 800,
        "download_path": "/bot/file/media%2Fspace_abc123%2F01J..."
      }
    ],
    "photo": [
      {
        "file_id": "media/space_abc123/01J...",
        "mime_type": "image/png",
        "file_size": 12345,
        "width": 1200,
        "height": 800,
        "download_path": "/bot/file/media%2Fspace_abc123%2F01J..."
      }
    ]
  }
}

Voice messages use the same rule. message.media remains the authoritative attachment list, and message.voice is a convenience copy of the first voice attachment:

{
  "update_id": "10005",
  "message": {
    "message_id": "47",
    "date": 1783000030,
    "chat": { "id": "space_abc123", "type": "private" },
    "from": {
      "id": "bot_scoped_user_abc",
      "is_bot": false,
      "display_name": "Alice"
    },
    "text": "",
    "media": [
      {
        "key": "media/space_abc123/01J...",
        "mime": "audio/mp4",
        "size": 45678,
        "file_id": "media/space_abc123/01J...",
        "mime_type": "audio/mp4",
        "file_size": 45678,
        "duration_ms": 8200,
        "duration": 8,
        "waveform": "12,18,24,33,...",
        "download_path": "/bot/file/media%2Fspace_abc123%2F01J..."
      }
    ],
    "voice": {
      "file_id": "media/space_abc123/01J...",
      "mime_type": "audio/mp4",
      "file_size": 45678,
      "duration_ms": 8200,
      "duration": 8,
      "waveform": "12,18,24,33,...",
      "download_path": "/bot/file/media%2Fspace_abc123%2F01J..."
    }
  }
}

Convenience fields:

FieldMeaning
message.mediaAuthoritative list of all attachments.
message.photoImage attachments, when present.
message.voiceFirst audio attachment with duration metadata, when present.
message.documentFirst non-photo, non-voice attachment, when present.

For each attachment:

FieldMeaning
file_idStable file id to pass to getFile or /bot/file/....
mime_typeFile MIME type.
file_sizeFile size in bytes.
file_nameOriginal file name when available.
width, heightImage dimensions when available.
duration_ms, durationAudio/video duration when available.
waveformVoice waveform when available.
download_pathRelative authenticated download path.

Agent implementation tip: parse message.media first. You may also read message.voice, message.photo, and message.document as convenience fields, but they should mirror items already present in message.media.

edited_message

Human message edit visible to the bot.

{
  "update_id": "10003",
  "edited_message": {
    "message_id": "45",
    "edit_date": 1783000300,
    "chat": { "id": "space_abc123", "type": "private" },
    "from": {
      "id": "bot_scoped_user_abc",
      "is_bot": false,
      "display_name": "Alice"
    },
    "text": "edited text"
  }
}

my_chat_member

Bot relationship or membership change.

Private stop/block uses started -> stopped. Group removal or group dissolution uses member -> removed.

{
  "update_id": "10002",
  "my_chat_member": {
    "chat": { "id": "space_abc123", "type": "private" },
    "from": {
      "id": "bot_scoped_user_abc",
      "is_bot": false,
      "display_name": "Alice"
    },
    "date": 1783000000,
    "old_status": "started",
    "new_status": "stopped"
  }
}

interaction

Button taps are delivered as interaction updates. They are not visible chat messages and do not create a new message_id.

{
  "update_id": "10006",
  "interaction": {
    "id": "ixn_01J...",
    "type": "callback",
    "chat": { "id": "space_abc123", "type": "private" },
    "from": {
      "id": "bot_scoped_user_abc",
      "is_bot": false,
      "display_name": "Alice"
    },
    "message": {
      "chat": { "id": "space_abc123", "type": "private" },
      "message_id": "43"
    },
    "component_id": "primary_actions",
    "item_id": "bind",
    "data": "bind_account",
    "created_at": "2026-07-03T02:00:00.000Z"
  }
}

Deduplicate interaction work by update_id or interaction.id.

Send text messages

POST https://ims.buko.app/bot/sendMessage

Request:

{
  "chat_id": "space_abc123",
  "text": "**Hello** from your agent",
  "reply_to_message_id": "42",
  "parse_mode": "app_markdown",
  "interactions": {
    "version": 1,
    "components": [
      {
        "type": "button_row",
        "id": "primary_actions",
        "items": [
          {
            "id": "bind",
            "label": "Bind account",
            "style": "primary",
            "action": { "type": "callback", "data": "bind_account" }
          },
          {
            "id": "docs",
            "label": "Docs",
            "action": {
              "type": "open_url",
              "url": "https://buko.app/dev-docs/bot-api/"
            }
          }
        ]
      }
    ]
  }
}

Optional fields:

FieldMeaning
reply_to_message_idReply to an existing message seq in the same chat.
parse_modeplain or app_markdown. Defaults to plain.
displayRich display object. Usually omit it and let Buko derive display from text + parse_mode.
interactionsButton rows. Tier-gated; free bots cannot send interactions.

app_markdown supports a conservative subset: bold, inline code, fenced code blocks, quotes, headings, lists, simple tables, and HTTPS links. Raw HTML is not rendered. Unsafe links are rejected, including localhost, private IP, loopback, link-local, and multicast targets.

Interaction limits:

Example:

curl -sS https://ims.buko.app/bot/sendMessage \
  -X POST \
  -H "Authorization: Bot $BUKO_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "chat_id": "space_abc123",
    "text": "**Got it.**",
    "parse_mode": "app_markdown",
    "reply_to_message_id": "42",
    "interactions": {
      "version": 1,
      "components": [
        {
          "type": "button_row",
          "id": "primary_actions",
          "items": [
            {
              "id": "ok",
              "label": "OK",
              "style": "primary",
              "action": { "type": "callback", "data": "ok" }
            }
          ]
        }
      ]
    }
  }'

Response:

{
  "ok": true,
  "result": {
    "message_id": "43",
    "chat": { "id": "space_abc123", "type": "private" },
    "date": 1783000000,
    "text": "Got it."
  }
}

Answer interactions

POST https://ims.buko.app/bot/answerInteraction

Use this to acknowledge a button tap with a short toast or alert on the device that tapped the button.

{
  "interaction_id": "ixn_01J...",
  "text": "Started.",
  "show_alert": false
}

Response:

{
  "ok": true,
  "result": {
    "delivered": true
  }
}

Interaction answers are short-lived and best-effort. The route is valid for about 10 seconds after the tap. For long-running work, answer quickly and send a normal message when the work finishes.

Send typing indicators

POST https://ims.buko.app/bot/sendChatAction

Supported actions:

Example:

curl -sS https://ims.buko.app/bot/sendChatAction \
  -X POST \
  -H "Authorization: Bot $BUKO_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id":"space_abc123","action":"typing"}'

Send media

Buko supports photos and documents with optional captions.

Do not send a remote URL. Upload bytes using multipart/form-data.

sendPhoto

POST https://ims.buko.app/bot/sendPhoto

curl -sS https://ims.buko.app/bot/sendPhoto \
  -X POST \
  -H "Authorization: Bot $BUKO_BOT_TOKEN" \
  -F "chat_id=space_abc123" \
  -F "caption=Here is the chart." \
  -F "parse_mode=app_markdown" \
  -F "photo=@./chart.png;type=image/png"

sendDocument

POST https://ims.buko.app/bot/sendDocument

curl -sS https://ims.buko.app/bot/sendDocument \
  -X POST \
  -H "Authorization: Bot $BUKO_BOT_TOKEN" \
  -F "chat_id=space_abc123" \
  -F "caption=Monthly report" \
  -F "parse_mode=plain" \
  -F "document=@./report.pdf;type=application/pdf"

Limits:

MethodMax size
sendPhoto20 MB
sendDocument50 MB

Captions can be up to 5000 characters.

Media methods accept the same optional reply_to_message_id, parse_mode, display, and interactions fields as sendMessage. Because media methods use multipart/form-data, send display and interactions as JSON strings:

curl -sS https://ims.buko.app/bot/sendPhoto \
  -X POST \
  -H "Authorization: Bot $BUKO_BOT_TOKEN" \
  -F "chat_id=space_abc123" \
  -F "caption=Choose what to do with this image." \
  -F "parse_mode=app_markdown" \
  -F 'interactions={
    "version": 1,
    "components": [
      {
        "type": "button_row",
        "id": "image_actions",
        "items": [
          {
            "id": "analyze",
            "label": "Analyze",
            "style": "primary",
            "action": { "type": "callback", "data": "analyze_image" }
          }
        ]
      }
    ]
  }' \
  -F "photo=@./chart.png;type=image/png"

Example open_app_link action:

{
  "type": "open_app_link",
  "target": {
    "type": "bot_profile",
    "value": "homefold"
  }
}

Download incoming files

Incoming attachment downloads require the bot token.

Bots can only download files from chats where the bot is currently allowed to read:

The file id must come from an incoming message.media[*].file_id value. Buko only allows media/<chat_id>/... file ids here. tmp_uploads, moments, avatars, arbitrary paths, and directory-like scans are rejected.

getFile

POST https://ims.buko.app/bot/getFile

Request:

{
  "file_id": "media/space_abc123/01J..."
}

Example:

curl -sS https://ims.buko.app/bot/getFile \
  -X POST \
  -H "Authorization: Bot $BUKO_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"file_id":"media/space_abc123/01J..."}'

Response:

{
  "ok": true,
  "result": {
    "file_id": "media/space_abc123/01J...",
    "file_size": 12345,
    "mime_type": "image/png",
    "download_path": "/bot/file/media%2Fspace_abc123%2F01J...",
    "download_url": "https://ims.buko.app/bot/file/media%2Fspace_abc123%2F01J...",
    "requires_authorization": true
  }
}

download_url is not public. You must send the same Authorization: Bot <token> header when downloading.

Direct download

GET https://ims.buko.app/bot/file/<encoded file_id>

Example:

curl -L https://ims.buko.app/bot/file/media%2Fspace_abc123%2F01J... \
  -H "Authorization: Bot $BUKO_BOT_TOKEN" \
  -o attachment.bin

Range requests are supported:

Range: bytes=0-1048575

Edit and delete messages

These methods are tier-gated. Free bots cannot use them.

Even when enabled, a bot can only edit or delete messages authored by the same bot.

editMessageText

POST https://ims.buko.app/bot/editMessageText

{
  "chat_id": "space_abc123",
  "message_id": "43",
  "text": "Updated text",
  "parse_mode": "plain"
}

deleteMessage

POST https://ims.buko.app/bot/deleteMessage

{
  "chat_id": "space_abc123",
  "message_id": "43"
}

Error envelope

All errors use this shape:

{
  "ok": false,
  "error_code": 403,
  "code": "CHAT_FORBIDDEN",
  "description": "User has not started this bot."
}

Common errors:

HTTPCodeWhat to do
400BAD_REQUESTFix request parameters.
400INVALID_INTERACTIONFix the button/component payload.
400INVALID_MARKDOWNFix Markdown syntax, remove unsafe HTML, or use HTTPS links.
400INTERACTION_EXPIREDThe button message has expired; send a fresh message.
400INTERACTION_NOT_FOUNDThe referenced button no longer exists.
400UNSUPPORTED_DISPLAY_FORMATUse display.version=1 and format=app_markdown.
401UNAUTHORIZEDStop and check token. Re-read token if it may have rotated.
403CHAT_FORBIDDENDo not retry until permission state changes.
403BOT_BLOCKEDStop sending to that chat.
403FILE_FORBIDDENBot cannot read this file id.
403FORBIDDEN_INTERACTIONThe user cannot tap this button in this chat.
403METHOD_NOT_ALLOWED_FOR_TIERDisable that feature or upgrade bot tier.
403MESSAGE_FORBIDDENBot tried to edit/delete a message it does not own.
404CHAT_NOT_FOUNDDrop or re-resolve the chat.
404FILE_NOT_FOUNDFile does not exist, expired, or is invisible to this bot.
409GATEWAY_ACTIVEStop polling or close Gateway.
410INTERACTION_DELIVERY_FAILEDThe tap route expired before the bot answered.
413DISPLAY_TOO_LARGEReduce the rich display payload.
413INTERACTION_TOO_LARGEReduce the interaction payload.
413PAYLOAD_TOO_LARGECompress or reject the file.
429RATE_LIMITEDWait for retry_after, then retry.
500INTERNALRetry with exponential backoff and jitter.

Default quota tiers

TierIncoming / minIncoming / dayMessages / minMessages / dayPolls / minPolls / dayEdit/deleteInteractions
free605,000201,0003010,000nono
pro18050,0006010,0006050,000yesyes
team300150,00010025,000100100,000yesyes
official600500,00012050,000120200,000yesyes

Quotas are conservative during early access and may change.

Minimal Python polling echo agent

This example uses polling because it works in most environments.

Install dependency:

python3 -m pip install requests

Run:

import os
import time
import requests

BASE = "https://ims.buko.app"
TOKEN = os.environ["BUKO_BOT_TOKEN"]
HEADERS = {
    "Authorization": f"Bot {TOKEN}",
    "Content-Type": "application/json",
}

offset = "0"
seen = set()


def api(method, payload=None):
    r = requests.post(
        f"{BASE}/bot/{method}",
        headers=HEADERS,
        json=payload or {},
        timeout=35,
    )
    data = r.json()
    if not data.get("ok"):
        raise RuntimeError(f"{method} failed: {data}")
    return data["result"]


def send_message(chat_id, text, reply_to=None):
    payload = {"chat_id": chat_id, "text": text}
    if reply_to:
        payload["reply_to_message_id"] = reply_to
    return api("sendMessage", payload)


def handle_update(update):
    message = update.get("message")
    if not message:
        return

    text = message.get("text") or ""
    chat_id = message["chat"]["id"]
    message_id = message["message_id"]

    if text == "/start":
        send_message(chat_id, "Buko bot is online. Send me a message.", message_id)
        return

    reply = f"Echo: {text}" if text else "I received your message."
    send_message(chat_id, reply, message_id)


while True:
    try:
        updates = api("getUpdates", {
            "offset": offset,
            "limit": 50,
            "timeout": 20,
        })
        for update in updates:
            update_id = update["update_id"]
            if update_id in seen:
                continue
            seen.add(update_id)
            handle_update(update)
            offset = str(int(update_id) + 1)
    except Exception as exc:
        print("bot loop error:", exc)
        time.sleep(3)

Production notes for this example:

Minimal WebSocket agent outline

Use this when you need low-latency realtime delivery.

import WebSocket from "ws";

const token = process.env.BUKO_BOT_TOKEN;
const ws = new WebSocket("wss://ims.buko.app/bot/ws", {
  headers: { Authorization: `Bot ${token}` },
});

ws.on("message", async (raw) => {
  const frame = JSON.parse(raw.toString());
  if (frame.type !== "update") return;

  const update = frame.update;

  try {
    // Run your agent logic here.
    console.log("update", update.update_id, update.message?.text);

    ws.send(JSON.stringify({
      type: "ack",
      update_id: update.update_id,
    }));
  } catch (err) {
    // Do not ack if processing failed and you want Buko to redeliver later.
    console.error(err);
  }
});

Suggested agent architecture

Use these components:

ComponentResponsibility
Update receiverWebSocket or polling loop.
Dedupe storeRemember handled update_id.
Conversation routerRoute by chat.id and from.id.
Agent coreYour LLM, tools, workflows, or business logic.
Buko senderCalls sendMessage, sendPhoto, sendDocument, and optional actions.
File downloaderDownloads incoming message.media[*].file_id through /bot/file/....
Error handlerDistinguishes retryable and permanent failures.

Recommended state keys:

Security checklist for AI agents

Implementation checklist

  1. Read BUKO_BOT_TOKEN from environment.
  2. Call getMe; fail fast if it returns UNAUTHORIZED.
  3. Choose WebSocket or polling.
  4. Deduplicate updates by update_id.
  5. Handle /start.
  6. If message.media exists, call getFile or download from download_path with the bot token.
  7. For each message, run your agent logic.
  8. Send replies with sendMessage.
  9. Handle my_chat_member stopped/removed by disabling that chat in your state.
  10. Implement retry/backoff for retryable errors.
  11. Keep the token out of logs and user-visible output.

Canonical source

This single page is the canonical Buko Bot API reference for both humans and AI agents.

Older split paths redirect back into this page: