Skip to main content
Everything starts with a Client. You construct it with your token, attach event listeners, and call login().
Server-scoped methods take a serverId as their first argument. If a handler does several things in one server, guild(serverId) binds that id once so you can drop it from every call.
Message.serverId is string | null. It is null for a direct message. Guard with if (msg.isDM || !msg.serverId) return; before anything server-scoped, or your bot breaks on the first DM it receives. See Direct messages.

Constructor options

Identity and storage

string
required
Your bot token, in the form botid.tokenid.secret. Create it in the Cloak app under Settings > My Bots. Treat it like a password. Read it from an environment variable, never hardcode it.
string
Path to a file where the SDK persists your bot’s identity, its conversation keys, and its peer pins. Shorthand for keystore: fileKeystore(path). Mutually exclusive with keystore: passing both throws.A bot with no keystore of any kind is single-run only. Its second start fails permanently, because identity publication is write-once on the server. See Keys and the keystore.
KeystoreAdapter
Where to persist identity and keys when a file will not do. Use envKeystore() on a host with no writable volume, or supply your own adapter. Mutually exclusive with keystorePath. See Keystore backends.
string
A stable device id used for clean takeover on reconnect. Defaults to a value derived from your token. Set it only if you have a specific reason to.

Behavior

PermissionName[]
The permissions your bot declares it needs, sent to the server on login. Declaring is a request, not a grant. A human approves a subset per server, and the resolved grant arrives on permissionsUpdate. See Permissions.
string
default:"/"
The prefix the text lane of the slash-command parser matches. Only the text lane uses it. A structured invocation carries the command name outright and is prefix-independent. See Slash commands.
string
default:"Bot"
Display name for the webhook that sendEmbed() mints. Cosmetic only. Reuse is matched on creator plus channel, never on name, so renaming this does not orphan an existing webhook.
WebhookFetch
An injected fetch for the webhook ingress, for tests or a proxy. Defaults to globalThis.fetch. This is the only outbound HTTP the SDK performs on its own.
boolean
default:"false"
Logs every frame the SDK sends and receives, with your bot token redacted. Useful while developing, noisy in production.

Endpoints and transport

Bots against the hosted Cloak service rarely set any of these.
string
The connection endpoint. Defaults to the hosted service on the lane implied by transport: DEFAULT_URL for 'ws', DEFAULT_WEBTRANSPORT_URL for 'webtransport'. A wss:// url speaks the WebSocket lane, an https:// url the WebTransport lane.
'ws' | 'webtransport'
default:"ws"
Which wire to speak. The default WebSocket lane rides the pure-JavaScript ws package, so npm install compiles nothing. 'webtransport' needs the optional Quiche packages installed. See Installing.
boolean
default:"false"
Permit a plain ws:// url, which sends the bot token in cleartext. Local development stacks only.
number
default:"60000"
Treat the connection as dead after this long with no inbound data, then reconnect. Defaults to 60000 on the WebSocket lane and is disabled on WebTransport, which has its own QUIC idle timeout. 0 disables it.
{ algorithm: 'sha-256'; value: Uint8Array }[]
WebTransport lane only: a self-signed QUIC certificate pin for a self-hosted or local stack. Throws on the WebSocket lane. Not needed for the hosted service.
string
The REST base that client.rest talks to. Defaults to DEFAULT_REST_URL. HTTPS only, except loopback. A bad value throws on the first client.rest call.
string
The LiveKit signalling url handed back on VoiceConnection.url. Defaults to livekitUrlFor(<transport url>), which is the same host on port 7880. The SDK never connects to it. See Joining voice.

Properties

