Skip to content
Fluxer API

Blocklists

Fluxer has eight blocklists for account access and user content. Each defines its accepted values, matching rules and metadata. Fluxer normalises a value when it adds the value and again when it checks the value.

Each list has its own Admin ACLs. A read needs the selected list’s check permission, an addition or an update needs its add permission, and a removal needs its remove permission. An account holding ban:ip:add writes to the ip list and to no other. Every operation records the audit reason on the Admin audit entries it produces, and each read produces one entry. An entry about one list has a target type built from the list name with each hyphen written as an underscore, so url-domain entries use url_domain.

Fluxer builds each permission name from ban:, the list name with each hyphen written as an underscore, and the verb, so the url-domain list uses ban:url_domain:check, ban:url_domain:add, and ban:url_domain:remove.

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.

ValueDescription
ip1IPv4 addresses, IPv6 addresses, and CIDR ranges denied service
email2Exact email addresses blocked from registration and from being set on an account
phrase3Phrases blocked from content
url4Absolute http and https URLs blocked from being posted
url-domain5Domains blocked from being linked
file-sha6SHA-256 hashes blocked from being uploaded
avatar-hash7Avatar hashes blocked from being set
profile-substring3 8Substrings blocked from one named profile field

1 Fluxer refuses an address with 400 IP_BAN_DECLINED when it is on the instance exemption list, and records the refusal in the Admin audit log

2 Stored lowercased, so a mixed-case value does not create a second row

3 Canonicalised by NFKC normalisation, removal of control, format and variation-selector characters, lowercasing, and trimming. When Fluxer matches a value, it also normalises inserted whitespace, punctuation, and compatibility characters

4 Canonicalised before storage. A value Fluxer cannot canonicalise returns 400 INVALID_FORM_BODY naming url

5 Stored lowercased, in ASCII form, and without a trailing dot. A row matches that hostname, and with match_subdomains true it also matches every subdomain. A value whose leftmost label has * is a domain pattern

6 Stored as lowercase hexadecimal

7 Stored as the 8-character hash with any a_ animation prefix stripped and the rest lowercased, so the animated and static forms of one avatar are the same row

8 The only scoped list. Scope and substring together identify a row, so the same substring can be stored once per scope

The fields and the operations a list accepts differ from list to list. Read the fields array and the supports_ flags of a blocklist object before writing to a list.

A url-domain value whose leftmost label has * is a pattern. Each * stands for any run of characters inside that one label, so *shop*.example.com matches shop.example.com and my-shop-2.example.com. The rest of the value is a literal domain, called the suffix.

A pattern only matches the label directly left of its suffix. It never matches the suffix itself, so *shop*.example.com does not block example.com. With match_subdomains true, the pattern also matches every subdomain of a matching host, such as www.shop.example.com. Blocking a whole suffix takes a plain domain row.

Fluxer refuses a pattern with 400 INVALID_FORM_BODY naming domain when:

  • * appears outside the leftmost label
  • the leftmost label has characters other than a-z, 0-9, -, and *
  • the leftmost label has more than 3 * or fewer than 3 other characters
  • the suffix is not a valid domain or is a public suffix such as com or co.uk

A domain under a shared hosting suffix, such as onrender.com or github.io, is a valid suffix. Fluxer folds a run of * into one before it stores the pattern.

Fluxer matches a pattern against the ASCII form of a hostname. An internationalised label is compared in its punycode form, so a pattern with a leading or trailing literal can miss it, while *shop* still matches it.

Fluxer checks every link in user content against the url and url-domain lists. This covers bare links, autolinks in angle brackets, the target of a masked link, and the link URLs of rich embeds. Fluxer reads the hostname of each link the way a browser does. It ignores user information and the port, lowercases the hostname, converts it to ASCII, decodes percent escapes, and drops trailing dots.

Every url, url-domain, file-sha, and avatar-hash row has a category naming where it came from. A row created through this resource without an explicit category is stored as manual.

ValueDescription
manualAdded by an Admin through this resource
urlhausImported from URLhaus
phishtankImported from PhishTank
google_safe_browsingImported from Google Safe Browsing
malware_bazaarImported from MalwareBazaar
ncmecImported from an NCMEC hash set
gifctImported from a GIFCT hash set
stop_nciiImported from a StopNCII hash set

With blocklist feeds off, Fluxer ignores and removes every file-sha row in the malware_bazaar category that no Admin added.

