/api/v1/users/:user_id/sanctions
Site-wide sanctions on an account — today, bans. Defined in
crates/rest/src/users.rs.
A ban blocks authentication and nothing else: the account is not deleted, its links stay, its posts stay, and it remains as visible as any other account everywhere except the sign-in form. That is what makes it different from DELETE /api/v1/users/:user_id, which undoes the account.
Forum warnings and mutes are a different mechanism, scoped to the forum and served by /api/v1/forum/sanctions.
Administrators only, both verbs. There is no self-service: nobody bans or unbans themselves.
Sanctions are additive. Banning and unbanning write a new row each time
rather than mutating one, so the history of past bans survives. A sanction
carries an internal_note and nothing that is shown to the banned user.
There is no route to list them here. An account's current state is visible
as is_banned on the short user returned by the moderation read
shared-browser-clusters.
POST /:user_id/sanctions
Bans the account. Creating the sanction also closes every session the account has open, so the ban takes effect immediately rather than at the next sign-in.
Body
| Field | Type | Meaning |
|---|---|---|
internal_note | string | Why, for other moderators. Required. |
Example
POST /api/v1/users/28dbb0bf-0fdc-40fe-ae5a-dde193f9fea8/sanctions
Content-Type: application/json
Cookie: sid=b8be19ef-2d61-44de-b7d2-9c34ccb8a763
{"internal_note": "ban evasion"}
{
"id": "a3d6f0b1-2c9e-4f5a-8b7d-1e0c4a2b9f38",
"user": {"type": "User", "id": "28dbb0bf-0fdc-40fe-ae5a-dde193f9fea8"},
"created_at": "2026-09-14T08:21:03.114Z",
"created_by": {"type": "User", "id": "9f310484-963b-446b-af69-797feec6813f"},
"deleted_at": null,
"deleted_by": null,
"internal_note": "ban evasion"
}
Errors
| Status | Body | When |
|---|---|---|
| 403 | {"error": "forbidden"} | Not an administrator. |
| 404 | {"error": "not found"} | No such account. |
| 409 | {"error": "already sanctioned"} | A live ban already covers this account. |
| 500 | {"error": "internal error"} | — |
DELETE /:user_id/sanctions/:sanction_id
Lifts a ban. :sanction_id is the id of the sanction; the :user_id in the
path is not used to find it, so it must be the right one for the URL to mean
what it says.
Answers 200 with the JSON literal null. The row is not removed: the
service closes it, recording deleted_at and deleted_by.
Example
DELETE /api/v1/users/28dbb0bf-0fdc-40fe-ae5a-dde193f9fea8/sanctions/a3d6f0b1-2c9e-4f5a-8b7d-1e0c4a2b9f38 Cookie: sid=b8be19ef-2d61-44de-b7d2-9c34ccb8a763
null
Errors
| Status | Body | When |
|---|---|---|
| 403 | {"error": "forbidden"} | Not an administrator. |
| 404 | {"error": "not found"} | No such sanction. |
| 500 | {"error": "internal error"} | — |