Bot API
Build bots for Koto Messenger. Register via KotoFather, receive messages through webhooks or long-polling, and reply with rich content including inline keyboards.
Overview
Koto Bot API allows developers to build bots that interact with users inside Koto Messenger. Bots are identified by their botId and authenticated via botToken.
Base URL
https://api.koto.run/v1/botAuthentication
There are two authentication contexts:
Request format
- ›All request bodies are JSON (
Content-Type: application/json) - ›Authentication token is sent via
Authorization: Bearer <token>header - ›All timestamps are Unix milliseconds
- ›Requests pass through OHTTP relay — the server never sees client IP
Types
BotInfo
| Field | Type | Description |
|---|---|---|
| botId | string | Unique bot identifier (ends with _bot) |
| ownerId | string | Fingerprint of the bot owner |
| displayName | string | Bot display name (3-32 characters) |
| description | string | Bot description (up to 256 characters) |
| avatarUrl | string | URL of bot avatar image |
| createdAt | number | Unix timestamp of bot creation |
| active | boolean | Whether the bot is active |
BotUpdate
| Field | Type | Description |
|---|---|---|
| updateId | string | Unique update identifier |
| chatId | string | Chat where the message was sent |
| senderFingerprint | string | Fingerprint of the user who sent the message |
| content | string | Message text content |
| contentType | number | Content type (1 = text, 2 = command) |
| timestamp | number | Unix timestamp of the message |
| type | UpdateType | Update type — MESSAGE or CALLBACK |
| callbackData? | string | Callback data from inline button (only for CALLBACK type) |
| messageId? | string | ID of the message containing the pressed button (only for CALLBACK type) |
UpdateType
| Field | Type | Description |
|---|---|---|
| MESSAGE | 0 | Regular text message from user |
| CALLBACK | 1 | Inline button callback (contains callbackData and messageId) |
BotCommand
| Field | Type | Description |
|---|---|---|
| command | string | Command string starting with / (e.g. /help) |
| description | string | Human-readable description of what the command does |
InlineButton
| Field | Type | Description |
|---|---|---|
| text | string | Button label shown to user |
| callbackData | string | Data sent back to bot when button is pressed |
BotReply
| Field | Type | Description |
|---|---|---|
| content | string | Reply message text |
| inlineButtons? | InlineButton[] | Optional inline keyboard buttons |
Webhook
| Field | Type | Description |
|---|---|---|
| url | string | HTTPS URL to receive updates |
| secret? | string | HMAC-SHA256 secret for payload signature |
| active | boolean | Whether webhook is currently active |
Methods
Bot Management
/v1/bot/registerUser tokenRegister a new bot. Returns bot info and a unique bot token. Store the token securely — it cannot be retrieved later.
{
"displayName": "MyBot",
"description": "A helpful assistant bot"
}{
"bot": {
"botId": "mybot_bot",
"ownerId": "a1b2c3...",
"displayName": "MyBot",
"description": "A helpful assistant bot",
"avatarUrl": "",
"createdAt": 1707580800000,
"active": true
},
"botToken": "nb_live_..."
}/v1/bot/listUser tokenList all bots owned by the authenticated user.
{
"bots": [
{
"botId": "mybot_bot",
"displayName": "MyBot",
"active": true,
...
}
]
}/v1/bot/{botId}User tokenGet detailed information about a specific bot.
{
"bot": {
"botId": "mybot_bot",
"ownerId": "a1b2c3...",
"displayName": "MyBot",
"description": "A helpful assistant bot",
"avatarUrl": "",
"createdAt": 1707580800000,
"active": true
}
}/v1/bot/revokeUser tokenRevoke a bot token and deactivate the bot. This action is irreversible.
{
"botId": "mybot_bot"
}{
"status": "ok"
}Messaging
/v1/bot/sendBot tokenSend a message from the bot to a user. Supports text and inline buttons.
{
"botToken": "nb_live_...",
"recipientFingerprint": "a1b2c3...",
"content": "Hello! Choose an option:",
"contentType": 1
}{
"messageId": "msg_abc123",
"timestamp": 1707580800000
}/v1/bot/conversation/startUser tokenStart a conversation with a bot. Creates the chat if it doesn't exist and triggers the bot's /start handler.
{
"botId": "mybot_bot"
}{
"status": "ok"
}/v1/bot/user-messageUser tokenSend a message from a user to a bot. The bot may return a synchronous reply.
{
"botId": "mybot_bot",
"content": "/help"
}{
"messageId": "msg_def456",
"timestamp": 1707580800000,
"botReply": {
"content": "Available commands:\n/start — Begin\n/help — Show help",
"inlineButtons": [
{ "text": "Getting Started", "callbackData": "help_start" }
]
}
}Callbacks
/v1/bot/callbackUser tokenSend a callback when the user presses an inline button. The bot receives a BotUpdate with type CALLBACK.
{
"botId": "mybot_bot",
"messageId": "msg_abc123",
"callbackData": "help_start"
}{
"callbackId": "cb_xyz789",
"timestamp": 1707580800000
}Media
/v1/bot/sendMediaBot tokenRequest a presigned upload URL for sending media to a user. After uploading the file, call confirmMedia to finalize.
{
"botToken": "nb_live_...",
"recipientFingerprint": "a1b2c3...",
"sizeBytes": 1048576,
"contentType": "image/png",
"caption": "Screenshot"
}{
"mediaId": "med_abc123",
"uploadUrl": "https://minio.nova.internal/..."
}/v1/bot/confirmMediaBot tokenConfirm that a media file has been uploaded. Returns the message ID and a download URL for the recipient.
{
"botToken": "nb_live_...",
"mediaId": "med_abc123"
}{
"messageId": "msg_med456",
"timestamp": 1707580800000,
"downloadUrl": "https://minio.nova.internal/..."
}Commands
/v1/bot/setMyCommandsBot tokenSet the list of commands for the bot. Up to 30 commands, each must start with /. Replaces any previously set commands.
{
"botToken": "nb_live_...",
"commands": [
{ "command": "/start", "description": "Begin interaction" },
{ "command": "/help", "description": "Show available commands" },
{ "command": "/settings", "description": "Bot settings" }
]
}{
"status": "ok"
}/v1/bot/{botId}/commandsPublic tokenGet the list of commands registered for a bot. Used by the frontend to populate the command menu.
{
"commands": [
{ "command": "/start", "description": "Begin interaction" },
{ "command": "/help", "description": "Show available commands" }
]
}Webhooks
Instead of polling for updates, you can register a webhook URL. Koto will send an HTTPS POST request to your URL whenever the bot receives a new message.
/v1/bot/setWebhookBot tokenSet or update the webhook URL for receiving updates. The URL must use HTTPS. Pass an empty URL to disable the webhook.
{
"botToken": "nb_live_...",
"url": "https://example.com/webhook/nova",
"secret": "my_webhook_secret"
}{
"status": "ok"
}Delivery format
Each webhook delivery is an HTTPS POST with a JSON body containing the update:
{
"updateId": "upd_abc123",
"chatId": "chat_xyz",
"senderFingerprint": "a1b2c3...",
"content": "/start",
"contentType": 2,
"timestamp": 1707580800000
}Signature verification
If you provided a secret when setting the webhook, each request will include an X-Koto-Signature header with an HMAC-SHA256 hex digest of the request body.
const crypto = require("crypto");
function verifySignature(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Retry policy
- ›Your endpoint must respond with HTTP 200 within 5 seconds
- ›Failed deliveries are retried 3 times with exponential backoff (5s, 15s, 45s)
- ›After 3 failures, the webhook is marked inactive. Re-set to reactivate.
Getting Updates
If you prefer polling over webhooks, use the getUpdates method. This uses long-polling — the server holds the connection open until new updates arrive or the timeout expires.
Important
- ›You cannot use getUpdates and webhooks at the same time
- ›Setting a webhook automatically disables getUpdates
- ›Set webhook to empty URL to switch back to getUpdates
/v1/bot/getUpdates?offset=0&limit=100&timeout=30Bot tokenFetch new updates via long-polling. The server waits up to `timeout` seconds for new updates before returning an empty array.
{
"updates": [
{
"updateId": "upd_abc123",
"chatId": "chat_xyz",
"senderFingerprint": "a1b2c3...",
"content": "Hello bot!",
"contentType": 1,
"timestamp": 1707580800000
}
]
}Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| offset | number | 0 | Identifier of the first update to return |
| limit | number | 100 | Max number of updates (1-100) |
| timeout | number | 30 | Long-polling timeout in seconds (0-60) |
KotoFather
KotoFather is a built-in system bot for managing your bots. Start a conversation with KotoFather in the messenger to create and manage bots interactively.
Commands
/newbotCreate a new bot. KotoFather will ask for a display name and generate a unique bot ID./mybotsList all your bots with their status and token info./revokeRevoke a bot token. Select which bot to deactivate./helpShow available commands.Bot creation flow
- 1.Send
/newbotto KotoFather - 2.Enter a display name for your bot (3-32 characters)
- 3.KotoFather generates a bot ID (lowercase, ending with
_bot) - 4.You receive a bot token (
nb_live_...) — store it securely - 5.Your bot is now active and can receive messages
Bot ID rules
- ›Must end with
_bot - ›Lowercase alphanumeric and underscores only
- ›3-32 characters total (including
_botsuffix) - ›Must be globally unique across all Koto bots
Examples
# Register a new bot
curl -X POST https://api.koto.run/v1/bot/register \
-H "Authorization: Bearer <user_jwt>" \
-H "Content-Type: application/json" \
-d '{"displayName": "MyBot", "description": "Test bot"}'
# Send a message
curl -X POST https://api.koto.run/v1/bot/send \
-H "Content-Type: application/json" \
-d '{
"botToken": "nb_live_...",
"recipientFingerprint": "a1b2c3...",
"content": "Hello from bot!",
"contentType": 1
}'
# Get updates (long-polling)
curl "https://api.koto.run/v1/bot/getUpdates?timeout=30" \
-H "Authorization: Bearer nb_live_..."Error Codes
All error responses follow the same format:
{ "error": "description of what went wrong" }| Code | Name | Description |
|---|---|---|
| 400 | Bad Request | Invalid request body or missing required fields |
| 401 | Unauthorized | Missing or invalid authentication token |
| 403 | Forbidden | You don't have permission for this action (e.g., revoking another user's bot) |
| 404 | Not Found | Bot or resource not found |
| 409 | Conflict | Resource already exists (e.g., duplicate bot ID) |
| 412 | Precondition Failed | Bot is inactive or token has been revoked |
| 429 | Too Many Requests | Rate limit exceeded. Retry after the period indicated in Retry-After header |
| 500 | Internal Server Error | Unexpected server error. Contact support if persistent |