The request field is a free string of 1 through 64 characters, so a value outside this registry is accepted and stored verbatim. Tolerate a stored category outside the registry.

Every url, url-domain, file-sha, and avatar-hash row has a severity. A row created without an explicit severity is stored as 2.

The severity is a stored label for moderators. Fluxer refuses the content of every matching row whatever its severity, so a row stored as 0 or 1 blocks exactly like one stored as 2. Delete a row to stop it blocking.

ValueNameDescription
0ALLOWLabelled as content to allow
1WARNLabelled as content to warn about
2BLOCKLabelled as content to refuse
3BLOCK_AND_REPORTLabelled as content to refuse and report
ValueDescription
usernameMatch the substring against the account username
global_nameMatch the substring against the account display name
nicknameMatch the substring against a guild nickname
bioMatch the substring against the profile biography
pronounsMatch the substring against the profile pronouns

One entry of the blocklist catalogue returned by List blocklists. The catalogue is fixed in each release and reports no counts.

FieldTypeDescription
list_typestringThe blocklist type this entry describes
descriptionstringThe human-readable summary of what the blocklist matches and how
value_field1stringThe request body field that has the value when adding
fields2array[string]The field names rows of this blocklist accept beyond the value (at most 8)
scoped3booleanWhether rows are scoped to a profile field and every operation has a scope
supports_bulk_create4booleanWhether Bulk add blocklist entries is accepted
supports_bulk_delete5booleanWhether Bulk remove blocklist entries is accepted
supports_update6booleanWhether Update blocklist entry is accepted

1 ip names ip, email names email, url-domain names domain, phrase names phrase, url names url, file-sha names sha256_hex, avatar-hash names hashes, and profile-substring names substrings

2 ip names duration_hours. Empty for email and phrase. url names category, severity, source_url, and notes. url-domain adds match_subdomains, file-sha adds content_type, and avatar-hash adds reason. profile-substring names scope, reason, and notes

3 True only for profile-substring

4 True only for file-sha

5 True only for avatar-hash and profile-substring

6 True for url, url-domain, file-sha, avatar-hash, and profile-substring

{
"list_type": "url-domain",
"description": "Domains blocked from being linked, optionally covering every subdomain rooted at the domain. A value whose leftmost label contains * is a pattern that matches that one label under a registrable domain.",
"value_field": "domain",
"fields": ["match_subdomains", "category", "severity", "source_url", "notes"],
"scoped": false,
"supports_bulk_create": false,
"supports_bulk_delete": false,
"supports_update": true
}

One stored row of one blocklist. Every field is present on every entry, and a field the row’s blocklist does not store is null.

FieldTypeDescription
list_typestringThe blocklist type the row belongs to
value1stringThe canonical stored value (1-2048 characters)
scope2?stringThe profile substring scope the row is stored under
category3?stringThe content blocklist category the row was recorded under
severity3?integerThe content blocklist severity label stored on the row
source_url3?stringThe feed or evidence URL the row was recorded from
notes4?stringThe internal note stored alongside the row
content_type5?stringThe media type hint recorded alongside a file-sha row
match_subdomains6?booleanWhether a url-domain row also covers subdomains
reason7?stringThe reason stored on the row
expires_at7?ISO8601 timestampWhen the row expires
created_at8?ISO8601 timestampWhen the row was added
created_by_user_id8?snowflakeThe Admin account that added the row, or null when unknown

1 The exact string the single-entry operations and the after cursor accept for that row

2 Non-null only on profile-substring

3 Non-null only on url, url-domain, file-sha, and avatar-hash, where category and severity are always set

4 Non-null only on url, url-domain, file-sha, avatar-hash, and profile-substring

5 Non-null only on file-sha

6 Non-null only on url-domain

7 Non-null only on ip. An address added through 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

8 Both null on email and phrase, which store nothing but the value. created_by_user_id is also null on ip

{
"list_type": "url-domain",
"value": "malicious.example.com",
"scope": null,
"category": "urlhaus",
"severity": 2,
"source_url": "https://urlhaus.example.com/entry/912",
"notes": null,
"content_type": null,
"match_subdomains": true,
"reason": null,
"expires_at": null,
"created_at": "2026-01-22T07:41:19.003Z",
"created_by_user_id": "1483920011884392448"
}

The decision Check blocklist entry returns for one value.

FieldTypeDescription
banned1booleanWhether the value is currently blocked by the named blocklist
expires_at2?ISO8601 timestampWhen the matching ban expires

