Bot REST API
The Bot REST API lets your bot read and write data using standard HTTP requests.
All endpoints live under /api/v1 and require a bot token header.
Bots must be installed in a guild to access its channels and members.
Authentication
Every request to /api/v1 must include your bot token in the
Authorization header:
Authorization: Bot YOUR_BOT_TOKEN
Bot tokens are 64-character hex strings generated when you create a bot through the developer portal. You can regenerate a token at any time — the old token immediately stops working.
Obtaining a Token
- Log in to the NexusGuild developer portal
- Create a new application under My Applications
- Go to Bot → copy the token
- To install the bot in a guild, use the invite link or call
PUT /api/bots/:botId/servers/:serverId
Bot User
Returns the authenticated bot's user object.
Response
{
"id": "snowflake",
"username": "MyBot",
"avatar": "/uploads/avatars/..." // or null,
"bot": true
}
Get Channel
Fetch a single channel. Bot must be a member of the channel's guild and have VIEW_CHANNEL permission.
Response — Channel Object
{
"id": "snowflake",
"type": 0, // 0=text 2=voice 4=category 5=announcement 11=thread 15=forum
"guild_id": "snowflake",
"name": "general",
"topic": "Channel topic" // or null,
"position": 0,
"parent_id": "snowflake" // category ID, or null
}
Get Channel Messages
Fetch messages from a channel. Requires VIEW_CHANNEL and READ_MESSAGE_HISTORY. Results ordered oldest-first; up to 100 per call.
Query Parameters
| Param | Default | Description | |
|---|---|---|---|
limit | optional | 50 | Max messages to return (1–100) |
before | optional | — | Return messages with ID less than this (cursor before) |
after | optional | — | Return messages with ID greater than this (cursor after) |
Response — Array of Message Objects
[
{
"id": "snowflake",
"channel_id": "snowflake",
"author": {
"id": "snowflake",
"username": "SomeUser",
"avatar": "/uploads/avatars/...",
"bot": false
},
"content": "Hello world",
"timestamp": "2026-03-07T12:00:00.000Z",
"edited_timestamp": null,
"attachments": [],
"embeds": [],
"pinned": false,
"type": 0
}
]
Send Message
Send a message to a channel. Requires VIEW_CHANNEL and SEND_MESSAGES. The message is broadcast to all clients in the channel via Socket.io.
Request Body (JSON)
| Field | Description | |
|---|---|---|
content | required* | Message text. Required if components is absent. |
components | optional | Array of action row objects — see Message Components |
Example
fetch(`/api/v1/channels/${channelId}/messages`, {
method: 'POST',
headers: {
'Authorization': `Bot ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ content: 'Hello from my bot!' }),
})
Response — Message Object
Returns the created message object (same shape as Get Messages).
Edit Message
Edit a message. Bots can only edit their own messages. Requires VIEW_CHANNEL.
Request Body (JSON)
| Field | Description | |
|---|---|---|
content | required | New message content (non-empty) |
Response — Updated Message Object
Delete Message
Delete a message. Bots can only delete their own messages. Requires VIEW_CHANNEL.
Response
204 No Content
Bulk Delete Messages
Delete up to 100 of the bot's own messages at once. Only deletes messages authored by this bot. Requires VIEW_CHANNEL.
Request Body (JSON)
| Field | Description | |
|---|---|---|
messages | required | Array of message ID strings (1–100) |
{ "messages": ["id1", "id2", "id3"] }
Response
204 No Content
Add Reaction
Add a reaction to a message. Requires VIEW_CHANNEL and ADD_REACTIONS.
The emoji must be URL-encoded (e.g. %F0%9F%91%8D for 👍,
or name%3Aid for custom emojis).
Response
204 No Content
Remove Reaction
Remove the bot's reaction from a message.
Response
204 No Content
Get Pinned Messages
Fetch all pinned messages in a channel. Requires VIEW_CHANNEL.
Response
Array of message objects where pinned: true.
Pin a Message
Pin a message. Requires VIEW_CHANNEL and MANAGE_MESSAGES. Emits message_pinned to all clients in the channel.
Response
204 No Content
Unpin a Message
Unpin a message. Requires VIEW_CHANNEL and MANAGE_MESSAGES.
Response
204 No Content
Get Guild
Fetch guild information. Bot must be installed in the guild.
Response — Guild Object
{
"id": "snowflake",
"name": "My Server",
"icon": "/uploads/icons/..." // or null,
"owner_id": "snowflake",
"approximate_member_count": 42
}
Get Guild Channels
List all channels in a guild ordered by position. Returns channel objects (same shape as Get Channel).
List Guild Members
List guild members paginated by join date. Max 1000 per page.
Query Parameters
| Param | Default | Description | |
|---|---|---|---|
limit | optional | 100 | Max results (1–1000) |
after | optional | — | Return members whose user ID is greater than this |
Response — Array of Member Objects
[
{
"user": {
"id": "snowflake",
"username": "Alice",
"avatar": "/uploads/avatars/...",
"bot": false
},
"nick": null,
"roles": ["roleSnowflake"],
"joined_at": "2026-01-01T00:00:00.000Z"
}
]
Get Guild Member
Fetch a single member. Returns a member object.
List Guild Roles
List all roles in a guild ordered by position (highest first).
Response — Array of Role Objects
[
{
"id": "snowflake",
"name": "Moderator",
"color": 3447003, // integer RGB (e.g. 0x3498DB)
"permissions": "8", // permission bitmask as string
"position": 2,
"mentionable": false,
"hoist": false
}
]
Create Role
Create a new role. Requires MANAGE_ROLES.
Request Body (JSON)
| Field | Description | |
|---|---|---|
name | required | Role name |
color | optional | RGB color as integer (e.g. 0xFF0000) or hex string ("#FF0000") |
permissions | optional | Bitmask string (default "0") |
position | optional | Hierarchy position (0 = lowest) |
mentionable | optional | Boolean — whether users can @mention this role |
Response — Role Object
Edit Role
Update a role. All body fields are optional — only provided fields are updated. Requires MANAGE_ROLES.
Body fields: same as Create Role (all optional).
Response — Updated Role Object
Delete Role
Delete a role. Requires MANAGE_ROLES.
Response
204 No Content
Add Role to Member
Assign a role to a guild member. Requires MANAGE_ROLES. Emits a GUILD_MEMBER_UPDATE gateway event with added_roles and removed_roles.
Response
204 No Content
Remove Role from Member
Remove a role from a guild member. Requires MANAGE_ROLES. Emits a GUILD_MEMBER_UPDATE gateway event with added_roles and removed_roles.
Response
204 No Content
Managing Bots (Session Auth)
| Method | Path | Description |
|---|---|---|
| GET | /api/bots |
List all bots owned by the session user |
| POST | /api/bots |
Create a new bot application |
| GET | /api/bots/:botId |
Get bot details |
| PATCH | /api/bots/:botId |
Update bot (name, description, callback URL, etc.) |
| DELETE | /api/bots/:botId |
Delete bot and its user account |
| GET | /api/bots/:botId/token |
Retrieve bot token (masked except last 4 chars) |
| POST | /api/bots/:botId/token/regenerate |
Regenerate bot token (immediately invalidates old token) |
| PUT | /api/bots/:botId/servers/:serverId |
Install bot into a guild (you must be Guild Leader or admin) |
| DELETE | /api/bots/:botId/servers/:serverId |
Remove bot from a guild |
Slash Commands (Session Auth)
Register and manage slash commands per guild. Commands are sent to connected clients
and trigger INTERACTION_CREATE gateway events when a user invokes them.
| Method | Path | Description |
|---|---|---|
| GET | /api/bots/:botId/servers/:serverId/commands |
List slash commands registered for this bot in this guild |
| PUT | /api/bots/:botId/servers/:serverId/commands |
Create or update a slash command (upsert by name) |
| DELETE | /api/bots/:botId/servers/:serverId/commands/:commandId |
Delete a slash command |
Slash Command Object
{
"id": "snowflake",
"bot_id": "snowflake",
"server_id": "snowflake",
"name": "ping", // unique per bot+server
"description": "Replies with pong",
"options": [] // array of command option objects (see below)
}
Command Options
Options define the arguments a slash command accepts. They are stored in the
options array on the command object and shown as type hints in the
chat input when a user types the command.
Option Object
| Field | Description | |
|---|---|---|
name | required | Option name (shown as the key in key:value input) |
description | required | Short description shown in the autocomplete hint |
type | required | One of the type integers below |
required | optional | Boolean — whether the argument is mandatory (default false) |
Option Types
| Value | Name | Description |
|---|---|---|
3 | STRING | Plain text argument |
4 | INTEGER | Whole number (parsed with parseInt) |
5 | BOOLEAN | true or false |
6 | USER | User mention or ID |
7 | CHANNEL | Channel mention or ID |
8 | ROLE | Role mention or ID |
10 | NUMBER | Decimal number (parsed with parseFloat) |
How arguments are passed to your bot
Users can provide arguments as key:value pairs (e.g. /roll sides:20)
or positionally (e.g. /roll 20). The client parses these into typed option
objects delivered in the data.options array of the INTERACTION_CREATE event:
// INTERACTION_CREATE data.options example
[
{ "name": "sides", "type": 4, "value": 20 }
]
Responding to Interactions
When a user invokes a slash command, your bot receives an INTERACTION_CREATE
gateway event. You must respond within 3 seconds, or use a deferred response.
Immediate Response (Type 4)
Respond to an interaction immediately. No auth required — the token in the URL grants access.
{
"type": 4, // CHANNEL_MESSAGE_WITH_SOURCE
"content": "Pong!"
}
Ephemeral Response
Add "flags": 64 to any callback or followup to make the response ephemeral —
only the user who triggered the interaction will see it. The message appears inline in
the channel as a dismissible notice and is never saved to the database.
{
"type": 4,
"content": "Only you can see this.",
"flags": 64 // EPHEMERAL
}
flags: 64 is also accepted on followup responses.
Deferred Response (Type 5)
Acknowledge the interaction immediately, then send the real response later (within 15 minutes).
// Step 1: Acknowledge immediately
{
"type": 5 // DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
}
// Step 2: Send follow-up (within 15 minutes)
fetch(`/api/interactions/${interactionId}/${token}/followup`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: 'Done!' }),
})
Interaction Type Constants
| Type | Name | Description |
|---|---|---|
4 | CHANNEL_MESSAGE_WITH_SOURCE | Reply with a message immediately |
5 | DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE | Acknowledge now, follow up within 15 minutes |
Message Flags
| Value | Name | Description |
|---|---|---|
64 | EPHEMERAL | Only the triggering user sees the message; not saved to the channel |
Message Components
Messages can include interactive components — currently buttons inside action rows.
Pass a components array when sending a message via either the REST API or an interaction callback/followup.
Structure
{
"content": "Choose an option:",
"components": [
{
"type": 1, // ACTION_ROW — up to 5 buttons per row
"components": [
{
"type": 2, // BUTTON
"style": 1, // 1=Primary 2=Secondary 3=Success 4=Danger 5=Link
"label": "Click me",
"custom_id": "my_button_1" // echoed back in the INTERACTION_CREATE event
}
]
}
]
}
Button Styles
| Value | Name | Color |
|---|---|---|
1 | Primary | Blue |
2 | Secondary | Grey |
3 | Success | Green |
4 | Danger | Red |
5 | Link | Grey (use url instead of custom_id) |
Receiving button clicks
When a user clicks a button, your bot receives an INTERACTION_CREATE event
with type: 3 (MESSAGE_COMPONENT). The data payload contains
the custom_id of the button that was clicked and the message_id
of the message it belongs to. Respond using the same callback endpoint as slash commands.
gateway.on('INTERACTION_CREATE', async (interaction) => {
if (interaction.type !== 3) return; // MESSAGE_COMPONENT
const { custom_id, message_id } = interaction.data;
await fetch(
`/api/interactions/${interaction.id}/${interaction.token}/callback`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 4, content: `You clicked: ${custom_id}` }),
}
);
});
Event Subscriptions (Webhook Delivery)
If your bot is not connected to the gateway, you can still receive events via webhook. Register a subscription URL and NexusGuild will POST events to it as they occur. The gateway takes priority — if your bot is connected, webhook delivery is skipped.
| Method | Path | Description |
|---|---|---|
| GET | /api/bots/:botId/subscriptions |
Get current subscription (null if none) |
| PUT | /api/bots/:botId/subscriptions |
Create or replace the subscription |
| DELETE | /api/bots/:botId/subscriptions |
Remove the subscription |
PUT Request Body (JSON)
| Field | Description | |
|---|---|---|
webhook_url | required | HTTPS URL that receives POST requests |
signing_key | required | Secret used to sign payloads (HMAC-SHA256) |
events | required | Array of event names to subscribe to |
// Example PUT body
{
"webhook_url": "https://mybot.example.com/nexus-events",
"signing_key": "my-secret-signing-key",
"events": ["MESSAGE_CREATE", "MESSAGE_UPDATE", "REACT_ADD", "REACT_REMOVE"]
}
Available Event Names
MESSAGE_CREATE MESSAGE_UPDATE MESSAGE_DELETE
REACT_ADD REACT_REMOVE
MEMBER_JOIN MEMBER_LEAVE
CHANNEL_CREATE CHANNEL_UPDATE CHANNEL_DELETE
ROLE_UPDATE
Webhook Payload
Your endpoint receives a POST with Content-Type: application/json
and two custom headers:
| Header | Description |
|---|---|
X-NexusGuild-Signature | HMAC-SHA256 hex digest of the raw body, signed with your signing_key |
X-NexusGuild-Event | The event name (e.g. MESSAGE_CREATE) |
// Webhook body shape
{
"event": "MESSAGE_CREATE",
"data": { /* same payload as the gateway event */ },
"timestamp": 1742400000000
}
Verifying the signature (Node.js)
import crypto from 'crypto';
function verifySignature(rawBody, signature, signingKey) {
const expected = crypto
.createHmac('sha256', signingKey)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
Rate Limiting
The /api/v1 endpoints enforce a sliding-window rate limit
of 50 requests per 10 seconds per bot token.
Exceeding the limit returns a 429 Too Many Requests response.
429 Response
HTTP/1.1 429 Too Many Requests
Retry-After: 3
{
"error": "Too many requests",
"retry_after": 3 // seconds until the oldest request leaves the window
}
Always check for a 429 status and wait retry_after seconds before retrying.