Declare what your bot needs
Pass the permissions your bot needs to theClient constructor. The SDK sends this list to the server when your bot logs in.
requiredPermissions at all sends no manifest. That is fine for a bot that only reads, and it is what the quickstart bot does.
A human approves the grant
When someone adds your bot to a server through Server Settings > Invites > Add a Bot, Cloak shows them a consent screen. They approve some or all of what the bot declared. The server then resolves the actual grant for that server and pushes it back to your bot. The result is that the same bot can hold different permissions in different servers, depending on what each server’s admin approved.Receive the resolved grant
The server sends your bot its resolved permissions per server. The SDK caches the result and surfaces it three ways.Listen for updates
ThepermissionsUpdate event fires whenever a server resolves or changes your bot’s grant. It is a live push, not a login-only event: a permission change mid-session arrives here.
effectiveRank is the bot’s effective rank in that server, carried alongside the named booleans on the same PermissionSet.
Check before you act
can() is an advisory check. Use it to avoid firing an action the server would only reject.
Read the full set
permissions() returns the whole capability set for a server, or undefined if no grant has arrived yet.
When the grant is populated
login() awaits the permission verdicts for every server the bot is already in before it emits ready. So can() and permissions() are already populated inside a ready handler, and you do not need to wait a round trip.
undefined from permissions() is for a server the bot joined later, before its verdict landed.
With no verdict,
can() returns false and visibleChannelIds() returns an empty array. Absence reads as denied, not as unknown. A false therefore means either “denied” or “no verdict yet”, and your code usually wants to treat both the same way.Advisory means the server decides
can(), permissions(), and visibleChannelIds() reflect the last grant the server sent. They exist so your bot can pre-gate its own commands, which keeps it from firing actions that will bounce.
The runtime counterparts are where a real denial shows up:
- Awaited actions (
react(),delete(),kick(),ban(), and the rest) reject with aCloakActionError. Itspermissionfield names the permission the bot lacked, when the code maps to one. - Sends are fire-and-forget, so a denied send cannot reject. It arrives on the
sendRejectedevent, which also carries apermissionfield.
Administrator implies everything
Theadministrator permission is special. If a bot holds it in a server, can() returns true for every other permission in that server.
voice_listen is the one to read twice
One permission does not behave like the rest, and its failure mode is silence.
Joining voice and hearing voice are separate grants. voice_connect (plus channel-level view) gets the bot into a voice channel. voice_listen decides whether it is sent any media, and whether it is given a key that could decrypt any.
Without voice_listen:
- The bot joins normally.
joinVoice()succeeds. - The bot publishes normally. A music or TTS bot works perfectly, and that is the intended configuration, not a degraded one.
- The bot receives nothing, and holds only its own one-way frame key, so it is cryptographically unable to decrypt any member.
- Nothing errors. Silence is the only symptom.
selfDeafened is not the answer to “can I hear”. It mirrors what the SDK put in the join frame and says nothing about the grant. To ask the real question, use client.can('voice_listen', serverId) (advisory, and remember false also covers “no verdict yet”), or read the bot’s own entry in voiceRoster(channelId).
Grant voice_listen deliberately in the other direction too: a bot that holds it is given the room key and can decrypt everyone in the channel. That is what the permission means. See Voice keys and scopes.
Permission names
You declare and query permissions by name:message_send, member_ban, manage_channels, administrator, and 27 more. The names are stable, and the integer codes they map to are the wire contract.
PERMISSION_CODES (name to code), PERMISSION_NAMES (code-indexed), and decodePermArray() are exported if you need to work with the codes directly. The full table, all 31 rows, is in the permissions reference.
Next
Permissions reference
All 31 names and codes in one table.
Message delivery
Permissions decide which channels your bot can view.
Errors
CloakActionError, sendRejected, and the error event.Voice keys and scopes
What
voice_listen actually changes.