BotUser | null
Your bot’s identity after login, or null before login() completes. Its id is normalized, so you can compare it against msg.authorId to detect your bot’s own messages. See BotUser.
CommandRegistry
The bot’s slash-command registry. register(decl, handler) is synchronous.Register before login(). Whatever is registered at that moment is published as the bot’s menu, which server / pickers and, on a wire version 3 backend, the DM picker read. A command added afterwards still dispatches locally, but it does not appear in the published menu until the next login, and the SDK never re-publishes on its own. See Slash commands.
WebhookApi
The incoming-webhook surface: list, create, edit, regenerate, delete, and post. The five CRUD calls require manage_server. post(url, payload) requires nothing at all and is the recommended path. See Webhooks.
Everything posted through this lane is not end-to-end encrypted and renders under a webhook identity rather than your bot’s.
RestApi
The authenticated REST lane: request, json, and binary. Every call transparently mints a short-lived credential over the realtime session, so your long-lived bot token never rides an HTTP header.This is off by default server-side, so the first call usually rejects with a CloakActionError carrying code -7. See REST.
RawApi
The escape hatch to opcodes the SDK does not map: send(frame, opts?) and request(frame, replyOpcode, opts?). Raw frames run on the same serialized chain as every mapped action.request() refuses reply opcodes the SDK correlates internally unless you pass { allowReserved: true }, because awaiting one can claim a reply the SDK was waiting for. See Raw frames.
The voice-key lane, and nothing else: getVoiceKey, acquireVoiceKey, voiceFrameKeyFor, and forgetVoiceKey. Hand this to a media session so it can install frame keys without reaching your conversation keys. See Voice keys.This accessor’s interface type is intentionally not exported, so annotate with typeof client.voiceKeys if you need a name for it.

Lifecycle

login

Checks the Node runtime, loads the keystore, connects, authenticates, publishes your bot’s identity if it has never been published, declares the permission manifest, publishes the command menu, emits guildCreate for existing servers, then emits ready. Call it once, after attaching your listeners and registering your commands.
Two login failures are terminal and stop the automatic reconnect loop: -2 (the token was revoked or regenerated) and -12 (this SDK is below the backend’s minimum wire version). Both emit disconnect rather than throwing from a call you can await. -8 is transient and keeps retrying. See Connection lifecycle.

watch

Optionally pre-selects a channel, for example to warm its key before your first send. You rarely need it, because the firehose already delivers every visible channel. groupId is auto-resolved from the server’s channel list when omitted. The target is remembered and re-established after a reconnect.

destroy

Closes the connection, stops reconnecting, and flushes any queued keystore writes. The clean way to shut a bot down. See Connection lifecycle.

Sending

send

Sends an encrypted message to a channel. Sends are serialized internally so they never race each other. opts.groupId is auto-resolved for any channel the bot has already seen, so you usually omit it.
SendOptions is { groupId?, mentions?, replyTo?, poll?, threadId?, postId?, ack?, ackTimeoutMs? }. Pass threadId to target a thread of the channel, postId to target a post of a forum channel, and poll to attach a native poll to the message. threadId and postId are mutually exclusive. See Threads, Forums, and Polls.
The fourth argument is an options object. It was a positional groupId before 0.2.0, and that form is gone. send(s, c, text, groupId) becomes send(s, c, text, { groupId }).
send() is fire-and-forget by default. The wire has no success acknowledgement for it, so the promise resolves once the frame is out, and a server-side denial arrives later on the sendRejected event. With ack: true the promise instead resolves with the persisted Message when the bot’s own firehose echo lands, rejects with CloakActionError on a denial pinned to this send, and rejects with CloakSendTimeoutError when the outcome is indeterminate. See Sending is fire-and-forget unless you ask for a receipt.
The one local throw is a plain Error when your bot does not hold the server’s conversation key yet, which happens in the moments right after a first join. Most handlers use msg.reply() or msg.channel.send() instead, which already know where the message came from. See Sending messages.

sendDM

Sends an encrypted direct message, creating the DM and exchanging its conversation key on first contact. Both the dm id and the key are persisted in the keystore, so that cost is paid once per person. Eligibility is the server’s decision and it is deny-by-default: a human may hold a DM with a bot only if they own it, or they share at least one server with it. An ineligible target rejects with a CloakActionError before anything is encrypted. Inside a DM, replyTo works, and role, @everyone, and @here mention pairs are dropped server-side. An ack: true DM send resolves on the DM lane’s echo, which only a backend that honors wire version 3 delivers; on an older one it times out as indeterminate. See Direct messages.

sendCardDM

Posts a rich card into a direct message: sendCard() on the DM lane, set up exactly like sendDM(). The card takes the same v1 or v2 envelope, and content and the card are encrypted under the one peer-keyed DM key. card.threadId and card.postId are not valid here (a local error), groupId is ignored, and a bot cannot DM itself. Clicks arrive on interaction with serverId: null and channelId equal to the dm id. On a backend that honors wire version 3, the bot receives its own echo as a messageCreate whose createdAt is what msg.editCard() needs later. On an older backend only the legacy notify signal arrives, which has no card slot, so a DM message’s card reads null there. See Direct messages.

sendCard

