A queue, an action log, and an auto-hide threshold.
End-users file flags from your backend (PAK lane). Your team works the queue from the workspace admin lane. Every action is recorded in an append-only audit table.
1
File a flag
PAK lane. Idempotent on (reporter, target) while the flag is open — re-submitting returns the existing flag with created: false. target_kind is one of post, comment, actor, or direct_message (a DM flag requires the reporter to be in the conversation). reason is optional and comes back as an empty string when omitted.
curl -X POST -H "Authorization: Bearer pcft_live_..." \
-H "content-type: application/json" \
-d '{
"reporter_actor_id": "<bob>",
"target_kind": "post",
"target_id": "<post-uuid>",
"reason": "Off-topic for the community"
}' \
https://social.productcraft.co/v1/communities/<c>/flags{
"flag": {
"id": "9d584af5-980f-423b-9360-84b566b3b3d1",
"community_id": "<c>",
"target_kind": "post",
"target_id": "<post-uuid>",
"reporter_actor_id": "<bob>",
"reason": "Off-topic for the community",
"status": "open",
"created_at": "2026-05-02T19:36:05.521Z",
"updated_at": "2026-05-02T19:36:05.521Z"
},
"created": true,
"auto_hidden": false
}2
Auto-hide threshold
When the number of distinct reporters on the same target hits community.settings.auto_hide_flag_threshold (default 3), Social flips the target to status="hidden" and the response carries auto_hidden: true. Hidden content stops appearing in feeds and is invisible to non-author requesters until a moderator applies unhide.
3
Work the queue (admin lane)
The admin endpoints are workspace-scoped. The same Authorization: Bearer pcft_live_... key you use on the customer lane works here (policy permitting); a PlatformUser bearer or the platform session cookie work too, which is what console-adjacent internal tools use. Required permission: social.moderation.queue.read.
curl -H "Authorization: Bearer pcft_live_..." \
"https://social.productcraft.co/v1/workspaces/<ws>/communities/<c>/moderation/flags?status=open&limit=20"
# Filter by status: open | dismissed | actioned (anything else is a 400)
# Cursor-paginated, newest first.4
Apply an action
One action resolves the flag — and resolves every other open flag on the same target in one transition, so the queue self-clears. Required permission: social.moderate.
- hide / unhide — set / clear
status="hidden"on the post or comment. A hidden post is still readable viaGET /posts/:id(you'll seestatus: "hidden"in the response), it just stops appearing in feeds and non-author listings. - remove / restore — set / clear
status="removed"(a stricter soft-delete thanhidden). A removed post returns404fromGET /posts/:identirely.restorebrings it back topublished. - warn / ban — audit-only on this endpoint; they record the decision without touching the target. For real actor-level enforcement use
PATCH /actors/:actorIdwithstatus="suspended", or the shadow-ban endpoints on the same admin controller. - dismiss — close the flag without touching the target.
A flag transitions to actioned on its first non-dismiss action, and closed is closed: re-POSTing a punitive action (hide, remove, warn, ban) to a resolved flag returns 409 FLAG_ALREADY_CLOSED so blind retries can't stack duplicate audit rows. The reversals — unhide, restore, dismiss — stay legal on a closed flag and each records another moderation_action row, so a moderator can undo a hide under the same flag thread.
curl -X POST -H "Authorization: Bearer pcft_live_..." \
-H "content-type: application/json" \
-d '{
"action": "dismiss",
"notes": "Reviewed — content is on-topic."
}' \
https://social.productcraft.co/v1/workspaces/<ws>/communities/<c>/moderation/flags/<flag-uuid>/actions{
"flag": { "status": "dismissed", ... },
"moderation_action": {
"id": "7c4461e6-4904-4627-ad3e-ec80212b0f5a",
"community_id": "<c>",
"flag_id": "<flag-uuid>",
"target_kind": "post",
"target_id": "<post-uuid>",
"moderator_account_id": "<your-platform-user-id>",
"action": "dismiss",
"notes": "Reviewed — content is on-topic.",
"created_at": "2026-05-02T19:36:06.506Z"
}
}5
Audit trail
Append-only record of every moderator action. Required permission: social.audit.read.
curl -H "Authorization: Bearer pcft_live_..." \
"https://social.productcraft.co/v1/workspaces/<ws>/communities/<c>/moderation/audit?limit=50"Wrap-up
One stage to go
The moderation loop closes the public-content story: actors, posts, comments, reactions, follows, mutes, blocks, feeds, stories, polls, notifications, flags, audits. Stage 10 covers the private half — direct and group conversations. And the tutorial deliberately stays on the golden path; surfaces like search, bookmarks, hashtags, lists, and muted terms live in the single-feature guides, with the complete endpoint inventory on the API reference.
- The console at console.productcraft.co/social gives your moderation team a UI on top of these admin endpoints. A community's Moderation tab is the queue — Open / Actioned / Dismissed, with per-flag Hide · Remove · Warn · Ban · Dismiss buttons (and Unhide / Restore on resolved ones). The audit log renders as Recent moderation actions on the Analytics tab. Moderators can also review DM content from Moderation → Direct messages (moderation), a read-only conversation lane (covered in stage 10).
- When you're ready to wire actor identity into Auth (so end-user signin auto-creates an actor), pass
app_idwhen creating a new community (the app must belong to the same workspace). Existing standalone communities keep working unchanged.