Skip to content
Fluxer API

Client commands

A client command is a payload a client sends to Fluxer over the Gateway WebSocket. Each one has an integer opcode in op and the command data in d. Fluxer answers with a Dispatch event, a close frame, or nothing at all.

Except for Heartbeat, Identify, and Resume, every command needs an authenticated session. Sending one too early closes with 4003 and reason Not authenticated. Heartbeat and Resume are accepted in any open state, and Identify is accepted only while the connection is unauthenticated.

OpcodeCommandResult
1HeartbeatOpcode 11 Heartbeat ACK
2IdentifyReady, a close frame, or silence when the payload is held or discarded
3Presence UpdateNo direct response
4Voice State UpdateVoice State Ack, Voice State Update, and Voice Server Update when state changes
6ResumeReplayed Dispatches followed by Resumed, Invalid Session, or a close frame
8Request Guild MembersOne or more Guild Members Chunk events
14Lazy RequestGuild Sync and Guild Member List Update
15Request Guild CountsGuild Counts Update
16Request Channel Member CountsChannel Member Counts Update

A frame that fails the size, decompression, or decoding checks closes the connection without consuming any budget. A frame that decodes to something other than an object closes with 4002 and reason Decode failed, and an object with no op closes with 4002 and reason Invalid payload.

Fluxer charges every command that gets past those checks against the source IP, session, and connection payload budgets before it handles the opcode. Each session gets its own session budget, so two sessions of one account never share one, and Fluxer skips that budget while the connection is unauthenticated.

An opcode outside the registry closes with 4001 and reason Unknown opcode once a session is attached, and with 4003 while the connection is unauthenticated. A payload that has op but no d also closes with 4001, except for Identify, which closes with 4005.

Opcode 1 has the last Dispatch sequence the client processed. Use null before the first Dispatch.

FieldTypeDescription
d?integerThe last Dispatch sequence the client processed, or null before the first Dispatch
{
"op": 1,
"d": 42
}

Before authentication the Gateway accepts any d value and sends Opcode 11. A payload with no d key closes with 4001 and reason Unknown opcode.

Once a session exists, a d that is neither null nor an integer closes with 4007 and reason Invalid sequence. Every integer is accepted. A sequence below the one the session has already acknowledged leaves the acknowledged sequence unchanged. Any other integer becomes the acknowledged sequence and trims every retained Dispatch at or below it from the replay buffer.

When the session ends, Fluxer sends Opcode 9 with d: false, after which heartbeats are acknowledged again. A heartbeat that arrives in the short window between the session ending and that frame closes with 4007.

See Gateway overview for the timing contract.

Opcode 2 authenticates and creates a new session.

FieldTypeDescription
tokenstringThe account or bot token, with no HTTP authentication prefix
propertiesidentify properties objectThe software and capabilities of the connecting client
presence?1?initial presence objectThe presence to publish when the session starts
ignored_events?2?array[string]The Dispatch event names this session does not want, at most 256 entries
flags?integerA non-negative session flags bitfield (default 0)
initial_guild_id?3?snowflakeThe snowflake of one guild the session joins as active
shard?4?array[integer]The [shard_id, shard_count] pair

1 Null and an omitted key both select the account’s saved status and saved custom status

2 Names are upper-cased and deduplicated. See Event filtering for the exact suppression rule

3 That guild is marked active and already synced when the session connects to it, so it delivers active traffic without a Lazy Request and sends no Guild Sync. Fluxer discards a value that is not a canonical decimal Snowflake string, and the Identify still succeeds

4 shard_count is an integer from 1 through 16,384, and shard_id is an integer that is at least 0 and below shard_count

{
"op": 2,
"d": {
"token": "...",
"properties": {
"os": "Linux",
"browser": "Fluxer Client",
"device": "desktop"
},
"presence": {
"status": "online",
"afk": false,
"mobile": false
},
"ignored_events": ["TYPING_START"],
"flags": 2,
"initial_guild_id": "1189375284394692608"
}
}

token and properties are required. Fluxer ignores unknown fields. There is no intents field and no intent system.

The following faults close with 4002 and reason Invalid identify payload:

  • A missing token or properties.
  • A properties value that is not an object.
  • A properties object whose os, browser, or device is missing or is not a string.
  • An ignored_events value that is not an array of strings, or one that holds more than 256 entries.
  • A flags value that is not a non-negative integer.

A malformed shard closes with 4010 and reason Invalid shard.

