Getting Started
Bots are self-hosted: you run the code yourself, a server owner invites the bot, and the bot connects with its token. Disband never hosts or executes your bot.
1. Create a bot
Open Disband → Settings → Bots (or the Developers portal) → click New bot. Pick a name and the scopes it needs, then copy the token — it's shown once.
2. Install the SDK
# Python (zero dependencies)
pip install disband-bot
# JavaScript / TypeScript
npm install @disband/bot
3. Write your bot
Python
from disband_bot import Client
client = Client(token="YOUR_BOT_TOKEN")
@client.on("ready")
def on_ready(me):
print(f"Logged in as {me['name']}")
@client.on("messageCreate")
def on_message(msg):
if msg.content == "!ping":
msg.reply("pong")
client.run()JavaScript / TypeScript
import { Client } from "@disband/bot";
const client = new Client({ token: process.env.DISBAND_BOT_TOKEN });
client.on("messageCreate", async (message) => {
if (message.author?.is_bot) return;
if (message.content !== "!ping") return;
await message.reply("pong");
});
await client.connect();4. Invite the bot to a space
In Settings → Bots, click Invite on your bot. Choose a space (or use General invite for a link anyone can use). Copy the invite link and share it — the space owner approves it.
Bot Scopes (Permissions)
Scopes control what a bot can do. Pick the minimum set your bot needs.
| Scope | Description |
|---|---|
| messages.read | Receive message events and read messages and channels. |
| messages.write | Send and reply to messages. |
| messages.delete | Delete messages the bot can see. |
| messages.react | Add and remove reactions on messages. |
| members.read | See the member list of spaces the bot has joined. |
| channels.read | List channels and categories. |
| channels.manage | Create, rename, and delete channels. |
| webhooks.manage | Create, edit, and delete webhooks. |
| roles.manage | Create, edit and delete roles, give and take members' roles, and set channel permissions for roles. |
Roles and Permissions
With roles.manage and a role that has Manage Roles, a bot can create, edit, reorder and delete roles and give or take members' roles. Like people, it only manages roles below its own highest role, only changes members below it, never the space owner, and can only switch on permissions it has itself. So give your bot a role and drag it above the roles it should look after.
| Request | What it does |
|---|---|
| GET /servers/{id}/permissions | The bot's own permissions, roles, scopes and highest_role_position. |
| GET /servers/{id}/roles | Every role, highest first. manageable is true for roles below the bot. |
| POST /servers/{id}/roles | Create a role: name, color, permissions, self_assignable, position. |
| PATCH /servers/{id}/roles/{roleId} | Edit a role. Only the fields you send change. |
| DELETE /servers/{id}/roles/{roleId} | Delete a role. |
| PUT /servers/{id}/members/{userId}/roles/{roleId} | Give a member a role. |
| DELETE /servers/{id}/members/{userId}/roles/{roleId} | Take a role from a member. |
| GET /channels/{id}/permissions | The channel's role overrides. |
| PUT /channels/{id}/permissions/{roleId} | Set can_view, can_post, can_react and can_attach: true, false or null (inherit). |
| DELETE /channels/{id}/permissions/{roleId} | Remove a role's override. |
| DELETE /channels/{id}/messages/{messageId} | Delete a message (messages.delete). Your own always; anyone's with Manage Messages. Returns 204. |
Channel permissions need Manage Channels and channels.manage or roles.manage. Setting @everyone to can_view: false and a Verified role to can_view: true hides a channel until someone is verified. Members from GET /servers/{id}/members list their roles.
Every role has a numeric display_id (a string, like "113456789012345678"), and messages have one too. Any {roleId} or {messageId} in a path takes either the id or the numeric display_id. Space owners can copy a role's number from Space Settings › Roles.
curl -X POST https://www.disband.dev/api/v1/servers/$SERVER/roles \
-H "Authorization: Bot $DISBAND_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "Verified", "color": "#3ba55c", "permissions": { "send_messages": true } }'
curl -X PUT https://www.disband.dev/api/v1/servers/$SERVER/members/$USER/roles/$VERIFIED \
-H "Authorization: Bot $DISBAND_BOT_TOKEN"
curl -X PUT https://www.disband.dev/api/v1/channels/$CHANNEL/permissions/$EVERYONE \
-H "Authorization: Bot $DISBAND_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{ "can_view": false }'General Invite Links
When creating an invite, enable General invite to generate a link that anyone can use. The recipient signs in, picks a space they manage, and approves the bot. Great for distributing bots publicly.
Managing Bots
Go to Developers → Applications to:
- Create and revoke bots
- Customize name, avatar, bio, and scopes
- Generate targeted or general invite links
- Copy bot tokens (shown once at creation)
SDK Reference
Python
client.run() — start the bot (blocking)
client.connect() — connect (async)
client.close() — disconnect
client.send_message(channel_id, content)
client.list_messages(channel_id, limit, before)
client.list_channels(server_id)
client.list_members(server_id)
client.create_channel(server_id, name, type, category_id)
client.rename_channel(channel_id, name)
client.delete_channel(channel_id)
client.leave_server(server_id)
client.create_invite(server_id, scopes)
JavaScript / TypeScript
client.connect() — connect (async)
client.on(event, handler) — register event handler
message.reply(content) — reply to a message
message.edit(content) — edit a message
Slash Commands, Interactions and Buttons
Register commands once and they appear in the / list in every space your bot is in. When someone runs one (or presses one of your bot's buttons) you receive an interactionCreate event and answer it within 15 minutes. Answering doesn't need messages.write or messages.read. Every request uses Authorization: Bot YOUR_TOKEN against https://www.disband.dev/api/v1.
1. Register commands
PUT /commands replaces all of your commands with the list you send. POST /commands creates or updates one, GET /commands lists them and DELETE /commands?name=weather removes one. Up to 100 commands with 25 options each. Names are 1–32 lowercase letters, digits, _ or -. Option types are string, integer, number, boolean, user and channel. Required options come first, and string or number options can list up to 25 choices.
curl -X PUT https://www.disband.dev/api/v1/commands \
-H "Authorization: Bot $DISBAND_BOT_TOKEN" -H "Content-Type: application/json" \
-d '[
{ "name": "weather", "description": "Today\u2019s weather",
"options": [
{ "name": "city", "description": "Which city", "type": "string", "required": true },
{ "name": "units", "description": "C or F", "type": "string",
"choices": [{ "name": "Celsius", "value": "c" }, { "name": "Fahrenheit", "value": "f" }] }
] },
{ "name": "poll", "description": "Start a yes/no poll" }
]'2. Receive the interaction
It arrives through the gateway (GET /gateway) like any other event. user and channel options arrive as IDs.
{ "type": "interactionCreate",
"payload": {
"id": "6f1c…", "type": "command", // or "component" for a button
"server_id": "…", "channel_id": "…",
"user": { "id": "…", "username": "kai", "display_name": "Kai", "avatar_url": null },
"command": { "name": "weather", "options": { "city": "London", "units": "c" } },
"component": null, // { "custom_id", "message_id" } for a button
"expires_at": "2026-11-01T12:15:00Z" } }3. Answer it
POST /interactions/{id}/callback with content, embeds and components. The reply shows “Kai used /weather” above it. You can answer up to 5 times (follow-ups). For a button press, send "update": true to edit the message the button is on instead, for pages, counters or disabling a button once it's used.
const base = "https://www.disband.dev/api/v1";
const headers = { Authorization: `Bot ${process.env.DISBAND_BOT_TOKEN}`, "Content-Type": "application/json" };
for (;;) {
const { events } = await fetch(`${base}/gateway`, { headers }).then((r) => r.json());
for (const { type, payload: i } of events ?? []) {
if (type !== "interactionCreate") continue;
const reply = (body) =>
fetch(`${base}/interactions/${i.id}/callback`, { method: "POST", headers, body: JSON.stringify(body) });
if (i.command?.name === "poll") {
await reply({
content: "Do you like pizza?",
components: [{ type: "action_row", components: [
{ type: "button", style: "success", label: "Yes", custom_id: "poll:yes" },
{ type: "button", style: "danger", label: "No", custom_id: "poll:no" },
{ type: "button", style: "link", label: "Docs", url: "https://www.disband.dev/developers/docs" },
] }],
});
} else if (i.component?.custom_id?.startsWith("poll:")) {
await reply({ update: true, content: `${i.user.display_name} voted ${i.component.custom_id.slice(5)}.`, components: [] });
}
}
}Buttons
Any message your bot sends (POST /channels/{id}/messages) or any answer can carry up to 5 action_rows of up to 5 buttons. Styles are primary, secondary, success, danger and link (Discord's numbers 1–5 work too). A link button needs an https url; every other button needs a unique custom_id of up to 100 characters. Labels are up to 80 characters, and a button can have an emoji or be disabled.
Events
| Event | Description |
|---|---|
| ready | Bot is connected and ready. |
| messageCreate | A new message was posted. |
| messageUpdate | A message was edited. |
| messageDelete | A message was deleted. |
| memberJoin | Someone joined a space (members.read). |
| memberLeave | Someone left or was removed from a space (members.read). |
| memberUpdate | A member's roles changed; carries their roles now (members.read). |
| interactionCreate | Someone ran one of your slash commands or pressed one of your bot's buttons. |
| error | An error occurred. |
Rate Limits
Bot API requests are rate-limited per bot. Limits vary by endpoint type (read, write, gateway). When rate-limited, the API returns 429 with aRetry-After header. Both SDKs handle this automatically with exponential backoff.
Disband Bot SDKs: pip install disband-bot · npm install @disband/bot