Docs

Custom Bots API

Created 2026-09-11·Last updated 2026-09-11

Custom Bots let you connect your own external tools to ReTalkia. A bot is created in the ReTalkia dashboard, authenticates with a token, is scoped by permissions, and only sees channels you explicitly install it in. ReTalkia does not host your bot code.

Create and manage bots at /bots (sign-in required). The API token and the webhook signing secret are shown only once.

1. Authentication

Every request uses a bot token: Authorization: Bearer rtk_bot_.... There are no cookies or user sessions. Missing, invalid or revoked tokens return 401 unauthorized; a disabled bot returns 403 bot_disabled.

curl — bot identity
curl -H "Authorization: Bearer rtk_bot_your_token" https://retalkia.com/api/bots/v1/me

2. Permissions

Permissions are a fixed set and default to none (deny). A permission alone is not enough: the bot must also be installed in the channel (section 3).

  • messages:read — receive message.created events.
  • messages:write — send messages to installed channels.
  • members:read — read the member list and receive member.joined / member.left.
  • channel:read — read channel metadata and receive channel.expired.

A request without the required permission returns 403 missing_permission.

3. Channel access

Effective access is permissions multiplied by installation. Install the bot into a channel from the dashboard; only the channel operator or a platform admin can do it. A bot that is not installed in a channel returns 403 channel_not_installed, even if it has the matching permission. Expired temporary channels no longer grant access.

4. REST endpoints

Base path: /api/bots/v1. Responses are JSON and errors use { "error": { "code": "...", "message": "..." } }.

GET /me — bot identity

response
{
  "id": "5f0c1e2a-...",
  "name": "GameBot",
  "status": "active",
  "permissions": ["messages:read", "messages:write"],
  "channels": ["a1b2c3d4-..."]
}

GET /channels/CHANNEL_ID — channel metadata (channel:read + installed)

response
{
  "channel": {
    "id": "a1b2c3d4-...",
    "name": "ops",
    "topic": "Deploy alerts",
    "kind": "channel",
    "visibility": "public",
    "createdAt": "2026-09-11T10:00:00.000Z"
  }
}

GET /channels/CHANNEL_ID/members — members, minimal (members:read + installed)

response
{ "members": [ { "id": "u-1", "name": "Marko" }, { "id": "u-2", "name": "Ana" } ] }
curl
curl -H "Authorization: Bearer rtk_bot_your_token" https://retalkia.com/api/bots/v1/channels/CHANNEL_ID/members

POST /channels/CHANNEL_ID/messages — send a text message (messages:write + installed)

Body: { "text": "..." }, 1 to 4000 characters. Voice, files and media are not accepted. The author is always the bot; it cannot choose another author.

curl
curl -X POST -H "Authorization: Bearer rtk_bot_your_token" -H "Content-Type: application/json" --data '{"text":"Hello from my bot"}' https://retalkia.com/api/bots/v1/channels/CHANNEL_ID/messages
Node (no dependencies beyond the runtime)
const res = await fetch("https://retalkia.com/api/bots/v1/channels/CHANNEL_ID/messages", {
  method: "POST",
  headers: {
    authorization: "Bearer " + process.env.RTK_BOT_TOKEN,
    "content-type": "application/json",
  },
  body: JSON.stringify({ text: "Hello from my bot" }),
});
console.log(res.status, await res.json());
response
{
  "message": {
    "id": 123,
    "channelId": "a1b2c3d4-...",
    "text": "Hello from my bot",
    "kind": "text",
    "mediaUrl": null,
    "durationMs": null,
    "system": false,
    "at": "2026-09-11T10:00:00.000Z",
    "author": { "id": "bot-user-id", "name": "GameBot", "role": "bot" }
  }
}

There are no history, search, edit or delete endpoints, and no endpoint returns message history.

5. Events

A bot receives events only for channels it is installed in and only for permissions it holds. Every delivery uses the same envelope:

envelope
{
  "event_id": "9f3c8b2a-...",
  "event_type": "message.created",
  "created_at": "2026-09-11T10:00:00.000Z",
  "bot_id": "5f0c1e2a-...",
  "channel_id": "a1b2c3d4-...",
  "payload": { }
}

message.created

Sent for text messages in installed channels. Voice messages are never sent and audio is never exposed.

payload
{ "message": { "id": 123, "kind": "text", "author": { "id": "u-1", "name": "Marko" }, "content": "!deploy", "createdAt": "2026-09-11T10:00:00.000Z" } }

member.joined / member.left

Sent for human members only; bot users are excluded.

payload
{ "user": { "id": "u-1", "name": "Marko" }, "channel_id": "a1b2c3d4-...", "at": "2026-09-11T10:00:00.000Z" }

channel.expired

Sent when a temporary channel expires. It carries metadata only, never channel content.

payload
{ "channelId": "a1b2c3d4-...", "occurredAt": "2026-09-11T10:00:00.000Z" }

6. Webhooks

If a bot has a webhook URL, events are delivered with an HTTPS POST and Content-Type: application/json. The body is the exact envelope above.

request headers we send
POST /your/webhook HTTP/1.1
Host: example.com
Content-Type: application/json
X-ReTalkia-Signature: sha256=6f2a...c9
X-ReTalkia-Timestamp: 1789135820

X-ReTalkia-Signature is sha256= followed by the hex HMAC-SHA256 of the raw request body, keyed with the bot webhook signing secret. X-ReTalkia-Timestamp is the Unix time in seconds.

  • Timeout: 5 seconds per delivery.
  • Retries: 1 initial attempt plus up to 3 retries, with backoff 1s, 5s, 25s; then the delivery is marked failed.
  • Delivery is at-least-once: the same event_id may arrive more than once. Deduplicate on it.
  • Order is best-effort per channel, not guaranteed.
  • A bot without a webhook URL receives no deliveries (the REST API still works).
  • Disabled or deleted bots are not delivered to.

