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

# The guild() handle

> Bind a serverId once and call server-scoped methods without repeating it.

Most server-scoped actions on the flat client take a `serverId` as their first argument. When a handler does several things in one server, you pass the same id over and over. `client.guild(serverId)` returns a lightweight handle that binds the id once, so every call after it reads a little cleaner.

## Bind the server once

Call `client.guild()` with a `serverId` and keep the handle it returns. Each of its methods delegates to the identically named flat client method with the id already filled in.

`Message.serverId` is `string | null`, and `guild()` takes a `string`. Direct messages arrive on the same `messageCreate` event with a `null` `serverId`, so guard before you bind.

```ts theme={null}
client.on('messageCreate', async (msg) => {
  if (msg.authorId === client.user?.id) return; // ignore our own messages
  if (msg.isDM || !msg.serverId) return;        // a DM has no server to bind
  if (msg.content.trim() !== '!sweep') return;

  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 });
  console.log(`${roles.length} roles, ${history.length} messages read`);
});
```

The handle carries no cache and no state. It is sugar over the flat methods, and the flat methods remain the source of truth. Reach for it when a single handler takes several actions in one server. For a one-off call, the flat form is just as good.

## It is optional sugar

The two forms below do exactly the same thing. `g.timeout(userId, 300)` delegates straight to `client.timeout(serverId, userId, 300)`.

```ts theme={null}
// Flat form: pass the serverId every time.
await client.timeout(serverId, userId, 300);

// Handle form: bind it once, then omit it.
const g = client.guild(serverId);
await g.timeout(userId, 300);
```

Because the handle methods delegate to their flat counterparts, the semantics, required permissions, return types, and errors are the same. The handle only removes the repeated `serverId`.

There is one exception. `webhooks.post()` does not mirror a `Client` method: it calls the SDK's module-level webhook poster directly and needs no permission at all, because it posts to a URL a human already created.

<Note>
  The handle holds no state of its own. It fetches nothing when you create it, and it never goes stale. You can make a new one per handler without cost.
</Note>

## What the handle exposes

`id` is the `serverId` you passed. Every other member is a bound version of a flat client method. The semantics live in the linked guides, since the handle changes nothing about how each method behaves.

### Identity

<ResponseField name="id" type="string">
  The `serverId` this handle is bound to.
</ResponseField>

### Messaging

<ParamField path="send" type="(channelId: string, text: string, opts?: SendOptions) => Promise<void>">
  Send an encrypted message into a channel in this server. `SendOptions` is `{ groupId?, mentions?, replyTo? }`. There is no positional `groupId` argument. See [Sending messages](/guides/sending-messages).
</ParamField>

<ParamField path="fetchMessages" type="(channelId: string, opts?) => Promise<Message[]>">
  Read message history from a channel, oldest first. See [Message history](/guides/message-history).
</ParamField>

<ParamField path="sendCard" type="(channelId: string, card: CardMessage) => Promise<void>">
  Post a rich card. End-to-end encrypted, under the bot's own identity, and it needs only `message_send`. This is the embed lane to default to. See [Rich cards](/guides/rich-cards).
</ParamField>

<ParamField path="sendEmbed" type="(channelId: string, post: UnencryptedWebhookPost) => Promise<void>">
  Post a rich embed through a webhook. Requires `manage_server`. See [Webhook embeds](/guides/webhook-embeds).
</ParamField>

<Warning>
  `sendEmbed()` is **not end-to-end encrypted**. The message is stored server-side as plaintext and renders under a webhook identity rather than the bot's. Use `sendCard()` when the content is sensitive.
</Warning>

<Note>
  `send()` and `sendCard()` are fire-and-forget. The promise resolves when the frame leaves the process, not when the server accepts it. A server-side denial arrives later on the `sendRejected` event, so a `try/catch` around the send will not see it.
</Note>

### Moderation

<ParamField path="kick" type="(userId: string) => Promise<void>">
  Remove a member from this server.
