Admin feed moderation
Admin feed routes remove a feed post, a comment on one or a reply to a comment, or put a public post that reports took off Discover back on it. Those routes require feed:moderate; the feed category routes require feed:category:manage instead. Reports about feed content arrive through the Admin Reports API with report type FEED_POST (3) or FEED_COMMENT (4); their reported_message_id is the post, comment or reply ID, and a comment report’s first context entry is its post. A reply report’s second context entry is the comment it answers, whose ID the reply delete route takes.
Delete feed post
Section titled “Delete feed post”DELETE/v1/admin/feed/posts/{post_id}Removes a post with its images or video, likes, comments and replies, and returns 204 with an empty body. Requires feed:moderate.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| post_id | snowflake | The ID of the post |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 204 | empty | The post was removed and the audit entry was written |
| 403 | error response | The caller lacks feed:moderate |
| 404 | error response | No post has the ID and the request returns UNKNOWN_FEED_POST |
Side effects
Section titled “Side effects”Home feeds drop the post on their next read, and its notifications leave the author’s inbox. The author receives a system DM in their locale saying a moderator removed one of their posts, through the same delivery job as System DMs. No Dispatch is emitted.
One Admin audit entry is recorded with the action delete_feed_post and the target type feed_post, targeting the post ID. Its metadata is author_id, and evidence_keys when the post had images or a video: the comma-separated reports bucket keys of their copies.
Rate limit
Section titled “Rate limit”100 requests per minute for each authenticated user, on the admin:feed:moderation bucket.
Restore feed post to Discover
Section titled “Restore feed post to Discover”DELETE/v1/admin/feed/posts/{post_id}/discover-hideUndoes the hide that enough reports put on a public post, and returns 204 with an empty body. The post’s reports are cleared, it returns to the Discover ranking with the score its reactions earned, and new reports cannot take it off Discover again for 72 hours. Requires feed:moderate.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| post_id | snowflake | The ID of the post |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 204 | empty | The hide was undone and the audit entry was written |
| 403 | error response | The caller lacks feed:moderate |
| 404 | error response | No post has the ID and the request returns UNKNOWN_FEED_POST |
Side effects
Section titled “Side effects”Discover shows the post again from its next list build, about a minute later; a post that is not public or is older than 72 hours stays off Discover. The author is not told, as they were not told of the hide. No Dispatch is emitted.
One Admin audit entry is recorded with the action restore_feed_post_discover and the target type feed_post, targeting the post ID. Its metadata is author_id.
Rate limit
Section titled “Rate limit”100 requests per minute for each authenticated user, on the admin:feed:moderation bucket.
Delete feed comment
Section titled “Delete feed comment”DELETE/v1/admin/feed/posts/{post_id}/comments/{comment_id}Removes a top-level comment on a post with its replies and returns 204 with an empty body. Requires feed:moderate.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| post_id | snowflake | The ID of the post |
| comment_id | snowflake | The ID of the comment |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 204 | empty | The comment was removed and the audit entry was written |
| 403 | error response | The caller lacks feed:moderate |
| 404 | error response | The post has no comment with the ID and the request returns UNKNOWN_FEED_COMMENT |
Side effects
Section titled “Side effects”The post’s comment count drops by one plus the number of replies. The comment’s author receives a system DM in their locale saying a moderator removed one of their comments; the authors of its replies are not told. No Dispatch is emitted.
One Admin audit entry is recorded with the action delete_feed_comment and the target type feed_comment, targeting the comment ID. Its metadata is post_id and author_id.
Rate limit
Section titled “Rate limit”100 requests per minute for each authenticated user, on the admin:feed:moderation bucket.
Delete feed comment reply
Section titled “Delete feed comment reply”DELETE/v1/admin/feed/posts/{post_id}/comments/{comment_id}/replies/{reply_id}Removes a reply to a comment on a post and returns 204 with an empty body. Requires feed:moderate.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| post_id | snowflake | The ID of the post |
| comment_id | snowflake | The ID of the top-level comment the reply answers |
| reply_id | snowflake | The ID of the reply |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 204 | empty | The reply was removed and the audit entry was written |
| 403 | error response | The caller lacks feed:moderate |
| 404 | error response | The comment has no reply with the ID and the request returns UNKNOWN_FEED_COMMENT |
Side effects
Section titled “Side effects”The post’s comment count and the comment’s reply count drop by one. The reply’s author receives a system DM in their locale saying a moderator removed one of their comments. No Dispatch is emitted.
One Admin audit entry is recorded with the action delete_feed_comment_reply and the target type feed_comment, targeting the reply ID. Its metadata is post_id, parent_id (the comment the reply answers) and author_id.
Rate limit
Section titled “Rate limit”100 requests per minute for each authenticated user, on the admin:feed:moderation bucket.
Feed categories
Section titled “Feed categories”The feed categories and the phrases refused in posts filed under one live in the instance configuration under the feed_category_config key, at most 50 categories and 500 phrases, and no public instance route returns them. Every route in this section requires feed:category:manage, which feed:moderate does not grant, and records an Admin audit entry, reads included. Writes are saved with a compare-and-set, and every API process picks them up through the instance configuration refresh. A value outside its range returns 400 INVALID_FORM_BODY.
Feed category admin object
Section titled “Feed category admin object”| Field | Type | Description |
|---|---|---|
| id | snowflake | The ID of the category |
| name | string | The name (1-32 characters) |
| emoji | string | The emoji shown before the name (1-16 characters) |
| position | integer | The display order, lowest first (0-1000) |
| enabled | boolean | Whether the category is listed and takes new posts |
| countries | array[string] | Upper case ISO 3166-1 alpha-2 codes the category is shown in, at most 50; empty shows it everywhere |
| created_at | ISO8601 timestamp | When the category was created |
Feed categories admin object
Section titled “Feed categories admin object”| Field | Type | Description |
|---|---|---|
| categories | array[feed category admin object] | Every category, disabled ones included, by position |
| blocked_phrases | array[string] | The phrases refused in posts filed under a category |
Get feed categories
Section titled “Get feed categories”GET/v1/admin/feed/categoriesReturns the feed categories admin object, read from storage. Requires feed:category:manage.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | feed categories admin object | The categories were returned |
| 403 | error response | The caller lacks feed:category:manage |
Side effects
Section titled “Side effects”One entry is recorded with action view_feed_categories, target type feed_category, target ID 0, and metadata keys result_count and phrase_count.
Rate limit
Section titled “Rate limit”60 requests per minute for each authenticated user, on the admin:feed:categories bucket.
Create feed category
Section titled “Create feed category”POST/v1/admin/feed/categoriesAdds a category and returns the feed category admin object. There is no delete; past 50 categories, reuse one that is turned off. Requires feed:category:manage.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| name | string | The name (1-32 characters) |
| emoji | string | The emoji (1-16 characters) |
| position? | integer | The display order (0-1000, default 0) |
| enabled? | boolean | Whether the category is on (default true) |
| countries? | array[string] | Two-letter country codes, in any case; repeats are dropped (default empty) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | feed category admin object | The category was created |
| 400 | error response | A value is outside its range, or 50 categories exist |
| 403 | error response | The caller lacks feed:category:manage |
Side effects
Section titled “Side effects”One entry is recorded with action create_feed_category, target type feed_category, the ID of the category as target ID, and metadata keys name, emoji, countries, enabled and position.
Rate limit
Section titled “Rate limit”60 requests per minute for each authenticated user, on the admin:feed:categories bucket.
Update feed category
Section titled “Update feed category”PATCH/v1/admin/feed/categories/{category_id}Changes a category and returns the feed category admin object. Any field of the create body is optional, and at least one is required; an omitted field stays as it is. A category turned off is no longer listed and takes no new posts, and its page is empty; posts already filed keep its name. Requires feed:category:manage.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| category_id | snowflake | The ID of the category |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | feed category admin object | The category was saved |
| 400 | error response | A value is outside its range, or the body is empty |
| 403 | error response | The caller lacks feed:category:manage |
| 404 | error response | No category has the ID and the request returns NOT_FOUND |
Side effects
Section titled “Side effects”One entry is recorded with action update_feed_category, target type feed_category, the ID of the category as target ID, and a <field>_old and <field>_new metadata key for every field it changed; countries are joined with commas.
Rate limit
Section titled “Rate limit”60 requests per minute for each authenticated user, on the admin:feed:categories bucket.
Replace feed category phrases
Section titled “Replace feed category phrases”PUT/v1/admin/feed/categories/phrasesReplaces the list of phrases refused in posts filed under a category and returns the feed categories admin object. The phrases are stored in lower case without repeats. Requires feed:category:manage.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| blocked_phrases | array[string] | The whole list, at most 500 phrases of 3-64 characters each, each keeping at least 3 letters or digits once folded |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | feed categories admin object | The list was saved |
| 400 | error response | A phrase is outside its length or keeps fewer than 3 letters or digits, or the list is too long |
| 403 | error response | The caller lacks feed:category:manage |
Side effects
Section titled “Side effects”One entry is recorded with action update_feed_category_phrases, target type feed_category, target ID 0, and metadata keys phrase_count_old and phrase_count_new; the phrases themselves are not recorded.
Rate limit
Section titled “Rate limit”60 requests per minute for each authenticated user, on the admin:feed:categories bucket.
Feed topics
Section titled “Feed topics”The feed topics live in feed_topics; the topic limits and the suggestion job’s switches live in the instance configuration under the feed_topic_config key. Every route in this section requires feed:category:manage, the same admins as the categories, and records an Admin audit entry, reads included. A topic lives at most 72 hours from when it was published. At most max_live topics (8 by default, 1-20), fixture topics apart, can be live or waiting to start at once, and at most max_per_day (20 by default, 1-20) can be published a UTC day, removed ones included. Fixture topics have their own cap, max_live_fixtures (4 by default, 1-20): the suggestion job publishes no fixture past it and queues it instead, while an admin may publish a queued fixture past it. The suggestion job reads the Gemini key from the integrations.gemini.api_key configuration (FLUXER_GEMINI_API_KEY) and the fixture key from integrations.api_football.api_key (FLUXER_API_FOOTBALL_KEY), each also from a _FILE path; the older GEMINI_API_KEY and APIFOOTBALL_KEY names still apply. Neither key is ever in this configuration. Only public headlines and patch note titles reach the LLM; rising feed words come to the queue as they are. A value outside its range returns 400 INVALID_FORM_BODY.
Feed topic admin object
Section titled “Feed topic admin object”| Field | Type | Description |
|---|---|---|
| id | snowflake | The ID of the topic |
| title | string | The title (1-60 characters) |
| description | ?string | One sentence (1-140 characters) |
| image | ?string | The hash of the image, served at branding/feed-topic-{id}/{image}.webp |
| category_id | snowflake | The category the topic and its posts are filed under |
| starts_at | ISO8601 timestamp | When it starts taking posts |
| ends_at | ISO8601 timestamp | When it stops taking posts |
| status | string | scheduled, live, ended or removed |
| source | string | fixture, news, patch, feed_trend or manual |
| source_ref | ?string | The item it came from, a fixture ID or a link |
| created_by | ?snowflake | The admin who published it; null when the system did |
| created_at | ISO8601 timestamp | When it was published |
| post_count | integer | How many posts its page lists |
Feed topic config object
Section titled “Feed topic config object”| Field | Type | Description |
|---|---|---|
| max_live | integer | Topics live or waiting to start at once, fixture topics apart (1-20) |
| max_live_fixtures | integer | Fixture topics live or waiting to start at once; binds the suggestion job, not an admin (1-20) |
| max_per_day | integer | Topics published a UTC day (1-20) |
| default_hours | integer | The life of a topic given no end (1-72) |
| auto_publish_fixtures | boolean | Whether fixture topics of the chosen leagues publish on their own |
| fixture_category_id | ?snowflake | The category fixture topics go under |
| fixture_leagues | array[integer] | API-Football league IDs whose matches become topics, at most 20 |
| llm_enabled | boolean | Whether the job asks the LLM to turn headlines into proposals |
| llm_model | string | The Gemini model the job asks (gemini-3.8-flash by default) |
| sources | array[object] | {id, enabled} for each source the job knows; the addresses are fixed in code |
Feed topics admin object
Section titled “Feed topics admin object”| Field | Type | Description |
|---|---|---|
| topics | array[feed topic admin object] | The topics published in the last four days, every status, newest first |
| config | feed topic config object | The topic config, every source the job knows listed |
Feed topic proposal object
Section titled “Feed topic proposal object”| Field | Type | Description |
|---|---|---|
| id | snowflake | The ID of the proposal |
| title | string | The proposed title |
| description | ?string | The proposed sentence |
| category_id | ?snowflake | The proposed category; null when none fit |
| source | string | fixture, news, patch or feed_trend |
| source_refs | array[string] | The items behind it, at most 10 |
| reason | ?string | The job’s short reason |
| sensitive1 | boolean | Whether the proposal is held for an admin; never published on its own |
| status | string | pending, published or rejected |
| starts_at | ?ISO8601 timestamp | The start a fixture proposal brings |
| ends_at | ?ISO8601 timestamp | The end a fixture proposal brings |
| topic_id | ?snowflake | The topic published from it |
| created_at | ISO8601 timestamp | When it was proposed |
1 Flagged by the LLM, holding a category phrase in its own text or in a headline it rests on, or resting on no headline
Feed topic proposals admin object
Section titled “Feed topic proposals admin object”| Field | Type | Description |
|---|---|---|
| proposals | array[feed topic proposal object] | The proposals of the day, newest first, at most 100 |
Get feed topics
Section titled “Get feed topics”GET/v1/admin/feed/topicsReturns the feed topics admin object, read from storage. Requires feed:category:manage.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | feed topics admin object | The topics were returned |
| 403 | error response | The caller lacks feed:category:manage |
Side effects
Section titled “Side effects”One entry is recorded with action view_feed_topics, target type feed_topic, target ID 0, and metadata key result_count.
Rate limit
Section titled “Rate limit”60 requests per minute for each authenticated user, on the admin:feed:topics bucket.
Publish feed topic
Section titled “Publish feed topic”POST/v1/admin/feed/topicsPublishes a topic and returns the feed topic admin object. A topic from a pending proposal (proposal_id) takes the proposal’s source and times unless the body sets them, and the proposal is marked published. Requires feed:category:manage.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| title | string | The title (1-60 characters) |
| description? | ?string | One sentence (1-140 characters) |
| category_id | snowflake | An enabled category |
| image? | string | A base64 PNG, JPEG or WebP image, still, at most 512 KB, 96-1,920 pixels wide and 96-1,080 high |
| starts_at? | ISO8601 timestamp | The start (default now) |
| ends_at? | ISO8601 timestamp | The end, after the start and now, at most 72 hours after publishing (default the start plus default_hours) |
| proposal_id? | snowflake | A pending proposal this publishes |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | feed topic admin object | The topic was published |
| 400 | error response | A value is outside its range, the category is off, the proposal is not pending, or max_live or max_per_day is reached |
| 403 | error response | The caller lacks feed:category:manage |
| 404 | error response | No proposal has the ID and the request returns NOT_FOUND |
Side effects
Section titled “Side effects”One entry is recorded with action create_feed_topic, target type feed_topic, the ID of the topic as target ID, and metadata keys title, category_id, source, starts_at and ends_at, and proposal_id when it was published from a proposal.
Rate limit
Section titled “Rate limit”60 requests per minute for each authenticated user, on the admin:feed:topics bucket.
Update feed topic
Section titled “Update feed topic”PATCH/v1/admin/feed/topics/{topic_id}Changes a topic and returns the feed topic admin object. At least one field is required; an omitted field stays as it is. status ended stops the topic now; removed takes it down for good, hides it from members and drops its name from posts. A removed topic cannot be changed. Requires feed:category:manage.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| topic_id | snowflake | The ID of the topic |
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| title? | string | The title (1-60 characters) |
| description? | ?string | One sentence (1-140 characters), or null to drop it |
| image? | ?string | A new image as in the publish body, or null to drop it |
| ends_at? | ISO8601 timestamp | A new end, after now and the start, at most 72 hours after publishing |
| status? | string | ended or removed |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | feed topic admin object | The topic was saved |
| 400 | error response | A value is outside its range, the body is empty, or the topic is removed |
| 403 | error response | The caller lacks feed:category:manage |
| 404 | error response | No topic has the ID and the request returns NOT_FOUND |
Side effects
Section titled “Side effects”One entry is recorded with action update_feed_topic, target type feed_topic, the ID of the topic as target ID, and <field>_old and <field>_new metadata for each of title, description, image, starts_at, ends_at and status that changed.
Rate limit
Section titled “Rate limit”60 requests per minute for each authenticated user, on the admin:feed:topics bucket.
List feed topic proposals
Section titled “List feed topic proposals”GET/v1/admin/feed/topics/proposalsReturns the feed topic proposals admin object for a UTC day. Proposals are kept for a week. Requires feed:category:manage.
Query parameters
Section titled “Query parameters”| Field | Type | Description |
|---|---|---|
| date? | string | The UTC day as YYYY-MM-DD (default today) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | feed topic proposals admin object | The proposals were returned, possibly none |
| 403 | error response | The caller lacks feed:category:manage |
Side effects
Section titled “Side effects”One entry is recorded with action view_feed_topic_proposals, target type feed_topic, target ID 0, and metadata keys result_count and, when given, date.
Rate limit
Section titled “Rate limit”60 requests per minute for each authenticated user, on the admin:feed:topics bucket.
Reject feed topic proposal
Section titled “Reject feed topic proposal”POST/v1/admin/feed/topics/proposals/{proposal_id}/rejectMarks a pending proposal rejected and returns the feed topic proposal object. The job does not propose the same title again for three days. Requires feed:category:manage.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| proposal_id | snowflake | The ID of the proposal |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | feed topic proposal object | The proposal was rejected |
| 400 | error response | The proposal was already published or rejected |
| 403 | error response | The caller lacks feed:category:manage |
| 404 | error response | No proposal has the ID and the request returns NOT_FOUND |
Side effects
Section titled “Side effects”One entry is recorded with action reject_feed_topic_proposal, target type feed_topic_proposal, the ID of the proposal as target ID, and metadata keys title and source.
Rate limit
Section titled “Rate limit”60 requests per minute for each authenticated user, on the admin:feed:topics bucket.
Replace feed topic config
Section titled “Replace feed topic config”PUT/v1/admin/feed/topics/configReplaces the feed topic config and returns it. Every field is required. Requires feed:category:manage.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | feed topic config object | The config was saved |
| 400 | error response | A value is outside its range |
| 403 | error response | The caller lacks feed:category:manage |
Side effects
Section titled “Side effects”One entry is recorded with action update_feed_topic_config, target type feed_topic, target ID 0, and metadata keys max_live, max_live_fixtures, max_per_day, default_hours, auto_publish_fixtures, fixture_category_id (when set), llm_enabled, llm_model, source_count and league_count, and sources_on and sources_off (the source IDs the update turned on and off, comma separated) when any changed.
Rate limit
Section titled “Rate limit”60 requests per minute for each authenticated user, on the admin:feed:topics bucket.