Sleepover bot API

Bots here work the way Discord's do: the same REST shapes, the same gateway with intents and sequence numbers, the same application commands and interactions. If you have written a Discord bot, you already know this API.

Getting started

Make an application under Settings → Bots in the app. You get a token, shown once — keep it, and reset it if it leaks. Every request carries it:

curl -H "Authorization: Bot <token>" https://sleepover.chat/api/v1/users/@me

Add your bot to a server from the same page, or send its link to someone who manages one. Whoever adds it chooses which permissions to give, and those arrive as a role of the bot's own that nobody else can be given and that goes when the bot does.

How this differs from Discord

The gateway

Connect to wss://sleepover.chat/ws/gateway. The server sends Hello (op 10) with a heartbeat interval; heartbeat on that interval, and Identify once:

{"op": 2, "d": {"token": "<token>", "intents": 33281,
    "properties": {"os": "linux", "browser": "my-bot", "device": "my-bot"}}}

READY carries the bot user, a session id and the list of servers, each of which then arrives as a GUILD_CREATE with its channels, roles and members. If the connection drops, reconnect and Resume with the session id and the last sequence number within two minutes and the events you missed are replayed.

OpcodeMeaning
0 Dispatchan event, with its name in t and a sequence number in s
1 Heartbeatsend every heartbeat_interval; the server answers with 11
2 Identify{token, intents, properties, shard, presence}
3 Presence Update{status, activities} — sets what the bot's status shows as
6 Resume{token, session_id, seq} to pick up where a dropped connection left off
8 Request Guild Members{guild_id, query, limit, user_ids, presences, nonce} → GUILD_MEMBERS_CHUNK
9 Invalid Sessionthe session can't be resumed; identify again
10 Hello{heartbeat_interval}, sent as soon as the socket opens
11 Heartbeat ACKyour heartbeat was received

Intents

Identify with the sum of the intents you need. The privileged ones have to be switched on for the application first, on its page in Settings → Bots; identifying with one that is off closes the connection with 4014.

IntentBitBrings
GUILDS1 << 0GUILD_CREATE, GUILD_UPDATE, GUILD_DELETE, GUILD_ROLE_*, CHANNEL_*, THREAD_CREATE, CHANNEL_PINS_UPDATE
GUILD_MEMBERSprivileged1 << 1GUILD_MEMBER_ADD, GUILD_MEMBER_UPDATE, GUILD_MEMBER_REMOVE, and listing members over REST
GUILD_MODERATION1 << 2GUILD_BAN_ADD, GUILD_BAN_REMOVE
GUILD_VOICE_STATES1 << 7VOICE_STATE_UPDATE
GUILD_PRESENCESprivileged1 << 8PRESENCE_UPDATE
GUILD_MESSAGES1 << 9MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, MESSAGE_DELETE_BULK
GUILD_MESSAGE_REACTIONS1 << 10MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVE, MESSAGE_REACTION_REMOVE_ALL, MESSAGE_REACTION_REMOVE_EMOJI
GUILD_MESSAGE_TYPING1 << 11TYPING_START
MESSAGE_CONTENTprivileged1 << 15not events of its own: without it, content, embeds, attachments and components arrive empty unless the bot wrote the message or is mentioned in it

Close codes

CodeMeaning
4000unknown error
4001unknown opcode
4002the payload could not be decoded, or was too large
4003an opcode arrived before Identify
4004the token is not a bot token
4005already identified on this connection
4008more than 120 payloads in a minute
4009no heartbeat arrived in time
4010invalid shard
4013the intents value has bits that don't exist
4014an intent the application has switched off

Interactions

Register commands, and an INTERACTION_CREATE arrives whenever someone uses one — over the gateway, or as a signed POST to an interactions URL if you set one on the application. Answer within three seconds:

POST /api/v1/interactions/{interaction.id}/{token}/callback
{"type": 4, "data": {"content": "Hello!", "flags": 64}}

Type 5 answers later — the message shows as the app thinking, and a PATCH to /messages/@original fills it in, any time in the next fifteen minutes. Buttons and select menus come back as their own interactions (type 3) carrying the message they are on; answer with type 7 to edit that message in place, or type 9 to open a modal, whose submission arrives as type 5.

