> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cloak.chat/llms.txt
> Use this file to discover all available pages before exploring further.

# Client

> The Client is your bot. It connects to Cloak, emits events, sends messages, and drives every action.

Everything starts with a `Client`. You construct it with your token, attach event listeners, and call `login()`.

```ts theme={null}
import { Client } from '@cloak-software/bot-sdk';

const client = new Client({
  token: process.env.CLOAK_TOKEN!,
  keystorePath: './bot.cloak-keystore.json',
  requiredPermissions: ['message_send', 'reaction_add'],
});

client.on('messageCreate', async (msg) => {
  if (msg.authorId === client.user?.id) return;
  if (msg.isDM || !msg.serverId) return;
  if (msg.content.trim() === '!ping') await msg.channel.send('pong');
});

await client.login();
```

Server-scoped methods take a `serverId` as their first argument. If a handler does several things in one server, [`guild(serverId)`](#guild) binds that id once so you can drop it from every call.

<Warning>
  `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](/guides/direct-messages).
</Warning>

## Constructor options

### Identity and storage

<ParamField path="token" type="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.
</ParamField>

<ParamField path="keystorePath" type="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](/concepts/keys-and-keystore).
</ParamField>

<ParamField path="keystore" type="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](/production/keystore-backends).
</ParamField>

<ParamField path="deviceId" type="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.
</ParamField>

### Behavior

<ParamField path="requiredPermissions" type="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`](/api-reference/events#permissionsupdate). See [Permissions](/concepts/permissions).
</ParamField>

<ParamField path="commandPrefix" type="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](/guides/slash-commands).
</ParamField>

<ParamField path="webhookName" type="string" default="Bot">
  Display name for the webhook that [`sendEmbed()`](#sendembed) mints. Cosmetic only. Reuse is matched on creator plus channel, never on name, so renaming this does not orphan an existing webhook.
</ParamField>

<ParamField path="webhookFetch" type="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.
</ParamField>

<ParamField path="debug" type="boolean" default="false">
  Logs every frame the SDK sends and receives, with your bot token redacted. Useful while developing, noisy in production.
</ParamField>

### Endpoints and transport

<Accordion title="Advanced: self-hosted, local dev, and lane selection" icon="wrench">
  Bots against the hosted Cloak service rarely set any of these.

  <ParamField path="url" type="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.
  </ParamField>

  <ParamField path="transport" type="'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](/install).
  </ParamField>

  <ParamField path="allowInsecure" type="boolean" default="false">
    Permit a plain `ws://` url, which sends the bot token in cleartext. Local development stacks only.
  </ParamField>

  <ParamField path="idleTimeoutMs" type="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.
  </ParamField>

  <ParamField path="serverCertificateHashes" type="{ 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.
  </ParamField>

  <ParamField path="restUrl" type="string">
    The REST base that [`client.rest`](#properties) talks to. Defaults to `DEFAULT_REST_URL`. HTTPS only, except loopback. A bad value throws on the first `client.rest` call.
  </ParamField>

  <ParamField path="livekitUrl" type="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](/voice/joining).
  </ParamField>
</Accordion>

## Properties

<ResponseField name="user" type="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](/api-reference/types#botuser).
</ResponseField>

<ResponseField name="commands" type="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. 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](/api-reference/commands).
</ResponseField>

<ResponseField name="webhooks" type="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](/api-reference/webhooks).

  <Warning>
    Everything posted through this lane is **not** end-to-end encrypted and renders under a webhook identity rather than your bot's.
  </Warning>
</ResponseField>

<ResponseField name="rest" type="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](/api-reference/rest).
</ResponseField>

<ResponseField name="raw" type="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](/guides/raw-frames).
</ResponseField>

<ResponseField name="voiceKeys">
  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](/voice/keys).

  This accessor's interface type is intentionally not exported, so annotate with `typeof client.voiceKeys` if you need a name for it.
</ResponseField>

## Lifecycle

### login

```ts theme={null}
login(): Promise<void>
```

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.

<Warning>
  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`](/api-reference/events#disconnect) rather than throwing from a call you can await. `-8` is transient and keeps retrying. See [Connection lifecycle](/concepts/connection-lifecycle).
</Warning>

### watch

```ts theme={null}
watch(serverId: string, channelId: string, groupId?: string): Promise<void>
```

Optionally pre-selects a channel, for example to warm its key before your first send. You rarely need it, because the [firehose](/concepts/message-delivery) 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

```ts theme={null}
destroy(): Promise<void>
```

Closes the connection, stops reconnecting, and flushes any queued keystore writes. The clean way to shut a bot down. See [Connection lifecycle](/concepts/connection-lifecycle#shutting-down).

## Sending

### send

```ts theme={null}
send(serverId: string, channelId: string, text: string, opts?: SendOptions): Promise<void>
```

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.

```ts theme={null}
import { userMention } from '@cloak-software/bot-sdk';

await client.send(serverId, channelId, `${userMention(msg.authorId)} on it`, {
  mentions: [{ userId: msg.authorId }],
  replyTo: msg,
});
```

[`SendOptions`](/api-reference/types#sendoptions) is `{ groupId?, mentions?, replyTo? }`.

<Warning>
  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 })`.
</Warning>

<Note>
  `send()` is fire-and-forget. The wire has no success acknowledgement for it, so the promise resolves once the frame is out. A server-side denial does **not** reject the promise: it arrives later on the [`sendRejected`](/api-reference/events#sendrejected) event. Silence does not mean delivered.
</Note>

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()`](/api-reference/types#message) or `msg.channel.send()` instead, which already know where the message came from. See [Sending messages](/guides/sending-messages).

### sendDM

```ts theme={null}
sendDM(userId: string, text: string, opts?: SendOptions): Promise<void>
```

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`](/api-reference/errors) before anything is encrypted.

Inside a DM, `replyTo` works, and role, `@everyone`, and `@here` mention pairs are dropped server-side. See [Direct messages](/guides/direct-messages).

### sendCard

```ts theme={null}
sendCard(serverId: string, channelId: string, card: CardMessage): Promise<void>
```

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 nothing else.

`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. See [Rich cards](/guides/rich-cards) and [Embeds](/api-reference/embeds).

### sendEmbed

```ts theme={null}
sendEmbed(serverId: string, channelId: string, post: UnencryptedWebhookPost): Promise<void>
```

Posts a rich embed by minting or reusing a webhook for the channel. Requires `manage_server`.

<Warning>
  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()`](#sendcard) unless you need per-post personas, no bot account at all, or clients older than the card rollout. See [Webhook embeds](/guides/webhook-embeds).
</Warning>

### sendCommand

```ts theme={null}
sendCommand(
  serverId: string,
  channelId: string,
  name: string,
  args?: CommandArgs,
  opts?: { botId: string; decl?: CommandDeclaration; groupId?: string },
): Promise<void>
```

Invokes a slash command, your bot's own or another bot's. `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. See [Slash commands](/api-reference/commands).

### sendTyping

```ts theme={null}
sendTyping(serverId: string, channelId: string, isTyping?: boolean, groupId?: string): Promise<void>
```

Shows or clears the typing indicator in a channel. `isTyping` defaults to `true`. 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 channel inside about a second is dropped without touching the wire. It moves the server-side selection cursor exactly like a send does, and it is fire-and-forget. See [Typing, presence, and profile](/guides/typing-presence-and-profile).

## Presence and profile

### setStatus

```ts theme={null}
setStatus(status: BotStatus): Promise<void>
```

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`](/api-reference/errors) if the server does not acknowledge it.

<Note>
  `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.
</Note>

### setAvatar

```ts theme={null}
setAvatar(iconUrl: string | null, miniIconUrl?: string | null): Promise<void>
```

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 never uploads images, because file upload is closed to bots.

## History and reads

### fetchMessages

```ts theme={null}
fetchMessages(
  serverId: string,
  channelId: string,
  opts?: {
    before?: Date | Message;
    after?: Date | Message;
    around?: Date | Message;
    limit?: number;
    groupId?: string;
  },
): Promise<Message[]>
```

Reads message history for a channel, returned oldest to newest and auto-paginated. Requires `message_read_history`. 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.

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](/guides/message-history).

### messageLocation

```ts theme={null}
messageLocation(messageId: string): MessageLocation | undefined
```

Returns where a message the bot has seen lives, or `undefined`. This is a bounded, best-effort cache. See [MessageLocation](/api-reference/types#messagelocation).

### fetchEmojis

```ts theme={null}
fetchEmojis(): Promise<{ emojis: unknown; category: unknown }>
```

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()`](/api-reference/types#message) accepts. `react()` itself stays an opaque pass-through with no client-side validation. See [Reactions](/guides/reactions).

### fetchRoles

```ts theme={null}
fetchRoles(serverId: string): Promise<Role[]>
```

Reads a server's roles on demand, with no caching. Returns each role's color, position, and member count. See [Role](/api-reference/types#role).

### fetchBans

```ts theme={null}
fetchBans(serverId: string): Promise<BanInfo[]>
```

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](/api-reference/types#baninfo).

### fetchMembers

```ts theme={null}
fetchMembers(serverId: string): Promise<Member[]>
```

<Warning>
  **`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 **neither resolves nor rejects**. `await` hangs until the transport timeout, and `try`/`catch` does not save you. This is not flakiness and not a permission problem, and there is no workaround in the SDK.

  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()`](#guild) binds the same broken call. [`fetchRoles()`](#fetchroles) is unaffected.
</Warning>

Enumerates a server's members. See [Member](/api-reference/types#member) and [Members and roles](/guides/members-and-roles).

## Moderation

All of these are server-scoped and reject with [`CloakActionError`](/api-reference/errors) on a server deny. See [Moderation](/guides/moderation).

### kick

```ts theme={null}
kick(serverId: string, userId: string): Promise<void>
```

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

### ban

```ts theme={null}
ban(serverId: string, userId: string, reason?: string): Promise<void>
```

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

### unban

```ts theme={null}
unban(serverId: string, userId: string): Promise<void>
```

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

### timeout

```ts theme={null}
timeout(serverId: string, userId: string, durationSeconds: number): Promise<void>
```

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`.

<Note>
  `timeout()` needs an up-to-date Cloak server. An older server drops the opcode, so the call times out rather than resolving.
</Note>

### removeTimeout

```ts theme={null}
removeTimeout(serverId: string, userId: string): Promise<void>
```

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

## Channels and groups

All of these require `manage_channels` and reject with [`CloakActionError`](/api-reference/errors) on a server deny. See [Channels and groups](/guides/channels-and-groups).

### createChannel

```ts theme={null}
createChannel(serverId: string, name: string, groupId: string, channelType?: number): Promise<string>
```

Creates a channel in a group and returns the new channel's id. `channelType` defaults to `0`, a text channel.

### editChannel

```ts theme={null}
editChannel(serverId: string, channelId: string, groupId: string, newName: string): Promise<void>
```

Renames a channel.

### deleteChannel

```ts theme={null}
deleteChannel(serverId: string, channelId: string, groupId: string): Promise<void>
```

Deletes a channel along with its messages and pins.

### moveChannel

```ts theme={null}
moveChannel(
  serverId: string,
  channelId: string,
  fromGroupId: string,
  toGroupId: string,
  newOrder: number,
): Promise<void>
```

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

```ts theme={null}
createGroup(serverId: string, name: string): Promise<string | undefined>
```

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

```ts theme={null}
editGroup(serverId: string, groupId: string, newName: string): Promise<void>
```

Renames a group.

### deleteGroup

```ts theme={null}
deleteGroup(serverId: string, groupId: string): Promise<void>
```

Deletes a group and its channels.

<Note>
  There are no methods to edit or delete a server. Those actions are owner-level and surface only as the [`serverUpdate`](/api-reference/events#serverupdate) and [`serverDelete`](/api-reference/events#serverdelete) events.
</Note>

## 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](/voice/overview).

### joinVoice

```ts theme={null}
joinVoice(
  serverId: string,
  channelId: string,
  opts?: { groupId?: string; muted?: boolean; deafened?: boolean },
): Promise<VoiceConnection>
```

Joins a voice channel and returns the [`VoiceConnection`](/api-reference/types#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.

<Warning>
  `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](/voice/keys).
</Warning>

### leaveVoice

```ts theme={null}
leaveVoice(): Promise<void>
```

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

### voiceConnection

```ts theme={null}
voiceConnection(): VoiceConnection | null
```

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

### setVoiceSelfState

```ts theme={null}
setVoiceSelfState(state: { muted?: boolean; deafened?: boolean }): Promise<void>
```

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.

<Note>
  `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.
</Note>

### voiceRoster

```ts theme={null}
voiceRoster(channelId: string): VoiceParticipant[]
```

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

```ts theme={null}
refreshVoiceRoster(serverId: string): Promise<void>
```

Re-seeds the roster for a server by re-selecting it, because the roster snapshot is a side effect of that selection.

<Note>
  Voice roster events are cursor-gated, not [firehose](/concepts/message-delivery). 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.
</Note>

## Permissions

These are advisory. They reflect the last grant the server sent, and the server re-checks every action. See [Permissions](/concepts/permissions).

### can

```ts theme={null}
can(permission: PermissionName, serverId: string): boolean
```

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

```ts theme={null}
permissions(serverId: string): PermissionSet | undefined
```

The full resolved capability set for a server, or `undefined` when no grant has arrived. See [PermissionSet](/api-reference/permissions#permissionset).

### visibleChannelIds

```ts theme={null}
visibleChannelIds(serverId: string): string[]
```

The channel ids your bot may view in a server. Mirrors the [firehose read-gate](/concepts/message-delivery#delivery-is-view-gated). Empty array if unknown.

## Encryption helpers

You need these only when you are building a frame by hand through [`client.raw`](#properties).

### encryptFor

```ts theme={null}
encryptFor(serverId: string, text: string): string
```

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

```ts theme={null}
decryptFrom(serverId: string, wire: string): string
```

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`](/api-reference/events). Your listener arguments are inferred.

