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 -H "Authorization: Bearer rtk_bot_your_token" https://retalkia.com/api/bots/v1/me2. 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— receivemessage.createdevents.messages:write— send messages to installed channels.members:read— read the member list and receivemember.joined/member.left.channel:read— read channel metadata and receivechannel.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
{
"id": "5f0c1e2a-...",
"name": "GameBot",
"status": "active",
"permissions": ["messages:read", "messages:write"],
"channels": ["a1b2c3d4-..."]
}GET /channels/CHANNEL_ID — channel metadata (channel:read + installed)
{
"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)
{ "members": [ { "id": "u-1", "name": "Marko" }, { "id": "u-2", "name": "Ana" } ] }curl -H "Authorization: Bearer rtk_bot_your_token" https://retalkia.com/api/bots/v1/channels/CHANNEL_ID/membersPOST /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 -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/messagesconst 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());{
"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:
{
"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.
{ "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.
{ "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.
{ "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.
POST /your/webhook HTTP/1.1
Host: example.com
Content-Type: application/json
X-ReTalkia-Signature: sha256=6f2a...c9
X-ReTalkia-Timestamp: 1789135820X-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_idmay 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.
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 */ }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 -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/commandscommand.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_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 -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/messagesephemeral: 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
{
"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.