REST: Moderation and Trust
This page documents the moderation REST surface in BuddyNext free: member reports, the moderation queue, appeals, the per-user trust actions (warnings, strikes, shadow-bans, suspensions, account type), per-space bans, and post content warnings. All routes live under the buddynext/v1 namespace and are registered by ModerationController in includes/Moderation/.

Overview / Contract
Section titled “Overview / Contract”- Base namespace:
buddynext/v1. Full base URL:/wp-json/buddynext/v1. - Three permission plans gate these routes:
- Auth (
require_auth) - any logged-in user. Used for filing reports and appeals. - Queue (
require_queue_access) - site admins (manage_options) plus space owners/moderators (scoped to their spaces). Used for reading and actioning the queue. - Admin (
require_admin) - site admins only. Used for trust actions and report dispositions.
- Auth (
- Unauthenticated calls to gated routes return
401 rest_forbidden; authenticated-but-unprivileged calls return403. - Path ids (
{id}for a report, user, or appeal;{sid}for a strike) are positive integers validated server-side. - Several surfaces share a path with both a GET (read) and a CREATE/EDIT (write) method; WordPress merges these registrations on the same route.
See the REST contract page (14-rest-contract) for the shared envelope, pagination, error shape, and nonce handling that apply to every route below.
Report routes
Section titled “Report routes”A report is filed by a member against an object (post, reply, user, etc.). Admins and queue-access roles triage it from the queue, then dismiss, escalate, resolve, or remove the content.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /reports |
Auth | File a report. Body: object_type, object_id, reason, optional notes, space_id. |
| GET | /reports |
Admin | List reports for a given object_type + object_id. |
| GET | /reports/queue |
Queue | Paginated moderation queue (pending + escalated). Space mods see only their spaces. |
| POST | /reports/{id}/dismiss |
Auth + report scope | Dismiss the report (no action warranted). |
| PUT | /reports/{id}/escalate |
Auth + report scope | Escalate the report to site-admin review. |
| PUT | /reports/{id}/resolve |
Auth + report scope | Resolve the report (handled). |
| POST | /reports/{id}/remove |
Auth + report scope | Remove the reported content. |
Note:
dismiss,escalate,resolve, andqueueare the report dispositions referenced across the moderation UI.escalateandresolveuseEDITABLE(PUT/PATCH);dismissandremoveuseCREATABLE(POST).
“Auth + report scope” is not “Admin”. The four report-action routes carry
require_authas theirpermission_callback- the route only checks that you are logged in. Authorization is per-report, inside the handler, viaguard_report_scope():
- a caller with
manage_optionsmay action any report;- otherwise the report must carry a
space_idthe caller owns or moderates (ModerationService::get_moderated_space_ids()), or the call returns403 bn_forbidden.This is deliberate, and it is the reason a
manage_optionspermission callback would be wrong here: a space owner or moderator has to be able to action the reports their own space Moderation tab shows them, and a site-admin-only gate would 403 them on their own queue. Do not “tighten” these callbacks torequire_admin- you would break space moderation. The same pattern applies toPOST /users/{id}/warn(authorized inwarn_user(): site admins may warn anyone; a space owner/moderator may warn a member in the context of a space they moderate, passed asspace_id).
Related queue surfaces
Section titled “Related queue surfaces”These sit alongside the report queue and share the same queue-access plan.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /moderation/pending |
Queue | Posts awaiting pre-moderation approval (paginated). |
| POST | /posts/{id}/approve |
Queue | Approve a pending post. |
| POST | /posts/{id}/reject |
Queue | Reject a pending post (optional reason). |
| GET | /moderation/log |
Admin | Moderation action log (paginated, filterable). |
Appeal routes
Section titled “Appeal routes”A member appeals a moderation action against them. Admins approve or deny.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /appeals |
Auth | File an appeal. |
| GET | /appeals |
Admin | List appeals. |
| PUT | /appeals/{id}/approve |
Admin | Approve the appeal (reverse the action). |
| PUT | /appeals/{id}/deny |
Admin | Deny the appeal. |
| POST | /appeals/{id}/resolve |
Admin | Mark the appeal resolved. |
| POST | /me/appeals |
Auth | File an appeal as the current user. |
| GET | /me/appeals |
Auth | The current user’s own appeals. |
User trust routes
Section titled “User trust routes”Per-user trust actions. All are site-admin only except POST /users/{id}/warn, which a space owner or moderator may also call in the context of a space they moderate (see the note above). Reads (warnings, suspension state, shadow-ban state, strikes) share paths with their write counterparts.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /users/{id}/warnings |
Admin | List warnings issued to the user. |
| POST | /users/{id}/warn |
Auth + space scope | Issue a warning to the user. Site admins may warn anyone; a space owner/moderator only within a space they moderate (pass space_id). |
| GET | /users/{id}/strikes |
Admin | List the user’s strikes. |
| POST | /users/{id}/strikes |
Admin | Add a strike to the user. |
| POST | /users/{id}/strikes/{sid}/reverse |
Admin | Reverse a specific strike. |
| GET | /users/{id}/shadow-ban |
Admin | Read the user’s shadow-ban state. |
| POST | /users/{id}/shadow-ban |
Admin | Shadow-ban the user. |
| DELETE | /users/{id}/shadow-ban |
Admin | Lift the shadow-ban. |
| GET | /users/{id}/suspension |
Admin | Read the user’s current suspension state. |
| GET | /users/{id}/suspensions |
Admin | List the user’s suspension history. |
| POST | /users/{id}/suspend |
Admin | Suspend the user. Body: optional reason, duration_days, hide_posts. |
| DELETE | /users/{id}/suspend |
Admin | Lift the suspension. |
| GET | /users/{id}/account-type |
Auth | The user’s account type (public/private). |
Note: Strikes and shadow-ban use a single path for read and write (GET/POST, plus DELETE for shadow-ban). Suspend likewise pairs POST (suspend) and DELETE (lift) on
/users/{id}/suspend, with separate GET reads on/suspension(current) and/suspensions(history).
Space bans
Section titled “Space bans”Per-space bans. Gated by require_space_owner_or_admin - site admins plus the owner/moderators of the target space.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /spaces/{id}/bans |
Space owner/admin | List the space’s bans. |
| POST | /spaces/{id}/bans |
Space owner/admin | Ban a user from the space. Body: user_id (required), optional reason. |
| DELETE | /spaces/{id}/bans/{user_id} |
Space owner/admin | Lift a user’s ban from the space. |
Content warnings
Section titled “Content warnings”A per-post content-warning flag. Reading it is public (so any viewer’s client can show the interstitial); setting or clearing it is site-admin only.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /posts/{id}/content-warning |
Public | Read the post’s content-warning state. |
| PUT | /posts/{id}/content-warning |
Admin | Set or clear the warning. Body: content_warning (boolean, required), optional content_warning_type. |
Examples
Section titled “Examples”File a report
Section titled “File a report”curl -X POST "https://example.com/wp-json/buddynext/v1/reports" \ -H "X-WP-Nonce: <wp_rest_nonce>" \ -H "Content-Type: application/json" \ --cookie "<auth cookies>" \ -d '{ "object_type": "post", "object_id": 128, "reason": "spam", "notes": "Repeated promotional links.", "space_id": 0 }'object_type, object_id, and reason are required; notes and space_id are optional. reason is sanitized as a key (lowercase, underscores).
Suspend a user
Section titled “Suspend a user”curl -X POST "https://example.com/wp-json/buddynext/v1/users/42/suspend" \ -H "X-WP-Nonce: <wp_rest_nonce>" \ -H "Content-Type: application/json" \ --cookie "<admin auth cookies>" \ -d '{ "reason": "Repeated harassment after warnings.", "duration_days": 7, "hide_posts": true }'All body fields are optional: omit duration_days for an indefinite suspension, set hide_posts to true to hide the user’s content for the duration. Lift the suspension with DELETE /users/42/suspend.
Notes / gotchas
Section titled “Notes / gotchas”- Queue scoping.
require_queue_accessgrants site admins the full queue, but space owners/moderators see only reports tied to their spaces. Build clients against the scoped result, not the assumption of a global view. - Read/write share a path. Where a GET and a POST/DELETE register on the same path (strikes, shadow-ban, appeals,
/me/appeals), WordPress merges them. Pick the method deliberately. - Disposition verbs are non-destructive vs destructive.
dismiss,escalate, andresolvechange a report’s state;removeacts on the underlying content. Treatremoveas the destructive path in any confirmation UI. - Appeals have two entry points.
/appeals(admin-facing list + per-appeal actions) and/me/appeals(the member’s own create + read). They are not interchangeable. - Account type is shared with the social graph surface. The same
/users/{id}/account-typeread documented in REST: Social Graph informs both follow-flow decisions and trust-context displays.