Posts a rich card. This is the encrypted embed lane and the one to reach for by default: the card is encrypted with the same conversation key and epoch as the message body, and it renders under your bot’s own identity. It requires message_send, and current backends also enforce embed_link: the server cannot read the payload, but it can see that the embed slot is present, and a denial arrives as -12 on sendRejected. Declare both in requiredPermissions. card.content is required and must be non-blank. Card media must be Cloak-hosted, and anything else throws a CloakEmbedError locally before a frame leaves your process. card.components adds up to five rows of buttons and selects, and their clicks arrive on the interaction event. card.threadId and card.postId place the card in a thread or a forum post. See Rich cards, Buttons and selects, and Embeds.

editCard

Replaces a message’s card, embed and component rows together, and re-encrypts its text in one edit. Author-only server-side, like edit(). CardEdit is the content, embed, and components of a CardMessage without the routing keys. opts.createdAt is required, because the edit addresses the row by its send time; msg.editCard(card) on a received Message fills the options for you. A null serverId with the dm id as channelId is the DM edit, under the DM key. Pass components: [] to retire a card’s buttons once a decision is made. The replacement arrives on messageUpdate as card. See Buttons and selects and editCard.

sendEmbed

Posts a rich embed by minting or reusing a webhook for the channel. Requires manage_server.
This lane is not end-to-end encrypted. The message is stored as plaintext server-side and renders under a webhook identity, not your bot’s. Your bot does not receive its own post back on messageCreate. Prefer sendCard() unless you need per-post personas, no bot account at all, or clients older than the card rollout. See Webhook embeds.

sendCommand

Invokes a slash command, your bot’s own or another bot’s. ack: true resolves with the persisted invocation message, as on send(). opts.botId names the target bot and has no default. Targeting a foreign bot also requires opts.decl, because the SDK cannot know another bot’s option order. Server channels only: there is no DM form. See Slash commands.

sendTyping

Shows or clears the typing indicator in a channel, a thread, or a forum post. isTyping defaults to true. target is the channel’s group id as a string (the pre-0.5 form) or TypingTarget, { groupId?, threadId?, postId? }: a thread of channelId, or a post of the forum channelId, one at most. The SDK never sends this implicitly, not even around send(). Receivers clear the indicator themselves about ten seconds after the last true, so re-send it every few seconds during long work. A repeat of the same boolean for the same target inside about a second is dropped without touching the wire. It moves the server-side selection cursor exactly like a send to that target does (a thread is entered, a post becomes the cursor), and it is fire-and-forget. See Typing, presence, and profile.

fetchMessage

Reads one message by id (op 804, a plan-136 backend). Explicitly addressed: channelId is the channel the message lives in, a thread or post id when inside one, or the dm id with serverId null. Needs message_read_history, which the server resolves against a container’s parent. Rejects with CloakActionError on a verdict (-5 is no such message) and with a transport timeout on an older backend. The row is a full history row, actionable like any other; threadId and postId are set when the SDK knows the container, so call resolveContainer() first on a cold start. See Message history.

resolveContainer

Turns a thread or forum post id into its kind and parent channel (op 805, a plan-136 backend). Resolves null for a plain channel or an id that is not this server’s, and rejects with CloakActionError when the bot is not a member. Seeds the SDK’s container index, so reply(), fetchMessage() and the Interaction container fields route afterwards.

capabilities

What the backend declared at login: which of its optional behaviors this deployment has. null before login() and on a backend that predates the map, in which case treat every flag as false. See BotCapabilities and What the server must support.

resumedLastLogin

true when the last reconnect was a resume: the backend replayed every frame the dropped socket missed before the login reply, so nothing was lost and no history backfill is needed. false on a first login and on a full rebuild. See Connection lifecycle.

sendTypingDM

sendTyping() for a direct message with userId. First contact pays the same one-time DM setup as sendDM(). Same throttle, same fire-and-forget contract.

Presence and profile

setStatus

Sets the bot’s presence. BotStatus is 'invisible' | 'online' | 'away' | 'busy' | 'dnd'. dnd also suppresses push notifications on every device. This writes an account-level status to a database column, so it survives reconnects, restarts, and redeploys. Set it once on ready, never on a timer. Rejects with a CloakActionError if the server does not acknowledge it.
online means “defer to liveness” rather than “show a green dot”. A bot with no live session displays as offline whatever its stored base says. The other four values pin the display outright.

