Developer docs
REST API reference
Every route of the Hubs developer API with its authentication, parameters, examples and error codes.
All routes live under https://hubs.example.com/api/developer/v1 on your Hubs address (incoming webhooks under /api/webhooks/in). Requests and responses are JSON in UTF-8. Ids are UUIDs and times are ISO 8601 in UTC.
Authentication
| Kind | Send | Used for |
|---|---|---|
| Bot token | Authorization: Bot hbot_... | The bot's own token from the developer portal. Calls act as the bot's account. |
| Hub API key | Authorization: Bearer hubs_... | A key a hub owner made in Manage, Developer tools, for chosen channels. Calls act for that owner. |
| Signed request | X-Hubs-Timestamp and X-Hubs-Signature | An HMAC-SHA256 signature made with the incoming webhook's secret. |
Rate limits
Each bot may make 120 calls a minute across every hub, and at most 30 of them may be writes (sends, edits, deletes, reactions, interaction answers and follow-ups). The window slides, so calls free up as each one ages past a minute, and refused calls do not count. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; a 429 adds Retry-After and X-RateLimit-Reset-After in seconds.
Pagination
Lists return a page and a cursor (nextBefore or nextAfter). Pass the cursor back to get the next page; null means there is no more.
Routes
Bot and hubs
Who the bot is and where it is installed.
Get the bot's own account
GET/api/developer/v1/users/@me
The bot's id, handle and name, and its application.
- Auth
- Bot token
- Counts as
- A call
curl https://hubs.example.com/api/developer/v1/users/@me \
-H "Authorization: Bot $HUBS_BOT_TOKEN"200
{
"id": "0b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"handle": "dice_bot",
"displayName": "Dice",
"bot": true,
"application": {
"id": "c4d5e6f7-a8b9-4c0d-8e1f-2a3b4c5d6e7f",
"name": "Dice"
}
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED
List the hubs the bot is in
GET/api/developer/v1/hubs
Every hub where the bot is installed, with the scopes and permissions that hub approved. Up to 200.
- Auth
- Bot token
- Counts as
- A call
curl https://hubs.example.com/api/developer/v1/hubs \
-H "Authorization: Bot $HUBS_BOT_TOKEN"200
{
"hubs": [
{
"id": "6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20",
"name": "Board Game Night",
"scopes": [
"bot",
"messages:read",
"messages:write"
],
"permissions": [
"read_history",
"send_messages"
]
}
]
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED
Channels
Chat and announcement channels the caller can open.
List a hub's channels
GET/api/developer/v1/hubs/:id/channels
Chat and announcement channels the caller can open, in sidebar order. Bots also see whether they may post (canPost). A hub API key sees only the channels it was made for.
- Auth
- Bot token or Hub API key
- Bot scope
channels:read- Key scope
channels:read- Counts as
- A call
Path
idThe hub's id.
curl https://hubs.example.com/api/developer/v1/hubs/6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20/channels \
-H "Authorization: Bot $HUBS_BOT_TOKEN"200
{
"channels": [
{
"id": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
"hubId": "6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20",
"name": "general",
"kind": "chat",
"topic": "",
"position": 0,
"canPost": true
}
]
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_NOT_IN_HUB, BOT_SCOPE_MISSING, BOT_PERMISSION_MISSING, KEY_INVALID, KEY_SCOPE_MISSING, KEY_OUT_OF_SCOPE, KEY_RATE_LIMITED, NOT_FOUND
Messages
Read history, send, edit and delete. Bots edit and delete only their own messages.
Read message history
GET/api/developer/v1/channels/:channelId/messages
Newest first. Pass nextBefore back as before to page further. Bots page by message id; hub API keys page by sequence number.
- Auth
- Bot token or Hub API key
- Bot scope
messages:read- Key scope
messages:read- Counts as
- A call
Path
channelIdThe channel's id.
Query, with a bot token
| Field | Type | Rules |
|---|---|---|
before | id | |
limit | integer | 1 to 100, default 50 |
Query, with a hub API key
| Field | Type | Rules |
|---|---|---|
before | integer | at least 1 |
limit | integer | 1 to 50, default 25 |
curl https://hubs.example.com/api/developer/v1/channels/2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d/messages \
-H "Authorization: Bot $HUBS_BOT_TOKEN"200
{
"messages": [
{
"id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
"channelId": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
"hubId": "6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20",
"author": {
"id": "0b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"handle": "dice_bot",
"displayName": "Dice",
"bot": true
},
"content": "You rolled a 17.",
"createdAt": "2026-10-11T16:00:00.000Z",
"editedAt": null,
"replyToId": null,
"attachments": [],
"reactions": [],
"applicationId": "c4d5e6f7-a8b9-4c0d-8e1f-2a3b4c5d6e7f",
"embeds": [],
"components": []
}
],
"nextBefore": null
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_NOT_IN_HUB, BOT_SCOPE_MISSING, BOT_PERMISSION_MISSING, KEY_INVALID, KEY_SCOPE_MISSING, KEY_OUT_OF_SCOPE, KEY_RATE_LIMITED, NOT_FOUND, BAD_REQUEST
Send a message
POST/api/developer/v1/channels/:channelId/messages
Posts as the bot (201), or as a labeled integration post for a hub API key. Channel rules, slow mode and content rules apply exactly as for a person. Bots may send embeds and components; with an embed, content may be empty.
- Auth
- Bot token or Hub API key
- Bot scope
messages:write- Key scope
messages:write- Counts as
- A write
Path
channelIdThe channel's id.
Headers
Idempotency-KeyRequired. 8 to 128 letters, numbers, dots, dashes, underscores or colons. A retry with the same key and the same body returns the first result; a different body with a used key is refused (409).
Body, with a bot token
| Field | Type | Rules |
|---|---|---|
content | string | up to 4000 characters, default "" |
replyToId | id or null | |
embeds | Embed[] | up to 5 items, default [] |
components | ActionRow[] | up to 5 items, default [] |
Body, with a hub API key
| Field | Type | Rules |
|---|---|---|
contentrequired | string | 1 to 4000 characters |
curl -X POST https://hubs.example.com/api/developer/v1/channels/2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d/messages \
-H "Authorization: Bot $HUBS_BOT_TOKEN" \
-H "Idempotency-Key: roll-0001" \
-H "Content-Type: application/json" \
-d '{"content":"You rolled a 17."}'201
{
"id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
"channelId": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
"hubId": "6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20",
"author": {
"id": "0b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"handle": "dice_bot",
"displayName": "Dice",
"bot": true
},
"content": "You rolled a 17.",
"createdAt": "2026-10-11T16:00:00.000Z",
"editedAt": null,
"replyToId": null,
"attachments": [],
"reactions": [],
"applicationId": "c4d5e6f7-a8b9-4c0d-8e1f-2a3b4c5d6e7f",
"embeds": [],
"components": []
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_NOT_IN_HUB, BOT_SCOPE_MISSING, BOT_PERMISSION_MISSING, KEY_INVALID, KEY_SCOPE_MISSING, KEY_OUT_OF_SCOPE, KEY_RATE_LIMITED, NOT_FOUND, BAD_REQUEST, IDEMPOTENCY_CONFLICT
Get one message
GET/api/developer/v1/channels/:channelId/messages/:messageId
- Auth
- Bot token
- Bot scope
messages:read- Counts as
- A call
Path
channelIdThe channel's id.messageIdThe message's id.
curl https://hubs.example.com/api/developer/v1/channels/2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d/messages/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b \
-H "Authorization: Bot $HUBS_BOT_TOKEN"200
{
"id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
"channelId": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
"hubId": "6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20",
"author": {
"id": "0b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"handle": "dice_bot",
"displayName": "Dice",
"bot": true
},
"content": "You rolled a 17.",
"createdAt": "2026-10-11T16:00:00.000Z",
"editedAt": null,
"replyToId": null,
"attachments": [],
"reactions": [],
"applicationId": "c4d5e6f7-a8b9-4c0d-8e1f-2a3b4c5d6e7f",
"embeds": [],
"components": []
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_NOT_IN_HUB, BOT_SCOPE_MISSING, BOT_PERMISSION_MISSING, NOT_FOUND
Edit the bot's own message
PATCH/api/developer/v1/channels/:channelId/messages/:messageId
Send any of content, embeds and components. Fields you leave out stay as they are.
- Auth
- Bot token
- Bot scope
messages:write- Counts as
- A write
Path
channelIdThe channel's id.messageIdThe message's id.
Body
| Field | Type | Rules |
|---|---|---|
content | string | up to 4000 characters |
embeds | Embed[] | up to 5 items |
components | ActionRow[] | up to 5 items |
curl -X PATCH https://hubs.example.com/api/developer/v1/channels/2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d/messages/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b \
-H "Authorization: Bot $HUBS_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content":"You rolled an 18. The dice slipped."}'200
{
"id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
"channelId": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
"hubId": "6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20",
"author": {
"id": "0b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"handle": "dice_bot",
"displayName": "Dice",
"bot": true
},
"content": "You rolled a 17.",
"createdAt": "2026-10-11T16:00:00.000Z",
"editedAt": "2026-10-11T16:00:00.000Z",
"replyToId": null,
"attachments": [],
"reactions": [],
"applicationId": "c4d5e6f7-a8b9-4c0d-8e1f-2a3b4c5d6e7f",
"embeds": [],
"components": []
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_NOT_IN_HUB, BOT_SCOPE_MISSING, BOT_PERMISSION_MISSING, BOT_NOT_MESSAGE_AUTHOR, NOT_FOUND, BAD_REQUEST
Delete the bot's own message
DELETE/api/developer/v1/channels/:channelId/messages/:messageId
- Auth
- Bot token
- Bot scope
messages:write- Counts as
- A write
Path
channelIdThe channel's id.messageIdThe message's id.
curl -X DELETE https://hubs.example.com/api/developer/v1/channels/2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d/messages/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b \
-H "Authorization: Bot $HUBS_BOT_TOKEN"204 (no body)Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_NOT_IN_HUB, BOT_SCOPE_MISSING, BOT_PERMISSION_MISSING, BOT_NOT_MESSAGE_AUTHOR, NOT_FOUND
Reactions
A bot's own reactions on messages it can read.
Add the bot's reaction
PUT/api/developer/v1/channels/:channelId/messages/:messageId/reactions/:emoji
URL-encode the emoji. Adding a reaction twice is harmless.
- Auth
- Bot token
- Bot scope
reactions:write- Counts as
- A write
Path
channelIdThe channel's id.messageIdThe message's id.emojiThe emoji, 1 to 64 characters, URL-encoded.
curl -X PUT https://hubs.example.com/api/developer/v1/channels/2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d/messages/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b/reactions/%F0%9F%8E%B2 \
-H "Authorization: Bot $HUBS_BOT_TOKEN"204 (no body)Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_NOT_IN_HUB, BOT_SCOPE_MISSING, BOT_PERMISSION_MISSING, NOT_FOUND
Remove the bot's reaction
DELETE/api/developer/v1/channels/:channelId/messages/:messageId/reactions/:emoji
- Auth
- Bot token
- Bot scope
reactions:write- Counts as
- A write
Path
channelIdThe channel's id.messageIdThe message's id.emojiThe emoji, URL-encoded.
curl -X DELETE https://hubs.example.com/api/developer/v1/channels/2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d/messages/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b/reactions/%F0%9F%8E%B2 \
-H "Authorization: Bot $HUBS_BOT_TOKEN"204 (no body)Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_NOT_IN_HUB, BOT_SCOPE_MISSING, BOT_PERMISSION_MISSING, NOT_FOUND
Members
Who is in a hub, with hub roles and a bot flag.
List a hub's members
GET/api/developer/v1/hubs/:id/members
Ordered by account id. Pass nextAfter back as after to page on. Deleted accounts are left out.
- Auth
- Bot token
- Bot scope
members:read- Counts as
- A call
Path
idThe hub's id.
Query
| Field | Type | Rules |
|---|---|---|
after | id | |
limit | integer | 1 to 100, default 50 |
curl https://hubs.example.com/api/developer/v1/hubs/6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20/members \
-H "Authorization: Bot $HUBS_BOT_TOKEN"200
{
"members": [
{
"id": "5d6e7f80-9a1b-4c2d-9e3f-4a5b6c7d8e9f",
"handle": "river",
"displayName": "River",
"role": "member",
"bot": false
}
],
"nextAfter": null
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_NOT_IN_HUB, BOT_SCOPE_MISSING, BOT_PERMISSION_MISSING
Gateway
Where and how to open the live events connection.
The live events address
GET/api/developer/v1/gateway
Open a WebSocket here over HTTP/1.1. A plain request without the upgrade answers 426, or 401 or 403 for a bad token. See the gateway guide.
- Auth
- Bot token
- Counts as
- A call
curl https://hubs.example.com/api/developer/v1/gateway \
-H "Authorization: Bot $HUBS_BOT_TOKEN"426
{
"error": "Open a WebSocket here over HTTP/1.1 (Connection: Upgrade, Upgrade: websocket).",
"code": "BOT_GATEWAY_UPGRADE_REQUIRED"
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_GATEWAY_UPGRADE_REQUIRED
Get connection details
GET/api/developer/v1/gateway/bot
Where to connect, how many shards to open, the heartbeat interval and what each intent needs.
- Auth
- Bot token
- Bot scope
gateway- Counts as
- A call
curl https://hubs.example.com/api/developer/v1/gateway/bot \
-H "Authorization: Bot $HUBS_BOT_TOKEN"200
{
"url": "wss://hubs.example.com/api/developer/v1/gateway",
"shards": 1,
"maxShards": 16,
"heartbeatInterval": 41250,
"intents": {
"HUB_MESSAGES": {
"bit": 512,
"scope": "messages:read",
"permission": "Read message history"
}
}
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_SCOPE_MISSING
Slash commands
Global commands, and commands for one hub.
List global commands
GET/api/developer/v1/applications/@me/commands
- Auth
- Bot token
- Bot scope
commands- Counts as
- A call
curl https://hubs.example.com/api/developer/v1/applications/@me/commands \
-H "Authorization: Bot $HUBS_BOT_TOKEN"200
{
"commands": [
{
"id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
"applicationId": "c4d5e6f7-a8b9-4c0d-8e1f-2a3b4c5d6e7f",
"hubId": null,
"name": "roll",
"description": "Roll some dice",
"options": [
{
"type": "integer",
"name": "sides",
"description": "How many sides",
"required": true,
"minValue": 2,
"maxValue": 100
}
],
"updatedAt": "2026-10-11T16:00:00.000Z"
}
]
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_SCOPE_MISSING
Replace global commands
PUT/api/developer/v1/applications/@me/commands
Send the whole list, as an array or as {"commands": [...]}. Commands that keep their name keep their id; commands left out are removed.
- Auth
- Bot token
- Bot scope
commands- Counts as
- A call
Body
A list of Command objects.
curl -X PUT https://hubs.example.com/api/developer/v1/applications/@me/commands \
-H "Authorization: Bot $HUBS_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '[{"name":"roll","description":"Roll some dice","options":[{"type":"integer","name":"sides","description":"How many sides","required":true,"minValue":2,"maxValue":100}]}]'200
{
"commands": [
{
"id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
"applicationId": "c4d5e6f7-a8b9-4c0d-8e1f-2a3b4c5d6e7f",
"hubId": null,
"name": "roll",
"description": "Roll some dice",
"options": [
{
"type": "integer",
"name": "sides",
"description": "How many sides",
"required": true,
"minValue": 2,
"maxValue": 100
}
],
"updatedAt": "2026-10-11T16:00:00.000Z"
}
]
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_SCOPE_MISSING, BAD_REQUEST
List one hub's commands
GET/api/developer/v1/hubs/:id/commands
- Auth
- Bot token
- Bot scope
commands- Counts as
- A call
Path
idThe hub's id.
curl https://hubs.example.com/api/developer/v1/hubs/6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20/commands \
-H "Authorization: Bot $HUBS_BOT_TOKEN"200
{
"commands": [
{
"id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
"applicationId": "c4d5e6f7-a8b9-4c0d-8e1f-2a3b4c5d6e7f",
"hubId": "6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20",
"name": "roll",
"description": "Roll some dice",
"options": [
{
"type": "integer",
"name": "sides",
"description": "How many sides",
"required": true,
"minValue": 2,
"maxValue": 100
}
],
"updatedAt": "2026-10-11T16:00:00.000Z"
}
]
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_NOT_IN_HUB, BOT_SCOPE_MISSING, BOT_PERMISSION_MISSING
Replace one hub's commands
PUT/api/developer/v1/hubs/:id/commands
A hub command replaces a global command with the same name in that hub. The change goes to the hub's audit trail.
- Auth
- Bot token
- Bot scope
commands- Counts as
- A call
Path
idThe hub's id.
Body
A list of Command objects.
curl -X PUT https://hubs.example.com/api/developer/v1/hubs/6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20/commands \
-H "Authorization: Bot $HUBS_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '[{"name":"scores","description":"This week's scores"}]'200
{
"commands": [
{
"id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
"applicationId": "c4d5e6f7-a8b9-4c0d-8e1f-2a3b4c5d6e7f",
"hubId": "6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20",
"name": "roll",
"description": "Roll some dice",
"options": [
{
"type": "integer",
"name": "sides",
"description": "How many sides",
"required": true,
"minValue": 2,
"maxValue": 100
}
],
"updatedAt": "2026-10-11T16:00:00.000Z"
}
]
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_NOT_IN_HUB, BOT_SCOPE_MISSING, BOT_PERMISSION_MISSING, BAD_REQUEST
Interactions
Answering commands, buttons, menus and forms.
Answer an interaction
POST/api/developer/v1/interactions/:id/callback
Once, within 3 seconds. The type decides what the person sees; see the interactions guide for which types fit which interaction.
- Auth
- Bot token
- Bot scope
commands- Counts as
- A write
Path
idThe interaction's id, from INTERACTION_CREATE.
Body type: "reply"
| Field | Type | Rules |
|---|---|---|
typerequired | "reply" | |
datarequired | object | |
data.content | string | up to 4000 characters, default "" |
data.embeds | Embed[] | up to 5 items, default [] |
data.components | ActionRow[] | up to 5 items, default [] |
data.ephemeral | boolean |
Body type: "defer"
| Field | Type | Rules |
|---|---|---|
typerequired | "defer" | |
data | object | default {} |
data.ephemeral | boolean |
Body type: "update"
| Field | Type | Rules |
|---|---|---|
typerequired | "update" | |
datarequired | object | |
data.content | string | up to 4000 characters |
data.embeds | Embed[] | up to 5 items |
data.components | ActionRow[] | up to 5 items |
Body type: "defer_update"
| Field | Type | Rules |
|---|---|---|
typerequired | "defer_update" |
Body type: "modal"
| Field | Type | Rules |
|---|---|---|
typerequired | "modal" | |
datarequired | Modal |
Body type: "autocomplete"
| Field | Type | Rules |
|---|---|---|
typerequired | "autocomplete" | |
datarequired | object | |
data.choicesrequired | object[] | up to 25 items |
data.choices[].namerequired | string | 1 to 100 characters |
data.choices[].valuerequired | string or number |
curl -X POST https://hubs.example.com/api/developer/v1/interactions/7f8e9d0c-1b2a-4c3d-9e4f-5a6b7c8d9e0f/callback \
-H "Authorization: Bot $HUBS_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"reply","data":{"content":"You rolled a 17.","ephemeral":false}}'204 (no body)Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_INTERACTION_UNKNOWN, BOT_INTERACTION_EXPIRED, BOT_INTERACTION_ACKNOWLEDGED, BAD_REQUEST
Download a file someone gave a command
GET/api/developer/v1/interaction-files/:token
The url in an attachment option's value. The address is the credential: no token header, and it works for 10 minutes.
- Auth
Path
tokenPart of the url in the option's value.
curl https://hubs.example.com/api/developer/v1/interaction-files/token200 (no body)Errors: BOT_FILE_EXPIRED
Send a follow-up
POST/api/developer/v1/interactions/:id/followups
For 15 minutes after the first answer. Public follow-ups are messages from the bot; ephemeral ones only the person sees.
- Auth
- Bot token
- Bot scope
commands- Counts as
- A write
Path
idThe interaction's id, from INTERACTION_CREATE.
Headers
Idempotency-KeyOptional. 8 to 128 letters, numbers, dots, dashes, underscores or colons. A retry with the same key posts once.
Body
| Field | Type | Rules |
|---|---|---|
content | string | up to 4000 characters, default "" |
embeds | Embed[] | up to 5 items, default [] |
components | ActionRow[] | up to 5 items, default [] |
ephemeral | boolean |
curl -X POST https://hubs.example.com/api/developer/v1/interactions/7f8e9d0c-1b2a-4c3d-9e4f-5a6b7c8d9e0f/followups \
-H "Authorization: Bot $HUBS_BOT_TOKEN" \
-H "Idempotency-Key: roll-0001" \
-H "Content-Type: application/json" \
-d '{"content":"Rolling again for the table."}'201
{
"id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
"channelId": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
"hubId": "6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20",
"author": {
"id": "0b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"handle": "dice_bot",
"displayName": "Dice",
"bot": true
},
"content": "You rolled a 17.",
"createdAt": "2026-10-11T16:00:00.000Z",
"editedAt": null,
"replyToId": null,
"attachments": [],
"reactions": [],
"applicationId": "c4d5e6f7-a8b9-4c0d-8e1f-2a3b4c5d6e7f",
"embeds": [],
"components": []
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_INTERACTION_UNKNOWN, BOT_INTERACTION_EXPIRED, BOT_INTERACTION_NOT_ACKNOWLEDGED, BAD_REQUEST
Edit the first answer
PATCH/api/developer/v1/interactions/:id/original
After a public defer, the first edit posts the answer. Fields you leave out stay as they are.
- Auth
- Bot token
- Bot scope
commands- Counts as
- A write
Path
idThe interaction's id, from INTERACTION_CREATE.
Body
| Field | Type | Rules |
|---|---|---|
content | string | up to 4000 characters |
embeds | Embed[] | up to 5 items |
components | ActionRow[] | up to 5 items |
curl -X PATCH https://hubs.example.com/api/developer/v1/interactions/7f8e9d0c-1b2a-4c3d-9e4f-5a6b7c8d9e0f/original \
-H "Authorization: Bot $HUBS_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content":"You rolled a 17 (best of three)."}'200
{
"id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
"channelId": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
"hubId": "6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20",
"author": {
"id": "0b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"handle": "dice_bot",
"displayName": "Dice",
"bot": true
},
"content": "You rolled a 17.",
"createdAt": "2026-10-11T16:00:00.000Z",
"editedAt": null,
"replyToId": null,
"attachments": [],
"reactions": [],
"applicationId": "c4d5e6f7-a8b9-4c0d-8e1f-2a3b4c5d6e7f",
"embeds": [],
"components": []
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_INTERACTION_UNKNOWN, BOT_INTERACTION_EXPIRED, BOT_INTERACTION_NOT_ACKNOWLEDGED, NOT_FOUND, BAD_REQUEST
Inline results
People type the bot's handle in the message box and pick from its results.
Turn inline results on or off
PUT/api/developer/v1/applications/@me/inline
When on, people type the bot's handle in the message box to ask it for results. The placeholder is the hint they see, up to 60 characters.
- Auth
- Bot token
- Counts as
- A call
curl -X PUT https://hubs.example.com/api/developer/v1/applications/@me/inline \
-H "Authorization: Bot $HUBS_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled":true,"placeholder":"Search GIFs"}'200
{
"enabled": true,
"placeholder": "Search GIFs"
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BAD_REQUEST
Answer an inline query
POST/api/developer/v1/inline-queries/:id/answer
Once, within 3 seconds of INLINE_QUERY on the gateway. Up to 20 results, each with its own id; a GIF needs its url.
- Auth
- Bot token
- Bot scope
commands- Counts as
- A call
Path
idThe inline query's id, from INLINE_QUERY.
curl -X POST https://hubs.example.com/api/developer/v1/inline-queries/6f1c2b9a-3d4e-4f50-8a61-7b8c9d0e1f20/answer \
-H "Authorization: Bot $HUBS_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"results":[{"id":"r1","type":"article","title":"Rules","content":"Be kind. No spoilers."}]}'200
{
"ok": true
}Errors: BOT_TOKEN_INVALID, BOT_DISABLED, BOT_OWNER_RESTRICTED, BOT_RATE_LIMITED, BOT_NOT_IN_HUB, BOT_SCOPE_MISSING, BOT_PERMISSION_MISSING, BOT_INLINE_UNKNOWN, BOT_INLINE_EXPIRED, BOT_INLINE_ANSWERED, BAD_REQUEST
Incoming webhooks
Post into a channel from another tool with a signed request.
Post through an incoming webhook
POST/api/webhooks/in/:hookId
Sign the exact bytes you send. The post carries the Webhook label and the hook's name. Each signature is accepted once.
- Auth
- Signed request
Path
hookIdFrom the webhook's address, shown when it was made.
Headers
X-Hubs-TimestampUnix seconds. Within 5 minutes of the server's clock.X-Hubs-Signaturev1= and the hex HMAC-SHA256 of "<timestamp>.<raw body>" with the secret.
Body
| Field | Type | Rules |
|---|---|---|
contentrequired | string | 1 to 2000 characters |
curl -X POST https://hubs.example.com/api/webhooks/in/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b \
-H "X-Hubs-Timestamp: $TIMESTAMP" \
-H "X-Hubs-Signature: $SIGNATURE" \
-H "Content-Type: application/json" \
-d '{"content":"Deploy finished."}'201
{
"id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b"
}Errors: WEBHOOK_UNKNOWN, WEBHOOK_SIGNATURE, WEBHOOK_REPLAY, WEBHOOK_RATE_LIMITED, BAD_REQUEST
Objects
Embed
A card of text under a message. Up to 5 per message and 6,000 characters in all. Pictures from other sites are left out on purpose.
Fields
| Field | Type | Rules |
|---|---|---|
title | string | 1 to 256 characters |
description | string | 1 to 4096 characters |
url | string | up to 512 characters, an https address |
color | integer | 0 to 16777215 |
author | object | |
author.namerequired | string | 1 to 256 characters |
fields | object[] | up to 25 items |
fields[].namerequired | string | 1 to 256 characters |
fields[].valuerequired | string | 1 to 1024 characters |
fields[].inline | boolean | |
footer | object | |
footer.textrequired | string | 1 to 2048 characters |
timestamp | timestamp |
Action row
Up to 5 rows per message. A row holds up to 5 buttons or one select menu. Custom ids are unique within a message.
Fields
| Field | Type | Rules |
|---|---|---|
typerequired | "row" | |
componentsrequired | (Button or SelectMenu)[] | 1 to 5 items |
Button
Link buttons take an https url and never reach the bot; every other style takes a customId.
Fields
| Field | Type | Rules |
|---|---|---|
typerequired | "button" | |
stylerequired | "primary" | "secondary" | "success" | "danger" | "link" | "panel" | |
labelrequired | string | 1 to 80 characters |
customId | string | 1 to 100 characters, printable ASCII |
url | string | up to 512 characters, an https address |
emoji | string | 1 to 32 characters |
disabled | boolean |
Select menu
Up to 25 options with unique values.
Fields
| Field | Type | Rules |
|---|---|---|
typerequired | "select" | |
customIdrequired | string | 1 to 100 characters, printable ASCII |
placeholder | string | 1 to 150 characters |
minValues | integer | 0 to 25 |
maxValues | integer | 1 to 25 |
optionsrequired | object[] | 1 to 25 items |
options[].labelrequired | string | 1 to 100 characters |
options[].valuerequired | string | 1 to 100 characters |
options[].description | string | 1 to 100 characters |
options[].default | boolean | |
disabled | boolean |
Form
Up to 5 fields, opened as a dialog for the person.
Fields
| Field | Type | Rules |
|---|---|---|
customIdrequired | string | 1 to 100 characters, printable ASCII |
titlerequired | string | 1 to 45 characters |
fieldsrequired | object[] | 1 to 5 items |
fields[].customIdrequired | string | 1 to 100 characters, printable ASCII |
fields[].labelrequired | string | 1 to 45 characters |
fields[].stylerequired | "short" | "paragraph" | |
fields[].required | boolean | |
fields[].placeholder | string | 1 to 100 characters |
fields[].minLength | integer | 0 to 4000 |
fields[].maxLength | integer | 1 to 4000 |
fields[].value | string | up to 4000 characters |
Command
A slash command. Required options come first.
Fields
| Field | Type | Rules |
|---|---|---|
namerequired | string | 1 to 32 characters |
description | string | up to 100 characters, default "" |
options | CommandOption[] | up to 25 items, default [] |
access | "everyone" | "admins" | default "everyone" |
type | "chat_input" | "message" | "user" | default "chat_input" |
contexts | ("hub" | "dm" | "group_dm")[] | 1 to 3 items, default ["hub"] |
Command option
An option has choices or autocomplete, not both. Only text and number options take either.
Fields
| Field | Type | Rules |
|---|---|---|
typerequired | "string" | "integer" | "number" | "boolean" | "user" | "channel" | "role" | "mentionable" | "attachment" | |
namerequired | string | matches ^[a-z0-9_-]{1,32}$ |
descriptionrequired | string | 1 to 100 characters |
required | boolean | |
choices | object[] | 1 to 25 items |
choices[].namerequired | string | 1 to 100 characters |
choices[].valuerequired | string or number | |
autocomplete | boolean | |
minValue | number | |
maxValue | number | |
minLength | integer | 0 to 6000 |
maxLength | integer | 1 to 6000 |
channelTypes | ("chat" | "announcements" | "voice")[] | 1 to 3 items |
Fields type: "subcommand"
| Field | Type | Rules |
|---|---|---|
typerequired | "subcommand" | |
namerequired | string | matches ^[a-z0-9_-]{1,32}$ |
descriptionrequired | string | 1 to 100 characters |
options | object[] | up to 25 items, default [] |
options[].typerequired | "string" | "integer" | "number" | "boolean" | "user" | "channel" | "role" | "mentionable" | "attachment" | |
options[].namerequired | string | matches ^[a-z0-9_-]{1,32}$ |
options[].descriptionrequired | string | 1 to 100 characters |
options[].required | boolean | |
options[].choices | object[] | 1 to 25 items |
options[].choices[].namerequired | string | 1 to 100 characters |
options[].choices[].valuerequired | string or number | |
options[].autocomplete | boolean | |
options[].minValue | number | |
options[].maxValue | number | |
options[].minLength | integer | 0 to 6000 |
options[].maxLength | integer | 1 to 6000 |
options[].channelTypes | ("chat" | "announcements" | "voice")[] | 1 to 3 items |
Fields type: "subcommand_group"
| Field | Type | Rules |
|---|---|---|
typerequired | "subcommand_group" | |
namerequired | string | matches ^[a-z0-9_-]{1,32}$ |
descriptionrequired | string | 1 to 100 characters |
optionsrequired | object[] | 1 to 25 items |
options[].typerequired | "subcommand" | |
options[].namerequired | string | matches ^[a-z0-9_-]{1,32}$ |
options[].descriptionrequired | string | 1 to 100 characters |
options[].options | object[] | up to 25 items, default [] |
options[].options[].typerequired | "string" | "integer" | "number" | "boolean" | "user" | "channel" | "role" | "mentionable" | "attachment" | |
options[].options[].namerequired | string | matches ^[a-z0-9_-]{1,32}$ |
options[].options[].descriptionrequired | string | 1 to 100 characters |
options[].options[].required | boolean | |
options[].options[].choices | object[] | 1 to 25 items |
options[].options[].choices[].namerequired | string | 1 to 100 characters |
options[].options[].choices[].valuerequired | string or number | |
options[].options[].autocomplete | boolean | |
options[].options[].minValue | number | |
options[].options[].maxValue | number | |
options[].options[].minLength | integer | 0 to 6000 |
options[].options[].maxLength | integer | 1 to 6000 |
options[].options[].channelTypes | ("chat" | "announcements" | "voice")[] | 1 to 3 items |
Errors
Errors are JSON with a sentence for people and, for bot errors, a stable code for your program: { "error": "This bot was not granted the messages:write scope in this hub.", "code": "BOT_SCOPE_MISSING" }. Match on code and the status, never on the sentence.
| Code | Status | Means | What to do |
|---|---|---|---|
BOT_TOKEN_INVALID | 401 | The token is missing, malformed, reset or revoked. | Send Authorization: Bot followed by the current token. Reset it in the portal if it leaked. |
BOT_DISABLED | 403 | Hubs switched the application off. | Read the notice in the developer portal for the reason. |
BOT_OWNER_RESTRICTED | 403 | The developer's own account is paused or suspended. | The bot acts again once the account is back in good standing. |
BOT_NOT_IN_HUB | 404 | The bot is not installed in that hub. Hubs it was never in look the same. | Share the install link with an owner or admin of the hub. |
BOT_SCOPE_MISSING | 403 | The application does not list the scope, or the hub did not approve it. | Add the scope in the portal, then ask the hub to approve a new install link. |
BOT_PERMISSION_MISSING | 403 | The bot's role in that hub lacks the permission the scope needs. | Ask the hub to approve the permission. |
BOT_RATE_LIMITED | 429 | Over 120 calls, or 30 writes, in the last minute. | Wait Retry-After seconds, then try again. |
BOT_NOT_MESSAGE_AUTHOR | 403 | Editing or deleting a message the bot did not send. | Bots change only their own messages. |
BOT_GATEWAY_UPGRADE_REQUIRED | 426 | The gateway address was reached without a WebSocket upgrade. | Connect over HTTP/1.1 with Connection: Upgrade and Upgrade: websocket. |
BOT_INTERACTION_UNKNOWN | 404 | No interaction with that id belongs to this bot. | Use the id from INTERACTION_CREATE. |
BOT_INTERACTION_EXPIRED | 404 | The first answer came after 3 seconds, or a follow-up after 15 minutes. | Answer or defer at once, then follow up. |
BOT_FILE_EXPIRED | 404 | The address of a file someone gave a command is unknown, or its 10 minutes have passed. | Download the file when the interaction arrives. |
BOT_INTERACTION_ACKNOWLEDGED | 409 | The interaction was already answered. | Use a follow-up or edit the first answer. |
BOT_INLINE_UNKNOWN | 404 | No inline query with that id is waiting for this bot. | Answer with the id from INLINE_QUERY. |
BOT_INLINE_EXPIRED | 404 | The inline query is more than 3 seconds old. | Answer within 3 seconds. The person can type again for a new query. |
BOT_INLINE_ANSWERED | 409 | The inline query was already answered. | Answer each inline query once. |
BOT_INTERACTION_NOT_ACKNOWLEDGED | 400 | A follow-up or edit before the first answer. | Answer with the callback first. |
BAD_REQUESTmessage only | 400 | The body, query or a header does not match the schema. | The error sentence names the first problem. |
NOT_FOUNDmessage only | 403 or 404 | The hub's own rules said no: a channel the role cannot see or post in, or a message that is gone. | Ask the hub to open the channel to the bot's role. |
IDEMPOTENCY_CONFLICTmessage only | 409 | The Idempotency-Key was used before with a different body. | Use a new key for new content. |
KEY_INVALIDmessage only | 401 | The hub API key is missing, expired or revoked. | Ask the hub owner for a new key. |
KEY_SCOPE_MISSINGmessage only | 403 | The key was not made with this scope. | Ask the hub owner for a key with the scope. |
KEY_OUT_OF_SCOPEmessage only | 404 | The hub or channel is not one the key was made for. | Use one of the key's channels. |
KEY_RATE_LIMITEDmessage only | 429 | Over 60 calls in a minute with this key. | Slow down and try again in a minute. |
WEBHOOK_UNKNOWNmessage only | 404 | No such incoming webhook, its hub switched webhooks off, or the hub left the organization. | Check the address, or ask for a new webhook. |
WEBHOOK_SIGNATUREmessage only | 401 | The signature is missing or wrong, or the timestamp is more than 5 minutes off. | Sign the exact bytes you send, with the current secret and clock. |
WEBHOOK_REPLAYmessage only | 409 | This signature was already received, or the channel cannot take posts now. | Sign each post with a fresh timestamp. |
WEBHOOK_RATE_LIMITEDmessage only | 429 | More than 30 posts in a minute through this webhook. | Wait a minute. |