An interactions URL is verified before it is saved: it must answer a signed PING with {"type": 1}, and refuse a request with a bad signature with 401. Requests are signed with the application's Ed25519 key, over the timestamp followed by the body, in X-Signature-Ed25519 and X-Signature-Timestamp — the same verification code as for Discord works unchanged.

Rate limits

Each route has a bucket per bot and per channel or server, and there is a limit of 50 requests a second across all of them. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-RateLimit-Reset-After and X-RateLimit-Bucket; going over answers 429 with retry_after in seconds. The gateway allows 120 payloads a minute.

Errors

CodeMeaning
10002 / 10003 / 10004unknown application, channel or server
10008 / 10011 / 10013unknown message, role or user
10062unknown interaction — usually one that was answered too late
40060the interaction was already answered
50001missing access: the bot can't see it
50013missing permissions
50035invalid form body; errors names the field
20016slowmode
429rate limited; retry_after says how long, in seconds

The bot and its application

GET /api/v1/gateway
Where to connect a gateway client.
GET /api/v1/gateway/bot
The same, with the session start limit.
GET /api/v1/users/@me
The bot user.
PATCH /api/v1/users/@me
Change the bot's name or picture.
Body: username, avatar_url
GET /api/v1/users/{user.id}
Anyone's public profile.
GET /api/v1/users/@me/guilds
The servers the bot is in.
Query: limit 1-200, before server id, after server id
DELETE /api/v1/users/@me/guilds/{guild.id}
Leave a server.
GET /api/v1/applications/@me
The application: description, keys, install permissions.
PATCH /api/v1/applications/@me
Change it.
Body: description, interactions_endpoint_url, install_params

Application commands

Slash commands (type 1) and the user (2) and message (3) commands in the Apps menu. A command with no guild is global, and appears in every server the bot is in. Names are 1-32 lowercase characters; a slash command needs a description; options follow Discord's types 1-11, at most 25, with required ones first.

GET /api/v1/applications/{application.id}/commands
List global commands.
POST /api/v1/applications/{application.id}/commands
Create one, replacing any with the same name and type.
Body: name, description, options, type, default_member_permissions, contexts, nsfw
PUT /api/v1/applications/{application.id}/commands
Replace every global command with the list given.
Body: an array of commands
PATCH /api/v1/applications/{application.id}/commands/{command.id}
Edit one.
DELETE /api/v1/applications/{application.id}/commands/{command.id}
Delete one.
GET /api/v1/applications/{application.id}/guilds/{guild.id}/commands
The same five routes, for one server.

Interaction responses

Answer within three seconds, with the interaction's id and token — no bot token needed. Then the token is good for fifteen minutes of followups. Callback types: 4 reply, 5 reply later ("thinking"), 6 acknowledge a component, 7 edit the component's message, 8 autocomplete choices, 9 open a modal. Set flags to 64 for a reply only the person who used it can see.

POST /api/v1/interactions/{interaction.id}/{token}/callback
Answer an interaction.
Body: type, data
Query: with_response true to get the resulting message back
GET /api/v1/webhooks/{application.id}/{token}/messages/@original
The reply you sent.
PATCH /api/v1/webhooks/{application.id}/{token}/messages/@original
Edit it — this is how a deferred reply is filled in.
Body: content, embeds, components
DELETE /api/v1/webhooks/{application.id}/{token}/messages/@original
Delete it.
POST /api/v1/webhooks/{application.id}/{token}
Send a followup message.
Body: content, embeds, components, flags
PATCH /api/v1/webhooks/{application.id}/{token}/messages/{message.id}
Edit a followup.

Channels and messages

A message can carry text, up to ten embeds, up to five action rows of buttons and select menus, one image and one other file. Files are sent as multipart/form-data with the JSON in payload_json and the files in files[0] and files[1]; they count against the application's own storage allowance.