Fluxer refuses a bot session that resolves to more than 2,500 guilds after any shard filter is applied. The connection closes with 4011 and reason Sharding required. A user session is never refused for its guild count.

A token the backend rejects closes with 4004 and reason Invalid token. A non-bot account that already holds 100 live sessions closes with 4008 and reason Too many sessions, and a bot credential is not bounded by that count. An Identify sent on a socket that already has a session attached closes with 4005 and reason Already authenticated, whether or not it has d.

ValueNameDescription
1 << 1DEBOUNCE_MESSAGE_REACTIONSCoalesce runs of reaction additions into Message Reaction Add Many

Bit 0 and every bit above 1 are undefined. An undefined bit is accepted and ignored without closing the connection.

DEBOUNCE_MESSAGE_REACTIONS applies to a reaction in a direct message or group direct message. A reaction in a guild channel is never coalesced and arrives as its own Message Reaction Add.

FieldTypeDescription
osstringThe operating system the client runs on
browserstringThe client library identifier
devicestringThe device or application identifier
e2ee_capable?1booleanWhether the client can take part in end-to-end encrypted voice
mobile?2booleanWhether the session is mobile (default false)
latitude?3stringThe client latitude as a decimal string of 1 through 32 characters
longitude?3stringThe client longitude as a decimal string of 1 through 32 characters

1 Only the exact value true sets it. A session without it is refused from an end-to-end encrypted voice channel with VOICE_E2EE_REQUIRED

2 Read only when presence is absent or null, in which case it decides the session’s mobile flag. Otherwise the initial presence object decides it

3 Used to order the Ready rtc_regions array by distance

os, browser, and device are required strings. The remaining fields are optional hints. Fluxer accepts and ignores unrecognised properties.

latitude and longitude are accepted here only as strings. A number fails validation and the whole session start fails, so send "52.52". Both must be sent together to have any effect, and a string that does not parse as a number counts as absent. Omit them when the client has no location, and Ready orders rtc_regions by region ID instead.

FieldTypeDescription
status?1stringThe initial status, accepting online, idle, dnd, invisible, or offline
afk?2booleanWhether the session is away (default false)
mobile?2booleanWhether the presence is mobile (default false)
custom_status?3?custom status objectThe custom status this session publishes

1 The account’s saved status wins when status is absent, when it is null or any other non-string, when it is the string unknown, and when it is online while the saved status is not online. The empty string resolves to online, and the remaining accepted values are used as sent

2 Only the exact value true sets the flag. Every other value resolves to false

3 Read only when the account has no saved custom status, and stored as sent with no validation

Identify accepts offline as a distinct initial status, and Presence Update normalises offline to invisible. A session whose resolved status is offline or invisible publishes status: "offline" and a null custom_status to other users.

FieldTypeDescription
text??stringThe text of the custom status, from 1 through 128 characters
expires_at?1?ISO8601 timestampThe time the custom status expires
emoji_id?2?snowflakeThe snowflake of the custom emoji
emoji_name?3?stringThe Unicode emoji, from 1 through 32 characters

1 The timestamp is in the future

2 The Snowflake names an emoji that exists. Fluxer drops the field from the published object, without failing the rest of it, when the account has no global expressions entitlement

3 A single Unicode emoji. Fluxer strips the field before validation when emoji_id is supplied

Only these fields are read. Presence Update validates the object against the account before publishing it. Identify does not validate it, and reads it only when the account has no saved custom status.

The published presence adds emoji_animated to this object.

Opcode 6 restores a retained session.

FieldTypeDescription
tokenstringThe token that owns the retained session
session_idstringThe session ID from Ready
seqintegerThe last Dispatch sequence the client processed

All three fields are required. A missing field, a non-string token or session_id, or a seq that is not an integer closes with 4002 and reason Invalid resume payload.

An unknown or expired session produces Opcode 9 with d: false and leaves the socket unauthenticated. A token that does not own the session closes with 4004 and reason Invalid token. A seq above the session’s current sequence, or below the sequence it has already acknowledged, closes with 4007 and reason Invalid sequence. A seq below the replay floor, the highest sequence already dropped from the buffer, also produces Opcode 9 with d: false. A negative seq closes with 4000 and reason Session unavailable, and so does a session that cannot be reached. None of those closes destroys a separately retained session.

A successful Resume replays every retained Dispatch strictly above seq in order and finishes with Resumed. It also replaces the session’s socket, and the displaced socket receives Opcode 7 followed by a close.