7. Receiving webhooks

Verify the signature against the raw body before trusting the payload, and reject stale timestamps. Respond with a 2xx status quickly.

Node
import { createHmac, timingSafeEqual } from "node:crypto";

// Keep the RAW request body: the signature is over the exact bytes we sent.
function verify(rawBody, signatureHeader, timestampHeader, secret) {
  const expected =
    "sha256=" + createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader || "");
  if (a.length !== b.length || !timingSafeEqual(a, b)) return false;
  const ts = Number(timestampHeader);
  if (!Number.isFinite(ts)) return false;
  // Reject old deliveries (replay protection); 5 minutes is a common window.
  return Math.abs(Math.floor(Date.now() / 1000) - ts) <= 300;
}

// Then: if (verify(...)) { const ev = JSON.parse(rawBody); /* dedupe on ev.event_id */ }
Python
import hashlib
import hmac
import json
import time

def verify(raw_body: bytes, signature_header: str, timestamp_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, signature_header or ""):
        return False
    try:
        ts = int(timestamp_header)
    except (TypeError, ValueError):
        return False
    return abs(int(time.time()) - ts) <= 300

# Then: if verify(...): ev = json.loads(raw_body); # dedupe on ev["event_id"]

8. Rate limits

  • 120 requests per minute per bot (all endpoints).
  • 20 messages per minute per bot.
  • 10 messages per minute per bot and channel.

Exceeding a limit returns 429 rate_limited with a Retry-After header (seconds).

9. Error codes

  • unauthorized — missing, invalid or revoked token.
  • bot_disabled — the bot is disabled.
  • missing_permission — the bot lacks the required permission.
  • channel_not_installed — the bot is not installed in that channel (or it expired).
  • not_found — channel or resource not found.
  • validation — invalid input.
  • rate_limited — too many requests.
  • forbidden — generic denial.

10. Rules and limits

Bots operate only through this API and cannot execute code on ReTalkia. In version 1 there is no voice or audio access, no message history or search, no permanent event archive, and no admin or moderation access. Normal retention and expiry apply to everything a bot sends or receives: bots cannot read deleted messages, retrieve old history, or bypass the 50-message retention, channel expiry or rate limits.

11. Support

Questions or problems: info@retalkia.com. Security reports are covered on the Security page.

12. Custom Bots v2

Custom Bots v2 extends the API without changing v1. All v1 endpoints above keep working; new capabilities live under /api/bots/v2 and are opt-in through a new permission.

Permission: commands:receive

A bot can receive command.invoked only for commands it has registered. The permission does not allow invoking commands; it only allows receiving command events.

Register commands — PUT /api/bots/v2/commands

Full replacement: the body is the complete current command namespace of the bot. Names are 1 to 20 characters matching ^[a-z][a-z0-9-]{0,19}$, globally unique among bots, and some names are reserved (for example the built-in assistant commands). At most 25 commands per bot.

curl
curl -X PUT -H "Authorization: Bearer rtk_bot_your_token" -H "Content-Type: application/json" --data '{"commands":[{"name":"example","description":"Example status","usage":"status|nodes"}]}' https://retalkia.com/api/bots/v2/commands

command.invoked

When a channel member sends /name args and this bot owns that command and is installed, ReTalkia sends this event to this bot only. The message itself is still a normal channel message for everyone else; the command owner does not also receive message.created for it.

event payload
{
  "event_id": "9f3c8b2a-...",
  "event_type": "command.invoked",
  "created_at": "2026-09-11T10:00:00.000Z",
  "bot_id": "5f0c1e2a-...",
  "channel_id": "a1b2c3d4-...",
  "payload": {
    "interaction_id": "1b2c3d4e-...",
    "message_id": 123,
    "command": "example",
    "args": ["status"],
    "raw": "/example status",
    "requested_by": { "id": "u-1", "name": "Marko" },
    "invoked_at": "2026-09-11T10:00:00.000Z"
  }
}

bot.installed / bot.uninstalled

Sent to this bot only when it is installed in or removed from a channel. bot.uninstalled is emitted before the installation is removed. Receivers must ignore event types they do not know.

Replies — POST /api/bots/v2/channels/CHANNEL_ID/messages

Same as v1 plus: format (text or markdown, a ReTalkia-defined subset), reply_to (a message in the same channel) and ephemeral.

curl — ephemeral markdown reply
curl -X POST -H "Authorization: Bearer rtk_bot_your_token" -H "Content-Type: application/json" --data '{"text":"**Status:** online","format":"markdown","ephemeral":true,"interaction_id":"1b2c3d4e-..."}' https://retalkia.com/api/bots/v2/channels/CHANNEL_ID/messages

ephemeral: true is allowed only with a valid interaction_id from a recent command.invoked. An ephemeral reply is persistent data with its own retention rule: only the user who invoked the command can see it, it does not participate in the normal 50-message channel window, and it is physically deleted after 1 hour. It cannot extend the interaction and it does not survive channel expiry.

Bot identity — GET /api/bots/v2/me

response
{
  "id": "5f0c1e2a-...",
  "name": "ExampleBot",
  "status": "active",
  "permissions": ["commands:receive", "messages:write"],
  "channels": ["a1b2c3d4-..."],
  "commands": [
    { "name": "example", "description": "Example status", "usage": "status|nodes" }
  ]
}

v2 rate limits: 120 requests/min per bot, 20 messages/min per bot, 10 messages/min per bot and channel, and 10 command registrations per hour. The full contract is in the repository at docs/custom-bots-v2.md.