1 Reports the decision the enforcement path would make for this value

2 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

The body of Add blocklist entry has one shape per blocklist, and the list_type path segment selects the shape. The field that has the value differs per blocklist, and two of the nine take an array, so one request can add up to 1000 avatar hashes or profile substrings.

BlocklistFieldAccepted value
ipipIPv4 address, IPv6 address, or CIDR range of 1 through 45 characters
emailemailEmail address of 1 through 254 characters
phrasephrasePhrase of 1 through 500 characters
urlurlAbsolute http or https URL of 1 through 2048 characters
url-domaindomainDomain or domain pattern of 1 through 253 characters
file-shasha256_hexExactly 64 hexadecimal characters
avatar-hash1hashes1 through 1000 hashes, each 8 through 10 characters matching ^(a_)?[0-9a-fA-F]{8}$
profile-substring1 2substrings1 through 1000 substrings, each 1 through 500 characters

1 Each value is canonicalised and written as its own row with its own Admin audit entry

2 scope is required in the same body

Each field is a string, except hashes and substrings, which are array[string].

avatar-hash deduplicates the array after canonicalisation. profile-substring does not, so a substring repeated in one request is written once and audited once per occurrence. A profile-substring value that canonicalises to an empty string is skipped.

FieldTypeDescription
scope?3stringThe profile substring scope to store the rows under
category?4stringThe content blocklist category (1-64 characters, default manual)
severity?4integerThe content blocklist severity (0-3, default 2)
match_subdomains?5booleanWhether the row also covers subdomains (default true)
content_type?6stringThe media type hint (1-128 characters)
source_url?4stringThe feed or evidence URL (1-2048 characters)
reason?7stringThe reason (1-1024 characters)
notes?8stringThe internal note (1-1024 characters)
duration_hours?9integerHours until the row expires (0-8760, default 0)

3 Required by profile-substring. Every other blocklist strips scope

4 Accepted only by url, url-domain, file-sha, and avatar-hash

5 Accepted only by url-domain

6 Accepted only by file-sha

7 Accepted only by avatar-hash and profile-substring, and recorded in the audit entry metadata

8 Accepted only by url, url-domain, file-sha, avatar-hash, and profile-substring

9 Accepted only by ip. 0 writes a permanent row. A positive value writes a row that stops applying after that many hours, and Fluxer then deletes it. Adding an address again replaces its expiry, so a permanent row can become temporary and a temporary row can become permanent

GET/v1/admin/blocklistsAudit reason

Returns one blocklist object for every blocklist this instance keeps. Requires any one of the check permissions.

The response is identical for every Admin.

FieldTypeDescription
items1array[blocklist object]Every blocklist exposed by this instance

1 Always the complete catalogue in one response. There is no cursor, no has_more, and no next_after

StatusBodyCondition
200response bodyCatalogue was returned

The operation records one Admin audit entry with action list_blocklists, target type blocklist, target ID 0, and metadata key result_count.

60 requests per minute for each authenticated user, on the admin:ban:operation bucket.

GET/v1/admin/blocklists/{list_type}/entriesAudit reason

Returns a value-ordered cursor page of blocklist entry objects. Requires the selected list’s check permission.

FieldTypeDescription
list_typestringThe blocklist type
FieldTypeDescription
limit?integerMaximum entries to return (1-200, default 50)
after?1stringReturn entries ordered strictly after this value (1-2048 characters)
scope?2stringThe profile substring scope to list

1 Compared as a string against the canonical stored value. Fluxer does not validate it against the selected blocklist, so an impossible cursor returns no entries

2 Required by profile-substring and rejected by every other blocklist

FieldTypeDescription
itemsarray[blocklist entry object]Entries in this page, ordered by value ascending
has_morebooleanWhether another page can be fetched with next_after
next_after3?stringThe stored entry value to send as after, or null on the last page

3 A value cursor, so it is sent back verbatim as after

StatusBodyCondition
200response bodyEntry page was returned
400error responsescope is supplied on an unscoped blocklist or omitted on profile-substring, and the request returns INVALID_FORM_BODY

The cost of a page does not fall as the cursor advances, so walk a large list with the largest limit the operation accepts.

The operation records one Admin audit entry with action list_blocklist_entries, the target type of the selected list, target ID 0, and metadata keys list_type, limit, result_count for the entries in this page, and has_more. profile-substring adds scope, and a supplied after adds has_after. The cursor value and the entry values are not recorded. A 400 records nothing.