Fluxer processes Resume in any authentication state. A socket that already has a session attached still processes one, and the named session takes the attached session’s place. Send Resume only on a fresh socket.

Opcode 3 replaces the current session presence.

FieldTypeDescription
statusstringThe status to publish, accepting online, idle, dnd, invisible, or offline
afk?1booleanWhether the session is away (default false)
mobile?1booleanWhether the session is mobile (default false)
custom_status?2?custom status objectThe custom status that replaces the current one

1 Only the exact value true sets the flag. Every other value resolves to false

2 An object identical to the current one in text, expires_at, emoji_id, and emoji_name is reused without revalidation

{
"op": 3,
"d": {
"status": "idle",
"afk": true,
"mobile": false,
"custom_status": {
"text": "away from keyboard"
}
}
}

status is required. A payload that is not an object, an object with no status key, and a status string outside the accepted set all close with 4002 and reason Invalid presence payload. The empty string resolves to online.

Fluxer accepts a status that is not a string. Null and a Boolean publish the session as offline, and every other non-string value resolves to online.

offline is normalised to invisible, so a Presence Update cannot publish a session as offline while it is connected.

custom_status is replaced only when the key is present. Omitting the key keeps the current custom status, null clears it, and a value that is neither an object nor null is ignored. An object the backend rejects, such as an emoji_id that names no emoji or an expires_at in the past, leaves the current custom status in place. The connection stays open.

The published presence has a custom status object and no activities.

Presence Update has a dedicated limit of five accepted commands per 20 seconds on one WebSocket. A further update inside that window is discarded without closing the connection, after it has consumed the shared payload budgets.

Opcode 4 joins, moves, updates, or leaves the voice membership associated with the current Gateway session.

FieldTypeDescription
guild_id??snowflakeThe guild containing the voice channel, where null selects the DM and group DM call context
channel_id??snowflakeThe channel to join or move to, where null leaves the current membership in that context
connection_id?1?stringThe existing voice connection this update applies to
self_mute?2booleanWhether the client has muted its own microphone (default false)
self_deaf?2booleanWhether the client has deafened its own output (default false)
self_video?2booleanWhether the client publishes camera video (default false)
self_stream?5booleanWhether this connection advertises a screenshare track (default false)
is_mobile?2booleanWhether this is a mobile voice client (default false)
viewer_stream_keys?3?array[string]The stream keys this connection is watching, where an omitted key keeps the current list and null clears it
latitude?number or stringThe client latitude, used to pick a voice region
longitude?number or stringThe client longitude, used to pick a voice region
mutation_id?stringA client-generated identity echoed in Voice State Ack
runtime_epoch?stringThe client runtime generation echoed in Voice State Ack
base_version?4integerThe voice state version this update was computed against

1 A channel_id with no connection_id opens a new connection, and a channel_id with one updates or moves that connection. An update that leaves one guild, meaning a non-null guild_id with channel_id: null, requires a connection_id, and one that omits it is refused with VOICE_MISSING_CONNECTION_ID

2 Only true and the string "true" set the flag. Every other value resolves to false

3 Every entry is a stream key whose scope, guild, and channel match this update. An entry that fails that check, or a value that is not an array, refuses the update with VOICE_INVALID_STATE, and an entry naming a connection that does not exist refuses it with VOICE_CONNECTION_NOT_FOUND

4 A non-negative integer. Fluxer treats every other value as absent, which disables the staleness check

5 Only true and the string "true" set it, and Fluxer publishes false when the member lacks STREAM in the channel. The screenshare track rides this same connection, so setting the flag mints no grant and sends no Voice Server Update

{
"op": 4,
"d": {
"guild_id": "1189375284394692608",
"channel_id": "1189375284394692610",
"self_mute": false,
"self_deaf": false,
"self_video": false,
"self_stream": false,
"mutation_id": "e0a1c2",
"base_version": 7
}
}

Every field is optional. A non-null guild_id or channel_id is a canonical decimal snowflake string, and a positive integer is also accepted. Every other value fails validation, and Fluxer drops the update with no close code and no event.

guild_id: null with channel_id: null and a connection_id leaves the DM or group DM call connection with that identifier. The same shape without a connection_id disconnects the session from every voice membership it holds. guild_id: null with a channel_id selects the DM or group DM call context.

latitude and longitude accept a number or a string here, and Fluxer coerces both to a string.

