Skip to content

Moderation

Partly out of date

Rendered from ProjectInfo/client/moderation.md on docs/encryption-modes-spec. Its protocol content is still useful, but parts of it describe an older client or an earlier API. Where it disagrees with the code, the code wins — see the spec errata.

⚠️ Where this document and Encryption Modes disagree, that document wins. It reconciles the mode names, key distribution and media model across the specs and the running code, which have diverged. The CSAM report body documented here is not what the server implements and omits report verification entirely — see §6.

Roles

Roles control what server members can do. Each role is a permission bitfield. The @everyone role always exists and has id === server_id.

List roles

GET /api/servers/{serverID}/roles
→ Role[]

Fetch on server load. Cache per-server; invalidate on ROLE_UPDATE gateway event.

Create a role

POST /api/servers/{serverID}/roles
{
  "name": "Moderator",
  "color": 16711680,    // 0xFF0000 = red (RGB packed integer)
  "hoist": true,        // show separately in member list
  "mentionable": false,
  "permissions": 8388608   // PermKickMembers = 1 << 22
}
→ 201 Role

Requires PermManageRoles.

Update a role

PATCH /api/servers/{serverID}/roles/{roleID}
{ "name": "string", "color": 0, "hoist": false, "mentionable": false, "permissions": 0 }
→ 200 Role

Delete a role

DELETE /api/servers/{serverID}/roles/{roleID}
→ 204

Assign a role to a member

PUT /api/servers/{serverID}/members/{userID}/roles/{roleID}
→ 204

Requires PermManageRoles.

Remove a role from a member

DELETE /api/servers/{serverID}/members/{userID}/roles/{roleID}
→ 204

Channel Permission Overwrites

Override role permissions for a specific channel.

List overwrites

GET /api/channels/{channelID}/overwrites
→ PermissionOverwrite[]

Set or replace an overwrite

PUT /api/channels/{channelID}/overwrites/{targetID}
{
  "target_type": "role" | "member",
  "allow": 0,   // int64 bitfield — permissions to explicitly grant
  "deny": 0     // int64 bitfield — permissions to explicitly deny
}
→ 200 PermissionOverwrite

targetID is a role ID or user ID. Bits not set in either allow or deny are inherited. Setting the same bit in both is undefined — deny takes precedence.

Remove an overwrite

DELETE /api/channels/{channelID}/overwrites/{targetID}
→ 204

Kicking Members

DELETE /api/servers/{serverID}/members/{userID}
→ 204

Requires PermKickMembers. Kicked members can rejoin via invite.


Banning Members

List bans

GET /api/servers/{serverID}/bans
→ Ban[]

Requires PermBanMembers.

Ban a user

PUT /api/servers/{serverID}/bans/{userID}
{ "reason": "optional string" }
→ 204

Banned users cannot use invites to rejoin (403 on invite accept).

Unban a user

DELETE /api/servers/{serverID}/bans/{userID}
→ 204

Managing Messages

Delete a message

DELETE /api/channels/{channelID}/messages/{messageID}
→ 204

Requires PermManageMessages (to delete others' messages). Fires MESSAGE_DELETE.

Edit a message

PATCH /api/channels/{channelID}/messages/{messageID}
{ "content": "new content" }
→ 200 MessageResponse

Author only.


Message Reporting

Allow users to report inappropriate messages. The report modal should: 1. Show 7 reason chips (spam, harassment, NSFW, etc.) 2. Allow an optional detail text area 3. Show an encryption disclosure for rolling_key and peer_provided channels: - "To investigate this report, your decryption key will be shared with our trust and safety team. This gives them the ability to read messages in this channel." - This disclosure is required because the key must be included in the report for moderators to read the content.

POST /api/reports/message
{
  "message_id": "uuid",
  "channel_id": "uuid",
  "reason": "harassment",
  "detail": "optional free text"
}
→ 204

(Exact endpoint shape may vary; refer to server API docs.)


Reporting CSAM Attachments

If a user or the client-side scanner detects CSAM:

POST /api/reports/csam
{
  "attachment_id": "uuid",
  "evidence_note": "optional"
}
→ 201 { "report_id": "uuid" }

One report per attachment per user. Duplicate returns 409.


Permission Bits Quick Reference

Constant Value Description
PermViewChannel 1 << 0 See channel + history
PermSendMessages 1 << 1 Post messages
PermManageMessages 1 << 3 Delete/pin others
PermAttachFiles 1 << 5 Upload files
PermReadMessageHistory 1 << 6 Load old messages
PermMentionEveryone 1 << 7 @everyone
PermConnect 1 << 10 Join voice
PermSpeak 1 << 11 Speak in voice
PermMuteMembers 1 << 12 Server-mute
PermDeafenMembers 1 << 13 Server-deafen
PermMoveMembers 1 << 14 Move in voice
PermStream 1 << 16 Screen share
PermManageRoles 1 << 19 Manage roles
PermManageChannels 1 << 20 Manage channels
PermManageServer 1 << 21 Server settings
PermKickMembers 1 << 22 Kick
PermBanMembers 1 << 23 Ban
PermModerateMembers 1 << 28 Timeout
PermAdministrator 1 << 29 Bypass all

Server owner always has PermAdministrator regardless of role configuration.


Permission Resolution Algorithm

function resolvePermissions(
  userId: string,
  serverId: string,
  channelId: string,
  member: ServerMember,
  roles: Role[],
  overwrites: PermissionOverwrite[]
): number {
  // Owner shortcut
  if (server.owner_id === userId) return (1 << 29); // PermAdministrator

  // 1. Start with @everyone (id === serverId)
  const everyoneRole = roles.find(r => r.id === serverId)!;
  let perms = everyoneRole.permissions;

  // 2. OR in each role the member holds
  for (const roleId of member.roles) {
    const role = roles.find(r => r.id === roleId);
    if (role) perms |= role.permissions;
  }

  // 3. Administrator short-circuit
  if (perms & (1 << 29)) return (1 << 29);

  // 4. Channel overwrites
  const rolesWithEveryone = [serverId, ...member.roles];
  for (const roleId of rolesWithEveryone) {
    const ow = overwrites.find(o => o.target_id === roleId && o.target_type === "role");
    if (ow) {
      perms &= ~ow.deny;
      perms |= ow.allow;
    }
  }
  const memberOw = overwrites.find(o => o.target_id === userId && o.target_type === "member");
  if (memberOw) {
    perms &= ~memberOw.deny;
    perms |= memberOw.allow;
  }

  return perms;
}

function hasPermission(perms: number, bit: number): boolean {
  if (perms & (1 << 29)) return true; // Administrator
  return (perms & bit) !== 0;
}