60 requests per minute for each authenticated user, on the admin:ban:operation bucket.

POST/v1/admin/blocklists/{list_type}/entriesAudit reason

Canonicalises the submitted value or values and writes them to the selected blocklist. Returns 204 with an empty body. Requires the selected list’s add permission.

FieldTypeDescription
list_typestringThe blocklist type

The body is one blocklist entry creation object. list_type selects the shape, so a body valid for one blocklist fails validation on another.

StatusBodyCondition
204emptyValues were written
400error responseINVALID_FORM_BODY because the value is not valid for the list, or a field the blocklist does not accept was supplied

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 returns the same 400 naming domain.

Fluxer refuses to add an ip with 400 IP_BAN_DECLINED when the address is on the instance exemption list.

The response has no body, so it does not report the canonical form that was stored. Read it back with List blocklist entries.

Fluxer checks later requests against the written rows. For every list except email, other nodes see the rows after a short propagation delay. No Gateway Dispatch is emitted.

Fluxer records one Admin audit entry per written value, with that value in its metadata. An ip added with a positive duration_hours adds the metadata keys duration_hours and expires_at. A url-domain entry also records match_subdomains and pattern, which is true for a domain pattern. Fluxer records an entry for a refused ip too, under the action ban_ip_skipped_exempt, before it returns the 400.

60 requests per minute for each authenticated user, on the admin:ban:operation bucket.

PUT/v1/admin/blocklists/{list_type}/entriesAudit reason

Queues a background job that adds up to 10000 SHA-256 hashes to the file-sha blocklist and returns the job ID immediately. Requires ban:file_sha:add.

Only file-sha accepts this operation, reported as supports_bulk_create by List blocklists. Every other list type returns 400 INVALID_FORM_BODY.

FieldTypeDescription
list_typestringThe blocklist type, which is file-sha here
FieldTypeDescription
sha256_list1array[string]The SHA-256 hashes to add (1-10000 items of exactly 64 characters)

1 The body enforces the length of each item. The job checks the alphabet and records a non-hexadecimal item as an invalid_sha256 failure

FieldTypeDescription
job_idsnowflakeThe background job applying the hashes
StatusBodyCondition
200response bodyJob was queued
400error responseThe list type is not file-sha and the request returns INVALID_FORM_BODY

The operation returns before any hash is written. Read progress and the final counts from Get job, and stop the job with Cancel job. The job reports progress every 50 hashes and finishes with a summary naming how many were written and how many failed.

Every hash is written with the category manual, the severity 2, and a null content type, source URL, and note. The bulk path accepts no metadata, so a hash that needs any of those goes through Add blocklist entry or a later Update blocklist entry.

Each hash is lowercased and replaces any existing entry. Changes become visible across the instance when the job stops, whether it ran to the end or was cancelled, so a cancelled job still enforces the hashes it already wrote. No Gateway Dispatch is emitted.

The request records one Admin audit entry with action queue_bulk_job, target type bulk_job, the job ID as target ID, and metadata keys task and entity_count. task is ban_file_shas, and entity_count is the number of submitted hashes.

The job records one Admin audit entry per written hash under the action ban_file_sha, with that hash in its metadata, exactly as Add blocklist entry does. It then records one aggregate entry under the action bulk_ban_file_shas, with the submitted, processed, successful, and failed counts. A cancelled job records the entries for the hashes it wrote and an aggregate entry marked cancelled.

60 requests per minute for each authenticated user, on the admin:ban:operation bucket.

DELETE/v1/admin/blocklists/{list_type}/entriesAudit reason

Canonicalises up to 1000 values and removes their rows from the selected blocklist. Returns 204 with an empty body. Requires the selected list’s remove permission.

Only avatar-hash and profile-substring accept this operation, reported as supports_bulk_delete by List blocklists. Every other list type returns 400 INVALID_FORM_BODY.

FieldTypeDescription
list_typestringThe blocklist type, which is avatar-hash or profile-substring here

The body is selected by list_type.

FieldTypeDescription
hashes?1array[string]Avatar hashes to remove (1-1000 items of 8 through 10 characters matching ^(a_)?[0-9a-fA-F]{8}$)
scope?2stringThe profile substring scope the substrings are stored under
substrings?2array[string]Substrings to remove (1-1000 items of 1 through 500 characters)
reason?3stringAccepted and ignored
notes?3stringAccepted and ignored

1 avatar-hash only, where it is required and is the whole body

2 profile-substring only, where both are required