The command has no session_id field. The current Gateway session is the membership identity.

Joining or replacing a grant produces Voice Server Update with the token and endpoint for the media connection, and Voice State Update for every session that can see the channel.

When a guild update has mutation_id, Fluxer also reports the outcome to the requesting session as Voice State Ack, whose status is applied or rejected and whose error_code names the exact refusal. Without mutation_id a refusal produces no event at all, and the DM and group DM call context never acks.

base_version is a staleness check for an update that names an existing guild connection. An update whose base_version is more than one behind that connection’s current voice state version is rejected with stale_base_version. The check runs after the member, channel, and connection lookups and before the permission checks. It does not apply to opening a new connection, to leaving a channel, or to the DM and group DM call context.

The first two updates in a rolling one-second window are processed immediately. Later updates enter a per-session queue that holds at most 64 commands and drains one command every 500 ms. A newer update replaces an older queued update for the same guild_id and connection_id pair, and a full queue discards its oldest entry before accepting the new one.

Opcode 8 requests bounded member chunks.

FieldTypeDescription
guild_id?1snowflakeThe ID of the single guild to query
guild_ids?1array[snowflake]A non-empty array of the guild IDs to query
query?2stringThe display name prefix to match (default empty string)
limit?3integerThe result limit, from 0 through 100 and clamped (default 0)
user_ids?4array[snowflake]The explicit user IDs to select, at most 100 (default empty)
presences?5booleanWhether results include presences (default false)
nonce?stringA value of at most 32 bytes echoed in each chunk

1 A non-empty guild_ids array wins. guild_id is read only when guild_ids is absent or empty

2 Matched case-insensitively as a prefix of the member’s display name, which is the guild nickname, then the global name, then the username

3 A limit of 0 with a non-empty query resolves to 25 results, and a limit of 0 with an empty query requests the full member list up to 100,000 entries

4 A non-empty user_ids array selects those members directly and ignores query and limit

5 Only the exact value true sets it. Presences whose status is offline or invisible are omitted from the result

{
"op": 8,
"d": {
"guild_id": "1189375284394692608",
"query": "ann",
"limit": 50,
"presences": true,
"nonce": "a1b2c3"
}
}

The command never closes the connection. Invalid input is coerced or discarded:

  • A guild ID that is not a positive Snowflake abandons the whole request.
  • Duplicate guild IDs are collapsed.
  • A user_ids array longer than 100 entries abandons the whole request. Individual entries that are not positive Snowflakes are dropped.
  • A limit that is not a non-negative integer becomes 0, and a larger value is clamped to 100.
  • A query that is not a string becomes the empty string.
  • A nonce that is not a string of at most 32 bytes becomes null.

The nonce is echoed only when the request named exactly one guild.

Fluxer skips a guild the session is not currently connected to. A request that resolves to no connected guild produces no chunk.

A bot requests one guild at a time, and a bot request naming two or more guilds is abandoned.

An empty query, a limit of 0, and an empty user_ids together request the complete member list. A human account requesting the complete list needs MANAGE_ROLES, KICK_MEMBERS, and BAN_MEMBERS together in that guild, and a request holding only some of them is dropped silently. The guild owner and any member with ADMINISTRATOR satisfy that check. A bot requesting the complete list is limited to one accepted request per guild every 30 seconds. A request inside that window produces Rate Limited and no member chunk.

Results arrive as Guild Members Chunk in pages of at most 1,000 members, each with chunk_index and chunk_count. Those chunks are delivered live and are never retained for Resume replay.

Only one member request runs at a time on one WebSocket. While one runs, a newer request replaces any earlier pending request and starts when the active one finishes. Bounded requests covers the four-slot limit this command shares with the other three.

Opcode 14 sets the per-guild subscriptions that decide member list, typing, and synchronisation traffic for the session.

FieldTypeDescription
subscriptions1map[snowflake, guild subscription object]The subscription options for each guild, keyed by guild ID

1 Fluxer keeps each guild’s options for the session and applies them again when the session connects to that guild later

{
"op": 14,
"d": {
"subscriptions": {
"1189375284394692608": {
"active": true,
"typing": true,
"member_list_channels": {
"1189375284394692610": [[0, 99]]
}
}
}
}
}

subscriptions is an object whose keys are canonical decimal Snowflake strings. A key that does not parse, a value that is not an object, and a guild the session is not connected to are all skipped without affecting the rest of the command. The command never closes the connection.