</ParamField>

<ParamField path="ban" type="(userId: string, reason?: string) => Promise<void>">
  Ban a member, with an optional reason capped at 256 characters.
</ParamField>

<ParamField path="unban" type="(userId: string) => Promise<void>">
  Lift a ban.
</ParamField>

<ParamField path="timeout" type="(userId: string, seconds: number) => Promise<void>">
  Time a member out for a number of seconds. `0` lifts it.
</ParamField>

<ParamField path="removeTimeout" type="(userId: string) => Promise<void>">
  Clear an active timeout.
</ParamField>

<ParamField path="fetchBans" type="() => Promise<BanInfo[]>">
  List the server's recorded bans.
</ParamField>

All of these are covered in [Moderation](/guides/moderation), including the role-rank and owner constraints that no permission check can predict.

### Reads

<ParamField path="fetchRoles" type="() => Promise<Role[]>">
  List the server's roles, with color, position, and member count.
</ParamField>

<ParamField path="fetchMembers" type="() => Promise<Member[]>">
  List the server's members. Currently out of service.
</ParamField>

<Warning>
  `g.fetchMembers()` binds the same broken read as `client.fetchMembers()`. Against every current Cloak server it neither resolves nor rejects: it hangs until the transport timeout, so a `try/catch` does not help. See [Members and roles](/guides/members-and-roles). `g.fetchRoles()` works normally.
</Warning>

### Channels and groups

<ParamField path="createChannel" type="(name: string, groupId: string, type?: number) => Promise<string>">
  Create a channel inside a group and resolve to its new id.
</ParamField>

<ParamField path="editChannel" type="(channelId: string, groupId: string, name: string) => Promise<void>">
  Rename a channel.
</ParamField>

<ParamField path="deleteChannel" type="(channelId: string, groupId: string) => Promise<void>">
  Delete a channel, along with its messages and pins.
</ParamField>

<ParamField path="moveChannel" type="(channelId: string, fromGroupId: string, toGroupId: string, order: number) => Promise<void>">
  Move a channel between groups and set its position.
</ParamField>

<ParamField path="createGroup" type="(name: string) => Promise<string | undefined>">
  Create a channel group. Resolves to `undefined` when the new group's id could not be captured.
</ParamField>

<ParamField path="editGroup" type="(groupId: string, name: string) => Promise<void>">
  Rename a group.
</ParamField>

<ParamField path="deleteGroup" type="(groupId: string) => Promise<void>">
  Delete a group and every channel inside it.
</ParamField>

The channel and group methods are covered in [Channels and groups](/guides/channels-and-groups).

### Webhooks

`webhooks` is a nested object on the handle, pre-bound to this server. Everything it posts is stored server-side as plaintext.

<ParamField path="webhooks.list" type="() => Promise<WebhookInfo[]>">
  Every webhook on the server. Never includes a token. Requires `manage_server`.
</ParamField>

<ParamField path="webhooks.create" type="(channelId: string, name: string, opts?: { icon?: string; miniIcon?: string }) => Promise<WebhookMint>">
  Mint a webhook on a channel and return its one-time delivery URL. Requires `manage_server`.
</ParamField>

<ParamField path="webhooks.edit" type="(webhookId: string, changes) => Promise<void>">
  Rename, re-icon, or move a webhook. Requires `manage_server`.
</ParamField>

<ParamField path="webhooks.regenerate" type="(webhookId: string) => Promise<WebhookMint>">
  Mint a fresh token and return the new URL. This permanently invalidates the old one. Requires `manage_server`.
</ParamField>

<ParamField path="webhooks.delete" type="(webhookId: string) => Promise<void>">
  Delete a webhook. Requires `manage_server`.
</ParamField>

<ParamField path="webhooks.post" type="(url: string, post: UnencryptedWebhookPost) => Promise<void>">
  Post to a webhook URL a human already created. No permission required, and the recommended path.
</ParamField>

