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 whyclient.raw.request refuses the opcodes the SDK correlates itself.
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 is0 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 trailingserverId (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
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:
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).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
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.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
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.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.
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
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.
VOICE_SEALED_PLAINTEXT_BYTES
VOICE_STATE_NAMES
VoiceStateFlag type.
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 animport 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.