setAvatar

Sets or clears the bot’s avatar from an already-hosted image url. miniIconUrl defaults to iconUrl. Passing null clears both. Urls are capped at 512 characters server-side. The SDK does not upload avatar images, so give it a url that is already hosted. Attaching files to messages is a separate lane: see sendFile().

History and reads

fetchMessages

Reads message history for a channel, returned oldest to newest and auto-paginated. Each wire page is its own serialized action (the selection is re-established before every page and the chain is released between pages), and signal cancels between pages and the wait for the page in flight. Requires message_read_history on a server channel. Cursors are a Date or a Message, never a message id, because Cloak message ids are random UUIDs and not time-sortable. around returns a single window and is not auto-paged. A null serverId with the dm id as channelId reads a DM’s history, on a backend that honors wire version 3. The DM’s peer must be known to this session (a message seen, or a send made, in it), else the call rejects and names fetchDmMessages() as the alternative. threadId and postId are rejected on the DM form. No permission gate applies, and a backend that denies the DM read resolves empty. Content decrypts per row against the key for that row’s own epoch, and is '' (never raw ciphertext) when the key is missing. Returned messages are fully actionable and carry pinned. See Message history.
string
Read this thread’s history instead of the channel’s. Address it exactly as send() does: the thread’s parent text channel as channelId, the thread id here. Every cursor above behaves identically over the thread’s own messages, and the same message_read_history permission applies.Returned rows carry threadId and threadParentId, and their channelId is the thread, matching how live thread messages report themselves. So reply(), react() and edit() on a fetched row act inside the thread rather than the parent channel.
string
Read a forum post’s history instead. channelId is then the parent forum channel. Rows come back with postId and forumChannelId set and channelId equal to the post. Mutually exclusive with threadId. See Forums.
Thread history needs a Cloak server that supports it. Against an older backend the call rejects rather than quietly returning the parent channel’s messages, because that is what the wire falls back to. See Known limitations.

fetchDmMessages

Reads a 1:1 DM’s history by the peer’s user id, on a backend that honors wire version 3. Resolves the dm the way sendDM() does (cached, created on first contact) and reads it like fetchMessages(null, dmId, opts): the same cursors and limits, oldest to newest, auto-paginated. Rows decrypt under the peer-keyed DM key and carry content, repliedTo, card, poll, and createdAt from the row’s own send time, with isDM: true, serverId: null, and channelId equal to the dm id. The bot’s own messages are included, so a recovered row can be edited or have its card retired with editCard(). command is never set on a history row. No permission gate applies in a DM. See Message history.

messageLocation

Returns where a message the bot has seen lives, or undefined. This is a bounded, best-effort cache. See MessageLocation.

fetchEmojis

Returns the global emoji catalog. It takes no serverId because the catalog is server-agnostic, and it does not move the selection cursor. Its ids are what msg.react() accepts. react() itself stays an opaque pass-through with no client-side validation. See Reactions.

fetchRoles

Reads a server’s roles on demand, with no caching. Returns each role’s color, position, and member count. See Role.

fetchBans

Returns the server’s ban list. Requires member_ban, and some older Cloak servers also require owner or admin standing. Usernames are resolved server-side and may be null. See BanInfo.

fetchMembers

fetchMembers() does not work against any current Cloak backend. Do not call it.It awaits an opcode the backend made DM-only. A server-scoped request produces no reply frame of any kind, not even an error code, so the returned promise waits out the transport’s 8 second reply timeout and rejects with a plain Error. No roster ever arrives. This is not flakiness and not a permission problem.The rebuild is planned and the signature will survive it, so code written against this method stays valid. It just cannot run today. guild().fetchMembers() binds the same broken call. fetchRoles() and searchMembers() work.
Enumerates a server’s members. See Member and Members and roles.

searchMembers

Finds members by name: a case-insensitive prefix match over username, display name, and nickname, offline members included, at most 25 hits (limit is clamped to that). channelId restricts hits to members who can see that channel. Does not move the selection cursor. A blank query rejects locally, and a server that stays silent (the bot is not a member) rejects after 10 seconds with a message saying so. guild().searchMembers(query, opts?) binds it. See MemberSearchResult and Search members by name.

Files

End-to-end encrypted file attachments. These ride the REST lane (client.rest), which is off until the server operator enables bot REST credentials, and the grant write requires upload_file. See File attachments.

sendFile