FieldTypeDescription
active?1booleanWhether the guild is active for this session
sync?1booleanWhether the session requests a Guild Sync for the guild
typing?booleanWhether the guild delivers Typing Start, overriding the active flag
member_list_channels?2map[snowflake, array[array[integer]]]The member list windows to subscribe to, keyed by channel ID
members?array[snowflake]The explicit member IDs to subscribe to, at most 1,000

1 Both are Booleans when present. Any other value abandons the rest of the command silently, without a close and without a result

2 A coalesced subscription waits 100 ms before Fluxer applies it, and ranges arriving inside that window merge into the ranges already buffered for the same channel. A request that has no ranges for a channel discards the ranges already buffered for it

Fluxer applies each option only when its key is present, in the order active, sync, member_list_channels, members, typing.

Marking a guild active changes how much traffic it produces, and Event filtering specifies the difference. A transition from passive to active, and a transition from active to passive, both imply a sync even when sync is absent. Every sync request, implied or explicit, is dropped when the guild is already marked synced for that session. Going passive clears that mark, so the next sync request produces a fresh Guild Sync.

member_list_channels maps a channel ID to a list of [start, end] ranges. A range needs start at least 0, end at least start, end at most 100,000, and end - start at most 99. Ranges that fail those bounds are dropped, and each channel keeps at most the first 10 that pass. A channel key that is not a Snowflake is skipped.

Fluxer applies a subscription at once when the guild has no coalescing window open, its buffer is empty, and the channel’s member list is already built. That request opens the window. Fluxer buffers it as applied and does not apply it a second time when the window closes. Every other subscription waits out the window, including one for a channel whose member list is not built yet and one arriving while the window is open.

VIEW_CHANNEL and VIEW_CHANNEL_MEMBERS together govern the member list subscription, and both are evaluated for each channel separately. A channel the session cannot view, or can view without holding VIEW_CHANNEL_MEMBERS there, receives no Guild Member List Update while its siblings subscribe normally.

Subscribing a channel to at least one range drops the session’s other member list subscriptions in that guild, so one session holds at most one member list per guild.

members entries that are not Snowflakes are dropped, and the first 1,000 that pass are kept. A member the session shares no viewable channel with is dropped as well.

typing is a Boolean, and Fluxer ignores any other value. It decides Typing Start delivery for the guild independently of the active flag.

Opcode 15 requests current count records.

FieldTypeDescription
guild_idsarray[snowflake]The guild IDs to query, deduplicated, sorted, and truncated to 100
nonce?stringA value from 1 through 64 bytes echoed in the result
{
"op": 15,
"d": {
"guild_ids": ["1189375284394692608"],
"nonce": "counts-1"
}
}

Entries that are not positive Snowflakes are dropped. A guild the session is not connected to is skipped. A nonce outside the length bound is omitted from the result. The command never closes the connection.

Results arrive in one Guild Counts Update. Each guild is queried with a 2,000 ms deadline under an overall 3,000 ms batch deadline, so a slow guild is omitted from the result.

Opcode 16 requests count records for channels in one guild.

FieldTypeDescription
guild_idsnowflakeThe ID of the guild whose channels are queried
channel_idsarray[snowflake]The channel IDs to query, deduplicated, sorted, and truncated to 25
channel_id?1snowflakeThe ID of the single channel to query, read only when channel_ids is absent
nonce?stringA value from 1 through 64 bytes echoed in the result

1 A channel_ids value that is present but is not an array also falls back to channel_id

{
"op": 16,
"d": {
"guild_id": "1189375284394692608",
"channel_ids": ["1189375284394692610"],
"nonce": "channel-counts-1"
}
}

A guild_id that is not a positive Snowflake, or a guild the session is not connected to, produces an empty result. Channel entries that are not positive Snowflakes are dropped. The command never closes the connection.

Results arrive in one Channel Member Counts Update with a 2,000 ms guild deadline. A channel is counted only when the requesting session holds both VIEW_CHANNEL and VIEW_CHANNEL_MEMBERS there, and every other named channel is omitted from the result.

One WebSocket processes at most four bounded requests at once across Request Guild Members, Lazy Request, Request Guild Counts, and Request Channel Member Counts. A command that arrives when all four slots are taken is dropped, without a close and without a result. Each request has a 10,000 ms deadline, after which it produces no further events.

Request Guild Members also keeps one replaceable pending request while another member request is active, whether or not a slot is free.