```ts theme={null}
client.on('messageCreate', (msg) => {
  // msg is typed as Message
});
```

Listeners are wrapped at registration so a handler that throws surfaces on the [`error`](/api-reference/events#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

```ts theme={null}
guild(serverId: string)
```

Returns a lightweight handle that binds `serverId` onto every server-scoped method. Pure sugar over the flat methods, with no cache and no state.

```ts theme={null}
if (msg.isDM || !msg.serverId) return; // serverId is null in a DM

const g = client.guild(msg.serverId);
await g.timeout(msg.authorId, 300);
const roles = await g.fetchRoles();
const history = await g.fetchMessages(msg.channelId, { limit: 100 });
```

The handle exposes `id`, plus:

| Group       | Bound methods                                                                                             |
| ----------- | --------------------------------------------------------------------------------------------------------- |
| Messaging   | `send(channelId, text, opts?)`, `sendCard(channelId, card)`, `sendEmbed(channelId, post)`                 |
| History     | `fetchMessages(channelId, opts?)`                                                                         |
| Moderation  | `kick`, `ban`, `unban`, `timeout`, `removeTimeout`, `fetchBans`                                           |
| Reads       | `fetchRoles()`, `fetchMembers()`                                                                          |
| Channels    | `createChannel`, `editChannel`, `deleteChannel`, `moveChannel`, `createGroup`, `editGroup`, `deleteGroup` |
| Voice       | `joinVoice(channelId, opts?)`, `refreshVoiceRoster()`                                                     |
| Webhooks    | `webhooks.list`, `.create`, `.edit`, `.regenerate`, `.delete`, `.post`                                    |
| Permissions | `can(perm)`, `permissions()`, `visibleChannelIds()`                                                       |

<Warning>
  `guild().fetchMembers()` binds the same broken call as [`fetchMembers()`](#fetchmembers) and hangs the same way.
</Warning>

See [The guild() handle](/guides/guild-handle).

## Exported constants

<ResponseField name="DEFAULT_URL" type="string">
  Cloak's hosted WebSocket endpoint, used when you omit `url`.
</ResponseField>

<ResponseField name="DEFAULT_WEBTRANSPORT_URL" type="string">
  Cloak's hosted WebTransport endpoint, used when you omit `url` and set `transport: 'webtransport'`.
</ResponseField>

<ResponseField name="DEFAULT_REST_URL" type="string">
  The default origin for [`client.rest`](#properties).
</ResponseField>

<ResponseField name="livekitUrlFor" type="(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.
</ResponseField>

## Next

<CardGroup cols={2}>
  <Card title="Events" icon="bell" href="/api-reference/events">
    All 32 events a Client emits.
  </Card>

  <Card title="Types" icon="brackets-curly" href="/api-reference/types">
    Message, SendOptions, Member, and the rest.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/api-reference/errors">
    Every error class an action can reject with.
  </Card>

  <Card title="The guild() handle" icon="server" href="/guides/guild-handle">
    Bind a server id once.
  </Card>
</CardGroup>
