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.
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.
avatar_url and icon_url rather than hashes.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.
| Opcode | Meaning |
|---|---|
0 Dispatch | an event, with its name in t and a sequence number in s |
1 Heartbeat | send 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 Session | the session can't be resumed; identify again |
10 Hello | {heartbeat_interval}, sent as soon as the socket opens |
11 Heartbeat ACK | your heartbeat was received |
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.
| Intent | Bit | Brings |
|---|---|---|
GUILDS | 1 << 0 | GUILD_CREATE, GUILD_UPDATE, GUILD_DELETE, GUILD_ROLE_*, CHANNEL_*, THREAD_CREATE, CHANNEL_PINS_UPDATE |
GUILD_MEMBERSprivileged | 1 << 1 | GUILD_MEMBER_ADD, GUILD_MEMBER_UPDATE, GUILD_MEMBER_REMOVE, and listing members over REST |
GUILD_MODERATION | 1 << 2 | GUILD_BAN_ADD, GUILD_BAN_REMOVE |
GUILD_VOICE_STATES | 1 << 7 | VOICE_STATE_UPDATE |
GUILD_PRESENCESprivileged | 1 << 8 | PRESENCE_UPDATE |
GUILD_MESSAGES | 1 << 9 | MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, MESSAGE_DELETE_BULK |
GUILD_MESSAGE_REACTIONS | 1 << 10 | MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVE, MESSAGE_REACTION_REMOVE_ALL, MESSAGE_REACTION_REMOVE_EMOJI |
GUILD_MESSAGE_TYPING | 1 << 11 | TYPING_START |
MESSAGE_CONTENTprivileged | 1 << 15 | not events of its own: without it, content, embeds, attachments and components arrive empty unless the bot wrote the message or is mentioned in it |
| Code | Meaning |
|---|---|
4000 | unknown error |
4001 | unknown opcode |
4002 | the payload could not be decoded, or was too large |
4003 | an opcode arrived before Identify |
4004 | the token is not a bot token |
4005 | already identified on this connection |
4008 | more than 120 payloads in a minute |
4009 | no heartbeat arrived in time |
4010 | invalid shard |
4013 | the intents value has bits that don't exist |
4014 | an intent the application has switched off |
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.
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.
| Code | Meaning |
|---|---|
10002 / 10003 / 10004 | unknown application, channel or server |
10008 / 10011 / 10013 | unknown message, role or user |
10062 | unknown interaction — usually one that was answered too late |
40060 | the interaction was already answered |
50001 | missing access: the bot can't see it |
50013 | missing permissions |
50035 | invalid form body; errors names the field |
20016 | slowmode |
429 | rate limited; retry_after says how long, in seconds |
limit 1-200, before server id, after server idSlash 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.
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.
with_response true to get the resulting message backA 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.
limit 1-100, default 50, before / after / around a message idwith_counts add member and presence countslimit 1-1000, after member idquery required, limit 1-1000limit 1-100, user_id only this person's actionsA 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.
wait true to get the message backThe REST routes are also served as OpenAPI at /api/openapi.json.