Encrypts and uploads the file, writes the share grant, then sends a message carrying its [file:id:name] token on the ordinary send() path. If the grant is refused, the upload is deleted and the call rejects, so a message never references an attachment nobody can open. FileInput is { name, bytes, contentType? } and SendFileOptions is SendOptions & { text?, signal? }, so threadId, postId, and ack all apply. With ack: true a denial pinned to the message deletes the upload; an indeterminate outcome leaves it. sendFile() is sendFiles() with one file.

sendFiles

Several files under one message. Each is encrypted, uploaded, and granted in turn, and the message carries every token, which is how the Cloak clients attach several files. Any upload or grant failure deletes every blob uploaded so far and rejects, so a partial delivery never lands. signal cancels the upload and grant legs; the final send is never cancelled once it starts. See File attachments.

sendFileDM

The DM twin of sendFile(). The file key is wrapped under the pairwise DM key and granted to the recipient. First contact pays the same one-time setup as sendDM().

sendFilesDM

sendFiles() on the DM lane.

downloadAttachment

Downloads and decrypts an attachment referenced by msg.attachments. Pass the message’s serverId; for a DM attachment that is null, which is correct.

Threads

Threads fork out of text channels, and a thread’s replies are ordinary messages whose channel is the thread. Send into one with send(serverId, parentChannelId, text, { threadId }), and read one with fetchMessages(serverId, parentChannelId, { threadId }). See Threads for the model and the thread permissions.

createThread

Creates a thread on a text channel. title is end-to-end encrypted and capped at 100 characters. Anchor it to an existing message by passing starter (a received Message fits); omit it for a standalone thread. Requires create_public_thread, or create_private_thread with isPrivate. autoArchiveMinutes is 0 (the channel default) or one of 60, 1440, 4320, 10080.

fetchThreads

Pages a channel’s threads, active first by default. Private threads are filtered to membership server-side, and manage_threads sees all. before continues a page from the previous page’s oldest sort key.
This enumerates threads, not their contents. To read the messages inside one, pass its id to fetchMessages() as opts.threadId.

joinThread

Joins a thread, the “follow” surface. A firehose bot already receives every visible thread’s messages either way. A private thread cannot be self-joined: its member list is the ACL, and someone inside must add the bot.

leaveThread

Leaves a thread. Sending into it later auto-rejoins, Discord-parity.

fetchThreadMembers

A thread’s member list: any viewer on public threads, members or manage_threads on private ones.

threadParent

The parent text channel of a thread the SDK has seen, from its thread index. Synchronous and best-effort.

Forums

Forum channels hold posts, and a post is a container like a thread: its replies are ordinary messages whose channel is the post. Send into one with send(serverId, forumChannelId, text, { postId }), and read one with fetchMessages(serverId, forumChannelId, { postId }). Every method below takes the parent forum channel id. See Forums for the model.
Forums add no permission names. Creating a post and sending inside one need message_send, resolved against the forum channel (which may override it per channel), so can('message_send', serverId) is the pre-check and createForumPost() denies -2 without it. Moderating other members’ posts (editForumPost(), setForumPostState()) needs message_manage. A refusal rejects with CloakActionError.

createForumPost

Opens a post with its first message in one server-side operation. title is end-to-end encrypted and capped at 100 characters after trimming. content is the starter message and must be non-blank. embed and components make the starter a card. Title, body, and card encrypt under one context so they never split across an epoch change. Resolves with the ForumPost as the server’s own create event describes it, which is where starterMessageId comes from. If that event does not arrive within a few seconds, the row is built from what the SDK sent and starterMessageId is null; the real row lands on forumPostUpdate when it arrives.

fetchForumPosts

Pages a forum’s posts, active first by default, with pinned posts at the front of the first page. solved: true keeps answered posts only, false unanswered only, and omitting it returns both. before continues from the previous page’s oldest sort key (lastActiveAt for the activity sort, createdAt for the created sort). limit is clamped to 50 by the server. Rows carry starterContent and the bot’s own following state.

fetchForumMeta

A forum’s settings: its tags (names end-to-end encrypted, decrypted best-effort), guidelines, sort and archive defaults, default reaction, and post count. A forum created before it had a settings row answers defaults, never an error. See ForumMeta.

editForumPost

Retitles or retags a post. Omit a field to keep it; omitting both is a local error. Author, or message_manage.

setForumPostState

