Interactions
An interaction is one use of a bot by a person: a slash command, a click on a button or a select menu, a keystroke in an option with autocomplete, or a submitted modal. Micup sends it to the bot as an Interaction Create Gateway Dispatch with a token, and the bot answers with that token. Every bot can use interactions; no review is required.
Commands are registered through Application commands. Buttons and select menus are message components the bot puts on its own messages.
Interaction lifecycle
Section titled “Interaction lifecycle”- A person runs a command, clicks a component, or submits a modal. The client sends the request, and Micup checks the caller, the command or component, and the channel.
- Micup sends one Interaction Create to exactly one session of the bot: a session of the guild’s shard, or in a direct message a session on shard 0 or without sharding. With several candidates the most recently connected one is chosen. It never goes to every session, to another bot, or to a user session.
- The bot sends its first answer to Create interaction response within 3 seconds. The token answers the first callback only once.
- For 15 minutes after the interaction was created, the bot may edit the original answer and send up to 20 followups with the same token, or with Create interaction followup. A started interaction stays answerable for those 15 minutes even if its command changes or is deleted, as on Discord; the bot’s membership and permissions are checked again on every answer.
Delivery is not retried. When no session of the bot is connected, nothing is delivered and the invoking user is told the bot did not receive it. A bot that misses the 3 second window cannot answer that interaction any more, and the user sees that the bot did not answer.
Bot requirements
Section titled “Bot requirements”- The bot must be a member of the guild. A public answer needs the bot’s SEND_MESSAGES in the channel, and a private answer needs its VIEW_CHANNEL.
- In a direct message, interactions work only in a one-to-one direct message between the user and the bot, never in a group direct message. A command needs
dm_permissiontrue and nodefault_member_permissions. - The bot’s
feature_bot_interactionslimit key must be1andfeature_bot_interactions_blockedmust be0.feature_bot_components,feature_bot_select_menus,feature_bot_modals, andfeature_bot_components_v2each gate one step. The invoking user’sfeature_app_commandsmust be1. A closed key returns 403APPLICATION_COMMAND_FEATURE_DISABLED.
There is no HTTP interactions endpoint, no PING interaction, and no Ed25519 signature: every interaction arrives over the Gateway.
Interaction object
Section titled “Interaction object”The d payload of Interaction Create.
Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| id | snowflake | The ID of the interaction |
| application_id | snowflake | The ID of the bot’s application |
| type | integer | Interaction type |
| token | string | The interaction token |
| version | integer | Always 1 |
| data | interaction data object | What was used |
| guild_id? | snowflake | The guild, absent in a direct message |
| channel_id | snowflake | The channel the interaction happened in |
| channel | partial channel object | That channel |
| member? | interaction member object | The invoking member, in a guild |
| user? | partial user object | The invoking user, in a direct message |
| message? | message object | The message whose component was used |
| app_permissions | string | The bot’s permissions in the channel |
| locale | string | The invoking user’s locale |
| guild_locale? | string | The guild’s locale |
| entitlements | array | Always empty |
| context | integer | 0 in a guild, 1 in a direct message with the bot |
| attachment_size_limit | integer | The largest upload in bytes |
The object also has authorizing_integration_owners, a map with the guild ID under the key 0, kept for Discord libraries.
Interaction member object
Section titled “Interaction member object”A guild member object with two more fields.
| Field | Type | Description |
|---|---|---|
| permissions | string | The member’s permissions in the channel, with overwrites |
| flags | integer | Always 0, kept for Discord libraries |
Interaction types
Section titled “Interaction types”| Value | Name | Description |
|---|---|---|
| 1 | PING | Not sent, because there is no HTTP endpoint |
| 2 | APPLICATION_COMMAND | A slash command was run |
| 3 | MESSAGE_COMPONENT | A button was clicked or a select menu was used |
| 4 | APPLICATION_COMMAND_AUTOCOMPLETE | The user is typing in an option with autocomplete |
| 5 | MODAL_SUBMIT | A modal the bot opened was submitted |
Interaction data objects
Section titled “Interaction data objects”Command data object
Section titled “Command data object”Sent with types 2 and 4.
| Field | Type | Description |
|---|---|---|
| id | snowflake | The ID of the command |
| name | string | The command name |
| type | integer | Always 1 |
| guild_id? | snowflake | Present for a guild command |
| options? | array[option value object] | The filled options |
| resolved? | resolved data object | Only the users, members, roles and channels the options name |
The options are plain values, or exactly one subcommand, or exactly one group holding one subcommand, each with its own options.
Option value object
Section titled “Option value object”| Field | Type | Description |
|---|---|---|
| name | string | The option name |
| type | integer | Option type |
| value? | string | number | boolean | The value, absent on a subcommand or group |
| options? | array[option value object] | The children of a subcommand or group |
| focused? | boolean | True on the one option being typed, type 4 only |
In an autocomplete interaction the focused option’s value is the raw text typed so far, even for an integer or number option, and the other options can be incomplete.
Component data object
Section titled “Component data object”Sent with type 3.
| Field | Type | Description |
|---|---|---|
| custom_id | string | The custom_id of the component |
| component_type | integer | The component type |
| values? | array[string] | The chosen values of a select menu |
| resolved? | resolved data object | The chosen users, members, roles or channels of an automatic select |
Modal submit data object
Section titled “Modal submit data object”Sent with type 5.
| Field | Type | Description |
|---|---|---|
| custom_id | string | The custom_id of the modal |
| components | array[component] object | The modal layout with each text input’s value filled in |
Resolved data object
Section titled “Resolved data object”| Field | Type | Description |
|---|---|---|
| users? | map[snowflake, partial user object] | Users by ID |
| members? | map[snowflake, interaction member object] | Members by ID, without user |
| roles? | map[snowflake, guild role object] | Roles by ID |
| channels? | map[snowflake, resolved channel object] | Channels by ID |
Resolved channel object
Section titled “Resolved channel object”| Field | Type | Description |
|---|---|---|
| id | snowflake | The ID of the channel |
| name | ?string | The channel name |
| type | integer | The channel type |
| permissions | string | The invoking member’s permissions in that channel |
| parent_id | ?snowflake | The parent category |
Interaction tokens
Section titled “Interaction tokens”A token has the form base64url("interaction:" + id + ":" + base64url(32 random bytes)), so it starts with aW50ZXJhY3Rpb246. It is bound to its interaction and channel and expires 900 seconds after the interaction was created. The first callback must arrive within 3,000 milliseconds of creation and can be made once.
A route that receives an unknown, mismatched, or expired token answers 404 UNKNOWN_WEBHOOK or 410 INTERACTION_EXPIRED, and reveals nothing about other interactions.
Interaction callback types
Section titled “Interaction callback types”| Value | Name |
|---|---|
| 1 | PONG |
| 4 | CHANNEL_MESSAGE_WITH_SOURCE |
| 5 | DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE |
| 6 | DEFERRED_UPDATE_MESSAGE |
| 7 | UPDATE_MESSAGE |
| 8 | APPLICATION_COMMAND_AUTOCOMPLETE_RESULT |
| 9 | MODAL |
Type 4 answers with a message. Type 5 shows that the bot is thinking, and the message follows as an edit of @original. Type 6 confirms a click and changes nothing yet. Type 7 edits the message the component is on. Type 8 sends autocomplete choices. Type 9 opens a modal. Type 1 is refused, because there is no HTTP endpoint.
| Callback | Valid for |
|---|---|
| 4, 5 | Types 2, 3 and 5 |
| 6, 7 | Type 3 |
| 8 | Type 4 alone, which accepts no other callback |
| 9 | Types 2 and 3, as the first answer |
Interaction callback object
Section titled “Interaction callback object”The body of Create interaction response.
Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| type | integer | Callback type |
| data? | object | The response message, {flags} for type 5, {choices} for type 8, or the modal for type 9 |
Type 6 has no data. Type 5 takes only flags, 0 or 64, which fixes whether the later answer is private.
Interaction callback response object
Section titled “Interaction callback response object”What Create interaction response returns with with_response, the shape of Discord’s InteractionCallbackResponse.
Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| interaction | callback interaction object | The answered interaction |
| resource | callback resource object | What the answer produced |
Callback interaction object
Section titled “Callback interaction object”| Field | Type | Description |
|---|---|---|
| id | snowflake | The ID of the interaction |
| type | integer | The interaction type |
| response_message_id? | snowflake | The message of a type 4 or 7 answer |
| response_message_loading? | boolean | true after type 5, false after type 4 or 7 |
| response_message_ephemeral? | boolean | Whether the answer is private, after type 4, 5 or 7 |
Callback resource object
Section titled “Callback resource object”| Field | Type | Description |
|---|---|---|
| type | integer | The callback type |
| message? | message object | The message of a type 4 or 7 answer |
A private message in resource goes only to the bot that answered and is stored nowhere else.
Interaction response message object
Section titled “Interaction response message object”The message of callback types 4 and 7, of followups, and of edits.
Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| content? | ?string | Message text |
| embeds? | ?array[rich embed object] | Up to 10 embeds, as a bot message takes |
| components? | ?array[component object] | Message components |
| allowed_mentions? | allowed mentions object | Which mentions notify, none by default |
| flags? | integer | 0, 64 (EPHEMERAL), 32768 (IS_COMPONENTS_V2), or both |
| tts? | boolean | Ignored |
| attachments? | array | Accepted only when empty |
A new message needs content, an embed, or components. With IS_COMPONENTS_V2 it carries neither content nor embeds. A whole response is at most 32,768 bytes of UTF-8 JSON. Embed media goes through the media proxy for public and private answers alike, so a client never loads a bot’s external URL directly.
Private responses
Section titled “Private responses”A response with flags 64 is visible only to the user who triggered the interaction. It is never stored as a channel message: it reaches no search index, push notification, unread count, pin, reply, or data export. Micup sends it only to the session the user invoked from, as Interaction Response Create, and the client can recover it for 15 minutes through List private interaction responses.
A public answer is a normal message by the bot, with application_id and interaction_metadata set by the server. After a public type 5, no placeholder message is posted: only the invoking user sees that the bot is thinking, and the first edit of @original creates the message.
Private response object
Section titled “Private response object”One private answer as List private interaction responses returns it, with the shape of Interaction Response Update.
Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| interaction_id | snowflake | The interaction this answers |
| kind | string | Always message |
| response_id | snowflake | The private message ID |
| sequence | integer | 0 for the original answer and 1 through 20 for followups |
| message | message object | The private message |
Modal object
Section titled “Modal object”Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| custom_id | string | Returned in the modal submit, 1 through 100 characters |
| title | string | 1 through 45 characters |
| components | array[component object] | 1 through 5 modal components |
A modal opens only as the first answer to a command or component interaction. Its submit arrives as a type 5 interaction. A submit with no open modal returns 409 MODAL_NOT_OPEN, a field the modal did not have or a value over its limits returns 400, and a submit after 900 seconds returns 410 INTERACTION_EXPIRED.
Autocomplete
Section titled “Autocomplete”A type 4 interaction is answered with callback type 8 within 3 seconds and cannot be deferred. choices has up to 25 entries, each a name of 1 through 100 characters and a value of the option’s type. The client sends a request only for options with autocomplete true, after a short pause in typing, and shows only the latest answer.
Webhook token routes
Section titled “Webhook token routes”After the first answer, the token works on the webhook token routes with the application ID in place of the webhook ID. A token that starts with aW50ZXJhY3Rpb246 selects the interaction branch, and any other token keeps the ordinary webhook behaviour.
POST /v1/webhooks/{application_id}/{token}sends a followup, up to 20.GET,PATCH, andDELETEon/v1/webhooks/{application_id}/{token}/messages/@originalread, edit, and delete the original answer.PATCHandDELETEon/v1/webhooks/{application_id}/{token}/messages/{message_id}edit and delete a followup.
The bodies are the response message. An edit keeps the visibility of the first answer and is checked against the stored IS_COMPONENTS_V2 flag. @original before the first public message exists returns 404. An application_id that does not match the interaction returns 404 UNKNOWN_WEBHOOK. Only the interaction’s own messages can be edited or deleted.
Differences from Discord
Section titled “Differences from Discord”The interaction types, callback types, component types, button styles, flag values, event name, and routes match Discord. These do not:
| Topic | Micup |
|---|---|
| API version | REST /v1, Gateway v=1. In discord.js set rest: {api, version: '1'} and ws: {version: 1} |
| HTTP endpoint | None. Every interaction arrives over the Gateway |
| Bot membership | The bot must be in the guild. A public answer needs its SEND_MESSAGES |
The dm_permission default | False, or set by contexts. integration_types is discarded |
| Deferred public answer | No placeholder message. The first edit of @original creates it |
| Private answers | Sent to the invoking session as Interaction Response events, never as a stored message |
Components v2 content | Carries a text fallback built by the server. A bot ignores it |
| Extra followup route | Create interaction followup, with the bot token and Idempotency-Key |
| Command writes | If-Match is optional. Without it the newest set is changed |
| Command localisation | name_localizations, description_localizations and nsfw are refused with 400 |
| Not offered | Context menu commands, premium buttons, SKUs, activities, files in a response, selects and uploads in a modal |
Error codes
Section titled “Error codes”| Status | Code | Meaning |
|---|---|---|
| 403 | APPLICATION_COMMAND_FEATURE_DISABLED | A limit key is closed for the bot or the user |
| 503 | APPLICATION_COMMAND_BOT_UNAVAILABLE | The bot cannot take the interaction |
| 409 | APPLICATION_COMMAND_STALE | The command changed, or its bot was closed or left, since the client loaded it |
| 410 | INTERACTION_EXPIRED | The 3 second or 15 minute window has passed |
| 409 | INTERACTION_TOKEN_CONSUMED | The interaction was already answered |
| 409 | INTERACTION_NONCE_CONFLICT | A nonce or idempotency key was reused with a different body |
| 503 | INTERACTION_OUTCOME_UNKNOWN | The result could not be confirmed. Retry with the same nonce or key |
| 400 | UNSUPPORTED_COMPONENT | A component type that is not offered |
| 400 | INVALID_COMPONENT_VALUE | A select value that is not on the component |
| 409 | MODAL_NOT_OPEN | A modal submit without an open modal |
| 404 | UNKNOWN_WEBHOOK | The token or application ID does not match |
Security notes
Section titled “Security notes”- A
custom_idand every select value are untrusted input. Micup checks only that they are on the message and that values are among the stored options, so the bot validates them like any user input and never puts secrets or permissions in them. - Check permissions in the bot.
default_member_permissionsonly hides a command, and an administrator can change who sees it. - A link button takes only an
httporhttpsURL of at most 512 characters without user information, and a blocklisted domain is refused. Clients open it after their external link warning. - Keep the token out of logs and error reports, and answer from the session that received the interaction.
Invoke a bot command
Section titled “Invoke a bot command”POST/v1/interactionsRuns a bot’s interaction for the signed-in user and returns its delivery status, never the token, the options, or the bot’s answer. The answer arrives over the Gateway. The same nonce returns the same interaction and never delivers twice.
Limitations
Section titled “Limitations”- This is a user-only operation that accepts only a session token. A bot, OAuth2 bearer, or admin credential is refused with 403
ACCESS_DENIED. - An unclaimed account is refused with 400
UNCLAIMED_ACCOUNT_CANNOT_SEND_MESSAGES, and one that has not started a session receives the field codeMUST_START_SESSION_BEFORE_SENDING. - The caller needs the command’s
default_member_permissionsand access to the channel.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| type | integer | 2 a command, 4 autocomplete, 5 a modal submit |
| application_id | snowflake | The bot’s application, types 2 and 4 |
| command_id | snowflake | The command, types 2 and 4 |
| command_version | snowflake | The command version the client shows, types 2 and 4 |
| channel_id | snowflake | The channel, types 2 and 4 |
| options | array[option value object] | The filled options, types 2 and 4 |
| parent_interaction_id | snowflake | The interaction that opened the modal, type 5 |
| custom_id | string | The modal’s custom_id, type 5 |
| components | array[modal field object] | Up to 5 field values, type 5 |
| nonce | string | 1 through 32 characters, unique per attempt |
| session_id | string | The Gateway session that receives private answers |
Type 4 needs exactly one option with focused true. A command_version that is not the stored one returns 409 APPLICATION_COMMAND_STALE.
Modal field object
Section titled “Modal field object”| Field | Type | Description |
|---|---|---|
| custom_id | string | The text input’s custom_id |
| value | string | At most 4000 characters |
Response body
Section titled “Response body”| Field | Type | Description |
|---|---|---|
| id | snowflake | The ID of the interaction |
| status | string | Delivery status |
| deadline_at | ISO8601 timestamp | When the first answer is due |
Delivery statuses
Section titled “Delivery statuses”| Value | Description |
|---|---|
| RESERVED | The interaction was accepted |
| PREPARED | The payload was built |
| DISPATCHING | Delivery to the bot is in progress |
| ENQUEUED | One bot session has it queued |
| NOT_DELIVERED | No session of the bot was connected, and nothing was sent |
| DELIVERY_UNKNOWN | The Gateway did not confirm, and the interaction is not sent again |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | response body | The interaction was accepted |
| 400 | error response | The body is invalid |
| 403 | error response | Caller is not a user session and the request returns ACCESS_DENIED |
| 403 | error response | A key is closed and the request returns APPLICATION_COMMAND_FEATURE_DISABLED |
| 404 | error response | The channel cannot be used and the request returns UNKNOWN_CHANNEL |
| 409 | error response | The command or its bot changed and the request returns APPLICATION_COMMAND_STALE |
| 409 | error response | The nonce was used for another body and the request returns INTERACTION_NONCE_CONFLICT |
| 503 | error response | No Gateway node took the interaction and the request returns APPLICATION_COMMAND_BOT_UNAVAILABLE |
Rate limit
Section titled “Rate limit”20 requests per 5 seconds for each authenticated user, on the interaction:route bucket. An invocation also counts 5 requests per 5 seconds for each authenticated user, on the interaction:invoke bucket, 30 requests per 1 minute for each authenticated user, on the interaction:invoke:minute bucket, and 10 requests per 1 minute for each user and application, on the interaction:invoke:application bucket.
Get interaction status
Section titled “Get interaction status”GET/v1/interactions/{interaction_id}Returns the delivery status of an interaction the signed-in user invoked. Another user receives 404.
Limitations
Section titled “Limitations”- This is a user-only operation that accepts only a session token.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| interaction_id | snowflake | The ID of the interaction |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | invoke response body | The interaction is the caller’s |
| 403 | error response | Caller is not a user session and the request returns ACCESS_DENIED |
| 404 | error response | No such interaction for the caller, and the request returns UNKNOWN_MESSAGE |
Rate limit
Section titled “Rate limit”30 requests per 10 seconds for each authenticated user, on the interaction:status bucket.
List private interaction responses
Section titled “List private interaction responses”GET/v1/interactions/{interaction_id}/responseReturns the private answers to an interaction the signed-in user invoked, for a client that missed them while reconnecting. Each entry has the shape of Interaction Response Update. They exist for 15 minutes and only for the invoking user.
Limitations
Section titled “Limitations”- This is a user-only operation that accepts only a session token.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| interaction_id | snowflake | The ID of the interaction |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | array[private response object] | The interaction is the caller’s |
| 403 | error response | Caller is not a user session and the request returns ACCESS_DENIED |
| 404 | error response | No such interaction for the caller, and the request returns UNKNOWN_MESSAGE |
Rate limit
Section titled “Rate limit”Shares the interaction:status bucket with Get interaction status.
Create interaction response
Section titled “Create interaction response”POST/v1/interactions/{interaction_id}/{interaction_token}/callbackSends the first answer to an interaction. Returns 204 with an empty body, or with with_response 200 and the callback response. discord.py asks for it on every first answer.
Limitations
Section titled “Limitations”- No
Authorizationheader is needed, which is what discord.js sends. A header that is present must be the token of the bot the interaction went to, and any other answers 404UNKNOWN_WEBHOOK. - The answer must arrive within 3 seconds of creation, and a later one returns 410
INTERACTION_EXPIRED. A second answer returns 409INTERACTION_TOKEN_CONSUMED. - The callback type must be valid for the interaction type, and callback type 1 is refused with 400.
- Callback type 9 needs the bot’s
feature_bot_modalskey.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| interaction_id | snowflake | The ID of the interaction |
| interaction_token | string | The token from Interaction Create |
Query parameters
Section titled “Query parameters”| Field | Type | Description |
|---|---|---|
| with_response? | string | true or 1 returns the callback response |
JSON body
Section titled “JSON body”An interaction callback object.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | interaction callback response object | The answer was accepted and with_response is true or 1 |
| 204 | empty | The answer was accepted without with_response |
| 400 | error response | The body is invalid or the callback type does not fit |
| 403 | error response | A key is closed and the request returns APPLICATION_COMMAND_FEATURE_DISABLED |
| 404 | error response | The token does not match and the request returns UNKNOWN_WEBHOOK |
| 409 | error response | The interaction was answered and the request returns INTERACTION_TOKEN_CONSUMED |
| 410 | error response | The 3 seconds passed and the request returns INTERACTION_EXPIRED |
Rate limit
Section titled “Rate limit”5 requests per 1 second for each interaction ID, on the interaction:callback::interaction_id bucket.
Create interaction followup
Section titled “Create interaction followup”POST/v1/interactions/{interaction_id}/followupsSends one more message for an answered interaction with the bot token. The Idempotency-Key header makes a retry safe: the same key returns the same message. Returns the message, whose visibility follows the first answer.
Limitations
Section titled “Limitations”- Only the bot the interaction went to is accepted, and a user credential is refused with 403
ACCESS_DENIED. Idempotency-Keyis required, 1 through 64 printable ASCII characters. A missing or malformed key returns 400, and a key reused with another body returns 409INTERACTION_NONCE_CONFLICT.- The interaction must have been answered, and at most 20 followups exist. After 15 minutes the route returns 410
INTERACTION_EXPIRED.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| interaction_id | snowflake | The ID of the interaction |
JSON body
Section titled “JSON body”An interaction response message object.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | message object | The followup was sent, or the key was already used for this body |
| 400 | error response | The body or the key is invalid |
| 403 | error response | Caller is not a bot and the request returns ACCESS_DENIED |
| 404 | error response | Not this bot’s answered interaction, and the request returns UNKNOWN_WEBHOOK |
| 409 | error response | The key was used for another body and the request returns INTERACTION_NONCE_CONFLICT |
| 410 | error response | The 15 minutes passed and the request returns INTERACTION_EXPIRED |
| 503 | error response | The result is unknown and the request returns INTERACTION_OUTCOME_UNKNOWN |
Rate limit
Section titled “Rate limit”5 requests per 1 second for each interaction ID, on the interaction:followup::interaction_id bucket.