GET /api/v1/channels/{channel.id}
A channel.
PATCH /api/v1/channels/{channel.id}
Edit it.
Body: name, topic, nsfw, rate_limit_per_user, parent_id, position, permission_overwrites
DELETE /api/v1/channels/{channel.id}
Delete it.
GET /api/v1/channels/{channel.id}/messages
Message history, newest first.
Query: limit 1-100, default 50, before / after / around a message id
POST /api/v1/channels/{channel.id}/messages
Post a message.
Body: content, embeds, components, message_reference, allowed_mentions, flags, nonce
GET /api/v1/channels/{channel.id}/messages/{message.id}
One message.
PATCH /api/v1/channels/{channel.id}/messages/{message.id}
Edit one of the bot's messages.
Body: content, embeds, components, attachments, flags
DELETE /api/v1/channels/{channel.id}/messages/{message.id}
Delete a message.
POST /api/v1/channels/{channel.id}/messages/bulk-delete
Delete 2-100 messages under 14 days old.
Body: messages
PUT /api/v1/channels/{channel.id}/messages/{message.id}/reactions/{emoji}/@me
React. A custom emoji is name:id.
DELETE /api/v1/channels/{channel.id}/messages/{message.id}/reactions/{emoji}/@me
Take the reaction back.
GET /api/v1/channels/{channel.id}/messages/{message.id}/reactions/{emoji}
Who reacted with it.
DELETE /api/v1/channels/{channel.id}/messages/{message.id}/reactions
Clear every reaction.
POST /api/v1/channels/{channel.id}/typing
Show the bot as typing.
GET /api/v1/channels/{channel.id}/pins
Pinned messages.
PUT /api/v1/channels/{channel.id}/pins/{message.id}
Pin a message.
DELETE /api/v1/channels/{channel.id}/pins/{message.id}
Unpin it.
PUT /api/v1/channels/{channel.id}/permissions/{role.id}
Set a role's overwrite on the channel.
Body: allow, deny, type (0)
POST /api/v1/channels/{channel.id}/invites
Make an invite.
Body: max_age, max_uses

Servers

GET /api/v1/guilds/{guild.id}
A server.
Query: with_counts add member and presence counts
PATCH /api/v1/guilds/{guild.id}
Edit it. Needs Manage Server.
Body: name, description, icon_url
GET /api/v1/guilds/{guild.id}/channels
Its channels and categories.
POST /api/v1/guilds/{guild.id}/channels
Create one. Needs Manage Channels.
Body: name, type, topic, parent_id, nsfw, rate_limit_per_user, permission_overwrites
GET /api/v1/guilds/{guild.id}/members
List members. Needs the Server Members intent.
Query: limit 1-1000, after member id
GET /api/v1/guilds/{guild.id}/members/search
Search members by name.
Query: query required, limit 1-1000
GET /api/v1/guilds/{guild.id}/members/{user.id}
One member.
PATCH /api/v1/guilds/{guild.id}/members/{user.id}
Nickname, roles, or a timeout.
Body: nick, roles, communication_disabled_until
DELETE /api/v1/guilds/{guild.id}/members/{user.id}
Kick. Needs Kick Members.
PUT /api/v1/guilds/{guild.id}/members/{user.id}/roles/{role.id}
Give a role.
DELETE /api/v1/guilds/{guild.id}/members/{user.id}/roles/{role.id}
Take it away.
GET /api/v1/guilds/{guild.id}/bans
The ban list. Needs Ban Members.
PUT /api/v1/guilds/{guild.id}/bans/{user.id}
Ban someone.
Body: delete_message_seconds
DELETE /api/v1/guilds/{guild.id}/bans/{user.id}
Unban them.
GET /api/v1/guilds/{guild.id}/roles
Roles.
POST /api/v1/guilds/{guild.id}/roles
Create a role. A bot can only grant permissions it holds.
Body: name, permissions, color
PATCH /api/v1/guilds/{guild.id}/roles/{role.id}
Edit a role.
DELETE /api/v1/guilds/{guild.id}/roles/{role.id}
Delete a role.
GET /api/v1/guilds/{guild.id}/emojis
Custom emoji.
GET /api/v1/guilds/{guild.id}/audit-logs
The audit log. Needs Manage Server.
Query: limit 1-100, user_id only this person's actions
GET /api/v1/guilds/{guild.id}/invites
Invites. Needs Manage Server.

Webhooks

A channel webhook posts without an application, using the id and token from the server's webhook settings. It may set a name and picture per message and send embeds and link buttons, but not interactive components.

POST /api/v1/webhooks/{webhook.id}/{webhook.token}
Post as the webhook.
Body: content, username, avatar_url, embeds
Query: wait true to get the message back
PATCH /api/v1/webhooks/{webhook.id}/{webhook.token}/messages/{message.id}
Edit one of its messages.
DELETE /api/v1/webhooks/{webhook.id}/{webhook.token}/messages/{message.id}
Delete one.

The machine-readable description

The REST routes are also served as OpenAPI at /api/openapi.json.