Flips one state on a post. 'archived', 'locked', and 'pinned' take a boolean. 'answered' takes the accepted answer (a received Message fits) or null to clear it. 'move' takes the target forum channel id. Locking, pinning, and moving need message_manage; archiving and answering also allow the post’s author. The wrong value type for a field is a local error.

followForumPost

Follows or unfollows a post: the bell. A notification preference only. A firehose bot already receives every visible post’s replies either way.

reactToStarter

Reacts to a post’s starter message from outside the post, like the chips on a post’s list card. add defaults to true. On a backend whose login capabilities say starterReactionAck, the call is awaited: it resolves on [759, 1] and rejects with CloakActionError on a verdict (see the 759 code table in Errors). On an older backend it stays fire-and-forget: the result is an ordinary reactionUpdate, and a denial produces no frame at all. Inside a post, msg.react() on the starter does the same thing.

postParent

The parent forum channel of a post the SDK has seen, or undefined. Fed by every post row and every in-post message the bot receives. Synchronous and best-effort.

Polls

Poll creation rides send(serverId, channelId, text, { poll }). The three methods below act on an existing poll and take the message’s location, so a Message’s own ids are exactly what to pass. All three accept threadId or postId (never both) for a poll living inside a thread or a forum post. There is no vote method: the backend refuses a bot vote by design, so bots create, close, and read polls, and only humans vote. See Polls.

closePoll

Closes a poll now: creator or message_manage. Sets the close time to now and broadcasts the final tally, which doubles as the reveal for a hidden poll.

fetchPollState

Reads a poll’s current tally. Nothing fires server-side when a poll’s end time passes, so a reader who wants the final (or hidden-poll) numbers asks, and this is the ask. Counts are null while a hidden poll is still open. The reply also fires pollUpdate.

fetchPollVoters

Who picked one option of an identified poll. The refusals are the feature’s promises, surfaced verbatim: an anonymous poll is never served, creator included, and a hidden poll refuses while still open.

Moderation

All of these are server-scoped and reject with CloakActionError on a server deny. See Moderation.

kick

Removes a member. Requires member_kick, a rank above the target, and the target not being the owner or the bot itself.

ban

Kicks a member and records the ban. Requires member_ban. reason is capped at 256 characters server-side.

unban

Lifts a ban. Requires member_ban. Some older Cloak servers also require owner or admin standing.

timeout

Times a member out for durationSeconds. A timed-out member cannot send or react in that server. Passing 0 lifts the timeout. Requires member_timeout.
timeout() needs an up-to-date Cloak server. An older server drops the opcode, so the call times out rather than resolving.

removeTimeout

Lifts a member’s timeout. Equivalent to timeout() with 0 seconds.

Channels and groups

Reading the channel list needs no permission beyond being able to see the server. Every method from createChannel down requires manage_channels. All of them reject with CloakActionError on a server deny. See Channels and groups.

fetchChannels

Lists a server’s channels in the server’s own order. Each entry is a ChannelInfo, carrying id, name, groupId, type, order, and lastMessageAt. Compare type against ChannelType to tell a text channel from a voice one.
The list is filtered server-side to the channels your bot may view, so a channel that is absent is one the bot cannot see rather than one that does not exist. A server with no channels at all resolves to an empty array rather than rejecting. This fetches rather than reading a cache, and deliberately so: the channel list arrives as a server push that carries no server id, so the call issues the selection itself and attributes the rows it gets back to that server. It also seeds getChannel().

getChannel

One channel from the cache, read synchronously, or undefined. The cache is seeded by fetchChannels() and kept current by createChannel() and the channel lifecycle events, so a hit is accurate. A miss is not proof the channel does not exist, only that the bot has not seen it yet. Call fetchChannels() when you need an authoritative answer.

createChannel

Creates a channel in a group and returns the new channel’s id. channelType defaults to 0, a text channel; pass a ChannelType value such as ChannelType.VOICE for anything else. The new channel is added to the getChannel() cache.

editChannel

Renames a channel.

deleteChannel

Deletes a channel along with its messages and pins.

moveChannel

Moves a channel to a group and position. newOrder is clamped to the range 0 to 126. Moving to the same group and order is a no-op the server rejects.

createGroup

Creates a channel group. The acknowledgement carries no id, so the new group’s id is captured best-effort from the server’s create broadcast within a short window. Handle the undefined case.

editGroup

Renames a group.

deleteGroup

