Moderation
Ban, unban, mute, and unmute players on your server.
Take moderation action on a player: ban / unban and mute / unmute.
Each action is attributed to the API key's owner (the server owner) in the moderation
history. Reading the current state and history lives on the Players
page (GET /moderation, GET /violations).
Identifying a player
Every endpoint takes the same {ref} segment as the Players endpoints — a Steam64
(76561198000000000) or a prefixed Discord id (discord:123456789012345678). A
discord: ref resolves to the currently-linked player (404 not_found if none is linked).
A well-formed Steam64 is accepted as-is (you can pre-ban a Steam id that has never joined).
Authorization
| Scope | Grants |
|---|---|
players:ban | POST / DELETE …/ban — ban and unban. |
players:mute | POST / DELETE …/mute — mute and unmute. |
Least-privilege: a chat-moderation bot can hold players:mute alone, without ban power. A
key missing the scope gets 403 insufficient_scope.
Every write is idempotent — repeating a ban/mute (or an unban/unmute) converges on the
same state. You may send an Idempotency-Key to make a retry after
a network blip provably safe.
Duration
A temporary penalty needs a duration (a positive integer) and an optional duration_unit
(default hours). Set permanent: true for a permanent penalty (then duration is
ignored). You must send one or the other — a body with neither is rejected
(400 validation_error), never silently defaulted.
duration_unit | Max (ban) | Max (mute) |
|---|---|---|
minutes | 5,256,000 | 5,256,000 |
hours | 87,600 | 87,600 |
days | 3,650 | 3,650 |
months | 120 | — (not allowed for mutes) |
Mutes round up to whole hours
A temporary mute is rounded up to the next whole hour by the game server (so the
saved row and the live in-game mute can't drift). Request duration: 90, duration_unit: "minutes" and the effective mute is 2 hours. Bans keep the exact duration you send.
POST /v1/players/{ref}/ban
Ban a player. Body (all fields optional except the duration/permanent rule above):
| Field | Type | Meaning |
|---|---|---|
reason | string | Shown in the moderation record (defaults to "No reason provided"). |
permanent | boolean | Permanent ban. Mutually exclusive with duration. |
duration | integer > 0 | Length, in duration_unit units. |
duration_unit | minutes | hours | days | months | Default hours. |
squad_ban | boolean | Also ban the player's squad. Requires squad_id. |
squad_id | string (digits) | The squad to ban when squad_ban is true. |
POST /v1/players/76561198000000000/ban
Content-Type: application/json
{ "reason": "aimbot", "duration": 7, "duration_unit": "days" }Returns the resulting moderation state — the same object as
GET /v1/players/{ref}/moderation:
{
"player": { "steam_id": "76561198000000000", "discord_id": "123456789012345678" },
"ban": { "banned": true, "reason": "aimbot", "permanent": false, "expires_at": "2026-07-28T…Z", "banned_at": "2026-07-21T…Z", "banned_by": { "steam_id": "76561198000000001", "discord_id": "223…", "name": null } },
"mute": { "muted": false, "permanent": false, "muted_at": null, "duration_minutes": null, "channels": [] }
}DELETE /v1/players/{ref}/ban
Unban a player. No body. Returns the resulting moderation state (ban.banned: false).
Unbanning a player who is not banned still succeeds (idempotent).
POST /v1/players/{ref}/mute
Mute a player. Body is the ban body minus squad_ban/squad_id/months, plus:
| Field | Type | Meaning |
|---|---|---|
channels | string[] | Chat channels to mute — any of Global, Local, Squad (omit to mute all three). Any other value is rejected (400). |
POST /v1/players/76561198000000000/mute
Content-Type: application/json
{ "duration": 3, "duration_unit": "hours", "channels": ["Local"] }Returns the resulting moderation state (mute.muted: true, with the rounded
duration_minutes and the muted channels).
DELETE /v1/players/{ref}/mute
Unmute a player. No body. Returns the resulting moderation state (mute.muted: false).
Idempotent.
A 503/504 on a write may still have applied
A write mutates the game server, then re-reads the new state to return it. If the server
drops between those two steps you may receive 503 server_unavailable /
504 gateway_timeout even though the ban/mute did apply. Such a response carries
error.details.mutation_applied: true (and the action) so you can tell the write landed.
Because every action here is idempotent, the safe recovery is to retry the same request
(optionally with the same Idempotency-Key), or to GET /v1/players/{ref}/moderation to
reconcile the true state.
Errors
Standard error envelope.
| Code | Status | When |
|---|---|---|
insufficient_scope | 403 | The key lacks players:ban (ban routes) or players:mute (mute routes). |
not_found | 404 | No player matches the {ref} (unlinked Discord id). |
validation_error | 400 | Neither duration nor permanent; a duration over its unit cap; squad_ban without squad_id; a bad field type; or a game-server-side rejection (its message is surfaced in error.details.mod_message). |
server_unavailable | 503 | The game server is offline or unreachable (see the note above). |
gateway_timeout | 504 | The server did not respond in time (see the note above). |