// bot api reference

BOT API

The complete technical reference for the Void bot platform: authentication, scopes, REST endpoints, the real-time gateway, interactions and webhooks. New here? Start with the friendly bots overview first.

Base URL and auth

Every endpoint lives under one base URL. On each request you pass the bot token as a Bearer value in the Authorization header. Tokens start with void_app_.

base url
https://api.thevoidhub.com
httpauth header
Authorization: Bearer void_app_XXXXXXXXXXXXXXXX

Tokens and scopes

You get a token by creating an app in the desktop app: Settings > Developer. The token is shown ONCE, so copy it then. When you create the app you choose which scopes it has. Every action needs BOTH the token scope AND the install permission.

messages.read Read messages in a channel.
messages.write Send messages and edit the bot’s own messages.
messages.manage Delete messages (moderation).
reactions.write Add reactions to messages.
channels.read Read server and channel info.
channels.manage Create, rename and delete channels.
members.read List and read members.
members.manage Kick, ban and timeout members.
roles.manage Read roles and add/remove them on members.

Installing into a server

Before a bot can act in a server (group), a server admin must install it there. The install is where a subset of permissions is granted for that server. Every action needs the token scope AND the matching install permission below.

VIEW_CHANNELSEND_MESSAGESADD_REACTIONSMANAGE_MESSAGESKICK_MEMBERSBAN_MEMBERSMUTE_MEMBERSMANAGE_CHANNELSMANAGE_ROLES

Rate limits

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds). On HTTP 429 there is also Retry-After (seconds). Buckets are per-app and per-capability.

30 / min Sending messages and reactions
20 / min Moderation (kick / ban / timeout / roles / channels)
60 / min Everything else
httpresponse headers
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 29
X-RateLimit-Reset: 42

# on HTTP 429 there is also:
Retry-After: 42

Errors

Every error has the same shape: an error object with a code and a human-readable message, plus the matching HTTP status.

jsonerror body
{
  "error": {
    "code": "missing_scope",
    "message": "This action needs the messages.write scope."
  }
}
400 validation_failed The request body or fields are invalid.
401 invalid_token The token is missing, wrong or expired.
403 forbidden / missing_scope / not_installed Missing scope, missing permission, or the bot is not installed here.
404 not_found The resource does not exist.
410 deleted The resource was deleted.
429 rate_limited Too many requests. Wait Retry-After seconds.

REST endpoints

All paths below are appended to the base URL. Responses and bodies are identical in both languages. Parts in { } are variables you fill in.

Messages

POST /bot/v1/channels/{channelId}/messages

requires messages.write + SEND_MESSAGES

Send a message to a channel. content is 1-4000 chars. replyTo and components are optional.

Returns a MessageDto (id, channelId, authorId, body, createdAt and more).

jsonrequest body
{
  "content": "Hello!",
  "replyTo": "b4d7e9a1-3c25-4f80-9d16-5e7a8b9c0d1e",
  "components": []
}
jsonresponse
HTTP/1.1 201 Created

{
  "message": {
    "id": "b4d7e9a1-3c25-4f80-9d16-5e7a8b9c0d1e",
    "channelId": "3f9a1c02-8b7e-4d21-9a10-0c1d2e3f4a5b",
    "authorId": "bot_a1b2c3",
    "body": "Hello!",
    "createdAt": "2026-08-25T12:00:00.000Z"
  }
}
PATCH /bot/v1/channels/{channelId}/messages/{messageId}

requires messages.write

Edit the bot’s OWN message. Send content, components or both (at least one). Omit a field to leave it unchanged; components: [] clears the buttons. 204 on success. 403 if it is not the bot’s message, 410 if it was deleted.

jsonrequest body
{
  "content": "new text",
  "components": []
}
DELETE /bot/v1/channels/{channelId}/messages/{messageId}

requires messages.manage + MANAGE_MESSAGES