Deletes a group and its channels.
There are no methods to edit or delete a server. Those actions are owner-level and surface only as the serverUpdate and serverDelete events.

Voice

This is the control plane. It mints a LiveKit credential and stops there. The SDK sends no media of its own. See Voice overview.

joinVoice

Joins a voice channel and returns the VoiceConnection the backend minted, including the LiveKit token and url. Requires voice_connect server-wide plus channel-level view. opts.groupId must be the channel’s real group and must never equal the channel id. That shape is Cloak’s DM-call form, and the SDK refuses it locally rather than round-tripping a rejection. The group is resolved from the server’s channel list when omitted.
voice_connect gets your bot into the channel. Hearing anyone is a separate grant, voice_listen. Without it the bot joins and publishes normally, receives no media, and is never given a key that could decrypt anyone. joinVoice() deliberately does not gate on it, so the failure mode is total silence with no error. See Voice keys.

leaveVoice

Leaves voice. A safe no-op server-side when the bot is not in a channel, so you may call it defensively.

voiceConnection

The bot’s own voice membership, or null. A synchronous cache read.

voiceConnectionReporter

The connection-state sender a VoiceMediaSession needs. Pass the result as VoiceMediaOptions.connectionReporter on every attach(). On a current backend the report a server-voice session sends before it publishes is what admits the microphone, and attach() waits for that admission (bounded by admissionTimeoutMs) before publishing. A session without the reporter skips the wait and is silent with no error. No-op when the bot is not in voice or is in a DM call, and it never throws. See Microphone admission.

setVoiceSelfState

Sets the bot’s own mute and deafen flags. Requires a live join. The server never acknowledges this, so local state is updated optimistically. Deafened implies muted, mirroring both shipped Cloak clients.
selfDeafened is a broadcast flag, not an answer to “can I hear”. Whether the bot receives audio is decided by voice_listen and the scope of the voice key it was handed.

voiceRoster

The cached roster of a voice channel, read synchronously. The array is a fresh copy, but its entries are the live cache objects, so copy them if you need a snapshot.

refreshVoiceRoster

Re-seeds the roster for a server by re-selecting it, because the roster snapshot is a side effect of that selection.
Voice roster events are cursor-gated, not firehose. Your bot sees them only for the server it currently has selected or is in voice in. Do not build on them as a firehose.

Permissions

These are advisory. They reflect the last grant the server sent, and the server re-checks every action. See Permissions.

can

Whether your bot holds permission in serverId per the last grant. administrator implies every permission here. Returns false when no grant has arrived yet, so a false means “not permitted, or not known yet”.

permissions

The full resolved capability set for a server, or undefined when no grant has arrived. See PermissionSet.

visibleChannelIds

The channel ids your bot may view in a server. Mirrors the firehose read-gate. Empty array if unknown.

Encryption helpers

You need these only when you are building a frame by hand through client.raw.

encryptFor

Encrypts text for a server’s current epoch key, producing exactly what send() would put on the wire. Throws when the bot holds no key for that server.

decryptFrom

Decrypts a wire ciphertext with the key for its own epoch. Returns '', never raw ciphertext, when the key is missing or stale.

Events

Client extends Node’s EventEmitter, and on, once, off, addListener, prependListener, prependOnceListener, removeListener, and emit all carry typed overloads over ClientEvents. Your listener arguments are inferred.
Listeners are wrapped at registration so a handler that throws surfaces on the error event instead of vanishing. That means client.listeners(event) returns the wrappers rather than your original functions. off() and removeListener() still work normally: pass the same function you registered.

guild

Returns a lightweight handle that binds serverId onto every server-scoped method. Pure sugar over the flat methods, with no cache and no state.
The handle exposes id, plus:
guild().fetchMembers() binds the same broken call as fetchMembers() and hangs the same way.
The handle does not bind the forum methods or editCard(). Call those on the client with the server id. See The guild() handle.

Exported constants

string
Cloak’s hosted WebSocket endpoint, used when you omit url.
string
Cloak’s hosted WebTransport endpoint, used when you omit url and set transport: 'webtransport'.
string
The default origin for client.rest.
(transportUrl: string) => string
Derives the LiveKit signalling url from a transport url: same host, port 7880. Returns '' for an unparseable url. This is what livekitUrl defaults to.

Next

Events

All 39 events a Client emits.

Types

Message, SendOptions, Member, and the rest.

Errors

Every error class an action can reject with.

The guild() handle

Bind a server id once.