Reference

Interactions

Registering slash commands, the INTERACTION_CREATE payload, callback types, the original response and follow-ups, ephemeral replies, buttons and select menus, embed limits and the error codes.

Slash commands and message components work the Discord way: register commands over REST, receive INTERACTION_CREATE on the gateway, answer through the callback endpoint, edit or follow up through the webhook-token routes. There is no interactions endpoint URL delivery, no PING, no autocomplete and no modals.

Registering slash commands

ScopeEndpoints
GlobalGET POST /applications/{app}/commands · GET PATCH DELETE /applications/{app}/commands/{cmd}
Per serverGET POST /applications/{app}/guilds/{guild}/commands · GET PATCH DELETE /applications/{app}/guilds/{guild}/commands/{cmd}

Up to 100 commands per scope. The Discord-shaped body (name, description, options, …) is stored and delivered back in INTERACTION_CREATE; options with autocomplete: true are stored but the autocomplete round-trip never happens.

Bulk overwrite works: PUT /applications/{application_id}/commands (and the guild variant) replaces the whole set in one transaction, so CommandTree.sync() and discord.js's rest.put(Routes.applicationCommands(id), { body }) deploy scripts run unchanged. Command permissions (…/commands/permissions) are not in the subset.

Receiving an interaction

A user invoking your command or clicking a component arrives as INTERACTION_CREATEregardless of intents, like on Discord:

{
  "id": "…", "application_id": "…", "token": "…", "version": 1,
  "type": 2,
  "data": { "id": "…", "name": "ping", "options": [] },
  "guild_id": "…", "channel_id": "…",
  "member": { "user": { "id": "…", "username": "…" }, "roles": ["…"] }
}
  • type 2 = application command, 3 = message component. data is Discord-shaped — the command name and options, or the component's custom_id and values — and for components the message the component sits on is included.
  • member in servers, user in DMs.
  • The token is valid for 15 minutes. It is the credential for the callback and follow-up routes — those routes take no Authorization header.

An interaction that fires while your bot has no live gateway session is dropped; it is not queued for RESUME.

Responding

POST /interactions/{id}/{token}/callback with { "type": <n>, "data": { … } }:

TypeNameOn Voidcom
1PONGanswers 204 locally (there is no PING to answer)
4CHANNEL_MESSAGE_WITH_SOURCEposts the response message
5DEFERRED_CHANNEL_MESSAGE_WITH_SOURCEshows "thinking"; post the message later via @original
6DEFERRED_UPDATE_MESSAGEcomponents: acknowledge, edit later
7UPDATE_MESSAGEcomponents: edit the message the component is on
8APPLICATION_COMMAND_AUTOCOMPLETE_RESULT501, code 50035
9MODAL501, code 50035

The callback answers 204; with ?with_response=true it answers 200 with Discord's { "interaction", "resource" } object (the resource carries the posted message for types 4 and 7).

data takes content, embeds, components and flags. Ephemeral (flags: 64) applies only on the callback that answers the interaction (type 4, or the first post after a deferral). A follow-up sent once the response exists is a normal message in the channel — there is no ephemeral plain send — so a late "ephemeral" follow-up is public.

The original response and follow-ups

The webhook-token routes use your application id and the interaction token:

RouteWhat
POST /webhooks/{application_id}/{token}follow-up. While the interaction is still pending or deferred this is the response (type 4); afterwards a plain message as the bot into the interaction's channel
GET PATCH DELETE /webhooks/{application_id}/{token}/messages/{message_id}fetch, edit, delete a response — message_id is @original for the initial response or a follow-up's id. A PATCH of @original while deferred posts the response — defer() then edit_original_response() works

On PATCH, "embeds": [] together with "components": [] clears them; omitting both keys keeps them; sending one embed replaces the whole set (no partial embed merge).

Components

Components ride in components as action rows, Discord's shape:

  • Action row (type 1): up to 5 rows per message; a row holds up to 5 buttons or one select menu.
  • Button (type 2): style 1–4 need a custom_id (≤ 100 chars) and a label (≤ 80) or an emoji; style 5 (link) needs a url and no custom_id.
  • String select (type 3): 1–25 options (label/value/description ≤ 100 chars each), placeholder ≤ 150, min_values/max_values within the option count.
  • User, role, mentionable and channel selects (types 5–8) are accepted as { type, custom_id, disabled } only: the client renders them greyed out and they never fire.

Clicks and selections arrive as INTERACTION_CREATE type 3 with data.custom_id (and data.values for selects).

Embeds

Up to 10 embeds per message, 6 000 characters in total across them, whole payload ≤ 32 KB. Per embed:

FieldLimit
title256
description4 096
fields25 · name 256 · value 1 024
footer.text2 048
author.name256
color0xFFFFFF

url, timestamp, image, thumbnail and footer.icon_url are accepted. Anything over a limit is rejected with 400 code 50035 and a message naming the offending path (for example embeds[0].fields exceeds 25 entries).

Error codes

HTTPcodeWhen
40410062unknown interaction: the token expired (15 min), was never issued, or the callback used a different interaction id
40040060the interaction was already acknowledged
40050035invalid payload — embed / component limits, missing custom_id, bad button style
50150035callback type 8 or 9