3 profile-substring only. Fluxer records neither in the audit entry

StatusBodyCondition
204emptyValues were processed
400error responseThe list accepts no bulk removal, or the body fails validation, and the request returns INVALID_FORM_BODY

The operation is idempotent and reports no counts. A value with no stored row still returns 204. Read the list before and after to tell a removal from a no-op.

avatar-hash deduplicates the array after canonicalisation. profile-substring does not, so a substring repeated in one request is removed once and audited once per occurrence. A profile-substring value that canonicalises to an empty string is skipped.

After the removal reaches every node, Fluxer no longer checks later requests against the removed entries. A value can remain blocked by another matching entry. No Gateway Dispatch is emitted.

The operation records one Admin audit entry per canonical value processed.

60 requests per minute for each authenticated user, on the admin:ban:operation bucket.

GET/v1/admin/blocklists/{list_type}/entries/{entry_value}Audit reason

Reports whether one value is currently blocked by the selected blocklist and returns a blocklist check object. Requires the selected list’s check permission.

FieldTypeDescription
list_typestringThe blocklist type
entry_value1stringThe percent-encoded value to check (1-2048 characters)

1 Percent encoding is required because a canonical value can contain a slash. Fluxer checks the value for length alone, so a value in the wrong form returns 200 with banned false

FieldTypeDescription
scope?2stringThe profile substring scope to check the value against

2 Required by profile-substring and rejected by every other blocklist

BlocklistRule
ip3 4The address itself, any stored CIDR range containing it, and any stored address the instance treats as the same origin
emailExact match on the lowercased address
phraseNormalised phrase matching, so a disguised form of a stored phrase still reads as blocked
url5Exact match on the canonicalised URL
url-domain6The hostname, each parent domain stored with match_subdomains true, and each domain pattern covering the hostname
file-shaExact match on the lowercased hexadecimal digest
avatar-hashExact match after the a_ prefix is stripped and the hash is lowercased
profile-substringNormalised substring matching within the named scope

3 Any value that is not a single address, including a CIDR range, always reads as not blocked. An address on the instance exemption list reads as not blocked even while a row exists

4 Two addresses are the same origin when they share a decision key, which is the exact address for IPv4 and the /64 prefix for IPv6

5 This check does not read the url-domain list, so a URL that a stored domain blocks reads as not blocked. Check the hostname separately

6 The value can be a hostname or an absolute http or https URL. Fluxer normalises the hostname as it does for link matching

StatusBodyCondition
200blocklist check objectValue was checked
400error responsescope is supplied on an unscoped blocklist or omitted on profile-substring, and the request returns INVALID_FORM_BODY

A value with no row and no covering match returns 200 with banned false, so this operation never reports whether a specific row exists. Use List blocklist entries for that.

For every list except email, a row written on another node becomes visible here after a short propagation delay.

The operation records one Admin audit entry with action check_blocklist_entry, the target type of the selected list, target ID 0, and metadata keys list_type and banned. profile-substring adds scope. The checked value is not recorded. A 400 records nothing.

60 requests per minute for each authenticated user, on the admin:ban:operation bucket.

PATCH/v1/admin/blocklists/{list_type}/entries/{entry_value}Audit reason

Rewrites the stored fields of one row without removing and re-adding it. Returns 204 with an empty body. Requires the selected list’s add permission.

Only url, url-domain, file-sha, avatar-hash, and profile-substring accept this operation, reported as supports_update by List blocklists. A list with no stored fields has nothing to update and returns 400 INVALID_FORM_BODY.

FieldTypeDescription
list_typestringThe blocklist type
entry_valuestringThe percent-encoded canonical value of the row to write (1-2048 characters)

The body is the creation shape of the selected blocklist with the value field removed, so the same field list and the same bounds apply.

FieldTypeDescription
scope?1stringThe profile substring scope to write the row under
category?2stringThe content blocklist category (1-64 characters, default manual)
severity?2integerThe content blocklist severity (0-3, default 2)
match_subdomains?3booleanWhether the row also covers subdomains (default true)
content_type?4stringThe media type hint (1-128 characters)
source_url?2stringThe feed or evidence URL (1-2048 characters)
reason?5stringThe reason (1-1024 characters)
notes?6stringThe internal note (1-1024 characters)

1 Required by profile-substring. This operation takes no query parameters, so the scope goes in the body, and every other blocklist ignores it there

2 Accepted only by url, url-domain, file-sha, and avatar-hash

