v1.0February 2026

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/bot

Authentication

There are two authentication contexts:

User tokenJWT obtained via Ed25519 challenge-response. Used for bot management (register, list, revoke) and starting conversations.
Bot tokenUnique token returned on bot registration. Used for sending messages, receiving updates, and setting webhooks.

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

FieldTypeDescription
botIdstringUnique bot identifier (ends with _bot)
ownerIdstringFingerprint of the bot owner
displayNamestringBot display name (3-32 characters)
descriptionstringBot description (up to 256 characters)
avatarUrlstringURL of bot avatar image
createdAtnumberUnix timestamp of bot creation
activebooleanWhether the bot is active

BotUpdate

FieldTypeDescription
updateIdstringUnique update identifier
chatIdstringChat where the message was sent
senderFingerprintstringFingerprint of the user who sent the message
contentstringMessage text content
contentTypenumberContent type (1 = text, 2 = command)
timestampnumberUnix timestamp of the message
typeUpdateTypeUpdate type — MESSAGE or CALLBACK
callbackData?stringCallback data from inline button (only for CALLBACK type)
messageId?stringID of the message containing the pressed button (only for CALLBACK type)

UpdateType

FieldTypeDescription
MESSAGE0Regular text message from user
CALLBACK1Inline button callback (contains callbackData and messageId)

BotCommand

FieldTypeDescription
commandstringCommand string starting with / (e.g. /help)
descriptionstringHuman-readable description of what the command does

InlineButton

FieldTypeDescription
textstringButton label shown to user
callbackDatastringData sent back to bot when button is pressed

BotReply

FieldTypeDescription
contentstringReply message text
inlineButtons?InlineButton[]Optional inline keyboard buttons

Webhook

FieldTypeDescription
urlstringHTTPS URL to receive updates
secret?stringHMAC-SHA256 secret for payload signature
activebooleanWhether webhook is currently active

Methods

Bot Management

POST/v1/bot/registerUser token

Register a new bot. Returns bot info and a unique bot token. Store the token securely — it cannot be retrieved later.

Request BodyJSON
{
  "displayName": "MyBot",
  "description": "A helpful assistant bot"
}
ResponseJSON
{
  "bot": {
    "botId": "mybot_bot",
    "ownerId": "a1b2c3...",
    "displayName": "MyBot",
    "description": "A helpful assistant bot",
    "avatarUrl": "",
    "createdAt": 1707580800000,
    "active": true
  },
  "botToken": "nb_live_..."
}
GET/v1/bot/listUser token

List all bots owned by the authenticated user.

ResponseJSON
{
  "bots": [
    {
      "botId": "mybot_bot",
      "displayName": "MyBot",
      "active": true,
      ...
    }
  ]
}
GET/v1/bot/{botId}User token

Get detailed information about a specific bot.

ResponseJSON
{
  "bot": {
    "botId": "mybot_bot",
    "ownerId": "a1b2c3...",
    "displayName": "MyBot",
    "description": "A helpful assistant bot",
    "avatarUrl": "",
    "createdAt": 1707580800000,
    "active": true
  }
}
POST/v1/bot/revokeUser token

Revoke a bot token and deactivate the bot. This action is irreversible.

Request BodyJSON
{
  "botId": "mybot_bot"
}
ResponseJSON
{
  "status": "ok"
}

Messaging

POST/v1/bot/sendBot token

Send a message from the bot to a user. Supports text and inline buttons.

Request BodyJSON
{
  "botToken": "nb_live_...",
  "recipientFingerprint": "a1b2c3...",
  "content": "Hello! Choose an option:",
  "contentType": 1
}
ResponseJSON
{
  "messageId": "msg_abc123",
  "timestamp": 1707580800000
}
POST/v1/bot/conversation/startUser token

Start a conversation with a bot. Creates the chat if it doesn't exist and triggers the bot's /start handler.

Request BodyJSON
{
  "botId": "mybot_bot"
}
ResponseJSON
{
  "status": "ok"
}
POST/v1/bot/user-messageUser token

Send a message from a user to a bot. The bot may return a synchronous reply.

