Skip to main content
These constants exist so that a bot writing raw frames does not have to guess wire indices. A bot that only calls mapped methods never needs this page.

The frame model

A frame is a JSON array with an integer opcode at index 0. Everything after that is positional: the protocol has no field names and no request ids. Replies correlate positionally by reply opcode. When you await a reply, the first inbound frame carrying that opcode is yours. Two waiters on the same opcode at once is a correlation bug, which is why client.raw.request refuses the opcodes the SDK correlates itself.
A reply payload is the frame minus the opcode, and when exactly one element remains it is unwrapped to that scalar. [638, -7] arrives as the number -7, while [638, 0, id, tokenId, secret, expiry] arrives as a five-element array. Maps marked “payload indices” below are relative to that unwrapped value, not to the frame.
Some opcodes are both an action reply and a broadcast hook. Opcodes 45 and 119 are the ones to know: a scalar payload is your own success or error acknowledgement, an array payload is the broadcast. Discriminate by shape, never by opcode.

Op

The opcode catalogue. Only the subset a bot needs is defined.
Prefer Op.UPLOAD_MESSAGE to the literal 1 in your own code. The constant is the thing this SDK audits against the backend.

Session and messaging

History

Servers, channels and groups

Message actions

Members, roles and moderation

Direct messages and keys

Bot lifecycle

Webhooks

Success is 0 on all five, and each requires manage_server on the currently selected server.

Commands and emoji

Voice

Broadcast hooks

Channel, group and server lifecycle pushes. Current backends append a trailing serverId (and channelId where it applies) so a bot can route them without a selection.

MessageType

The message type carried on live message payloads and history rows.
The SDK routes every type above 0 to the systemMessage event, so your bot’s own webhook posts never echo back as messageCreate.

RESERVED_REPLY_OPCODES

The reply opcodes the SDK registers its own waiters on, plus opcode 1. client.raw.request refuses these unless you pass allowReserved: true, because a raw waiter can claim a reply the SDK was waiting for, or lose its own. The set covers the mapped action surface: sends, history, server and channel reads, every message action, moderation, DM creation, the key exchange, login, the manifest, the command menu, the webhook five, the REST credential mint, and the voice key. Test membership rather than memorizing it:
Opcode 24 is in the set even though the SDK registers no waiter on it. The relay reaches your bot for every visible channel, so a raw waiter would resolve with a stranger’s typing payload.

Column maps

Each map names the positions of one wire payload. Frame indices count the opcode at 0. Payload indices are relative to the unwrapped reply.

UploadMessageIdx

Frame indices of an outbound message (opcode 1).
The MENTIONS slot is plaintext by design. The server routes notification fanout from it without decrypting the body. See Mention helpers.
A plain send emits the six-element frame the backend has always seen. Only a sidecar path pads slots 6, 7 and 8 with 0, false and null so a later index can carry its payload. A frame that wants slot 9 must pad all three.

UploadDenyIdx

Frame indices of the op-1 deny echo, [1, "", "", "", "", "", -N]. The SDK decodes this for you and emits sendRejected. Two DM paths still answer with no frame at all, so silence is not proof of delivery.

DmNotifyIdx

Frame indices of opcode 636. One map covers both flavors, because the server builds them with identical positions. On the DM flavor, SERVER_ID is the literal 0 and CHANNEL_ID carries the dm id. There is no mention array, no reply array and no command data on this frame, which is why a DM message has no repliedTo.

DM_NOTIFY_KIND

The KIND value that means “a direct message”, and a total discriminator. Every other kind duplicates a server message the bot already receives on the firehose, so the SDK drops them.

CreateDmIdx

Payload indices of the opcode 150 reply.
A null DM_ID with CODE 0 is a denial, not a success. An ineligible target falls through the insert and still answers code 0 with no id. Check the id, not just the code.

TypingIdx

Frame indices of opcode 24, the same in both directions. Outbound, USER_ID is a placeholder that must still exist: the backend overwrites index 1 in place, so a two-element frame is an error server-side.
This relay carries no routing tail. The source channel travels in the backend’s fanout envelope and never reaches the frame, which is why the typing event reports serverId and channelId as null.

UserStatusIdx

Frame indices of opcode 19. MANUAL true sets the account-level base status and persists it. False is a per-device activity report, which the SDK never sends.

USER_STATUS_CODES

The named base statuses setStatus() accepts. The keys are the BotStatus type.

WebhookRowIdx

One row of the opcode 616 reply. CREATOR may be null on an old row. Discriminate the reply with Array.isArray, not by length: a server with no webhooks legitimately answers [], while every error is a bare number.

WebhookMintIdx

Payload indices of the create and regenerate replies. Both carry the code at index 0, so check it before trusting anything else.
SECRET and URL hold the delivery token. Never log them, never persist them, never put them in an error message.

MintRestCredentialIdx

Payload indices of the opcode 638 reply. Success is 0, and every failure arrives as a bare number instead of an array. Discriminate on the code, never on array versus scalar shape.
USER_ID and TOKEN_ID are dashed uuid strings and must reach the REST service byte for byte. Normalizing them makes every subsequent request return 401 with no other symptom. SECRET is returned exactly once and is stored server-side only as a hash.

RetrieveVoiceKeyIdx

Payload indices of the opcode 629 reply. These are payload indices, not frame indices. The unwrap context uses SERVER_ID and KEY_VERSION at payload positions 3 and 4, and reading them one slot over fails with a bad tag and no other symptom.

Voice key constants

VoiceScopeTag

Byte 0 of the sealed voice plaintext. PARTICIPANT is the bot’s own frame key, publish only. ROOM is the room key, which a bot receives when it holds voice_listen or administrator.
Any other value is an error, never a default. Treating an unknown tag as “probably participant” installs a byte-shifted key that opens cleanly, passes the cipher, and produces silently undecryptable audio.

VOICE_SEALED_PLAINTEXT_BYTES

The sealed voice plaintext is exactly 33 bytes: a one-byte scope tag followed by a 32-byte key. Exactly, not “at least”.

VOICE_STATE_NAMES

Index equals the opcode 31 state type. The array is the VoiceStateFlag type.
p2pWebcam and p2pStream are dead API. Peer-to-peer video was removed from the platform. The names survive so the array stays index-aligned with the wire, and the matching VoiceParticipant.p2pWebcam and .p2pStream fields are permanently false. Do not build on them. Video is not available to bots at all: the backend restricts a bot’s publish sources to the microphone at token-mint time.

Types

'invisible' | 'online' | 'away' | 'busy' | 'dnd'
The keys of USER_STATUS_CODES, and what setStatus() accepts.
'muted' | 'deafened' | 'webcam' | 'stream' | 'p2pWebcam' | 'p2pStream'
The members of VOICE_STATE_NAMES, and the state field on voiceStateUpdate.

What is not exported

Several names appear in SDK signatures or in the source without being on the public export surface. Writing an import for one of these does not resolve.

Next

Raw frames

Where these constants are used.

Events

The decoded form of most of these frames.

Errors

The three success-code conventions on the wire.

REST client

Where MintRestCredentialIdx is consumed.