See [Webhook embeds](/guides/webhook-embeds) for the full lane, including why `webhooks.post` is preferred over minting.

### Voice

<ParamField path="joinVoice" type="(channelId: string, opts?: { groupId?: string; muted?: boolean; deafened?: boolean }) => Promise<VoiceConnection>">
  Join a voice channel and return the credential the server minted. Sends no media by itself.
</ParamField>

<ParamField path="refreshVoiceRoster" type="() => Promise<void>">
  Ask the server to push a fresh voice roster snapshot for this server.
</ParamField>

See [Joining voice](/voice/joining) for the permission ladder and the `groupId` rule.

### Permissions and visibility

<ParamField path="can" type="(permission: PermissionName) => boolean">
  Advisory check for a single permission in this server. Returns `false` when no verdict has arrived yet.
</ParamField>

<ParamField path="permissions" type="() => PermissionSet | undefined">
  The resolved permission set for this server, or `undefined` if no verdict has arrived yet.
</ParamField>

<ParamField path="visibleChannelIds" type="() => string[]">
  The channel ids the bot may currently view in this server.
</ParamField>

These mirror the advisory checks described in [Permissions](/concepts/permissions).

## What the handle does not bind

The handle covers server-scoped methods only. Everything below stays on the flat client, so call it there rather than looking for it on `g`.

| Not on the handle                                               | Why                                                            |
| --------------------------------------------------------------- | -------------------------------------------------------------- |
| `sendTyping(serverId, channelId, isTyping?, groupId?)`          | Not bound. Use the flat method.                                |
| `sendCommand(serverId, channelId, name, args, opts)`            | Not bound. Use the flat method.                                |
| `sendDM(userId, text, opts?)`                                   | A DM has no server.                                            |
| `watch(serverId, channelId, groupId?)`                          | Not bound. Use the flat method.                                |
| `fetchEmojis()`                                                 | The emoji catalog is global, not per server.                   |
| `messageLocation(messageId)`                                    | Client-wide lookup across every channel seen.                  |
| `voiceRoster(channelId)`                                        | Keyed by channel, and the bot is in at most one voice channel. |
| `leaveVoice()`, `voiceConnection()`, `setVoiceSelfState(state)` | Act on the bot's single live voice session, not on a server.   |
| `setStatus(status)`, `rest`, `destroy()`                        | Account-wide or client-wide.                                   |

## Putting it together

A moderation handler is where the handle earns its place. One bind, then several bound calls that never repeat the server id.

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

client.on('messageCreate', async (msg) => {
  if (msg.authorId === client.user?.id) return; // ignore our own messages
  if (msg.isDM || !msg.serverId) return;        // a DM has no server to bind
  if (msg.content.trim() !== '!lockdown') return;

  const g = client.guild(msg.serverId);
  if (!g.can('member_ban')) return; // advisory pre-gate

  try {
    const bans = await g.fetchBans();
    console.log(`${g.id} currently has ${bans.length} bans`);

    await g.timeout(msg.authorId, 600);
    await g.send(msg.channelId, 'Channel is quiet for ten minutes.');
  } catch (err) {
    if (err instanceof CloakActionError) {
      console.error(`lockdown denied: ${err.message}`);
    } else {
      throw err;
    }
  }
});
```

Every call above delegates to the flat client, so if you already know the flat methods you already know the handle.

## Next

<CardGroup cols={2}>
  <Card title="Moderation" icon="gavel" href="/guides/moderation">
    Kicks, bans, and timeouts in full.
  </Card>

  <Card title="Channels and groups" icon="folder-tree" href="/guides/channels-and-groups">
    Create, edit, and move channels and groups.
  </Card>

  <Card title="Sending messages" icon="paper-plane" href="/guides/sending-messages">
    SendOptions, and why a send is fire-and-forget.
  </Card>

  <Card title="Client reference" icon="book" href="/api-reference/client">
    Every flat method the handle binds.
  </Card>
</CardGroup>
