mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
feat(enforcement): record decisions and send notices (#3492)
This commit is contained in:
1 parent
da1842ecab
commit
f5c30fdf8f
383 files changed
+29692
-4139
No files matched your search
@@ -309,6 +309,7 @@ export default defineConfig({
|
||||
'admin-api/threads',
|
||||
'admin-api/applications',
|
||||
'admin-api/reports',
|
||||
'admin-api/enforcement',
|
||||
'admin-api/messages',
|
||||
'admin-api/blocklists',
|
||||
'admin-api/discovery',
|
||||
|
||||
@@ -14,7 +14,18 @@ Fluxer builds each permission name from `ban:`, the list name with each hyphen w
|
||||
|
||||
## Blocklist types
|
||||
|
||||
`ip` and `email` gate account access and registration. `phrase`, `url`, `url-domain`, `file-sha`, `avatar-hash`, and `profile-substring` gate what an account may post, link, upload, or display. `email` stores nothing but the value. An `ip` row also stores a ban kind, a reason, an expiry, and its creation time. An `ip` row with an expiry stops applying when it expires, and Fluxer then deletes it.
|
||||
`ip` and `email` gate account access and registration. `phrase`, `url`, `url-domain`, `file-sha`, `avatar-hash`, and `profile-substring` gate what an account may post, link, upload, or display. `email` stores nothing but the value. An `ip` row also stores a ban kind, a reason, an expiry, its creation time, and the enforcement decision it belongs to, if any. An `ip` row with an expiry stops applying when it expires, and Fluxer then deletes it.
|
||||
|
||||
## Network blocks from decisions
|
||||
|
||||
A permanent suspension or a closure for a broken rule can block networks the account used in the last 30 days, through `block_networks` on [Suspend user](/admin-api/users/#suspend-user) and [Schedule user deletion](/admin-api/users/#schedule-user-deletion). Each such `ip` row belongs to that [enforcement decision](/admin-api/enforcement/) and expires after 90 days unless the request set another length. These rows are only ever created by a decision:
|
||||
|
||||
- Adding or removing the address through this resource returns 409 `IP_BAN_LINKED_TO_DECISION` with the `decision_id`.
|
||||
- A plain write, including a temporary block from Fluxer's own abuse defences, never replaces a decision's row.
|
||||
- A plain row with an expiry becomes the decision's row and keeps the later of the two expiries. A permanent plain row or another decision's row is left as it is, and the request reports the address in `skipped_ips` as `already_blocked`.
|
||||
- Ending the decision removes its rows. So does executing the account's deletion, for the rows of every decision on that account.
|
||||
|
||||
Ending a decision removes its rows even if a plain block existed before the decision took the row over. Add the plain block again if it is still wanted.
|
||||
|
||||
| Value | Description |
|
||||
| --- | --- |
|
||||
@@ -186,7 +197,7 @@ One stored row of one blocklist. Every field is present on every entry, and a fi
|
||||
|
||||
<sup>6</sup> Non-null only on `url-domain`
|
||||
|
||||
<sup>7</sup> Non-null only on `ip`. An address added through [Add blocklist entry](#add-blocklist-entry) reads back with the reason `platform_admin_enforcement`. Its `expires_at` is null unless it was added with a positive `duration_hours`. A row past its `expires_at` is not listed
|
||||
<sup>7</sup> Non-null only on `ip`. An address added through [Add blocklist entry](#add-blocklist-entry) reads back with the reason `platform_admin_enforcement`. Its `expires_at` is null unless it was added with a positive `duration_hours`. A row a decision created reads back with the reason `enforcement_decision` and always has an `expires_at`. A row past its `expires_at` is not listed
|
||||
|
||||
<sup>8</sup> Both null on `email` and `phrase`, which store nothing but the value. `created_by_user_id` is also null on `ip`
|
||||
|
||||
@@ -224,11 +235,14 @@ The decision [Check blocklist entry](#check-blocklist-entry) returns for one val
|
||||
| --- | --- | --- |
|
||||
| banned<sup>1</sup> | boolean | Whether the value is currently blocked by the named blocklist |
|
||||
| expires_at<sup>2</sup> | ?ISO8601 timestamp | When the matching ban expires |
|
||||
| decision_id<sup>3</sup> | ?snowflake | The [enforcement decision](/admin-api/enforcement/) the matching block belongs to |
|
||||
|
||||
<sup>1</sup> Reports the decision the enforcement path would make for this value
|
||||
|
||||
<sup>2</sup> Non-null only on `ip` when the matching ban has an expiry. Null when the ban is permanent, when nothing matches, and on every other blocklist
|
||||
|
||||
<sup>3</sup> Non-null only on `ip` when the matching block belongs to a decision. Read from the same cache the enforcement path uses, so a block written in the last few moments on another node can read as null
|
||||
|
||||
:::note[A block and a row are different things]
|
||||
A value can be blocked with no row of its own, as a single address covered by a stored CIDR range is. A row can exist while the value reads as not blocked, as an exempt address or an exempt email domain does.
|
||||
:::
|
||||
@@ -393,6 +407,7 @@ Every write is an upsert on the canonical value. An omitted optional field is wr
|
||||
| --- | --- | --- |
|
||||
| 204 | empty | Values were written |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY` because the value is not valid for the list, or a field the blocklist does not accept was supplied |
|
||||
| 409 | [error response](/admin-api/#error-response) | `IP_BAN_LINKED_TO_DECISION` with `decision_id`, because the address is blocked by an enforcement decision |
|
||||
|
||||
A body field the selected blocklist does not accept is stripped and never produces that 400. A `url` Fluxer cannot canonicalise returns 400 `INVALID_FORM_BODY` naming `url` in the `errors` array. A `url-domain` value that is not a valid domain or [domain pattern](#domain-patterns) returns the same 400 naming `domain`.
|
||||
|
||||
@@ -675,6 +690,7 @@ Removes one row from the selected blocklist. Returns 204 with an empty body. Req
|
||||
| --- | --- | --- |
|
||||
| 204 | empty | Value was processed |
|
||||
| 400 | [error response](/admin-api/#error-response) | `scope` is supplied on an unscoped blocklist or omitted on `profile-substring`, and the request returns `INVALID_FORM_BODY` |
|
||||
| 409 | [error response](/admin-api/#error-response) | `IP_BAN_LINKED_TO_DECISION` with `decision_id`, because an enforcement decision owns the `ip` row |
|
||||
|
||||
On the `url` blocklist, an `entry_value` Fluxer cannot canonicalise returns 400 `INVALID_FORM_BODY` naming `url` in the `errors` array.
|
||||
|
||||
|
||||
@@ -50,7 +50,11 @@ ACL `bulk:add:guild_members`. Adds every targeted user to one guild. Runs as `bu
|
||||
|
||||
### `schedule_user_deletion`
|
||||
|
||||
ACL `bulk:delete:users`. Schedules account deletion for every targeted user. Runs as `bulkScheduleUserDeletion`.
|
||||
ACL `bulk:delete:users`. Closes every targeted account and schedules its deletion. Runs as `bulkScheduleUserDeletion`. A job queued in the shape used before enforcement decisions, with `reason_code` and no `basis`, fails when the worker runs it instead of inventing a decision.
|
||||
|
||||
### `delete_user_messages`
|
||||
|
||||
ACL `bulk:delete:user_messages`. Deletes every message each targeted account wrote. Runs as `bulkDeleteMessagesForUsers`. A job queued without a `basis` fails when the worker runs it.
|
||||
|
||||
Each worker task is recorded as `task_type` on the job and listed under [background job task types](/admin-api/jobs/#background-job-task-types). Every bulk task runs in the `lifecycle` [processing lane](/admin-api/jobs/#processing-lanes).
|
||||
|
||||
@@ -109,18 +113,29 @@ The `task` discriminator selects one of these structures. Every ID array has an
|
||||
| --- | --- | --- |
|
||||
| task | string | The discriminator selecting this variant, `schedule_user_deletion` |
|
||||
| user_ids | array[snowflake] | The users to schedule for deletion (max 1000 entries) |
|
||||
| reason_code | integer | [Deletion reason](/admin-api/users/#deletion-reasons) recorded against every targeted account |
|
||||
| public_reason? | string | The reason shown to each account holder in the deletion notice (0-512 characters after normalisation) |
|
||||
| days_until_deletion?<sup>1</sup> | integer | The delay before deletion in whole days (1-365, default 60) |
|
||||
| notify_user? | boolean | Whether to email each account holder about the scheduled deletion (default true) |
|
||||
| days_until_deletion<sup>1</sup> | integer | The delay before deletion in whole days (1-365) |
|
||||
| basis<sup>2</sup> | [basis](/admin-api/enforcement/#basis) object | Why the accounts are closed |
|
||||
|
||||
<sup>1</sup> Fluxer raises the value to the reason-specific minimum, 14 days for `USER_REQUESTED` and 60 days for every other code
|
||||
<sup>1</sup> Fluxer raises the value to the minimum of the basis, 74 days for a broken rule and 14 days otherwise. A self-hosted instance has no appeal route and raises every basis to 14 days
|
||||
|
||||
<sup>2</sup> The `enforcement` basis takes a bulk decision with `reason_code`, `source` (always `own_review`), `automation` and a required `statement`. Reports, evidence messages and any other field are refused, because per-account facts cannot be given in bulk. The `X-Audit-Log-Reason` header is required with this basis, or the request fails with `ENFORCEMENT_INTERNAL_NOTE_REQUIRED`, and becomes the first note on every decision
|
||||
|
||||
#### Delete user messages structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| task | string | The discriminator selecting this variant, `delete_user_messages` |
|
||||
| user_ids | array[snowflake] | The users whose messages are deleted (max 1000 entries) |
|
||||
| basis<sup>1</sup> | [basis](/admin-api/enforcement/#basis) object | Why the messages are deleted |
|
||||
|
||||
<sup>1</sup> Takes the same bulk decision and `X-Audit-Log-Reason` rule as the schedule user deletion structure. A reason with restricted handling is refused for each account, because it needs evidence messages
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | [bulk job creation](#bulk-job-creation-object) object | The bulk job was queued |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY`, including for a restriction flag or a missing internal note |
|
||||
| 403 | [error response](/admin-api/#error-response) | `MISSING_ACL` without the ACL selected by `task` |
|
||||
| 500<sup>1</sup> | [error response](/admin-api/#error-response) | The job could not be queued |
|
||||
|
||||
@@ -136,15 +151,15 @@ Every task updates its progress before work starts and at completion. Every task
|
||||
|
||||
Every task writes one summary Admin audit entry when it finishes, with the action `bulk_update_user_flags`, `bulk_update_guild_features`, `bulk_add_guild_members`, `bulk_schedule_deletion`, `bulk_ban_file_shas`, or `bulk_delete_user_messages`. The summary has the audit reason, the entity count, the operation-specific parameters, the job identifier, and the processed, successful, and failed counts. Its `target_type` is `bulk_job` and its `target_id` is the job identifier, except for `add_guild_members`, which targets the guild. A failed job writes no summary entry. A cancelled job writes one, marked `cancelled`, covering the entities it processed before it stopped.
|
||||
|
||||
`update_user_flags` writes one `update_flags` entry for each account and dispatches [User Update](/gateway/events/#user-update) to the account's sessions. A change to a publicly visible flag also dispatches [Guild Member Update](/gateway/events/#guild-member-update) to every guild the account is in.
|
||||
`update_user_flags` refuses the same restriction flag changes as [Update user flags](/admin-api/users/#update-user-flags). Adding one fails the request with `FLAG_REQUIRES_ENFORCEMENT_ROUTE`, and a removal an account refuses counts that account as failed. It writes one `update_flags` entry for each account and dispatches [User Update](/gateway/events/#user-update) to the account's sessions. A change to a publicly visible flag also dispatches [Guild Member Update](/gateway/events/#guild-member-update) to every guild the account is in.
|
||||
|
||||
`update_guild_features` writes one `update_features` entry for each guild, dispatches [Guild Update](/gateway/events/#guild-update), and reindexes the guild for search. Fluxer reconciles a guild that already has a discovery application record against the new feature set, so gaining `DISCOVERABLE` approves the record and losing it marks the record removed. A guild with no discovery record is left alone.
|
||||
|
||||
`add_guild_members` bypasses the ban check that a normal join runs. The task suppresses the join system message, records the join source as an Admin force add, and dispatches [Guild Member Add](/gateway/events/#guild-member-add) to the guild and [Guild Create](/gateway/events/#guild-create) to the added account's sessions. The task still enforces the per-account guild cap and the guild member cap, so an account at either ceiling is counted as failed. An account that is already a member is left unchanged and counted as successful, with no second membership and no Dispatch. Adding a bot account also records a `BOT_ADD` guild audit log entry attributed to the acting Admin.
|
||||
|
||||
`delete_user_messages` deletes every message each account wrote, across every channel. It writes one `delete_all_user_messages` entry for each account, with the audit reason, the account, and the deleted message count, and reports progress after every account.
|
||||
`delete_user_messages` runs the same steps as [Delete all user messages](/admin-api/users/#delete-all-user-messages) for each account, with the job's basis. With the `enforcement` basis each account that wrote any message gets its own content removal decision and notice. Each account writes one `delete_all_user_messages` entry with the audit reason, the basis and the message count, and the summary entry records `basis`, plus `reason_code` for the `enforcement` basis. Progress is reported after every account.
|
||||
|
||||
`schedule_user_deletion` marks each account deleted. The task runs the same steps as [Schedule user deletion](/admin-api/users/#schedule-user-deletion) for one account. It stores the reason code, public reason, and audit reason on the account, reschedules its pending deletion, terminates its sessions, cancels and refunds its Stripe subscription when one is on file, dispatches [User Update](/gateway/events/#user-update), writes one `schedule_deletion` entry for each account with the audit reason and the reason code, and emails the account holder when `notify_user` is true and an address is on file. A failed email is logged and does not fail the entity. The summary entry records `notify_user`. When the reason is not `USER_REQUESTED`, the task also bans the account's identifiers. When the reason is one that resolves reports under [Schedule user deletion](/admin-api/users/#schedule-user-deletion), the task also resolves the pending reports against the account, leaves the `child_safety`, `underage_user`, and `self_harm` categories pending, and sends each reporter of a resolved report the generic notice [Update report](/admin-api/reports/#update-report) describes. The skip cases of that notice apply.
|
||||
`schedule_user_deletion` runs the same steps as [Schedule user deletion](/admin-api/users/#schedule-user-deletion) for each account, with the job's basis. With the `enforcement` basis each account gets its own decision and notice, its email is blocked when the rule bars new accounts, and the pending reports against it are resolved for the reasons that resolve reports, leaving the `child_safety`, `underage_user`, and `self_harm` categories pending. Each account writes one `schedule_deletion` entry with the audit reason, and the summary entry records `basis`, plus `reason_code` for the `enforcement` basis. A failed email is logged and does not fail the entity. An account that already has a pending deletion, or that a decision owns for a holder request or housekeeping deletion, counts as failed.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
---
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
title: Enforcement decisions
|
||||
description: The record behind every restriction staff apply, the notice it sends, and the routes that read, annotate and end it.
|
||||
---
|
||||
|
||||
import RouteHeader from '@/components/RouteHeader.astro';
|
||||
|
||||
An enforcement decision records why staff restricted an account. The route that applies the restriction records it in the same request, so a restriction never exists without its statement of reasons. [Suspend user](/admin-api/users/#suspend-user), [Warn user](/admin-api/users/#warn-user), [Limit user features](/admin-api/users/#limit-user-features) and [Schedule user deletion](/admin-api/users/#schedule-user-deletion) with a broken rule as its basis record decisions today, and so does Fluxer itself when a payment fraud signal arrives.
|
||||
|
||||
The decision holds the explanation. The restriction itself stays where it always was, so a suspension is still `temp_banned_until` and the `DISABLED` flag on the account, a closure is still `pending_deletion_at` and the `DELETED` flag, and a feature limit is still its account flag. The account's `enforcement_decision_id` names the suspension or closure decision behind the current state.
|
||||
|
||||
Every decision sends its notice to the person it is about. There is no switch to withhold it.
|
||||
|
||||
## Concepts
|
||||
|
||||
| Term | Meaning |
|
||||
| --- | --- |
|
||||
| kind | What the decision did: `warning`, `suspension`, `closure`, `feature_limit`, `content_removal`, `profile_reset`, or `community_removal` |
|
||||
| reason code | One of the enforcement [deletion reasons](/admin-api/users/#deletion-reasons). `USER_REQUESTED`, `OTHER` and `INACTIVITY` are refused |
|
||||
| rule | The policy section the reason code points to. The notice links it |
|
||||
| handling | `standard`, `protected` or `restricted`, fixed by the reason code. It decides how much detail the notice gives |
|
||||
| evidence | A snapshot of the subject's own messages, or for a community the messages posted in it, taken when the decision is made |
|
||||
| notes | Internal staff notes. The `X-Audit-Log-Reason` header of the request that made the decision is the first note |
|
||||
| notice | The statement of reasons the holder receives by email, system DM, or at sign-in |
|
||||
|
||||
### Handling
|
||||
|
||||
| Handling | Reason codes | Notice detail |
|
||||
| --- | --- | --- |
|
||||
| standard | every code not listed below | The rule, where and when it happened, and the note to the user |
|
||||
| protected | 7, 10, 11, 15, 23 | The same, except that messages in direct and group conversations show the month only |
|
||||
| restricted | 9, 13, 24 | The rule only. No locations and no note to the user |
|
||||
|
||||
A restricted reason allows only a permanent suspension and content removal, needs at least one evidence message and both confirmations, and refuses a note to the user. Its evidence keeps the message ID, channel ID, time and attachment count only.
|
||||
|
||||
### Lifecycle
|
||||
|
||||
A decision is `active` until staff end it as `lifted` or `reversed`. A temporary suspension that runs out is ended by Fluxer as `expired`, with the system user (ID 0) as `ended_by` and its end as `ended_at`. That happens when the holder next signs in or in a scheduled task that runs every six hours, whichever comes first, and sends no notice. A suspension that ended as `expired` can still be ended as `reversed`, which sends the ended notice.
|
||||
|
||||
Two states are derived when it is read and never stored:
|
||||
|
||||
- `expired`: an active suspension or feature limit whose `ends_at` has passed, before Fluxer ends it
|
||||
- `replaced`: an active suspension or closure that the account's `enforcement_decision_id` no longer names, because a later decision replaced it
|
||||
|
||||
A replacing suspension or closure sends its own notice and no ended email for the one it replaces. A closure replaces a suspension and lifts it in the same write.
|
||||
|
||||
### Basis
|
||||
|
||||
Routes whose action is not always enforcement take a `basis` instead of a `decision`. Only the `enforcement` basis records a decision and sends its notice.
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| type | string | `enforcement`, `holder_request` or `housekeeping` |
|
||||
| decision?<sup>1</sup> | [enforcement decision input](#enforcement-decision-input) object | Why the subject is restricted |
|
||||
|
||||
<sup>1</sup> Required for the `enforcement` type and refused for the others. A bulk job takes a narrower decision, which [Bulk jobs](/admin-api/bulk-jobs/) describes
|
||||
|
||||
- `enforcement`: a rule was broken
|
||||
- `holder_request`: the account holder or community owner asked
|
||||
- `housekeeping`: nobody's conduct is involved, such as inactivity or cleanup
|
||||
|
||||
Each route accepts these bases:
|
||||
|
||||
| Route | Bases |
|
||||
| --- | --- |
|
||||
| [Schedule user deletion](/admin-api/users/#schedule-user-deletion) | `enforcement`, `holder_request`, `housekeeping` |
|
||||
| [Clear user profile fields](/admin-api/users/#clear-user-profile-fields) | `enforcement`, `holder_request`, `housekeeping` |
|
||||
| [Shred user messages](/admin-api/users/#shred-user-messages) | `enforcement`, `holder_request`, `housekeeping` |
|
||||
| [Delete all user messages](/admin-api/users/#delete-all-user-messages) | `enforcement`, `holder_request`, `housekeeping` |
|
||||
| [Delete message](/admin-api/messages/#delete-message) | `holder_request`, `housekeeping` |
|
||||
| [Delete guild](/admin-api/guilds/#delete-guild) | `enforcement`, `holder_request` |
|
||||
|
||||
The audit entry of each route records the basis in a `basis` metadata key.
|
||||
|
||||
### Feature limits
|
||||
|
||||
Each feature limit decision limits one feature, so ending or expiring one never touches another.
|
||||
|
||||
- `direct_messages` sets `SPAMMER` and is allowed for reason codes 3, 6, 8, 18 and 21. It has no end.
|
||||
- `reports` sets `REPORT_BANNED` and is allowed for reason code 29. It ends after 90 days by default and after at most 365.
|
||||
- `purchases` sets `PURCHASE_DISABLED` and is allowed for reason codes 12 and 14. It has no end.
|
||||
|
||||
A reports limit expires on its own. The next report from the account after `ends_at` clears `REPORT_BANNED` and is accepted. A reporter under a reports limit is still told the outcome of reports filed before the limit started.
|
||||
|
||||
### Removals
|
||||
|
||||
- Content removal: [Shred user messages](/admin-api/users/#shred-user-messages) snapshots the first 50 entries as the evidence, and every entry for restricted reasons, before it queues the shred job. The entries are the evidence, so `evidence` is refused there. [Delete all user messages](/admin-api/users/#delete-all-user-messages) cannot snapshot an unbounded set first, so it refuses restricted reasons. To remove a single message for a rule, shred it.
|
||||
- Profile reset: the old values of the cleared fields are saved as the evidence's `profile_values`. The cleared fields are the facts, so neither evidence nor a statement is needed.
|
||||
- Community removal: the subject is the community and the owner receives the notice. Evidence must be posted in that community, and linked reports must name it. Restricted reasons are refused, because the removal deletes the content.
|
||||
|
||||
Removed content cannot be restored, so these decisions end only as `reversed`.
|
||||
|
||||
### Reporter outcomes
|
||||
|
||||
A decision that resolves linked reports tells each reporter what happened to the item. `removed` covers a reported message the decision removed, a profile report met by a profile reset, and a community removal. `restricted` covers a suspension, closure or feature limit. `action_taken` covers a warning and tells the reporter that Fluxer acted on what they reported, without naming the action or saying the account was restricted. A content removal or profile reset that leaves the reported item in place leaves the report pending. A manual resolution through [Update report](/admin-api/reports/#update-report) names the outcome itself, and a duplicate takes the outcome stored on the report it repeats.
|
||||
|
||||
### Network blocks
|
||||
|
||||
A permanent suspension or a closure for a broken rule can also block networks the account used in the last 30 days, through `block_networks` on [Suspend user](/admin-api/users/#suspend-user) and [Schedule user deletion](/admin-api/users/#schedule-user-deletion). [List network block candidates](/admin-api/users/#list-network-block-candidates) shows which addresses qualify. The blocks last 90 days unless the request sets another length, belong to the decision, and are removed when it ends or the account's deletion runs. [Blocklists](/admin-api/blocklists/#network-blocks-from-decisions) explains how these rows relate to plain IP blocks.
|
||||
|
||||
Fluxer never blocks an exempt address or a special-purpose range, such as a private, shared carrier, loopback, link-local or documentation range, and lists those in `skipped_ips`. It refuses an address on which another account that is not itself suspended or closed was last active, matching IPv6 addresses by their /64, so a block never shuts out people who share a network with the account.
|
||||
|
||||
The notice says the account's networks were blocked and until when, and never names an address. The block screen shows the rule only for rules that read safely to a bystander on the same network: spam, service abuse, disruptive automation, raids, impersonation, adult content outside 18+ spaces, graphic violence, misuse of reporting and copyright. Every other block looks exactly like a plain one.
|
||||
|
||||
### Automated decisions and the review queue
|
||||
|
||||
A Stripe early fraud warning or chargeback records a `purchases` limit with reason code 12, `automation` `automated`, `payment_at` set to the time of the payment, and the system user (ID 0) as `decided_by`. Fluxer never suspends or closes an account, or blocks its email, because of a payment signal. The decision is placed in the review queue until a person ends it or records a warning, suspension or closure on the same account. A won dispute ends the decision as `reversed`, with the system user as `ended_by`.
|
||||
|
||||
## Enforcement decision input
|
||||
|
||||
The `decision` object every restricting route takes.
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| reason_code | integer | An enforcement [deletion reason](/admin-api/users/#deletion-reasons) |
|
||||
| source | string | `report` when staff reviewed this after a report, `own_review` otherwise |
|
||||
| automation | string | `flagged` when automated tools flagged it before staff decided, `none` otherwise |
|
||||
| report_ids?<sup>1</sup> | array[snowflake] | Reports about the subject that the decision resolves (at most 20) |
|
||||
| evidence?<sup>2</sup> | array[object] | `channel_id` and `message_id` of messages the decision relies on (at most 50) |
|
||||
| statement?<sup>3</sup> | string | Note to the user, shown in the notice (1-500 characters) |
|
||||
| rights_holder?<sup>4</sup> | string | The rights holder or the right concerned (1-200 characters) |
|
||||
| confirmations?<sup>5</sup> | array[string] | `evidence_preserved` and `reports_filed_or_not_required` |
|
||||
|
||||
<sup>1</sup> Required when `source` is `report`. Each report must name the subject, the reported account or the reported community. Messages in a report's snapshot that the subject wrote, or that were posted in the community, become evidence too, and other messages are dropped. Pending reports are resolved as actioned and their reporters are told the [outcome](#reporter-outcomes)
|
||||
|
||||
<sup>2</sup> Every message must exist and be the subject's own, or for a community be posted in it. A message in a direct or group conversation is accepted only when a linked report covers that conversation. Attachments are kept as file name, size and type, never as files
|
||||
|
||||
<sup>3</sup> The decision needs at least one evidence message or a statement, except for a profile reset. Restricted reasons refuse it
|
||||
|
||||
<sup>4</sup> Required for code 31 (`COPYRIGHT`) and refused for every other code
|
||||
|
||||
<sup>5</sup> Both are required for restricted reasons. They are added to the first note
|
||||
|
||||
The `X-Audit-Log-Reason` header is required on every route that takes a decision and becomes its first note.
|
||||
|
||||
### Validation
|
||||
|
||||
Fluxer checks ACLs before it looks anything up, so a caller without the ACL learns nothing about the messages or reports it names. Evidence needs `message:lookup`, reports need `report:view` and `report:resolve`, and `block_networks` needs `ban:ip:add` and `user:view:ip`. Each other failure is a 400 `INVALID_FORM_BODY` whose error has one of these codes.
|
||||
|
||||
- `ENFORCEMENT_INTERNAL_NOTE_REQUIRED`: the `X-Audit-Log-Reason` header is missing
|
||||
- `ENFORCEMENT_KIND_NOT_ALLOWED`: the reason code does not allow this kind, a bot is given anything other than a suspension or closure, or a restricted reason is used to delete all of an account's messages
|
||||
- `ENFORCEMENT_LIMIT_NOT_ALLOWED`: the reason code does not allow this feature limit
|
||||
- `ENFORCEMENT_LIMIT_END_INVALID`: a reports limit ends in the past or more than 365 days from now, or another limit is given an end
|
||||
- `ENFORCEMENT_REPORT_REQUIRED`: `source` is `report` and no report is linked
|
||||
- `ENFORCEMENT_REPORT_MISMATCH`: a linked report does not exist or is about someone else
|
||||
- `ENFORCEMENT_EVIDENCE_NOT_ALLOWED`: a shred is given evidence messages, because its entries are the evidence
|
||||
- `ENFORCEMENT_EVIDENCE_NOT_FOUND`: an evidence message does not exist
|
||||
- `ENFORCEMENT_EVIDENCE_NOT_SUBJECT`: an evidence message is not the subject's own, or not posted in the community
|
||||
- `ENFORCEMENT_EVIDENCE_DM_UNREPORTED`: an evidence message is in a conversation no linked report covers
|
||||
- `ENFORCEMENT_FACTS_REQUIRED`: there is no evidence message and no statement
|
||||
- `ENFORCEMENT_EVIDENCE_REQUIRED`: a restricted reason has no evidence, or `remove_evidence` has nothing to remove
|
||||
- `ENFORCEMENT_STATEMENT_NOT_ALLOWED`: a restricted reason has a statement
|
||||
- `ENFORCEMENT_PERMANENT_REQUIRED`: a restricted reason is given a temporary suspension
|
||||
- `ENFORCEMENT_CONFIRMATIONS_REQUIRED`: a restricted reason is missing a confirmation
|
||||
- `ENFORCEMENT_RIGHTS_HOLDER_REQUIRED`: code 31 has no `rights_holder`
|
||||
- `ENFORCEMENT_RIGHTS_HOLDER_NOT_ALLOWED`: another code has a `rights_holder`
|
||||
- `ENFORCEMENT_NETWORK_BLOCK_NOT_ALLOWED`: `block_networks` is given with a temporary suspension or a closure that is not for a broken rule
|
||||
- `ENFORCEMENT_NETWORK_UNKNOWN`: the account did not use an address in `block_networks` in the last 30 days
|
||||
- `ENFORCEMENT_NETWORK_SHARED`: another account that is not suspended or closed was last active on an address in `block_networks`
|
||||
|
||||
### Order of effects
|
||||
|
||||
1. Fluxer validates the input and snapshots the evidence.
|
||||
2. It stores the decision with `notice_status` `pending`, together with its evidence and first note.
|
||||
3. It applies the restriction. A suspension writes `enforcement_decision_id` in the same account write as `temp_banned_until`, and a closure in the same write as `pending_deletion_at`. A content removal queues its shred job only now, after the evidence is stored. When this step fails, the decision is deleted and nothing is sent.
|
||||
4. It resolves the linked reports.
|
||||
5. It sends the notice and stores what it sent.
|
||||
|
||||
A failure after step 3 leaves the restriction and its decision in place with `notice_status` `pending`.
|
||||
|
||||
### Notice delivery
|
||||
|
||||
Suspensions and closures are sent by email only. Every other kind is also sent as a system DM. The decision records `undelivered` when no channel reaches the holder, because the account has no email address or the instance has email turned off and the kind sends no system DM. A suspension or closure that was not sent is delivered when the holder next signs in. The sign-in records `sent` with the channel `sign_in`, the request locale, the text and the time, and the appeal deadline counts from then. A bot's notice goes to its owner, and a community's to its owner.
|
||||
|
||||
The email subject names the kind of decision, except for a restricted reason, where it is neutral. The system DM leaves out the greeting and sign-off and shows links as Markdown links.
|
||||
|
||||
On a self-hosted instance the notice has no appeal section and no out-of-court redress lines, and it uses the instance's own name.
|
||||
|
||||
## Enforcement decision object
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| decision_id | snowflake | The ID of the decision, which the holder quotes in an appeal |
|
||||
| subject_type | string | `user` or `guild` |
|
||||
| subject_id | snowflake | The ID of the account, bot or community |
|
||||
| subject_name | ?string | The bot username or community name when the decision was made, null for a person's own account |
|
||||
| recipient_user_id | ?snowflake | Who receives the notice, or null when nobody can |
|
||||
| kind | string | What the decision did |
|
||||
| reason_code | integer | [Deletion reason](/admin-api/users/#deletion-reasons) |
|
||||
| source | string | `report` or `own_review` |
|
||||
| automation | string | `none`, `flagged` or `automated` |
|
||||
| statement | ?string | Note to the user |
|
||||
| rights_holder | ?string | The rights holder or the right concerned |
|
||||
| ends_at | ?ISO8601 timestamp | When a suspension or feature limit ends, null when permanent |
|
||||
| deletion_at | ?ISO8601 timestamp | When a closed account is deleted |
|
||||
| limit | ?string | `direct_messages`, `reports` or `purchases` for a feature limit |
|
||||
| profile_fields | array[string] | The fields a profile reset cleared |
|
||||
| removed_message_count | ?integer | Messages the decision removed |
|
||||
| removed_all_messages | boolean | Whether every message of the account was removed |
|
||||
| payment_at | ?ISO8601 timestamp | The charge a payment decision is about |
|
||||
| report_ids<sup>1</sup> | array[snowflake] | Linked reports |
|
||||
| decided_by | snowflake | The staff member who decided, or 0 for an automated decision |
|
||||
| created_at | ISO8601 timestamp | When the decision was made |
|
||||
| status | string | `active` or `ended` |
|
||||
| end_outcome | ?string | `lifted`, `reversed` or `expired` |
|
||||
| ended_at | ?ISO8601 timestamp | When the decision ended |
|
||||
| ended_by | ?snowflake | Who ended it |
|
||||
| end_statement | ?string | Note to the user sent with the ended email |
|
||||
| notice_status | string | `pending`, `sent` or `undelivered` |
|
||||
| notice_channels | array[string] | `email`, `system_dm` and `sign_in` |
|
||||
| notice_locale | ?string | The locale the notice was written in |
|
||||
| notice_text | ?string | The plain text that was sent |
|
||||
| notice_sent_at | ?ISO8601 timestamp | When the holder received the notice. The 60-day appeal window counts from here |
|
||||
| expired | boolean | See [Lifecycle](#lifecycle) |
|
||||
| replaced | boolean | See [Lifecycle](#lifecycle) |
|
||||
|
||||
<sup>1</sup> Empty without `report:view`
|
||||
|
||||
[Get enforcement decision](#get-enforcement-decision) adds these fields.
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| notes | array[object] | Internal notes in order, each with `note_id`, `author_id`, `body` and `created_at` |
|
||||
| evidence<sup>1</sup> | array[object] | Snapshot messages, each with `message_id`, `channel_id`, `timestamp`, `content`, `attachments` and `attachment_count` |
|
||||
| profile_values<sup>1</sup> | ?object | Profile values before a reset |
|
||||
| network_blocks<sup>2</sup> | array[object] | The decision's [network blocks](#network-blocks) that have not expired, each with `ip` and `expires_at` |
|
||||
| notice | object | The notice as the holder sees it now, in English |
|
||||
|
||||
<sup>1</sup> Message content, attachment details and profile values are null without `message:lookup`. Evidence and notes are deleted a year after the decision, and the decision itself is kept
|
||||
|
||||
<sup>2</sup> Each `ip` is masked to its /24 or /64 without `user:view:ip`
|
||||
|
||||
## List enforcement decisions
|
||||
|
||||
<RouteHeader method="GET" path="/v1/admin/enforcement-decisions" />
|
||||
|
||||
Lists the decisions about one account or community, newest first, without notes or evidence, or the [review queue](#automated-decisions-and-the-review-queue). Requires `enforcement:view`.
|
||||
|
||||
### Query parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| review_queue?<sup>1</sup> | boolean | List the automated decisions waiting for a person instead of one subject |
|
||||
| subject_type? | string | `user` or `guild` |
|
||||
| subject_id? | snowflake | The ID of the account or community |
|
||||
| limit? | integer | Maximum number of decisions (1-100, default 50) |
|
||||
| before? | snowflake | Return decisions older than this decision |
|
||||
|
||||
<sup>1</sup> Without `review_queue=true`, `subject_type` and `subject_id` are required
|
||||
|
||||
### Response body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| decisions | array[[enforcement decision](#enforcement-decision-object) object] | The decisions, without the fields only the single read returns |
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | response body | The decisions were listed |
|
||||
|
||||
### Side effects
|
||||
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `list_enforcement_decisions` and the metadata keys `list` and `result_count`. A subject list has the subject as its target and `list` `subject`. The review queue has target type `enforcement_decision`, target ID 0 and `list` `review_queue`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
200 requests per minute for each authenticated user, on the `admin:lookup` bucket.
|
||||
|
||||
## Get enforcement decision
|
||||
|
||||
<RouteHeader method="GET" path="/v1/admin/enforcement-decisions/{decision_id}" />
|
||||
|
||||
Returns one decision with its notes, its evidence and the notice the holder sees now. Requires `enforcement:view`.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| decision_id | snowflake | The ID of the decision |
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | [enforcement decision](#enforcement-decision-object) object | The decision was found |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_ENFORCEMENT_DECISION`, because no decision has this ID |
|
||||
|
||||
### Side effects
|
||||
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `view_enforcement_decision`, target type `enforcement_decision`, and the metadata keys `decision_id` and `kind`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
200 requests per minute for each authenticated user, on the `admin:lookup` bucket.
|
||||
|
||||
## Add a note to an enforcement decision
|
||||
|
||||
<RouteHeader method="POST" path="/v1/admin/enforcement-decisions/{decision_id}/notes" auditReason />
|
||||
|
||||
Appends an internal note. Requires `enforcement:annotate`. Notes are staff only and never change.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| decision_id | snowflake | The ID of the decision |
|
||||
|
||||
### JSON body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| body | string | The note (1-2000 characters) |
|
||||
|
||||
### Response body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| note_id | snowflake | The ID of the note |
|
||||
| author_id | snowflake | The staff member who wrote it |
|
||||
| body | string | The note |
|
||||
| created_at | ISO8601 timestamp | When it was written |
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | response body | The note was added |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_ENFORCEMENT_DECISION`, because no decision has this ID |
|
||||
|
||||
### Side effects
|
||||
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `annotate_enforcement_decision`, target type `enforcement_decision`, and the metadata keys `decision_id` and `note_id`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
100 requests per minute for each authenticated user, on the `admin:user:modify` bucket.
|
||||
|
||||
## End an enforcement decision
|
||||
|
||||
<RouteHeader method="POST" path="/v1/admin/enforcement-decisions/{decision_id}/end" auditReason />
|
||||
|
||||
Ends an active decision, or reverses a suspension that ended as `expired`, undoes what can be undone and emails the holder. Requires `enforcement:view` and the ACL of the route that made the decision, `user:temp_ban` for a suspension, `user:delete` for a closure, and `user:update:flags` for a warning or a feature limit, plus `ban:ip:remove` when the decision has network blocks. Returns the ended decision.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| decision_id | snowflake | The ID of the decision |
|
||||
|
||||
### JSON body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| outcome<sup>1</sup> | string | `lifted` or `reversed` |
|
||||
| statement? | string | Note to the user, sent in the email (1-500 characters) |
|
||||
|
||||
<sup>1</sup> `lifted` ends a restriction that was correct. `reversed` says the decision was wrong. Only suspensions, closures and feature limits can be lifted. Every other kind can only be reversed, because there is nothing to lift. A suspension that ended as `expired` can only be reversed
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | [enforcement decision](#enforcement-decision-object) object | The decision ended |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY` with `ENFORCEMENT_OUTCOME_NOT_ALLOWED`, because a warning was given `lifted` |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_ENFORCEMENT_DECISION`, because no decision has this ID |
|
||||
| 409 | [error response](/admin-api/#error-response) | `ENFORCEMENT_DECISION_ENDED`, because the decision already ended, or ended as `expired` and was given `lifted` |
|
||||
| 409 | [error response](/admin-api/#error-response) | `ENFORCEMENT_DECISION_REPLACED`, because a later suspension or closure replaced this one |
|
||||
| 409 | [error response](/admin-api/#error-response) | `ENFORCEMENT_DELETION_STARTED`, because the deletion of a closed account has started |
|
||||
|
||||
### Side effects
|
||||
|
||||
Ending a suspension clears `temp_banned_until`, `enforcement_decision_id` and the `DISABLED` flag, and lifts the new conversation limit, as [Lift a suspension without a decision](/admin-api/users/#lift-a-suspension-without-a-decision) does for older suspensions. Ending a closure cancels the deletion, clears `DELETED`, `deletion_reason_code` and `enforcement_decision_id`, and unblocks the account's email address when the closure blocked it. Ending either removes the decision's network blocks. Ending a feature limit removes its flag unless another active decision holds the same limit, and takes the decision out of the review queue. Ending a warning changes nothing on the account.
|
||||
|
||||
The holder is emailed that the restriction ended or was reversed, with the statement when one is given. The ended notice of every kind except a suspension or closure is also sent as a system DM.
|
||||
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `end_enforcement_decision`, target type `enforcement_decision`, and the metadata keys `decision_id`, `kind`, `outcome` and `subject_id`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
100 requests per minute for each authenticated user, on the `admin:user:modify` bucket.
|
||||
@@ -413,6 +413,14 @@ Permanently deletes a guild and every record it owns. Requires `guild:delete`. T
|
||||
| --- | --- | --- |
|
||||
| guild_id | snowflake | The ID of the guild |
|
||||
|
||||
### JSON body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| basis<sup>1</sup> | [basis](/admin-api/enforcement/#basis) object | `enforcement` when a rule was broken, `holder_request` when the owner asked. `housekeeping` fails body validation |
|
||||
|
||||
<sup>1</sup> The `enforcement` basis records a community removal [decision](/admin-api/enforcement/) before the guild is deleted and sends its notice to the owner by email and system DM. Its evidence must be posted in the guild, and its linked reports must name it. Restricted reasons are refused with `ENFORCEMENT_KIND_NOT_ALLOWED`, because the deletion destroys the content
|
||||
|
||||
### Response body
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -424,6 +432,7 @@ Permanently deletes a guild and every record it owns. Requires `guild:delete`. T
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | response body | Guild was deleted |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY`, because the basis or the [decision](/admin-api/enforcement/#validation) is invalid |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_GUILD`, because the guild does not exist |
|
||||
|
||||
:::danger[Archive content before deleting the guild]
|
||||
@@ -434,7 +443,7 @@ The guild, its channels, roles, memberships, messages, and attachments are destr
|
||||
|
||||
Members receive [Guild Delete](/gateway/events/#guild-delete). The guild disappears from their [settings and folders](/http-api/users/settings/) and from search. Its invites, webhooks, messages, attachments and discovery application are also deleted.
|
||||
|
||||
One Admin audit entry is recorded with the action `delete_guild`, the target type `guild`, and the guild ID in both the target and the metadata.
|
||||
One Admin audit entry is recorded with the action `delete_guild`, the target type `guild`, the guild ID in both the target and the metadata, and the `basis` metadata key. The `enforcement` basis adds `decision_id`, `reason_code`, `kind` and `notice_status`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -214,6 +214,7 @@ An entry has no request method, request path, status, duration, IP address, or f
|
||||
| bulk_job | Background job |
|
||||
| admin_api_key | Admin API key |
|
||||
| audit_log | Admin audit entry |
|
||||
| enforcement_decision | Enforcement decision |
|
||||
| archive<sup>2</sup> | Archive list |
|
||||
| admin_acl<sup>2</sup> | Admin ACL registry |
|
||||
| blocklist<sup>2</sup> | Blocklist catalogue |
|
||||
@@ -280,6 +281,8 @@ An entry with one of these actions has `access` set to `read`.
|
||||
| list_discovery_categories | The discovery categories were read |
|
||||
| list_discovery_category_listings | A page of the discovery listings in one category was read |
|
||||
| list_discovery_listings | Approved discovery listings were read |
|
||||
| list_enforcement_decisions | The enforcement decisions about an account or community, or the review queue, were listed |
|
||||
| list_network_block_candidates | The networks an account used in the last 30 days were listed, with how many other accounts share each |
|
||||
| list_guild_applications | The applications whose bots are members of a guild were read |
|
||||
| list_guild_audit_logs | The audit log of a guild was read |
|
||||
| list_guild_emojis | The custom emojis of a guild were listed |
|
||||
@@ -304,6 +307,7 @@ An entry with one of these actions has `access` set to `read`.
|
||||
| search_messages | The messages of a channel were searched, or one message was looked up |
|
||||
| search_reports | Reports were listed or searched |
|
||||
| search_users | Accounts were listed or searched |
|
||||
| view_enforcement_decision | An enforcement decision was read |
|
||||
|
||||
#### Write actions
|
||||
|
||||
@@ -311,7 +315,7 @@ An entry with any other action has `access` set to `write`.
|
||||
|
||||
| Value | Description |
|
||||
| --- | --- |
|
||||
| annotate_ban | A note was appended to the current ban of an account |
|
||||
| annotate_enforcement_decision | A note was added to an enforcement decision |
|
||||
| approve_discovery_application | A discovery application was approved |
|
||||
| approve_registration | A pending registration was approved |
|
||||
| auto_resolve_reports_on_deletion | The pending reports against an account were processed as part of its deletion |
|
||||
@@ -354,9 +358,11 @@ An entry with any other action has `access` set to `write`.
|
||||
| delete_voice_server | A registered voice server was deleted |
|
||||
| delete_webauthn_credential | A WebAuthn credential was removed from an account |
|
||||
| disable_mfa | Multi-factor authentication was disabled on an account |
|
||||
| end_enforcement_decision | An enforcement decision was ended as lifted or reversed |
|
||||
| force_add_to_guild | An account was added to a guild while bypassing the ban check |
|
||||
| generate_gift_codes | Gift codes were generated |
|
||||
| kick_member | A member was removed from a guild |
|
||||
| limit_features | A feature limit decision was recorded for an account, by staff or by Fluxer after a payment fraud signal |
|
||||
| purge_asset | A stored media asset was purged |
|
||||
| purge_guild_emoji_asset | A guild emoji asset was purged |
|
||||
| purge_guild_sticker_asset | A guild sticker asset was purged |
|
||||
@@ -385,13 +391,13 @@ An entry with any other action has `access` set to `write`.
|
||||
| set_traits | Account traits were replaced |
|
||||
| shutdown_guild | One guild was stopped on the main Gateway |
|
||||
| system_dm.send | A system DM broadcast was created |
|
||||
| temp_ban | A temporary or permanent account ban was applied |
|
||||
| temp_ban | An account was suspended for a time or permanently |
|
||||
| terminate_sessions | Every login session of an account was revoked |
|
||||
| test_smtp_connection | An SMTP connection test was run |
|
||||
| transfer_ownership<sup>1</sup> | Guild ownership or OAuth2 application ownership was transferred |
|
||||
| trigger_guild_archive | A guild archive was created |
|
||||
| trigger_user_archive | A user archive was created |
|
||||
| unban | An account ban was lifted |
|
||||
| unban | A suspension without an enforcement decision was lifted |
|
||||
| unban_avatar_hash | An avatar hash was removed from the avatar blocklist |
|
||||
| unban_email | An address was removed from the email blocklist |
|
||||
| unban_file_sha | A file hash was removed from the attachment blocklist |
|
||||
@@ -416,6 +422,7 @@ An entry with any other action has `access` set to `write`.
|
||||
| update_voice_server | A registered voice server was updated |
|
||||
| upload_branding_asset | An instance branding asset was uploaded or cleared |
|
||||
| verify_email | An email address was marked verified |
|
||||
| warn_user | A warning decision was recorded for an account |
|
||||
|
||||
<sup>1</sup> The same action name is recorded for two different resources, so `target_type` tells them apart
|
||||
|
||||
@@ -553,6 +560,8 @@ The registry is returned in this order by [List ACLs](#list-acls). A value outsi
|
||||
| bulk:update:user_flags | Queues the bulk account flag task |
|
||||
| discovery:remove | Removes a guild from [discovery](/admin-api/discovery/) |
|
||||
| discovery:review | Reads, reviews, and edits discovery applications, categories, and listings |
|
||||
| enforcement:annotate | Adds notes to [enforcement decisions](/admin-api/enforcement/) |
|
||||
| enforcement:view | Reads [enforcement decisions](/admin-api/enforcement/), and is needed with the kind's own ACL to end one |
|
||||
| gateway:memory_stats | Reads main [Gateway](/admin-api/gateway/) statistics, memory statistics, and voice state counts |
|
||||
| gateway:reload_all | Reloads guilds across the main Gateway |
|
||||
| gift_codes:generate | Generates [gift codes](/admin-api/gift-codes/) |
|
||||
@@ -598,7 +607,7 @@ The registry is returned in this order by [List ACLs](#list-acls). A value outsi
|
||||
| user:view:dob<sup>6</sup> | Unredacts user dates of birth |
|
||||
| user:view:email<sup>6</sup> | Unredacts user email addresses and email verification state |
|
||||
| user:view:ip<sup>6</sup> | Unredacts the last active IP address and the location derived from it |
|
||||
| user:temp_ban | Applies and removes an account ban |
|
||||
| user:temp_ban | Suspends accounts and lifts suspensions |
|
||||
| user:update:bot_status | Updates bot and system account state |
|
||||
| user:update:dob | Updates dates of birth |
|
||||
| user:update:email | Updates the email address, marks the address verified, resends verification, and sends a password reset |
|
||||
|
||||
@@ -233,6 +233,8 @@ One [Admin audit entry](/admin-api/#admin-audit-entry-object) is recorded with t
|
||||
|
||||
Permanently deletes one message and purges the attachments it owned. Requires `message:delete`.
|
||||
|
||||
This route is for removals that involve nobody's conduct, such as cleanup of webhook or system messages, or a holder's request. To remove a message for breaking a rule, [shred it](/admin-api/users/#shred-user-messages) with the `enforcement` basis, so a decision records the removal and the author is told.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -240,6 +242,12 @@ Permanently deletes one message and purges the attachments it owned. Requires `m
|
||||
| channel_id | snowflake | The ID of the channel |
|
||||
| message_id | snowflake | The ID of the message |
|
||||
|
||||
### JSON body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| basis | [basis](/admin-api/enforcement/#basis) object | `holder_request` or `housekeeping`. The `enforcement` basis fails body validation |
|
||||
|
||||
### Response body
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -266,7 +274,7 @@ The message and its attachments are permanently removed, including from search r
|
||||
|
||||
A deleted message in a thread no longer counts towards `message_count` of the [thread object](/http-api/threads/#thread-object). Fluxer emits no [Thread Update](/gateway/threads/#thread-update) for the change.
|
||||
|
||||
One [Admin audit entry](/admin-api/#admin-audit-entry-object) is recorded with the action `delete_message` and the target type `message`, targeting the message ID. Its metadata is `channel_id` and `message_id`. The entry is written whether or not a message was found.
|
||||
One [Admin audit entry](/admin-api/#admin-audit-entry-object) is recorded with the action `delete_message` and the target type `message`, targeting the message ID. Its metadata is `channel_id`, `message_id` and `basis`. The entry is written whether or not a message was found.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -676,15 +676,23 @@ Resolves one report and returns a [report resolution](#report-resolution-object)
|
||||
| status | resolved | The status to move the report to |
|
||||
| public_comment?<sup>1</sup> | string | The comment shown to the reporter (0-512 characters) |
|
||||
| notify_reporter?<sup>2</sup> | boolean | Whether the notice to the reporter includes `public_comment` (default true) |
|
||||
| resolution?<sup>3</sup> | string | How the report was resolved, one of `actioned`, `no_violation`, or `duplicate` |
|
||||
| resolution<sup>3</sup> | string | How the report was resolved, one of `actioned`, `no_violation`, or `duplicate` |
|
||||
| outcome?<sup>4</sup> | string | What happened to the reported item, `removed`, `restricted`, `action_taken`, or `no_violation` |
|
||||
| duplicate_of?<sup>5</sup> | snowflake | The report this one repeats |
|
||||
|
||||
<sup>1</sup> Stored as null when it is omitted or empty. Otherwise it is stored, and the resolution notice shows it to the reporter unless `notify_reporter` is false
|
||||
|
||||
<sup>2</sup> The reporter is notified either way. False sends the generic notice without the comment, and the comment is still stored on the report
|
||||
<sup>2</sup> The reporter is notified either way. False sends the notice without the comment, and the comment is still stored on the report
|
||||
|
||||
<sup>3</sup> Optional, so older admin clients that send none still work. It is not stored on the report, not returned by any report route, and not shown to the reporter. It is recorded in the audit entry
|
||||
<sup>3</sup> Not stored on the report and not returned by any report route. It is recorded in the audit entry
|
||||
|
||||
The admin panel always sends `resolution`. Its resolve form has a required Resolution select with three choices: Action taken (`actioned`), No violation (`no_violation`), and Duplicate (`duplicate`). The browser refuses to submit the form without a choice, and the panel checks again on the server: a submission with no choice, or with any other value, is refused with the error "Choose a resolution" and the report stays pending. After a resolve, the panel confirms the choice in its message, such as "Report resolved: No violation". The report page itself does not show the choice, because no report route returns it. Staff find it in the `resolution` metadata of the `resolve_report` entry in the audit log.
|
||||
<sup>4</sup> Required for `actioned`, which refuses `no_violation`. Optional for `no_violation`, which sets `no_violation` and refuses any other value. Refused for `duplicate`. A mistake fails with `REPORT_OUTCOME_INVALID`. `removed` means the reported content, profile field or community was removed. `restricted` means the reported account was restricted and the item was left up. `action_taken` means Fluxer acted under its rules in another way, such as a warning, and the reporter is not told how. `no_violation` means the item breaks no rule and was left as it is. The outcome is stored on the report and the reporter is told it
|
||||
|
||||
<sup>5</sup> Required for `duplicate` and refused otherwise, as is a report naming itself, with `REPORT_OUTCOME_INVALID`. The duplicate takes the outcome stored on that report and the reporter is told it. While that report has no outcome, because it is still pending or was resolved before outcomes were stored, the duplicate is resolved without a notice to its reporter. An unknown report returns 404 `UNKNOWN_REPORT`
|
||||
|
||||
The admin panel's resolve form has a required Resolution select with three choices: Action taken (`actioned`), No violation (`no_violation`), and Duplicate (`duplicate`). Action taken also shows a required Outcome select: Content removed (`removed`), Account restricted (`restricted`), and Other action taken (`action_taken`). No violation sends the outcome `no_violation`. Duplicate shows a required Duplicate of field for the report ID. The panel refuses a submission without a resolution, without an outcome for Action taken, or without a report ID for Duplicate, and the report stays pending. After a resolve, the panel confirms the choice in its message, such as "Report resolved: No violation". Staff find the choice in the `resolution` and `outcome` metadata of the `resolve_report` entry in the audit log.
|
||||
|
||||
A [decision](/admin-api/enforcement/) that links pending reports resolves them as `actioned` itself, with the outcome its kind implies, so no separate update is needed.
|
||||
|
||||
:::caution[Resolution is terminal]
|
||||
Resolving a report notifies the reporter whatever `notify_reporter` says, as the side effects below describe. A resolved report never returns to PENDING, and a second update against it returns 400 `REPORT_ALREADY_RESOLVED` without changing the stored public comment.
|
||||
@@ -696,15 +704,16 @@ Resolving a report notifies the reporter whatever `notify_reporter` says, as the
|
||||
| --- | --- | --- |
|
||||
| 200 | [report resolution](#report-resolution-object) object | Report was resolved |
|
||||
| 400 | [error response](/admin-api/#error-response) | The report is already resolved and the request returns `REPORT_ALREADY_RESOLVED` |
|
||||
| 404 | [error response](/admin-api/#error-response) | Report does not exist and the request returns `UNKNOWN_REPORT` |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY`, because `resolution` is missing or the outcome or `duplicate_of` does not match it |
|
||||
| 404 | [error response](/admin-api/#error-response) | The report or the `duplicate_of` report does not exist and the request returns `UNKNOWN_REPORT` |
|
||||
|
||||
### Side effects
|
||||
|
||||
The report moves to RESOLVED and records `resolved_at`, `resolved_by_admin_id`, and the public comment.
|
||||
The report moves to RESOLVED and records `resolved_at`, `resolved_by_admin_id`, the public comment and the outcome.
|
||||
|
||||
Fluxer records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with target type `report`, target ID equal to the report ID, and action `resolve_report`. The metadata is `report_id`, `report_type`, `notify_reporter`, `reporter_dm_sent`, `reporter_email_sent`, and `reporter_dsa_email_sent`, plus `resolution` when the request sets one. When Fluxer does not attempt a notice, `reporter_notice_skipped` names the reason: `reporter_account_deleted`, `reporter_report_banned`, `reporter_lookup_failed`, or `no_reporter_email`.
|
||||
Fluxer records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with target type `report`, target ID equal to the report ID, and action `resolve_report`. The metadata is `report_id`, `report_type`, `notify_reporter`, `reporter_dm_sent`, `reporter_email_sent`, `reporter_dsa_email_sent`, `resolution`, `duplicate_of` for a duplicate, and `outcome`, which a duplicate of a report with no outcome leaves out. When Fluxer does not attempt a notice, `reporter_notice_skipped` names the reason: `reporter_account_deleted`, `reporter_report_banned`, `reporter_lookup_failed`, `no_reporter_email`, or `duplicate_outcome_unknown`.
|
||||
|
||||
Fluxer notifies the reporter of every resolution. A reporter with an account receives a system direct message, and an email when the account has an address. A Digital Services Act notice has no reporter account, so its verified address receives a decision email with the report ID, the comment when one is shared, and how to appeal. Nothing is sent when the reporter account is deleted or barred from reporting.
|
||||
Fluxer tells the reporter the decision about the item they reported: that the content was removed, that the account was restricted without saying how, that action was taken without saying what, or that it broke no rule and was left as it is. A reporter with an account receives a system direct message, and an email when the account has an address. A Digital Services Act notice has no reporter account, so its verified address receives the same decision by email. Each notice gives the report ID, the comment when one is shared, and on hosted instances how to appeal within 60 days and the EU redress routes. It never names a decision, a sanction or the other account's history. Nothing is sent when the reporter account is deleted, or was barred from reporting when the report was filed.
|
||||
|
||||
A reporter who does not already have the system channel open receives [Channel Create](/gateway/events/#channel-create), and every session of the reporter receives [Message Create](/gateway/events/#message-create). Fluxer logs a delivery failure, and the request still succeeds.
|
||||
|
||||
|
||||
@@ -55,10 +55,11 @@ When the caller lacks `user:view:email`, `user:view:dob`, or `user:view:ip`, Flu
|
||||
| pending_deletion_at | ?ISO8601 timestamp | The time the scheduled deletion runs, or null when none is scheduled |
|
||||
| pending_bulk_message_deletion_at<sup>6</sup> | ?ISO8601 timestamp | The time the account's own scheduled bulk message deletion runs |
|
||||
| deletion_reason_code | ?integer | [Deletion reason](#deletion-reasons) |
|
||||
| deletion_public_reason | ?string | The reason shown to the account holder, or null when none was supplied |
|
||||
| deletion_public_reason | ?string | The reason shown to the holder of a deletion scheduled before enforcement decisions existed. No longer written |
|
||||
| deletion_audit_log_reason | ?string | The private reason stored with the pending deletion, and null without `audit_log:view` |
|
||||
| deletion_scheduled_by | ?snowflake | The ID of the account that scheduled the pending deletion, or null when it was not recorded |
|
||||
| deletion_scheduled_at | ?ISO8601 timestamp | The time the pending deletion was scheduled, or null when it was not recorded |
|
||||
| enforcement_decision_id | ?snowflake | The [enforcement decision](/admin-api/enforcement/) behind the current suspension, or null when none owns it |
|
||||
| acls<sup>7</sup> | array[string] | Effective [Admin ACLs](/admin-api/#acl-registry), with at most 106 entries |
|
||||
| traits<sup>8</sup> | array[string] | The free-form operator labels set on the account, with at most 100 entries |
|
||||
| has_totp<sup>9</sup> | boolean | Whether a TOTP authenticator is registered |
|
||||
@@ -76,7 +77,7 @@ When the caller lacks `user:view:email`, `user:view:dob`, or `user:view:ip`, Flu
|
||||
|
||||
<sup>4</sup> Requires `user:view:dob`, and without it the field is null
|
||||
|
||||
<sup>5</sup> Set by [Ban user](#ban-user) for both ban modes. An expiry that has already passed is reported as null, so an elapsed temporary ban reads the same as no ban
|
||||
<sup>5</sup> Set by [Suspend user](#suspend-user) for both modes. An expiry that has already passed is reported as null, so an elapsed temporary ban reads the same as no ban
|
||||
|
||||
<sup>6</sup> Written when the account holder schedules its own bulk message deletion, and cleared by [Cancel scheduled message deletion](#cancel-scheduled-message-deletion)
|
||||
|
||||
@@ -182,7 +183,7 @@ The `flags` field of the [Admin user object](#admin-user-object) is a 64-bit bit
|
||||
| 3 | SPAM | Spam |
|
||||
| 4 | CHEATING_OR_EXPLOITATION | Cheating or exploitation |
|
||||
| 5 | COORDINATED_RAIDING | Coordinated raiding |
|
||||
| 6 | AUTOMATION_OR_SELFBOT | Automation or selfbot use |
|
||||
| 6 | AUTOMATION_OR_SELFBOT | Disruptive automation |
|
||||
| 7 | NONCONSENSUAL_SEXUAL_CONTENT | Non-consensual sexual content |
|
||||
| 8 | SCAM_OR_SOCIAL_ENGINEERING | Scam or social engineering |
|
||||
| 9 | CHILD_SEXUAL_CONTENT | Child sexual content |
|
||||
@@ -199,10 +200,19 @@ The `flags` field of the [Admin user object](#admin-user-object) is a 64-bit bit
|
||||
| 20 | HATE_SPEECH_OR_EXTREMIST_CONTENT | Hate speech or extremist content |
|
||||
| 21 | MALICIOUS_LINKS_OR_MALWARE | Malicious links or malware |
|
||||
| 22 | IMPERSONATION_OR_FAKE_IDENTITY | Impersonation or fake identity |
|
||||
| 23 | THREATS_OR_VIOLENT_SPEECH | Threats or violent speech |
|
||||
| 24 | VIOLENT_EXTREMISM | Terrorism or violent extremism |
|
||||
| 25 | SELF_HARM_PROMOTION | Promoting self-harm |
|
||||
| 26 | SEXUAL_CONTENT_PLACEMENT | Sexual content outside 18+ spaces |
|
||||
| 27 | ILLEGAL_GOODS_OR_ACTIVITY | Illegal goods or activity |
|
||||
| 28 | HARMFUL_DECEPTION | Harmful deception |
|
||||
| 29 | REPORTING_ABUSE | Misuse of reporting |
|
||||
| 30 | UNDERAGE_ACCOUNT | Account holder under the minimum age |
|
||||
| 31 | COPYRIGHT | Copyright |
|
||||
|
||||
<sup>1</sup> The one value that lowers the minimum scheduling delay to 14 days and suppresses the identifier blocklisting and report auto-resolution described under [Schedule user deletion](#schedule-user-deletion)
|
||||
<sup>1</sup> Written by self-deletion and by [Schedule user deletion](#schedule-user-deletion) with the `holder_request` basis. Signing in cancels such a deletion
|
||||
|
||||
A `reason_code` request value outside this registry fails body validation with 400 `INVALID_FORM_BODY`.
|
||||
Values 1 (`USER_REQUESTED`), 2 (`OTHER`) and 19 (`INACTIVITY`) are not enforcement reasons. Every other value is an enforcement reason, and only enforcement reasons are accepted in an [enforcement decision input](/admin-api/enforcement/#enforcement-decision-input). A `reason_code` outside them fails body validation with 400 `INVALID_FORM_BODY`.
|
||||
|
||||
## Profile fields
|
||||
|
||||
@@ -1005,9 +1015,12 @@ Clearing is the only profile change on this resource. No route sets a biography,
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| fields<sup>1</sup> | array[string] | [Profile fields](#profile-fields) to clear, with at most 10 entries |
|
||||
| fields<sup>1</sup> | array[string] | [Profile fields](#profile-fields) to clear, with 1 to 10 entries |
|
||||
| basis<sup>2</sup> | [basis](/admin-api/enforcement/#basis) object | Why the fields are cleared |
|
||||
|
||||
<sup>1</sup> Required. An empty array is accepted, clears nothing, and still records an audit entry. A value outside the registry fails body validation
|
||||
<sup>1</sup> Required. A value outside the registry fails body validation
|
||||
|
||||
<sup>2</sup> The `enforcement` basis saves the current values of the fields as the evidence's `profile_values`, records a profile reset [decision](/admin-api/enforcement/) and sends its notice by email and system DM. The cleared fields are its facts, so it needs neither evidence nor a statement
|
||||
|
||||
### Response body
|
||||
|
||||
@@ -1020,6 +1033,7 @@ Clearing is the only profile change on this resource. No route sets a biography,
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | response body | The named fields were cleared |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY`, because the body or the [decision](/admin-api/enforcement/#validation) is invalid |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the account does not exist |
|
||||
|
||||
### Side effects
|
||||
@@ -1028,7 +1042,7 @@ Clearing `avatar` or `banner` schedules the previous asset for deletion.
|
||||
|
||||
[User Update](/gateway/events/#user-update) is emitted to the account's own sessions, and each guild the account is a member of receives [Guild Member Update](/gateway/events/#guild-member-update) when the operation changes `avatar` or `global_name`.
|
||||
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `clear_fields`, target type `user`, and a metadata key `fields` with the submitted names joined by commas.
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `clear_fields`, target type `user`, a metadata key `fields` with the submitted names joined by commas, and `basis`. The `enforcement` basis adds `decision_id`, `reason_code`, `kind` and `notice_status`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -1235,6 +1249,8 @@ Adds and removes [account flags](#account-flags) and returns the resulting accou
|
||||
|
||||
Additions are applied before removals, so a flag named in both arrays ends up cleared. A bit outside the [account flag](#account-flags) registry is applied unchanged.
|
||||
|
||||
Flags that record a restriction change only through their own routes. Adding `DISABLED`, `DELETED`, `SPAMMER` or `REPORT_BANNED`, removing `DELETED`, and removing `DISABLED` while the account is suspended are refused. Removing `SPAMMER` or `REPORT_BANNED` while an active [feature limit](#limit-user-features) holds it is refused with the decision ID. A self-disabled account, which has `DISABLED` without a suspension, can still have it removed.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -1261,7 +1277,9 @@ Additions are applied before removals, so a flag named in both arrays ends up cl
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | response body | Flags were updated |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY` with `FLAG_REQUIRES_ENFORCEMENT_ROUTE`, because the change touches a restriction flag |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the account does not exist |
|
||||
| 409 | [error response](/admin-api/#error-response) | `ENFORCEMENT_DECISION_OWNS_STATE` with the `decision_id`, because a feature limit holds the flag |
|
||||
|
||||
[Bulk jobs](/admin-api/bulk-jobs/) applies the same change to up to 1,000 accounts as a queued `update_user_flags` task.
|
||||
|
||||
@@ -1285,6 +1303,8 @@ Adds and removes [premium flags](#premium-flags) and returns the resulting accou
|
||||
|
||||
Premium flags control badge display, the premium override, the purchase block, and perk sanitisation. They do not grant or revoke a subscription.
|
||||
|
||||
Adding `PURCHASE_DISABLED` is refused, because a purchase block is recorded through [Limit user features](#limit-user-features). Removing it while an active feature limit holds it is refused with the decision ID. A purchase block Fluxer set after repeated refunds has no decision and can be removed.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -1311,7 +1331,9 @@ Premium flags control badge display, the premium override, the purchase block, a
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | response body | Premium flags were updated |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY` with `FLAG_REQUIRES_ENFORCEMENT_ROUTE`, because `PURCHASE_DISABLED` was added |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the account does not exist |
|
||||
| 409 | [error response](/admin-api/#error-response) | `ENFORCEMENT_DECISION_OWNS_STATE` with the `decision_id`, because a feature limit holds the purchase block |
|
||||
|
||||
### Side effects
|
||||
|
||||
@@ -1392,14 +1414,14 @@ The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-obje
|
||||
|
||||
100 requests per minute for each authenticated user, on the `admin:user:modify` bucket.
|
||||
|
||||
## Ban user
|
||||
## Suspend user
|
||||
|
||||
<RouteHeader method="PUT" path="/v1/admin/users/{user_id}/ban" auditReason />
|
||||
|
||||
Disables an account temporarily or permanently and returns the resulting account. Requires `user:temp_ban`.
|
||||
Suspends an account for a number of hours or permanently, records the [enforcement decision](/admin-api/enforcement/) behind it, and returns the resulting account. Requires `user:temp_ban`. The `X-Audit-Log-Reason` header is required and becomes the first note on the decision.
|
||||
|
||||
:::note[A permanent ban is a distant expiry]
|
||||
Both modes add the `DISABLED` [account flag](#account-flags) and set `temp_banned_until`, a permanent ban to 100 years out. The field cannot tell the modes apart, and [Unban user](#unban-user) clears either one.
|
||||
:::note[A permanent suspension is a distant expiry]
|
||||
Both modes add the `DISABLED` [account flag](#account-flags) and set `temp_banned_until`, a permanent suspension to 100 years out. The decision's `ends_at` is null for a permanent suspension, which is how the two are told apart.
|
||||
:::
|
||||
|
||||
### Path parameters
|
||||
@@ -1412,54 +1434,102 @@ Both modes add the `DISABLED` [account flag](#account-flags) and set `temp_banne
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| duration_hours<sup>1</sup> | integer | Whole-hour ban length, where `0` bans permanently (0-8760) |
|
||||
| reason?<sup>2</sup> | string | Statement of reasons sent to the banned account holder (at most 512 characters) |
|
||||
| notify_user?<sup>3</sup> | boolean | Whether to email the account holder about a temporary ban (default true) |
|
||||
| duration_hours<sup>1</sup> | integer | Whole-hour suspension length, where `0` suspends permanently (0-8760) |
|
||||
| decision | [enforcement decision input](/admin-api/enforcement/#enforcement-decision-input) object | Why the account is suspended |
|
||||
| remove_evidence?<sup>2</sup> | boolean | Whether to remove the evidence messages after they are snapshotted |
|
||||
| block_networks?<sup>3</sup> | [network block request](#network-block-request) object | Networks the account used to block with the decision |
|
||||
|
||||
<sup>1</sup> Required. The expiry is computed from the moment the ban is applied, so no absolute-expiry field is accepted. The 8760 ceiling is one year
|
||||
<sup>1</sup> Required. The expiry is computed from the moment the suspension is applied. The 8760 ceiling is one year
|
||||
|
||||
<sup>2</sup> Included in the temporary ban email and in the audit entry, but not stored on the account, so no read operation returns it
|
||||
<sup>2</sup> Requires `message:shred`. Queues a [message shred job](#shred-user-messages) for the evidence messages and sets the decision's `removed_message_count`
|
||||
|
||||
<sup>3</sup> A permanent ban sends no email whatever this field says
|
||||
<sup>3</sup> Permanent suspensions only. Requires `ban:ip:add` and `user:view:ip`
|
||||
|
||||
### Network block request
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| ips<sup>1</sup> | array[string] | IPv4 or IPv6 addresses to block (1-20 items) |
|
||||
| days? | integer | How many days the blocks last (1-365, default 90) |
|
||||
|
||||
<sup>1</sup> Each address must be one the account used in the last 30 days, as [List network block candidates](#list-network-block-candidates) shows. An address on which another account that is not suspended or closed was last active fails with `ENFORCEMENT_NETWORK_SHARED`. Exempt addresses and special-purpose ranges are skipped, not refused
|
||||
|
||||
### Response body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| user | [Admin user](#admin-user-object) object | The resulting account |
|
||||
| user | [Admin user](#admin-user-object) object | The resulting account, with `enforcement_decision_id` set |
|
||||
| skipped_ips | array[object] | Requested addresses that were not blocked, each with `ip` and `reason` |
|
||||
|
||||
`reason` is `exempt`, `special_purpose`, or `already_blocked` when a permanent plain block or another decision already covers the address. [Network blocks](/admin-api/enforcement/#network-blocks) describes the rules.
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | response body | The account was banned |
|
||||
| 200 | response body | The account was suspended |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY`, with one of the [validation codes](/admin-api/enforcement/#validation) |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the account does not exist |
|
||||
| 409 | [error response](/admin-api/#error-response) | `ENFORCEMENT_ACCOUNT_CLOSED`, because the account is closed |
|
||||
|
||||
:::caution[The ban signs the account out everywhere]
|
||||
Every authentication session of the account is deleted. [Unban user](#unban-user) restores account access, but it does not restore any deleted session.
|
||||
:::caution[The suspension signs the account out everywhere]
|
||||
Every authentication session of the account is deleted. Ending the decision restores account access, but it does not restore any deleted session.
|
||||
:::
|
||||
|
||||
### Side effects
|
||||
|
||||
`DISABLED` is added to the account flags and `temp_banned_until` is set to the resolved expiry. Every authentication session is then deleted, so the account is signed out on every device.
|
||||
The decision is stored first. `DISABLED` is then added to the account flags, and `temp_banned_until` and `enforcement_decision_id` are set in the same write. Every authentication session is deleted, the requested networks are blocked, linked reports are resolved, and the notice is emailed. A suspension of an account that is already suspended replaces it, and the earlier decision reads as `replaced`.
|
||||
|
||||
An authentication attempt while the ban stands fails with 403 `ACCOUNT_SUSPENDED_TEMPORARILY`. An attempt after the expiry has passed clears the `DISABLED` flag and `temp_banned_until` in the same request, so a temporary ban ends without an Admin operation.
|
||||
An authentication attempt while the suspension stands fails with 403 `ACCOUNT_SUSPENDED_TEMPORARILY`, or `ACCOUNT_SUSPENDED_PERMANENTLY` for a suspension with no end date, and shows the notice. The notice of an account with no email address counts as received at its first such attempt. After the expiry, Fluxer clears `DISABLED`, `temp_banned_until` and `enforcement_decision_id` and ends the decision as `expired`. This happens at the next authentication attempt or in a scheduled task that runs every six hours, whichever comes first, and sends no email.
|
||||
|
||||
When `notify_user` is true, the account has an email address, and `duration_hours` is greater than zero, the account holder is emailed the duration, the expiry, and the supplied `reason`. A permanent ban sends no email. A failed email is logged and the ban still applies.
|
||||
[User Update](/gateway/events/#user-update) is emitted after the sessions are deleted, so no connection of the account remains to receive it.
|
||||
|
||||
[User Update](/gateway/events/#user-update) is emitted after the sessions are deleted, so no connection of the account remains to receive it. `DISABLED` is not a public account flag, and no other account observes the change.
|
||||
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `temp_ban`, target type `user`, and the metadata keys `duration_hours`, `reason`, `banned_until`, `notify_user`, and `notification_sent`.
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `temp_ban`, target type `user`, and the metadata keys `duration_hours`, `banned_until`, `decision_id`, `reason_code`, `kind` and `notice_status`. When networks were blocked it adds `blocked_ips` and `network_blocks_until`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
100 requests per minute for each authenticated user, on the `admin:user:modify` bucket.
|
||||
|
||||
## Unban user
|
||||
## List network block candidates
|
||||
|
||||
<RouteHeader method="GET" path="/v1/admin/users/{user_id}/network-block-candidates" />
|
||||
|
||||
Lists the addresses the account used in the last 30 days, most recent first, which are the addresses `block_networks` accepts on [Suspend user](#suspend-user) and [Schedule user deletion](#schedule-user-deletion). Requires `user:view:ip`.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| user_id | snowflake | The ID of the target account |
|
||||
|
||||
### Response body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| networks | array[object] | One entry per address, each with `ip`, `last_seen_at`, `skip_reason` and `other_unrestricted_accounts` |
|
||||
|
||||
`last_seen_at` comes from the account's last activity and its current and ended sessions. `skip_reason` is `exempt` or `special_purpose` for an address Fluxer never blocks, and null otherwise. `other_unrestricted_accounts` counts the other accounts that are not suspended or closed and were last active on the address, matching IPv6 by its /64. An address with a count above zero cannot be blocked.
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | response body | The addresses were listed |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the account does not exist |
|
||||
|
||||
### Side effects
|
||||
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `list_network_block_candidates`, target type `user`, and the metadata key `result_count`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
200 requests per minute for each authenticated user, on the `admin:lookup` bucket.
|
||||
|
||||
## Lift a suspension without a decision
|
||||
|
||||
<RouteHeader method="DELETE" path="/v1/admin/users/{user_id}/ban" auditReason />
|
||||
|
||||
Clears the account ban and returns the resulting account. Requires `user:temp_ban`.
|
||||
Lifts a suspension that no enforcement decision owns, such as one applied before decisions existed, and returns the resulting account. Requires `user:temp_ban`. A suspension with a decision is lifted by [ending the decision](/admin-api/enforcement/#end-an-enforcement-decision).
|
||||
|
||||
### Path parameters
|
||||
|
||||
@@ -1467,15 +1537,6 @@ Clears the account ban and returns the resulting account. Requires `user:temp_ba
|
||||
| --- | --- | --- |
|
||||
| user_id | snowflake | The ID of the target account |
|
||||
|
||||
### JSON body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| notify_user? | boolean | Whether to email the account holder that the ban was lifted (default true) |
|
||||
| public_reason?<sup>1</sup> | string | Statement shown to the account holder in the email (at most 512 characters) |
|
||||
|
||||
<sup>1</sup> Also recorded in the audit entry. The `X-Audit-Log-Reason` value is never emailed
|
||||
|
||||
### Response body
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -1486,28 +1547,27 @@ Clears the account ban and returns the resulting account. Requires `user:temp_ba
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200<sup>1</sup> | response body | The ban was cleared |
|
||||
| 200<sup>1</sup> | response body | The suspension was lifted |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the account does not exist |
|
||||
| 409 | [error response](/admin-api/#error-response) | `ENFORCEMENT_DECISION_OWNS_STATE` with the `decision_id`, because a decision owns the suspension |
|
||||
|
||||
<sup>1</sup> The operation checks no precondition. On an account that was never banned it writes `temp_banned_until` as null, clears `DISABLED`, and sends no email
|
||||
<sup>1</sup> On an account that was never suspended it writes `temp_banned_until` as null, clears `DISABLED`, and sends no email
|
||||
|
||||
### Side effects
|
||||
|
||||
`temp_banned_until` is cleared and `DISABLED` is removed from the account flags.
|
||||
`temp_banned_until` is cleared and `DISABLED` is removed from the account flags. Deleted sessions are not restored. When the suspension was still in force and the account is not closed, the holder is emailed that the restriction ended. The `X-Audit-Log-Reason` value is never emailed. [User Update](/gateway/events/#user-update) is emitted to the account's own sessions.
|
||||
|
||||
Deleted sessions are not restored. Fluxer emails the account holder, with `public_reason` when one is supplied, only when `notify_user` is true, the ban was still in force, the account has an email address, and the account is neither closed nor pending deletion. A failed email is logged and the ban is still cleared. [User Update](/gateway/events/#user-update) is emitted to the account's own sessions.
|
||||
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `unban`, target type `user`, and the metadata keys `notify_user`, `notification_sent`, and `public_reason`.
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `unban`, target type `user`, and the metadata key `ended_notice`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
100 requests per minute for each authenticated user, on the `admin:user:modify` bucket.
|
||||
|
||||
## Add a note to a user ban
|
||||
## Warn user
|
||||
|
||||
<RouteHeader method="POST" path="/v1/admin/users/{user_id}/ban/notes" />
|
||||
<RouteHeader method="POST" path="/v1/admin/users/{user_id}/warnings" auditReason />
|
||||
|
||||
Appends a note to the ban that currently stands on the account. Requires `user:temp_ban`. The ban entry and earlier notes are never changed.
|
||||
Records a warning decision and sends its notice by email and system DM. Requires `user:update:flags`. Nothing about the account changes. The `X-Audit-Log-Reason` header is required and becomes the first note on the decision.
|
||||
|
||||
### Path parameters
|
||||
|
||||
@@ -1519,23 +1579,75 @@ Appends a note to the ban that currently stands on the account. Requires `user:t
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| ban_audit_log_id<sup>1</sup> | snowflake | The ID of the `temp_ban` audit entry of the current ban |
|
||||
| note | string | The note (1-512 characters) |
|
||||
| decision<sup>1</sup> | [enforcement decision input](/admin-api/enforcement/#enforcement-decision-input) object | Why the account is warned |
|
||||
|
||||
<sup>1</sup> The entry must target this account, and its `banned_until` metadata must equal the current `temp_banned_until`
|
||||
<sup>1</sup> Reasons that allow only a suspension or content removal, such as child safety, refuse a warning with `ENFORCEMENT_KIND_NOT_ALLOWED`
|
||||
|
||||
### Response body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| decision_id | snowflake | The ID of the warning decision |
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 204 | none | The note was recorded |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY`, because `ban_audit_log_id` does not name a ban of this account |
|
||||
| 200 | response body | The warning was recorded |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY`, with one of the [validation codes](/admin-api/enforcement/#validation) |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the account does not exist |
|
||||
| 409 | [error response](/admin-api/#error-response) | `CONFLICT`, because the named ban is no longer the current ban |
|
||||
|
||||
### Side effects
|
||||
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `annotate_ban`, target type `user`, the note as `audit_log_reason`, and a metadata key `ban_audit_log_id`. The `X-Audit-Log-Reason` header is not read.
|
||||
Linked reports are resolved and the notice is sent. The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `warn_user`, target type `user`, and the metadata keys `decision_id`, `reason_code`, `kind` and `notice_status`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
100 requests per minute for each authenticated user, on the `admin:user:modify` bucket.
|
||||
|
||||
## Limit user features
|
||||
|
||||
<RouteHeader method="PUT" path="/v1/admin/users/{user_id}/feature-limits" auditReason />
|
||||
|
||||
Records an enforcement decision that limits one feature, applies its account flag and returns the resulting account. Requires `user:update:flags`. The `X-Audit-Log-Reason` header is required and becomes the first note on the decision. The [feature limits](/admin-api/enforcement/#feature-limits) table lists the flag and the reason codes of each limit.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| user_id | snowflake | The ID of the target account |
|
||||
|
||||
### JSON body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| limit | string | `direct_messages`, `reports` or `purchases` |
|
||||
| ends_at?<sup>1</sup> | ISO8601 timestamp | When a reports limit ends |
|
||||
| decision<sup>2</sup> | [enforcement decision input](/admin-api/enforcement/#enforcement-decision-input) object | Why the feature is limited |
|
||||
|
||||
<sup>1</sup> Reports only, at most 365 days away, and 90 days from now when omitted. Any other limit refuses it with `ENFORCEMENT_LIMIT_END_INVALID`
|
||||
|
||||
<sup>2</sup> A reason code that does not allow the limit fails with `ENFORCEMENT_LIMIT_NOT_ALLOWED`, and one that allows no feature limit at all with `ENFORCEMENT_KIND_NOT_ALLOWED`
|
||||
|
||||
### Response body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| user | [Admin user](#admin-user-object) object | The resulting account |
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | response body | The limit was recorded |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY`, with one of the [validation codes](/admin-api/enforcement/#validation) |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the account does not exist |
|
||||
|
||||
### Side effects
|
||||
|
||||
The decision is stored first and the flag is then added. Linked reports are resolved, and the notice is sent by email and system DM.
|
||||
|
||||
[User Update](/gateway/events/#user-update) is emitted to the account's own sessions. The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `limit_features`, target type `user`, and the metadata keys `limit`, `decision_id`, `reason_code`, `kind` and `notice_status`, plus `ends_at` for a reports limit.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -1545,11 +1657,13 @@ The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-obje
|
||||
|
||||
<RouteHeader method="PUT" path="/v1/admin/users/{user_id}/deletion" auditReason />
|
||||
|
||||
Creates an account deletion schedule and returns the resulting account. Requires `user:delete`. A pending schedule is replaced only when the request names it in `replace_pending_deletion_at`, and a schedule cannot be changed once erasure starts.
|
||||
Closes the account, schedules its deletion and returns the resulting account. Requires `user:delete`. The `basis` says why, and the [basis](/admin-api/enforcement/#basis) decides everything else. A pending schedule is replaced only when the request names it in `replace_pending_deletion_at`, and a schedule cannot be changed once erasure starts.
|
||||
|
||||
Fluxer raises the submitted delay to the minimum for the [deletion reason](#deletion-reasons), so a request for one day under any reason other than `USER_REQUESTED` is stored as 60 days.
|
||||
- `enforcement` writes the decision's reason code and `DELETED`, keeps the account for at least 74 days (14 on a self-hosted instance), and sends the enforcement notice.
|
||||
- `holder_request` writes `USER_REQUESTED` and `SELF_DELETED`, keeps the account for at least 14 days, and emails that the deletion is scheduled as asked.
|
||||
- `housekeeping` writes `INACTIVITY` and `SELF_DELETED`, keeps the account for at least 14 days, and emails that the account is deleted for inactivity.
|
||||
|
||||
The `X-Audit-Log-Reason` value is also stored on the account as the private deletion reason and reused as the blocklist reason for the identifier bans below.
|
||||
The 74 days of an enforcement closure outlast the 60-day appeal window, so an appeal sent on its last day can be reviewed before the account is gone. A self-hosted instance has no appeal route, so its enforcement closures keep the account for at least 14 days, as the other bases do. A deletion with `SELF_DELETED` is cancelled when the holder signs in, as a self-deletion is.
|
||||
|
||||
### Path parameters
|
||||
|
||||
@@ -1561,47 +1675,50 @@ The `X-Audit-Log-Reason` value is also stored on the account as the private dele
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| reason_code<sup>1</sup> | integer | [Deletion reason](#deletion-reasons) |
|
||||
| public_reason?<sup>2</sup> | string | Statement of reasons shown to the account holder (at most 512 characters) |
|
||||
| days_until_deletion?<sup>3</sup> | integer | Requested whole-day delay (1-365, default 60) |
|
||||
| replace_pending_deletion_at?<sup>4</sup> | ISO8601 timestamp | The `pending_deletion_at` of the schedule this request replaces |
|
||||
| notify_user? | boolean | Whether to email the account holder about the scheduled deletion (default true) |
|
||||
| days_until_deletion<sup>1</sup> | integer | Requested whole-day delay (1-365) |
|
||||
| basis | [basis](/admin-api/enforcement/#basis) object | Why the account is closed. The `enforcement` basis includes a `decision` |
|
||||
| replace_pending_deletion_at?<sup>2</sup> | ISO8601 timestamp | The `pending_deletion_at` of the schedule this request replaces |
|
||||
| remove_evidence?<sup>3</sup> | boolean | Remove the evidence messages after they are snapshotted |
|
||||
| block_networks?<sup>4</sup> | [network block request](#network-block-request) object | Networks the account used to block with the decision |
|
||||
|
||||
<sup>1</sup> Required, and validated against the [deletion reason](#deletion-reasons) registry. `USER_REQUESTED` selects the 14-day minimum, and every other value selects the 60-day minimum
|
||||
<sup>1</sup> Raised to the minimum of the basis. There is no absolute-timestamp field
|
||||
|
||||
<sup>2</sup> Stored on the account and included in the deletion email. Omitting it stores null
|
||||
<sup>2</sup> Required when the account already has a pending deletion, including one the caller scheduled. It must equal the current `pending_deletion_at`
|
||||
|
||||
<sup>3</sup> The stored deadline is the request instant plus the greater of this value and the minimum for the reason code. There is no absolute-timestamp field
|
||||
<sup>3</sup> Enforcement basis only. Any other basis refuses it with `ENFORCEMENT_EVIDENCE_REQUIRED`
|
||||
|
||||
<sup>4</sup> Required when the account already has a pending deletion, including one the caller scheduled. It must equal the current `pending_deletion_at`
|
||||
<sup>4</sup> Enforcement basis only. Any other basis refuses it with `ENFORCEMENT_NETWORK_BLOCK_NOT_ALLOWED`. Requires `ban:ip:add` and `user:view:ip`
|
||||
|
||||
### Response body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| user | [Admin user](#admin-user-object) object | The resulting account |
|
||||
| skipped_ips | array[object] | Requested addresses that were not blocked, as on [Suspend user](#suspend-user) |
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | response body | The deletion schedule was stored |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY`, with one of the [validation codes](/admin-api/enforcement/#validation) |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the account does not exist |
|
||||
| 409 | [error response](/admin-api/#error-response) | `ENFORCEMENT_DECISION_OWNS_STATE` with the `decision_id`, because a decision owns the account |
|
||||
| 409 | [error response](/admin-api/#error-response) | `CONFLICT`, because a pending deletion was not named, erasure has started, or the deletion state changed |
|
||||
|
||||
`user:delete` also allows scheduling deletion of the acting Admin or an account with broader permissions.
|
||||
|
||||
### Side effects
|
||||
|
||||
The account can no longer authenticate, and its existing authentication sessions and OAuth2 access and refresh tokens are deleted. The acting Admin and the time are stored as `deletion_scheduled_by` and `deletion_scheduled_at`. Erasure is scheduled for the resulting deadline.
|
||||
The account can no longer authenticate, and its existing authentication sessions and OAuth2 access and refresh tokens are deleted. The acting Admin and the time are stored as `deletion_scheduled_by` and `deletion_scheduled_at`. Erasure is scheduled for the resulting deadline. The `X-Audit-Log-Reason` value is not stored on the account.
|
||||
|
||||
With the `enforcement` basis, the decision is recorded first and `enforcement_decision_id` is written in the same account write as the deadline. A suspension on the account is replaced and lifted in that write. When the rule of the reason code bars new accounts, which is every rule except the minimum age, Fluxer blocks the account's email address. For the reasons `SPAM`, `CHEATING_OR_EXPLOITATION`, `COORDINATED_RAIDING`, `AUTOMATION_OR_SELFBOT`, `SCAM_OR_SOCIAL_ENGINEERING`, `HARASSMENT_OR_BULLYING`, `BAN_EVASION`, `TOKEN_OR_CREDENTIAL_SCAM`, `HATE_SPEECH_OR_EXTREMIST_CONTENT`, `MALICIOUS_LINKS_OR_MALWARE`, and `IMPERSONATION_OR_FAKE_IDENTITY`, Fluxer also resolves the pending reports against the account. A pending report in the `child_safety`, `underage_user`, or `self_harm` category is left for a moderator and stays pending. These steps are best-effort and can fail without cancelling the deletion schedule. Restricted reasons cannot close an account.
|
||||
|
||||
A holder request or housekeeping deletion blocks nothing and resolves no reports.
|
||||
|
||||
Fluxer cancels a Stripe subscription on the account without proration and refunds the charge behind its latest invoice as fraudulent. When the cancellation or refund fails, Fluxer logs the failure and still keeps the deletion schedule.
|
||||
|
||||
The account holder is emailed the deadline and the supplied `public_reason` when `notify_user` is true and the account has an email address. The wording depends on `reason_code`: `USER_REQUESTED` and `INACTIVITY` get a neutral notice, and every other code gets an enforcement notice. A failed email is logged and the schedule is still stored.
|
||||
|
||||
For every reason other than `USER_REQUESTED`, Fluxer also blocks the account's email address. For the reasons `SPAM`, `CHEATING_OR_EXPLOITATION`, `COORDINATED_RAIDING`, `AUTOMATION_OR_SELFBOT`, `SCAM_OR_SOCIAL_ENGINEERING`, `HARASSMENT_OR_BULLYING`, `BAN_EVASION`, `TOKEN_OR_CREDENTIAL_SCAM`, `HATE_SPEECH_OR_EXTREMIST_CONTENT`, `MALICIOUS_LINKS_OR_MALWARE`, and `IMPERSONATION_OR_FAKE_IDENTITY`, Fluxer also resolves the pending reports against the account. A pending report in the `child_safety`, `underage_user`, or `self_harm` category is left for a moderator and stays pending. Every other reason leaves all reports pending. These enforcement steps are best-effort and can fail without cancelling the deletion schedule.
|
||||
|
||||
[User Update](/gateway/events/#user-update) is emitted after the sessions have already been deleted. The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `schedule_deletion`, target type `user`, and the metadata keys `days`, `reason_code`, `pending_deletion_at`, `notify_user`, and `notification_sent`. An attempted email adds `notification_template`. A replacement adds `replaced_pending_deletion_at`, `replaced_scheduled_by`, `replaced_scheduled_at`, and `replaced_reason_code` for the schedule it replaced. The identifier bans record their own blocklist entries. For each reason that resolves reports, Fluxer also records a second entry with action `auto_resolve_reports_on_deletion`, target type `user`, and the metadata keys `outcome`, `found_count`, `resolved_count`, `failed_count`, and `notified_count`. `found_count` leaves out the reports in the three categories that stay pending. No such entry is written for any other reason. `outcome` is `completed`, `search_unavailable` when the instance has no report search backend and every report stays pending, or `enumeration_failed` when listing the pending reports failed part way and only those already found were resolved. Each resolved report sends its reporter the generic notice [Update report](/admin-api/reports/#update-report) describes, with no public comment.
|
||||
[User Update](/gateway/events/#user-update) is emitted after the sessions have already been deleted. The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `schedule_deletion`, target type `user`, and the metadata keys `basis`, `days`, `reason_code` and `pending_deletion_at`. The enforcement basis adds `decision_id`, `kind` and `notice_status`, plus `blocked_ips` and `network_blocks_until` when networks were blocked, and the other bases add `notification_sent`. A replacement adds `replaced_pending_deletion_at`, `replaced_scheduled_by`, `replaced_scheduled_at`, and `replaced_reason_code` for the schedule it replaced. The email block records its own blocklist entry. For each reason that resolves reports, Fluxer also records a second entry with action `auto_resolve_reports_on_deletion`, target type `user`, and the metadata keys `outcome`, `found_count`, `resolved_count`, `failed_count`, and `notified_count`. `found_count` leaves out the reports in the three categories that stay pending. `outcome` is `completed`, `search_unavailable` when the instance has no report search backend and every report stays pending, or `enumeration_failed` when listing the pending reports failed part way and only those already found were resolved. Each resolved report tells its reporter the `restricted` outcome [Update report](/admin-api/reports/#update-report) describes, with no public comment.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -1611,10 +1728,10 @@ For every reason other than `USER_REQUESTED`, Fluxer also blocks the account's e
|
||||
|
||||
<RouteHeader method="DELETE" path="/v1/admin/users/{user_id}/deletion" auditReason />
|
||||
|
||||
Cancels the scheduled deletion named in the request and returns the resulting account. Requires `user:delete`. Erasure cannot be cancelled once it starts.
|
||||
Cancels a scheduled deletion that no enforcement decision owns, such as one the holder asked for, an inactivity deletion, or a closure from before decisions existed, and returns the resulting account. Requires `user:delete`. A closure with a decision is cancelled by [ending the decision](/admin-api/enforcement/#end-an-enforcement-decision). Erasure cannot be cancelled once it starts.
|
||||
|
||||
:::caution[Cancellation does not undo enforcement]
|
||||
Clearing the deadline permits the account to authenticate again, but it does not restore deleted sessions, reinstate a cancelled subscription, lift the email and IP blocklist entries the schedule wrote, or reopen the reports it resolved.
|
||||
Clearing the deadline permits the account to authenticate again, but it does not restore deleted sessions, reinstate a cancelled subscription, lift the email and IP blocklist entries an older closure wrote, or reopen the reports it resolved.
|
||||
:::
|
||||
|
||||
### Path parameters
|
||||
@@ -1628,7 +1745,6 @@ Clearing the deadline permits the account to authenticate again, but it does not
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| expected_pending_deletion_at<sup>1</sup> | ISO8601 timestamp | The `pending_deletion_at` of the deletion being cancelled |
|
||||
| notify_user? | boolean | Whether to email the account holder (default false) |
|
||||
|
||||
<sup>1</sup> Required. It must equal the current `pending_deletion_at`, so a cancel written against one schedule never clears a different one
|
||||
|
||||
@@ -1645,15 +1761,16 @@ Clearing the deadline permits the account to authenticate again, but it does not
|
||||
| 200 | response body | The deletion was cancelled |
|
||||
| 400 | [error response](/admin-api/#error-response) | `NO_PENDING_DELETION`, because no deletion is scheduled |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the account does not exist |
|
||||
| 409 | [error response](/admin-api/#error-response) | `ENFORCEMENT_DECISION_OWNS_STATE` with the `decision_id`, because a decision owns the deletion |
|
||||
| 409 | [error response](/admin-api/#error-response) | `CONFLICT`, because a different deletion is pending, erasure has started, or the deletion state changed |
|
||||
|
||||
### Side effects
|
||||
|
||||
The deletion schedule, its reasons, and the recorded scheduler are cleared, allowing the account to authenticate again unless another restriction applies.
|
||||
|
||||
When `notify_user` is true and the account has an email address, Fluxer emails the account holder. The email never includes the `X-Audit-Log-Reason` value.
|
||||
When the account has an email address, Fluxer always emails the holder. A deletion with an enforcement reason code, which only a closure from before decisions can have here, sends the notice that the restriction ended. Every other deletion sends the notice that the deletion was cancelled. The email never includes the `X-Audit-Log-Reason` value.
|
||||
|
||||
[User Update](/gateway/events/#user-update) is emitted to the account's own sessions. The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `cancel_deletion`, target type `user`, the metadata keys `cancelled_pending_deletion_at`, `cancelled_scheduled_by`, `cancelled_scheduled_at`, and `cancelled_reason_code` for the cancelled schedule, and `notify_user`.
|
||||
[User Update](/gateway/events/#user-update) is emitted to the account's own sessions. The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `cancel_deletion`, target type `user`, the metadata keys `cancelled_pending_deletion_at`, `cancelled_scheduled_by`, `cancelled_scheduled_at`, and `cancelled_reason_code` for the cancelled schedule, and `notice` naming the email that was sent.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -2132,7 +2249,7 @@ Counts every message attributed to the account, and queues their deletion when t
|
||||
|
||||
The request counts messages before responding, so accounts with more messages take longer. A misspelled `dry_run` parameter leaves the request a dry run.
|
||||
|
||||
The operation does not resolve the target account, so it never answers `UNKNOWN_USER`. An ID with no matching account is accepted and matches no message.
|
||||
Only the `enforcement` basis resolves the target account, so only it answers `UNKNOWN_USER`. With another basis, or in a dry run, an ID with no matching account is accepted and matches no message.
|
||||
|
||||
### Path parameters
|
||||
|
||||
@@ -2140,6 +2257,14 @@ The operation does not resolve the target account, so it never answers `UNKNOWN_
|
||||
| --- | --- | --- |
|
||||
| user_id | snowflake | The ID of the message author |
|
||||
|
||||
### JSON body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| basis?<sup>1</sup> | [basis](/admin-api/enforcement/#basis) object | Why the messages are removed |
|
||||
|
||||
<sup>1</sup> Required unless the request is a dry run, which ignores it. Without it a deletion fails with `MODERATION_BASIS_REQUIRED`. The `enforcement` basis records a content removal [decision](/admin-api/enforcement/) with `removed_all_messages` and sends its notice. It refuses child safety and violent extremism reasons with `ENFORCEMENT_KIND_NOT_ALLOWED`, because the messages cannot all be snapshotted first. Use [Shred user messages](#shred-user-messages) with the messages instead
|
||||
|
||||
### Query parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -2157,16 +2282,21 @@ The operation does not resolve the target account, so it never answers `UNKNOWN_
|
||||
| channel_count<sup>1</sup> | integer | The number of channels the account has messages in |
|
||||
| message_count | integer | The number of messages the walk matched |
|
||||
| job_id?<sup>2</sup> | string | The ID of the queued shred job |
|
||||
| decision_id?<sup>3</sup> | snowflake | The content removal decision |
|
||||
|
||||
<sup>1</sup> Summed for each page of 200, so a channel spanning pages is counted once per page. Treat it as an upper bound
|
||||
|
||||
<sup>2</sup> Present only when `dry_run` resolved to false and `message_count` is above zero
|
||||
|
||||
<sup>3</sup> Present only when a job was queued with the `enforcement` basis
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | response body | The count completed, and the deletion job was queued when applicable |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY`, because the basis is missing or the [decision](/admin-api/enforcement/#validation) is invalid |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the `enforcement` basis names an account that does not exist |
|
||||
|
||||
:::danger[A non-dry run permanently deletes messages]
|
||||
The response only confirms that the job was queued. When `dry_run` resolves to false, the background job deletes every matching message, and no operation restores them.
|
||||
@@ -2176,9 +2306,9 @@ The queued job is an ordinary shred job, so its progress, totals, skips, and fai
|
||||
|
||||
### Side effects
|
||||
|
||||
A dry run reads and counts only. A deletion run also queues one shred job with every matched message identity, and the request returns before any message is processed.
|
||||
A dry run reads and counts only. A deletion run also queues one shred job with every matched message identity, and the request returns before any message is processed. With the `enforcement` basis the decision is stored before the job is queued.
|
||||
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with target type `message_deletion`, the target account ID as its target ID, and the metadata keys `user_id`, `channel_count`, `message_count`, and `dry_run`. The action is `delete_all_user_messages_dry_run` for a dry run and `delete_all_user_messages` otherwise. A deletion run also records the `queue_message_shred` entry described under [Shred user messages](#shred-user-messages).
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with target type `message_deletion`, the target account ID as its target ID, and the metadata keys `user_id`, `channel_count`, `message_count`, and `dry_run`. The action is `delete_all_user_messages_dry_run` for a dry run and `delete_all_user_messages` otherwise. A deletion run adds `basis`, `job_id` when a job was queued, and for the `enforcement` basis `decision_id`, `reason_code`, `kind` and `notice_status`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -2190,7 +2320,7 @@ The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-obje
|
||||
|
||||
Queues irreversible deletion of an explicit list of message identities and returns a job ID read through [Get message shred job](/admin-api/messages/#get-message-shred-job). Requires `message:shred`.
|
||||
|
||||
The operation does not resolve the target account, so it never answers `UNKNOWN_USER`.
|
||||
Only the `enforcement` basis resolves the target account, so only it answers `UNKNOWN_USER`.
|
||||
|
||||
### Path parameters
|
||||
|
||||
@@ -2203,8 +2333,11 @@ The operation does not resolve the target account, so it never answers `UNKNOWN_
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| entries<sup>1</sup> | array[[message shred entry](#message-shred-entry-object) object] | Explicit message identities, with 1 to 1000 values |
|
||||
| basis<sup>2</sup> | [basis](/admin-api/enforcement/#basis) object | Why the messages are removed |
|
||||
|
||||
<sup>1</sup> Required and non-empty. The entries are not checked against the target account at submission time
|
||||
<sup>1</sup> Required and non-empty. Without the `enforcement` basis, the entries are not checked against the target account at submission time
|
||||
|
||||
<sup>2</sup> The `enforcement` basis records a content removal [decision](/admin-api/enforcement/) and sends its notice. The first 50 entries are its evidence, every entry for child safety and violent extremism reasons, and they are checked and snapshotted before the job is queued. Each one must exist and be the target account's own, and a message in a direct or group conversation needs a linked report that covers it. `decision.evidence` is refused with `ENFORCEMENT_EVIDENCE_NOT_ALLOWED`
|
||||
|
||||
### Response body
|
||||
|
||||
@@ -2213,14 +2346,19 @@ The operation does not resolve the target account, so it never answers `UNKNOWN_
|
||||
| success | boolean | Whether the request succeeded, always true |
|
||||
| job_id | string | The ID of the queued shred job |
|
||||
| requested<sup>1</sup> | integer | The number of submitted entries |
|
||||
| decision_id?<sup>2</sup> | snowflake | The content removal decision |
|
||||
|
||||
<sup>1</sup> An upper bound on the messages the job shreds
|
||||
|
||||
<sup>2</sup> Present only for the `enforcement` basis
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | response body | The shred job was queued |
|
||||
| 400 | [error response](/admin-api/#error-response) | `INVALID_FORM_BODY`, because the basis or the [decision](/admin-api/enforcement/#validation) is invalid |
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the `enforcement` basis names an account that does not exist |
|
||||
|
||||
:::danger[Shredding deletes messages permanently]
|
||||
A shred deletes every matching message with its reactions, purges the attachments it owned, and removes it from search. Only a preceding [Create user archive](#create-user-archive) retains the original text.
|
||||
@@ -2232,7 +2370,7 @@ A failed job is not retried automatically.
|
||||
|
||||
The request returns before any message is processed. The job checks each referenced message again before it deletes it, and skips a missing message or one no longer authored by the target account.
|
||||
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `queue_message_shred`, target type `message_shred`, the target account ID as its target ID, and the metadata keys `user_id`, `job_id`, and `requested_entries`.
|
||||
The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `queue_message_shred`, target type `message_shred`, the target account ID as its target ID, and the metadata keys `user_id`, `job_id`, `requested_entries` and `basis`. The `enforcement` basis adds `decision_id`, `reason_code`, `kind` and `notice_status`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -669,11 +669,15 @@ When the instance has disabled new-IP authorisation or sends no email, Fluxer au
|
||||
|
||||
Account policy can return 403 `REGISTRATION_PENDING_APPROVAL`, 403 `REGISTRATION_REJECTED`, 403 `ACCOUNT_SUSPENDED_TEMPORARILY`, or 403 `ACCOUNT_SUSPENDED_PERMANENTLY`. A bot account returns 403 `BOT_USER_AUTH_ENDPOINT_ACCESS_DENIED`.
|
||||
|
||||
:::note[A verified password reactivates the account]
|
||||
On an account the user had disabled, a correct password clears the disabled state. It also cancels a self-scheduled deletion if erasure has not started. Both happen before any second factor is requested, without a separate confirmation step. Once erasure starts, login returns `ACCOUNT_SUSPENDED_PERMANENTLY`.
|
||||
A suspended or closed account gets its 403 after the last factor. Without a second factor that is the password, so login returns it. With a second factor, login returns the MFA ticket and the second factor returns the 403. Both suspension codes have the account's [enforcement notice](/http-api/errors/#supplementary-members). A suspension with no end date and a closed account return `ACCOUNT_SUSPENDED_PERMANENTLY`, and a suspension with an end date returns `ACCOUNT_SUSPENDED_TEMPORARILY`. An expired temporary suspension is cleared at the same point.
|
||||
|
||||
:::note[Signing in reactivates the account]
|
||||
On an account the user had disabled, signing in clears the disabled state. It also cancels a self-scheduled deletion if erasure has not started. Both happen when the session is created, after any second factor, without a separate confirmation step. Once erasure starts, login returns `ACCOUNT_SUSPENDED_PERMANENTLY`.
|
||||
:::
|
||||
|
||||
A correct password also clears an expired temporary suspension before any second factor is requested. A live temporary or permanent administrator suspension stays in place, and the login returns its 403.
|
||||
:::note[Signing in from a blocked network]
|
||||
A [blocked network](/http-api/errors/#supplementary-members) can still reach this operation, the second factor operations, passkey sign-in and SSO, so the holder of a suspended or closed account sees its notice. Every other outcome returns the same `GLOBAL_IP_BANNED` or `GLOBAL_IP_TEMPORARILY_BANNED` body any other request from that network gets. That covers an unknown account, a wrong password, and an account that is not suspended or closed, with or without a second factor. No session, MFA ticket or IP authorisation message is created, and SSO provisions no new account.
|
||||
:::
|
||||
|
||||
### Response
|
||||
|
||||
|
||||
@@ -25,10 +25,34 @@ The error code determines which supplementary members a failure has, and most co
|
||||
`GLOBAL_IP_BANNED` and `GLOBAL_IP_TEMPORARILY_BANNED` have their own members:
|
||||
|
||||
- `ip_address` is the normalised client address.
|
||||
- `appeal_email` is the address an appeal is sent to. It is `null` on a self-hosted instance, where `message` points at the administrators of the instance and names no address.
|
||||
- `appeals_supported` is `true` for both kinds of ban.
|
||||
- `appeal_email` is the address to write to about the block. It is the appeals address when `reason` is present and the support address otherwise. It is `null` on a self-hosted instance, where `message` names no address.
|
||||
- `appeals_supported` is `true` on the hosted service and `false` on a self-hosted instance.
|
||||
- `ban_kind` is `permanent` or `temporary_24h`. `temporary_24h` covers every ban that records an expiry, whatever its length.
|
||||
- `expires_at` is an ISO 8601 timestamp when the ban records an expiry, and `null` otherwise, including on every permanent ban.
|
||||
- `reason` is the rule behind the block, as `title` and `url`, or `null`. It is present only when an account decision placed the block and its rule is one Fluxer names on this screen, such as spam or service abuse. Every other block, including a decision whose rule is withheld, returns a response identical to a plain block of the same kind, so `null` says nothing about why the network was blocked. `url` is `null` when the instance has no policy page for the rule.
|
||||
- `product_name` is the instance name the server uses in `message`. On a self-hosted instance with no name, or one still named Fluxer, it is the web app host name.
|
||||
|
||||
`message` starts with the block and its end date, then tells someone on a shared network, such as a school, a workplace, public Wi-Fi, a mobile carrier or a VPN, that the block may have nothing to do with them and that switching networks is the quickest fix. `GET /.well-known/fluxer` is never blocked, so a client can still discover the instance and show this screen.
|
||||
|
||||
A blocked network still reaches the sign-in operations: [log in with a password](/http-api/authentication/#log-in-with-a-password), the second factor operations, passkey sign-in and SSO. There the holder of a suspended or closed account gets its suspension error with the notice, and every other outcome gets this same block.
|
||||
|
||||
`ACCOUNT_SUSPENDED_TEMPORARILY` and `ACCOUNT_SUSPENDED_PERMANENTLY` have `notice`, the enforcement notice for the account, in the request locale. It is `null` only for an account its holder deleted. Its members are:
|
||||
|
||||
- `decision_id` is the decision reference, or `null` for a restriction from before decisions.
|
||||
- `kind` is what the decision did, `suspension` or `closure` on these codes.
|
||||
- `permanent` is `true` for a suspension with no end date.
|
||||
- `issued_at`, `ends_at` and `deletion_at` are ISO 8601 timestamps or `null`.
|
||||
- `summary` is one sentence saying what happened, and `effects` lists what the decision means for the holder.
|
||||
- `rule` is the rule broken, as `title` and `url`, or `null`.
|
||||
- `facts` is the finding first, then where and when it happened.
|
||||
- `statement` is a note from the team, or `null`.
|
||||
- `how_we_decided` lists how the case reached the team and who decided it.
|
||||
- `appeal` is `email`, `deadline` and `account_has_email`, or `null` on an instance with no appeal route. `deadline` is already formatted for the reader.
|
||||
- `redress` lists other ways to challenge the decision.
|
||||
|
||||
A line in `effects`, `facts` or `redress` can hold a link written as a Markdown link, `[text](url)`. A client shows the text as a link to the address.
|
||||
|
||||
An account with no email address receives its notice here. The first sign-in that shows it records it as received, and the appeal deadline counts from then.
|
||||
|
||||
## Validation failure codes
|
||||
|
||||
@@ -306,6 +330,26 @@ Channels with threads can't be converted to announcement channels
|
||||
|
||||
Only text and announcement channels can be converted into each other
|
||||
|
||||
### `ENFORCEMENT_ACCOUNT_CLOSED`
|
||||
|
||||
This account is closed, so it can't be suspended
|
||||
|
||||
### `ENFORCEMENT_DECISION_ENDED`
|
||||
|
||||
This decision has already ended
|
||||
|
||||
### `ENFORCEMENT_DECISION_OWNS_STATE`
|
||||
|
||||
An enforcement decision owns this restriction
|
||||
|
||||
### `ENFORCEMENT_DECISION_REPLACED`
|
||||
|
||||
A later decision replaced this one, so it can no longer be ended
|
||||
|
||||
### `ENFORCEMENT_DELETION_STARTED`
|
||||
|
||||
The deletion of this account has already started, so the decision can no longer be ended
|
||||
|
||||
### `NEW_CONVERSATIONS_LIMITED`
|
||||
|
||||
You can't start new conversations right now. Please try again later
|
||||
@@ -472,11 +516,11 @@ Gift code is already redeemed
|
||||
|
||||
### `GLOBAL_IP_BANNED`
|
||||
|
||||
Your IP address {ipAddress} has been permanently blocked from the {product_name} API by platform administrators
|
||||
{product_name} blocked access from this network
|
||||
|
||||
### `GLOBAL_IP_TEMPORARILY_BANNED`
|
||||
|
||||
Your IP address {ipAddress} has been temporarily blocked from the {product_name} API
|
||||
{product_name} blocked access from this network
|
||||
|
||||
### `GONE`
|
||||
|
||||
@@ -678,6 +722,10 @@ IP authorization resend limit exceeded
|
||||
|
||||
This IP address cannot be added to the blocklist
|
||||
|
||||
### `IP_BAN_LINKED_TO_DECISION`
|
||||
|
||||
This network block belongs to an enforcement decision
|
||||
|
||||
### `MAX_ACTIVE_THREADS`
|
||||
|
||||
This community has reached its active thread limit of {count}
|
||||
@@ -1158,6 +1206,10 @@ Channel wasn't found
|
||||
|
||||
Unknown emoji
|
||||
|
||||
### `UNKNOWN_ENFORCEMENT_DECISION`
|
||||
|
||||
Unknown enforcement decision
|
||||
|
||||
### `UNKNOWN_FAVORITE_MEME`
|
||||
|
||||
Unknown favorite meme
|
||||
@@ -1347,10 +1399,6 @@ Attachment with ID {attachmentId} wasn't found in the message
|
||||
|
||||
Attachment "{filename}" must be an image file (png, jpg, jpeg, webp, or gif)
|
||||
|
||||
### `AT_LEAST_ONE_ENTRY_IS_REQUIRED`
|
||||
|
||||
At least one entry is required
|
||||
|
||||
### `AT_LEAST_ONE_RECIPIENT_REQUIRED`
|
||||
|
||||
At least one recipient is required
|
||||
@@ -1563,6 +1611,90 @@ Embed index {embedIndex} is out of bounds (message has {embedCount, plural, one
|
||||
|
||||
Embed splash requires INVITE_SPLASH feature
|
||||
|
||||
### `ENFORCEMENT_CONFIRMATIONS_REQUIRED`
|
||||
|
||||
Confirm that the evidence is preserved and that any required report is filed
|
||||
|
||||
### `ENFORCEMENT_EVIDENCE_DM_UNREPORTED`
|
||||
|
||||
Messages in direct messages can only be used as evidence when a linked report covers that conversation
|
||||
|
||||
### `ENFORCEMENT_EVIDENCE_NOT_ALLOWED`
|
||||
|
||||
The messages being removed are saved as evidence, so leave out separate evidence messages
|
||||
|
||||
### `ENFORCEMENT_EVIDENCE_NOT_FOUND`
|
||||
|
||||
One of the evidence messages wasn't found
|
||||
|
||||
### `ENFORCEMENT_EVIDENCE_NOT_SUBJECT`
|
||||
|
||||
Every evidence message must be from the account or community this decision is about
|
||||
|
||||
### `ENFORCEMENT_EVIDENCE_REQUIRED`
|
||||
|
||||
This decision needs at least one evidence message
|
||||
|
||||
### `ENFORCEMENT_FACTS_REQUIRED`
|
||||
|
||||
Add an evidence message or a note to the user
|
||||
|
||||
### `ENFORCEMENT_INTERNAL_NOTE_REQUIRED`
|
||||
|
||||
Add an internal note in the X-Audit-Log-Reason header
|
||||
|
||||
### `ENFORCEMENT_KIND_NOT_ALLOWED`
|
||||
|
||||
This reason can't be used for this action
|
||||
|
||||
### `ENFORCEMENT_LIMIT_END_INVALID`
|
||||
|
||||
Only a reports limit has an end date, and it must be within the next 365 days
|
||||
|
||||
### `ENFORCEMENT_LIMIT_NOT_ALLOWED`
|
||||
|
||||
This reason can't be used for this limit
|
||||
|
||||
### `ENFORCEMENT_NETWORK_BLOCK_NOT_ALLOWED`
|
||||
|
||||
Only a permanent suspension or a closure for a broken rule can block networks
|
||||
|
||||
### `ENFORCEMENT_NETWORK_SHARED`
|
||||
|
||||
Other accounts that aren't restricted were last active on this network, so it can't be blocked
|
||||
|
||||
### `ENFORCEMENT_NETWORK_UNKNOWN`
|
||||
|
||||
This account hasn't used this network in the last 30 days
|
||||
|
||||
### `ENFORCEMENT_OUTCOME_NOT_ALLOWED`
|
||||
|
||||
Only a reversal can end this kind of decision, because there is nothing to lift
|
||||
|
||||
### `ENFORCEMENT_PERMANENT_REQUIRED`
|
||||
|
||||
Suspensions for this reason must be permanent
|
||||
|
||||
### `ENFORCEMENT_REPORT_MISMATCH`
|
||||
|
||||
Every linked report must exist and be about the account or community this decision is about
|
||||
|
||||
### `ENFORCEMENT_REPORT_REQUIRED`
|
||||
|
||||
Link at least one report when the source is a report
|
||||
|
||||
### `ENFORCEMENT_RIGHTS_HOLDER_NOT_ALLOWED`
|
||||
|
||||
A rights holder can only be given for copyright decisions
|
||||
|
||||
### `ENFORCEMENT_RIGHTS_HOLDER_REQUIRED`
|
||||
|
||||
Copyright decisions need the rights holder or the right concerned
|
||||
|
||||
### `ENFORCEMENT_STATEMENT_NOT_ALLOWED`
|
||||
|
||||
Decisions for this reason can't include a note to the user
|
||||
|
||||
### `ENTRANCE_SOUND_DURATION_EXCEEDS_LIMIT`
|
||||
|
||||
Entrance sounds can be at most {max_ms, number}ms long
|
||||
@@ -1627,6 +1759,10 @@ File index {index} exceeds the maximum allowed index of {maxIndex}
|
||||
|
||||
File not found
|
||||
|
||||
### `FLAG_REQUIRES_ENFORCEMENT_ROUTE`
|
||||
|
||||
This flag is part of a restriction. Change it through the suspension, deletion or feature limit routes
|
||||
|
||||
### `FORWARD_MESSAGES_CANNOT_CONTAIN_CONTENT`
|
||||
|
||||
Forwarded messages can't include content, embeds, attachments, or stickers
|
||||
@@ -1879,6 +2015,10 @@ Message history cutoff can't be in the future
|
||||
|
||||
`message_ids` can't be empty
|
||||
|
||||
### `MODERATION_BASIS_REQUIRED`
|
||||
|
||||
Give a basis to delete the messages
|
||||
|
||||
### `MULTIPLE_FILES_FOR_INDEX_NOT_ALLOWED`
|
||||
|
||||
Multiple files for index {index} aren't allowed
|
||||
@@ -2003,6 +2143,10 @@ Recipient IDs must be valid snowflakes
|
||||
|
||||
Referenced attachment "{filename}" wasn't found in message attachments
|
||||
|
||||
### `REPORT_OUTCOME_INVALID`
|
||||
|
||||
An actioned resolution needs an outcome other than no violation. A duplicate needs the report it repeats and takes no outcome. A no violation resolution takes no other outcome
|
||||
|
||||
### `SESSION_TIMEOUT`
|
||||
|
||||
Session timed out
|
||||
|
||||
@@ -790,7 +790,7 @@ While email is on, registration checks the address domain. It has to publish an
|
||||
|
||||
#### `FLUXER_EMAIL_FROM_NAME`
|
||||
|
||||
`.env.example` empty. The sender name. Default empty. An empty value sends mail under the instance product name, which is the name saved in the admin dashboard or else `FLUXER_APP_PRODUCT_NAME`, and a rename in the dashboard applies to the next email. A set value always wins.
|
||||
`.env.example` empty. The sender name. Default empty. An empty value sends mail under the instance product name, which is the name saved in the admin dashboard or else `FLUXER_APP_PRODUCT_NAME`, with the [self-hosted host name rule](#instance-identity-and-branding) applied, and a rename in the dashboard applies to the next email. A set value always wins.
|
||||
|
||||
#### `FLUXER_EMAIL_REPLY_TO_EMAIL`
|
||||
|
||||
@@ -1154,7 +1154,7 @@ Default `production` when `FLUXER_SELF_HOSTED` is `true`, otherwise `development
|
||||
|
||||
#### `FLUXER_SELF_HOSTED`
|
||||
|
||||
Default `false`. The self-host switch. Compose sets `true`. It relaxes the production Postgres SSL requirement, seeds the limit tier, gates registration, donation and discovery controllers, keeps premium billing off until it is set up as [Payments](#payments) describes, turns blocklist feeds off, and keeps hosted-only artwork from `fluxer.app` out of the app, webhook avatars and link embeds.
|
||||
Default `false`. The self-host switch. Compose sets `true`. It relaxes the production Postgres SSL requirement, seeds the limit tier, gates registration, donation and discovery controllers, keeps premium billing off until it is set up as [Payments](#payments) describes, turns blocklist feeds off, keeps an account closed for a broken rule for at least 14 days instead of 74 because there is no appeal route, and keeps hosted-only artwork from `fluxer.app` out of the app, webhook avatars and link embeds.
|
||||
|
||||
## Feature flags and development switches
|
||||
|
||||
@@ -1260,6 +1260,8 @@ A brand asset on another origin than the instance needs no CSP setting. `app-pro
|
||||
|
||||
Default `Fluxer`. The product name clients display and the API writes in its emails and report flows.
|
||||
|
||||
On a self-hosted instance, a name that is empty or `Fluxer` is replaced by the host name of the web app endpoint in everything the server writes: emails and their sender name, system messages and the name of the system user, report flows, enforcement notices, IP block errors, the name of the built-in admin application and donation checkout. Clients keep showing the saved name, except on the network blocked screen, which takes the name from the error. Set a name of your own to use it everywhere.
|
||||
|
||||
#### `FLUXER_APP_ICON_URL`
|
||||
|
||||
Default empty. The client icon.
|
||||
@@ -1790,7 +1792,7 @@ The admin dashboard at `FLUXER_ADMIN_BASE_PATH` stores these in the database. Th
|
||||
|
||||
#### Instance Config, Public App Identity
|
||||
|
||||
Product name, client-visible brand assets, the terms, privacy, and community guidelines links, and the setup state in the instance discovery document. No environment variable sets a legal link. On a self-hosted instance, the enforcement emails list only the terms and guidelines links set here, and the clients and report flows show no guidelines link until one is set. Its emails and its IP block errors name no contact address. They tell the reader to contact the administrators of the instance, and no setting adds an address. The emails, the report flows, the IP block errors, the title of an APNs alert that has none, and the built-in admin application use the product name set here, and a rename applies without a restart.
|
||||
Product name, client-visible brand assets, the terms, privacy, and community guidelines links, and the setup state in the instance discovery document. No environment variable sets a legal link. On a self-hosted instance, enforcement notices link a rule to the terms or guidelines page set here, without an anchor, and show the rule title alone when no page is set. The clients and report flows show no guidelines link until one is set. Self-hosted emails, enforcement notices and IP block errors name no contact address and no appeal route, and no setting adds one. The emails, the report flows, the IP block errors, the title of an APNs alert that has none, and the built-in admin application use the product name set here, with the [self-hosted host name rule](#instance-identity-and-branding), and a rename applies without a restart.
|
||||
|
||||
#### Instance Config, Registration Controls
|
||||
|
||||
|
||||
Reference in new issue
Block a user