Delete a message (moderation). 204 on success.

GET /bot/v1/channels/{channelId}/messages

requires messages.read

Read the last 50 messages in a channel.

jsonresponse
{
  "messages": [
    {
      "id": "b4d7e9a1-3c25-4f80-9d16-5e7a8b9c0d1e",
      "authorId": "9e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
      "content": "gm",
      "createdAt": "2026-08-25T12:00:00.000Z"
    }
  ]
}

Reactions

POST /bot/v1/channels/{channelId}/reactions

requires reactions.write + ADD_REACTIONS

Add a reaction (emoji) to a message.

jsonrequest body
{
  "messageId": "b4d7e9a1-3c25-4f80-9d16-5e7a8b9c0d1e",
  "emoji": "👍"
}

Buttons and menus

PUT /bot/v1/channels/{channelId}/messages/{messageId}/components

requires messages.write

Replace the buttons and selects on the bot’s own message. components is an array of button/select objects; an empty array ([]) clears them all. When a user taps a button or select, an interaction.create (kind: component) arrives over the gateway.

jsonrequest body
{
  "components": []
}

Servers and channels

GET /bot/v1/groups/{groupId}

requires installed in the server

Get server info and its public channels.

jsonresponse
{
  "group": { "id": "7c2e5d18-1f4a-4b93-8e6c-2a9b0c1d3e4f", "name": "My Server" },
  "channels": [
    { "id": "3f9a1c02-8b7e-4d21-9a10-0c1d2e3f4a5b", "name": "general" }
  ]
}
POST /bot/v1/groups/{groupId}/channels

requires channels.manage + MANAGE_CHANNELS

Create a text channel.

jsonrequest body
{
  "name": "announcements"
}
PATCH /bot/v1/channels/{channelId}

requires channels.manage + MANAGE_CHANNELS

Rename a channel.

jsonrequest body
{
  "name": "new-name"
}
DELETE /bot/v1/channels/{channelId}

requires channels.manage + MANAGE_CHANNELS

Delete a channel.

Members

GET /bot/v1/groups/{groupId}/members

requires members.read

List members (paginated). Use limit and after (the last userId from the previous page). Keep going until nextAfter is null.

query
GET /bot/v1/groups/{groupId}/members?limit=100&after=9e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b
jsonresponse
{
  "members": [
    {
      "userId": "9e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
      "username": "ada",
      "displayName": "Ada",
      "role": "member",
      "isBot": false,
      "joinedAt": "2026-01-02T09:00:00.000Z"
    }
  ],
  "nextAfter": "9e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b"
}
GET /bot/v1/groups/{groupId}/members/{userId}

requires members.read

Get one member with their role ids.

jsonresponse
{
  "userId": "9e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
  "username": "ada",
  "displayName": "Ada",
  "role": "member",
  "isBot": false,
  "joinedAt": "2026-01-02T09:00:00.000Z",
  "roleIds": ["d2c3b4a5-6f70-4819-a2b3-c4d5e6f7a8b9", "e3d4c5b6-7081-492a-b3c4-d5e6f7a8b9c0"]
}

Roles

GET /bot/v1/groups/{groupId}/roles

requires roles.manage

List the server’s roles.

POST /bot/v1/groups/{groupId}/members/{userId}/roles

requires roles.manage + MANAGE_ROLES

Add a role to a member. A bot can only assign a role whose permissions are a subset of its own grant, and never to staff (owner/admin).

jsonrequest body
{
  "roleId": "d2c3b4a5-6f70-4819-a2b3-c4d5e6f7a8b9"
}
DELETE /bot/v1/groups/{groupId}/members/{userId}/roles/{roleId}

requires roles.manage + MANAGE_ROLES

Remove a role from a member.

Moderation

POST /bot/v1/groups/{groupId}/members/{userId}/kick

requires members.manage + KICK_MEMBERS

Kick a member. Bots can only act on ordinary members, never owner/admin.