3 Accepted only by url-domain

4 Accepted only by file-sha

5 Accepted only by avatar-hash and profile-substring, and recorded in the audit entry metadata

6 Accepted only by url, url-domain, file-sha, avatar-hash, and profile-substring

StatusBodyCondition
204emptyRow was written
400error responseThe list accepts no update, or the body fails validation, and the request returns INVALID_FORM_BODY

On the url blocklist, an entry_value Fluxer cannot canonicalise returns 400 INVALID_FORM_BODY naming url in the errors array. On the url-domain blocklist, an entry_value that is not a valid domain or domain pattern returns the same 400 naming domain.

The write is an upsert on the canonical value, so a value with no existing row is created. This operation never returns 404. A url blocked only by a stored url-domain row has no row of its own, and updating it through the URL silently creates a new exact-URL row.

Changing the scope of a profile-substring row writes a second row under the new scope and leaves the original in place. Removing the original takes Remove blocklist entry.

After the change reaches every node, later matches use the updated fields. No Gateway Dispatch is emitted.

The write records one Admin audit entry under the same action an add records, with the canonical value but no previous values of the changed fields.

60 requests per minute for each authenticated user, on the admin:ban:operation bucket.

DELETE/v1/admin/blocklists/{list_type}/entries/{entry_value}Audit reason

Removes one row from the selected blocklist. Returns 204 with an empty body. Requires the selected list’s remove permission.

FieldTypeDescription
list_typestringThe blocklist type
entry_value1stringThe percent-encoded canonical value of the row to remove (1-2048 characters)

1 Canonicalised the same way it was on the add path and matched exactly, so a url blocked only by a broader url-domain row cannot be removed through the URL. A url-domain value that fails validation is lowercased and removed as written

FieldTypeDescription
scope?2stringThe profile substring scope the row is stored under

2 Required by profile-substring and rejected by every other blocklist

StatusBodyCondition
204emptyValue was processed
400error responsescope is supplied on an unscoped blocklist or omitted on profile-substring, and the request returns INVALID_FORM_BODY

On the url blocklist, an entry_value Fluxer cannot canonicalise returns 400 INVALID_FORM_BODY naming url in the errors array.

Removal is idempotent. A value with no stored row returns 204 and still records an Admin audit entry, so this operation never returns 404 and never reports whether anything was removed.

The removed row stops affecting subsequent blocklist decisions. For every list except email, other nodes stop applying it after a short propagation delay. A value can remain blocked by another matching row, such as a single address covered by a stored CIDR range. No Gateway Dispatch is emitted.

The removal records one Admin audit entry, with the canonical value in its metadata.

60 requests per minute for each authenticated user, on the admin:ban:operation bucket.

POST/v1/admin/users/{user_id}/avatar-blockAudit reason

Adds the account’s current avatar hash to the avatar-hash blocklist and returns the stored hash. Requires ban:avatar_hash:add.

This is a shortcut over Add blocklist entry for blocking an avatar an Admin is looking at. The operation reads the current avatar hash and strips any a_ animation prefix, so the stored value is the 8-character form. It writes the same row an add writes, with the category manual, the severity 2, and a null source URL. It does not ban, suspend, or otherwise act on the account.

FieldTypeDescription
user_idsnowflakeThe account whose current avatar is blocked

Both fields are optional, so an empty body and {} are both valid requests.

FieldTypeDescription
reason?1stringThe reason (1-1024 characters)
notes?2stringThe internal note (1-1024 characters)

1 Recorded in the metadata of the Admin audit entry, so no read of the row returns it

2 Stored on the row. Omitting it stores the generated note banned via user shortcut user_id=<user_id>

FieldTypeDescription
hash_shortstringThe stored 8-character avatar hash
StatusBodyCondition
200response bodyAvatar hash was written
404error responseUNKNOWN_USER when the account does not exist, or NOT_FOUND when the account has no avatar

The account keeps the blocked avatar until it next sets one. Clearing it is a separate request naming avatar in the fields array of Clear user profile fields.

The avatar hash is the first 8 characters of the MD5 digest of the image, so every account using that image has the same avatar hash, so blocking the hash blocks the image for every account.

The hash blocks subsequent avatar uploads after the change propagates across the instance. No Gateway Dispatch is emitted.

Fluxer records one Admin audit entry under the action ban_avatar_hash, with the stored hash and the supplied reason.

60 requests per minute for each authenticated user, on the admin:ban:operation bucket.