Request BodyJSON
{
  "botId": "mybot_bot",
  "content": "/help"
}
ResponseJSON
{
  "messageId": "msg_def456",
  "timestamp": 1707580800000,
  "botReply": {
    "content": "Available commands:\n/start — Begin\n/help — Show help",
    "inlineButtons": [
      { "text": "Getting Started", "callbackData": "help_start" }
    ]
  }
}

Callbacks

POST/v1/bot/callbackUser token

Send a callback when the user presses an inline button. The bot receives a BotUpdate with type CALLBACK.

Request BodyJSON
{
  "botId": "mybot_bot",
  "messageId": "msg_abc123",
  "callbackData": "help_start"
}
ResponseJSON
{
  "callbackId": "cb_xyz789",
  "timestamp": 1707580800000
}

Media

POST/v1/bot/sendMediaBot token

Request a presigned upload URL for sending media to a user. After uploading the file, call confirmMedia to finalize.

Request BodyJSON
{
  "botToken": "nb_live_...",
  "recipientFingerprint": "a1b2c3...",
  "sizeBytes": 1048576,
  "contentType": "image/png",
  "caption": "Screenshot"
}
ResponseJSON
{
  "mediaId": "med_abc123",
  "uploadUrl": "https://minio.nova.internal/..."
}
POST/v1/bot/confirmMediaBot token

Confirm that a media file has been uploaded. Returns the message ID and a download URL for the recipient.

Request BodyJSON
{
  "botToken": "nb_live_...",
  "mediaId": "med_abc123"
}
ResponseJSON
{
  "messageId": "msg_med456",
  "timestamp": 1707580800000,
  "downloadUrl": "https://minio.nova.internal/..."
}

Commands

POST/v1/bot/setMyCommandsBot token

Set the list of commands for the bot. Up to 30 commands, each must start with /. Replaces any previously set commands.

Request BodyJSON
{
  "botToken": "nb_live_...",
  "commands": [
    { "command": "/start", "description": "Begin interaction" },
    { "command": "/help", "description": "Show available commands" },
    { "command": "/settings", "description": "Bot settings" }
  ]
}
ResponseJSON
{
  "status": "ok"
}
GET/v1/bot/{botId}/commandsPublic token

Get the list of commands registered for a bot. Used by the frontend to populate the command menu.

ResponseJSON
{
  "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.

POST/v1/bot/setWebhookBot token

Set or update the webhook URL for receiving updates. The URL must use HTTPS. Pass an empty URL to disable the webhook.

Request BodyJSON
{
  "botToken": "nb_live_...",
  "url": "https://example.com/webhook/nova",
  "secret": "my_webhook_secret"
}
ResponseJSON
{
  "status": "ok"
}

Delivery format

Each webhook delivery is an HTTPS POST with a JSON body containing the update:

Webhook PayloadJSON
{
  "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.

Verify Signature (Node.js)JavaScript
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
GET/v1/bot/getUpdates?offset=0&limit=100&timeout=30Bot token

Fetch new updates via long-polling. The server waits up to `timeout` seconds for new updates before returning an empty array.

ResponseJSON
{
  "updates": [
    {
      "updateId": "upd_abc123",
      "chatId": "chat_xyz",
      "senderFingerprint": "a1b2c3...",
      "content": "Hello bot!",
      "contentType": 1,
      "timestamp": 1707580800000
    }
  ]
}

Query parameters

ParameterTypeDefaultDescription
offsetnumber0Identifier of the first update to return
limitnumber100Max number of updates (1-100)
timeoutnumber30Long-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. 1.Send /newbot to KotoFather
  2. 2.Enter a display name for your bot (3-32 characters)
  3. 3.KotoFather generates a bot ID (lowercase, ending with _bot)
  4. 4.You receive a bot token (nb_live_...) — store it securely
  5. 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 _bot suffix)
  • Must be globally unique across all Koto bots

Examples

cURLbash
# 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" }
CodeNameDescription
400Bad RequestInvalid request body or missing required fields
401UnauthorizedMissing or invalid authentication token
403ForbiddenYou don't have permission for this action (e.g., revoking another user's bot)
404Not FoundBot or resource not found
409ConflictResource already exists (e.g., duplicate bot ID)
412Precondition FailedBot is inactive or token has been revoked
429Too Many RequestsRate limit exceeded. Retry after the period indicated in Retry-After header
500Internal Server ErrorUnexpected server error. Contact support if persistent