POST /bot/v1/groups/{groupId}/members/{userId}/ban

requires members.manage + BAN_MEMBERS

Ban a member. reason is optional; durationDays is optional (leave it out for a permanent ban).

jsonrequest body
{
  "reason": "spam",
  "durationDays": 7
}
DELETE /bot/v1/groups/{groupId}/members/{userId}/ban

requires members.manage + BAN_MEMBERS

Unban a member.

POST /bot/v1/groups/{groupId}/members/{userId}/timeout

requires members.manage + MUTE_MEMBERS

Timeout (mute) a member for a number of minutes.

jsonrequest body
{
  "minutes": 10
}
DELETE /bot/v1/groups/{groupId}/members/{userId}/timeout

requires members.manage + MUTE_MEMBERS

Clear a member’s timeout.

Slash commands

PUT /bot/v1/commands

requires replace-all, up to 25

Register your slash commands (replace-all, up to 25). When a user types /ping in a channel where your app is installed, the bot receives an interaction.create over the gateway.

jsonrequest body
{
  "commands": [
    { "name": "ping", "description": "Check the bot is alive" }
  ]
}

Real-time gateway

To receive events live you open a WebSocket connection to the gateway. Send the bot token either with a Bearer header or with the bearer.<token> WebSocket subprotocol. On connect you get a hello message, then events stream in. Events are scoped to the servers the bot is installed in and the channels it can see.

wssconnect
wss://api.thevoidhub.com/bot/v1/gateway?intents=messages,reactions,members
httptwo auth options
# 1) Bearer header on the upgrade request
Authorization: Bearer void_app_YOUR_TOKEN

# 2) or the WebSocket subprotocol
Sec-WebSocket-Protocol: bearer.void_app_YOUR_TOKEN
jsonincoming frames
{ "t": "hello", "d": {} }

{ "t": "message.create", "d": { "id": "b4d7e9a1-3c25-4f80-9d16-5e7a8b9c0d1e", "channelId": "3f9a1c02-8b7e-4d21-9a10-0c1d2e3f4a5b" } }

Events you can receive:

message.create A message was posted.
message.update A message was edited.
message.delete A message was deleted.
reaction.add A reaction was added.
reaction.remove A reaction was removed.
member.join A member joined the server.
member.leave A member left the server.
interaction.create A slash command, button/select, or modal submit (kind: command | component | modal_submit).

! There is currently NO resume/replay

If the socket drops, events during the gap are missed. Reconnect and continue. Verify critical state by also reading over REST.

Interactions

When a user runs a slash command or taps a button/select, the bot receives an interaction.create over the gateway carrying an interactionId. The bot responds via the REST API: send a message in the channel and/or open a modal. If the bot is offline, the user is told "this bot is not responding".

jsoninteraction.create
{
  "t": "interaction.create",
  "d": {
    "interactionId": "int_9x2",
    "kind": "command"
  }
}

To open a modal:

httpopen modal
POST /bot/v1/interactions/{interactionId}/modal

{
  "modal": {
    "customId": "feedback",
    "title": "Send feedback",
    "fields": [
      { "customId": "summary", "label": "Summary", "style": "short", "required": true },
      { "customId": "details", "label": "Details", "style": "paragraph", "required": false }
    ]
  }
}

Incoming webhooks

The simplest path, with no bot needed. A server admin opens Server Settings > Webhooks, creates a webhook bound to a text channel and copies its URL (it looks like https://api.thevoidhub.com/hooks/{id}/{token}). Any external system that POSTs { "text": "..." } to that URL posts a message to the channel. No token header, no install; the secret is in the URL.

bashwebhook
curl -X POST https://api.thevoidhub.com/hooks/ID/TOKEN \
  -H "Content-Type: application/json" \
  -d '{"text":"Build finished ✅"}'

Need a hand

Send your first message in 3 steps with the quickstart on the bots page.