Build and deploy

Moderation

Kick and ban players from room code, ban them from a project, and kick or mute them from your backend, the dashboard or the CLI.

A room can kick a connection and ban a player from itself in room code, which suits games where players run their own rooms. To act from outside a room, from your backend, the dashboard or the CLI, use bans, kicks and voice moderation through the HTTP API. Each takes an API key, a personal token or a dashboard session.

Players are named by their player ID: your player token’s sub, or a guest’s guest: ID. A room sees it as connection.player.

In room code

Call Close code Kept Scope
room.kick(conn, reason?) 4006 no; the player can join again one connection
room.ban(player, { reason?, expiresAt? }) 4014 in room state, across hibernation this room
A project ban, below 4013 by the control server every room of the project

The client sees the code as IrtError.code and the room’s reason as IrtError.reason, so it can say why. A banned player is turned away before onJoin runs, and never shows in other clients’ presence; so is a connection the room kicks in onJoin before any await.

This room lets its owner, the player whose token says so, kick and ban, and turns banned players away with a message:

// moderated-room.ts
import { CallRejected, defineCalls, defineRoom, defineSchema, entity } from "@irt/room";

const calls = defineCalls<{
  ban(args: { player: string; reason: string; hours?: number }): null;
  unban(args: { player: string }): boolean;
}>();
const schema = defineSchema({ state: entity("lobby", { topic: "string" }), calls });

const isOwner = (meta: unknown) => (meta as { owner?: boolean } | null)?.owner === true;

export default defineRoom({
  schema,
  calls: {
    ban(room, conn, { player, reason, hours }) {
      if (!isOwner(conn.meta)) throw new CallRejected("only the room's owner can ban");
      room.ban(player, { reason: reason.slice(0, 100), expiresAt: hours ? Date.now() + hours * 3600_000 : undefined });
      return null;
    },
    unban(room, conn, { player }) {
      if (!isOwner(conn.meta)) throw new CallRejected("only the room's owner can unban");
      return room.unban(player);
    },
  },
});

On the client, tell kicked and banned players apart:

// closed.ts
import { CloseCode, type RoomClient } from "@irt/client";

export function onClosed(room: RoomClient, show: (text: string) => void) {
  room.on("closed", (e) => {
    if (e.code === CloseCode.RoomBanned) show(`You're banned from this room${e.reason ? `: ${e.reason}` : "."}`);
    else if (e.code === CloseCode.Kicked) show(`You were removed${e.reason ? `: ${e.reason}` : ""}. You can rejoin.`);
    else if (e.code === CloseCode.Banned) show("You're banned from this game.");
  });
}

To hide players from each other, such as after a block, give the room seesPresence(room, viewer, entry) beside an interest sees: presence then follows the same rule as entities. See Presence.

Room code mutes a player’s voice with room.muteVoice(player, { expiresAt }), kept in room state like a ban: the room leaves the player out of every listener’s speakers, and the voice server stops it publishing until the mute ends. See Proximity voice. Room code has no call for project bans; call the HTTP API from your backend.

Bans

A ban keeps a player out of every room of the project, for good or until it expires.

curl -X POST https://api.irt.io/v1/projects/my-game/bans \
  -H "Authorization: Bearer $IRT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"player": "u_42", "reason": "cheating", "expiresAt": "2026-11-11T00:00:00Z"}'
  • The router refuses a banned player’s connections with close code 4013, which the client SDK doesn’t retry. Connections the player has when the ban is made close with 4013 within a few seconds.
  • With a banned player’s token, the client HTTP API’s room creation, join code lookup and public room list answer 403 with code banned.
  • expiresAt is optional; without it the ban is permanent. Once it passes, the player can connect again.
  • Banning a player again replaces the ban’s reason and expiry.
  • A project can have 10,000 active bans. Expired bans don’t count.

Banning a guest keeps out only that guest ID, and a guest gets a new one by clearing the browser’s storage. A game that needs bans to stick should sign its own player tokens and turn guests off.

On the client, tell a banned player why they can’t join:

// banned.ts
import { CloseCode, connect, defineSchema, entity, IrtError } from "@irt/client";

const schema = defineSchema({ state: entity("table", { players: "u8" }) });

try {
  const room = await connect({ router: "wss://eu.irt.io", project: "my-game", room: "main", schema });
  room.on("closed", (error) => {
    if (error.code === CloseCode.Banned) console.log("you were banned from this game");
  });
} catch (e) {
  if (e instanceof IrtError && e.code === CloseCode.Banned) console.log("you are banned from this game");
  else throw e;
}

Kicks

A kick closes a player’s connection to one room with close code 4006, the same as room.kick. The client doesn’t reconnect by itself, but the player can join again; ban them to keep them out.

curl -X POST https://api.irt.io/v1/projects/my-game/rooms/forest-7/kick \
  -H "Authorization: Bearer $IRT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"player": "u_42"}'

It answers 204, or 404 when the player isn’t connected to the room.

Voice

With proximity voice on, a player’s voice can be muted for everyone in a room, or the player disconnected from the room’s voice:

Path Body Does
POST /projects/{p}/rooms/{id}/voice/mute {player, muted} Mutes or unmutes the player’s microphone
POST /projects/{p}/rooms/{id}/voice/remove {player} Disconnects the player from the room’s voice

Both answer 204, or 404 when the player isn’t in the room’s voice. The mute applies to the player’s tracks as they are, and its client can unmute them; for a mute that holds, have the room call room.muteVoice. Removing a player from voice doesn’t stop them rejoining it with their voice token; kick or ban them for that.

In the dashboard and the CLI

The dashboard’s Bans tab lists a project’s bans and adds and removes them, and each active room on the Rooms tab has a Moderate panel for kicks and voice. In a terminal:

irt bans list
irt bans add u_42 --reason cheating --for 7d
irt bans remove u_42

View this page as Markdown