Cloud Bans
A shared, customer-vouched community ban pool for Citadel Cloud subscribers. The premise: every Cloud-protected server has independent operators dealing with the same handful of serial cheaters and griefers. Cloud Bans makes those bans collective so each operator only has to do the work once.
This page documents the data model, trust-and-safety policy, customer experience, and the appeal flow. If you just want the what and why, the Cloud product overview is the right starting point — this page is for operators who want to understand what they're opting into.
Mental model
Three independent pieces:
- Customers — Cloud subscribers. Each has a
vouch_weightthat starts at1.0and rises or falls based on submission/overturn history. - Submissions — a customer attaching a SteamID to a reason category. A submission is a vouch of weight equal to the customer's current vouch_weight.
- Community bans — pool entries keyed by SteamID. A ban accumulates vouch weight from independent submissions until it crosses a threshold and goes live.
A pool entry is active (propagating to all subscribers) only after independent customers vouch for it with cumulative weight ≥ a configurable threshold. Until then it sits as pending and isn't visible to other subscribers' /sync queries.
The point: a single bad-faith customer can't get someone banned across the network alone.
The reputation model
vouch_weight
Every customer starts with vouch_weight = 1.0. It changes based on history:
- Each successful overturn of a ban you submitted multiplies your weight by an overturn penalty (default
0.7). Three overturns drops you to0.343— meaning you'd need 9+ co-signers per submission to hit the activation threshold, instead of 3. - High overturn rate triggers a hard lock. If your overturn rate over the last N submissions (default
20) crosses a threshold (default30%), your weight is clamped to0and submissions stop counting until a moderator manually resets you.
This isn't moralistic — it's purely mechanical. A customer who submits ten valid bans for every one wrong call retains full weight. A customer with consistently bad bans loses influence on the network.
Activation threshold
A pool entry flips from pending to active when its accumulated vouch_weight_total reaches the configured vouchThreshold (default 3.0). With everyone at the default vouch_weight = 1.0, that's three independent customers vouching. After overturns, it's higher.
High-impact manual review
A pool entry that accumulates more than the highImpactThreshold (default 50 vouches) is flagged with manual_review_required = true and held back from /sync until a moderator approves. This catches the worst-case scenario of "everyone copied the same wrongful ban" before it propagates further.
This mostly never fires in practice — most legitimate bans top out at 5–10 vouches. The threshold is a circuit breaker for the rare case where a popular but-wrong ban goes viral.
Expiry
Bans auto-expire after expiryMonths of no fresh submissions (default 12). The intent is to age out historic bans on accounts that may have been resold, recovered, or otherwise legitimately changed hands. A fresh submission resets the expiry clock.
All knobs
| Config | Default | Env var |
|---|---|---|
vouchThreshold | 3.0 | CLOUD_BANS_VOUCH_THRESHOLD |
overturnPenalty | 0.7 | CLOUD_BANS_OVERTURN_PENALTY |
overturnRateLockThreshold | 0.30 | CLOUD_BANS_OVERTURN_RATE_LOCK |
overturnRateWindow | 20 | CLOUD_BANS_OVERTURN_WINDOW |
highImpactThreshold | 50 | CLOUD_BANS_HIGH_IMPACT_THRESHOLD |
expiryMonths | 12 | CLOUD_BANS_EXPIRY_MONTHS |
rateLimit24h | 50 | CLOUD_BANS_RATE_LIMIT_24H |
rateLimit30d | 1000 | CLOUD_BANS_RATE_LIMIT_30D |
The defaults are calibrated for the current customer base. We'll publish updates here if we tune them after seeing real submission patterns at scale.
Customer flow
Submitting a ban
When you ban a SteamID locally with the Submit to Cloud Bans checkbox enabled, the dashboard calls:
POST /api/v1/cloud-bans/submit
{
"steamId": "76561198012345678",
"reasonCategory": "cheating", // 'cheating' | 'griefing' | 'exploiting' | 'other'
"notesLocal": "free-text — optional, only stored locally with your submission, never shared"
}
The endpoint:
- Verifies your license token + active Cloud subscription.
- Checks rate limits (50/24h, 1000/30d per customer by default).
- Re-fetches your current
vouch_weight(refuses if zero — see lockouts below). - Creates or updates the pool's
community_bansentry, adds yourban_submissionsrow, recomputes the threshold. - Returns the resulting state:
pending(if threshold not yet hit) oractive(propagated).
If your customer-side vouch_weight is locked to zero (overturn-rate trip), the call returns 403 with a message pointing to the appeal/moderator-review path. You can keep banning locally — Cloud just ignores the submission.
Receiving bans
Your local install polls /api/v1/cloud-bans/sync periodically (cursor-paginated by updated_at):
GET /api/v1/cloud-bans/sync?since=2026-05-07T08:00:00Z&limit=500
Returns a page of community bans whose status is active, overturned, or expired. Your local cache adds the actives and removes the overturns/expires. Pending bans never show up here — they haven't earned propagation yet.
For just-in-time checks at player connect time:
GET /api/v1/cloud-bans/check?steamId=76561198012345678
Returns { banned: true, reasonCategory, vouchCount, activatedAt } or { banned: false }.
Unenrolling a ban
If you change your mind about a ban you submitted (e.g., you discover the player was framed):
POST /api/v1/cloud-bans/unenroll
{ "steamId": "76561198012345678" }
This marks your submission as withdrawn and recomputes the pool entry's threshold. If your withdrawal drops the weight below the activation threshold, the ban flips back to pending and stops propagating to new sync requests. Existing sync recipients pick up the change as overturned on their next page.
Unenrolling on a ban that hasn't been independently vouched yet effectively retracts it. Unenrolling on a ban that ten other customers also vouched leaves the ban active — you've just removed your contribution.
Appeals — the public path
Players can appeal through the public form at /appeal (see the Trust Network overview for the operator-facing summary). Anyone (including non-customers — typically the banned player themselves) can file an appeal for a SteamID they believe was wrongly banned. The endpoint is unauthenticated and per-IP rate-limited to discourage spam:
POST /api/v1/appeals
{
"steamId": "76561198012345678",
"appellantEmail": "[email protected]",
"reason": "I was using legitimate keybinds, not autohotkey...",
"evidence": "https://youtube.com/watch?v=..."
}
The endpoint returns a tracking token the appellant can use to check status:
GET /api/v1/appeals/:appellantToken
Anti-enumeration: appeals against SteamIDs that aren't actually community-banned still return a fake-looking tracking token. Otherwise the public endpoint would be a way to enumerate the ban list by trial-and-error.
Moderators review appeals in a queue and decide:
- Overturned — ban reversed; propagates as
overturnedin/syncso all subscribers remove their local copy. Each contributing customer'svouch_weightis multiplied by the overturn penalty (default 0.7). - Upheld — appeal denied; the ban stays active. No customer-side weight change.
- Dismissed — insufficient information to act. No-op; appellant can submit a new appeal with more evidence.
The appellant gets an email at every step (received, decided), and decisions land in their tracking page.
Audit and moderation
The full Cloud Bans story is recorded in two audit logs:
audit_log(the cloud-side table, separate from the controller-side audit log) — records every submission, unenroll, appeal, moderation decision, and customer reputation event. See audit log codes for the action strings.- Customer reputation history — every change to a customer's
vouch_weightand lockout state, with reason. Surfaces in the admin dashboard.
Moderators have a batch-overturn endpoint (/api/v1/admin/cloud-bans/batch-overturn) for the case where a single bad-faith customer's submissions need to be reversed at once.
Privacy carve-out
Citadel's normal "nothing leaves your machine" promise gets a specific, narrow carve-out for Cloud Bans:
What's submitted to citadels.cc:
- The SteamID being banned.
- The reason category (
cheating | griefing | exploiting | other). - Your account's vouch_weight at submission time (recorded with the submission so the threshold math is reproducible).
What's NOT submitted:
- Player names.
- Server names, IPs, or addresses.
- Mod lists or server configs.
- Chat logs.
- The
notesLocalfree-text field — that stays on your install only. - Any data about non-banned players.
The data model is intentionally minimal. The minimum signal needed to do useful cross-customer protection is the SteamID + reason + the submitter's reputation; everything else is local concern.
If you'd rather not participate in Cloud Bans even with a Cloud subscription, the Submit to Cloud Bans checkbox is unchecked by default for new bans. You can subscribe to Cloud, use other features (when they ship), and never submit a single SteamID to the pool.
Public stats
The /api/v1/cloud-bans/stats endpoint is unauthenticated and returns aggregate-only numbers for the citadels.cc/cloud marketing page:
{
"activeBans": 1247,
"bansActivatedThisWeek": 38,
"contributingCustomers": 142
}
No per-customer or per-SteamID data is exposed. The customer count is rounded to "how many distinct customers have at least one non-unenrolled submission ever" — it's a participation metric, not a leaderboard.
Operational notes
- The trust-and-safety logic lives in
packages/api/src/lib/cloud-bans-reputation.ts. Route handlers must go through it; bypassing it would skip the rate limits, weight updates, and audit writes. - Submission endpoints are rate-limited per-customer inside the reputation module; the global Fastify rate limit on the route scope is just network smoothing on top.
/api/v1/appealsis per-IP rate limited (10/min) to discourage moderation-queue spam.
Next
- Citadel Cloud overview — pricing, what the add-on includes
- Audit Log Codes — the action strings emitted by Cloud Bans events