DevPlace REST: Reporting and Moderation Queue
The polymorphic report endpoint, the machine-readable reason list, and the administrator moderation queue: its state machine, actions, seniority rule and acknowledgement contract with the published review window.
Every externally visible surface on DevPlace is reportable through one polymorphic endpoint, and every report lands in one queue with one state machine. The reason list is served by GET /reports/reasons, so a native client renders the same dialog the web UI does.
DevPlace commits to reviewing every report within the window published on the content moderation page. Filing a report always returns an acknowledgement carrying that window.
The moderation endpoints under /admin/moderation are administrator-only and are subject to the admin seniority rule: a junior administrator cannot action a more senior one.
Every endpoint follows the shared Conventions & Errors (auth, content negotiation, pagination, status codes); see Authentication for the four ways to sign requests.
GET /reports/reasons - List report reasons
The reason keys a report may be filed under, with their labels.
Minimal role: Public
Sample response
{
"reasons": [
{
"key": "harassment",
"label": "Harassment or bullying"
}
],
"severities": [
"info",
"warn",
"critical"
]
}
POST /reports/{targettype}/{targetuid} - Report content
File a report against any user-generated surface.
Minimal role: Member
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
target_type | path | enum | yes | The kind of content being reported. Allowed: post, comment, gist, project, projectfile, news, attachment, message, quiz, poll, award, user, issue, workspace, deviioutput. |
target_uid | path | string | yes | UID of the reported item. |
reason | form | enum | yes | Why the content breaks the guidelines. Allowed: hate, violence, weapons, sexual, religious, misinformation, exploitative, harassment, spam, intellectualproperty, selfharm, illegal, other. |
detail | form | string | no | Free text for the moderator, up to 2000 characters. |
A second report on the same target by the same reporter updates the open report instead of creating a duplicate.
You cannot report your own content.
Sample response
{
"ok": true,
"redirect": "/reports/mine",
"data": {
"uid": "REPORT_UID",
"status": "open",
"severity": "warn",
"sla_hours": 24
}
}
GET /reports/mine - List your reports
The reports you filed and the outcome of each.
Minimal role: Member
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
status | query | enum | no | Filter by report status. Allowed: open, acknowledged, actioned, dismissed. |
page | query | integer | no | Page number. |
Sample response
{
"reports": [
{
"uid": "REPORT_UID",
"target_type": "post",
"target_uid": "POST_UID",
"target_url": "/posts/a-post",
"reason": "harassment",
"reason_label": "Harassment or bullying",
"detail": "Repeated personal attacks in the thread.",
"severity": "warn",
"status": "open",
"origin": "member",
"categories": [],
"created_at": "2026-01-05T10:00:00+00:00",
"resolved_at": "",
"reporter_name": "alice",
"owner_name": "bob",
"report_count": 2
}
],
"pagination": {
"page": 1,
"total": 1,
"total_pages": 1
},
"status": ""
}
GET /admin/moderation - The moderation queue
Reported content awaiting a decision, oldest open first.
Minimal role: Admin
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
status | query | enum | no | Filter by report status. Allowed: open, acknowledged, actioned, dismissed. |
page | query | integer | no | Page number. |
Sample response
{
"reports": [
{
"uid": "REPORT_UID",
"target_type": "post",
"target_uid": "POST_UID",
"target_url": "/posts/a-post",
"reason": "harassment",
"reason_label": "Harassment or bullying",
"detail": "Repeated personal attacks in the thread.",
"severity": "warn",
"status": "open",
"origin": "member",
"categories": [],
"created_at": "2026-01-05T10:00:00+00:00",
"resolved_at": "",
"reporter_name": "alice",
"owner_name": "bob",
"report_count": 2
}
],
"counts": {
"open": 1,
"acknowledged": 0,
"actioned": 0,
"dismissed": 0
},
"sla": {
"sla_hours": 24,
"oldest_open_hours": 1.5,
"breached": 0,
"within_sla": true
}
}
GET /admin/moderation/{uid} - Read one report
One report with its decisions and the author's history.
Minimal role: Admin
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
uid | path | string | yes | Report UID. |
Sample response
{
"report": {
"uid": "REPORT_UID",
"target_type": "post",
"target_uid": "POST_UID",
"target_url": "/posts/a-post",
"reason": "harassment",
"reason_label": "Harassment or bullying",
"detail": "Repeated personal attacks in the thread.",
"severity": "warn",
"status": "open",
"origin": "member",
"categories": [],
"created_at": "2026-01-05T10:00:00+00:00",
"resolved_at": "",
"reporter_name": "alice",
"owner_name": "bob",
"report_count": 2
},
"actions": [],
"history": [],
"available_actions": [
"remove_content",
"restore_content",
"warn",
"suspend",
"ban",
"lift",
"dismiss",
"escalate"
],
"can_remove": true
}
POST /admin/moderation/{uid}/status - Set a report status
Move a report through the triage state machine.
Minimal role: Admin
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
uid | path | string | yes | Report UID. |
status | form | enum | yes | New status. Allowed: open, acknowledged, actioned, dismissed. |
Sample response
{
"ok": true,
"redirect": "/admin/moderation/REPORT_UID"
}
POST /admin/moderation/{uid}/decide - Decide a report
Apply a moderation decision and notify the affected user.
Minimal role: Admin
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
uid | path | string | yes | Report UID. |
action | form | enum | yes | The decision to apply. Allowed: removecontent, restorecontent, warn, suspend, ban, lift, dismiss, escalate. |
reason | form | string | no | Reason shown to the affected user. |
notes | form | string | no | Internal notes. |
duration_hours | form | integer | no | Suspension length in hours. |
A report already resolved by another moderator answers 409.
Content removal is unavailable for targets that have no removal path (direct messages, accounts, workspaces, polls, assistant output); act on the account instead.
Sample response
{
"ok": true,
"redirect": "/admin/moderation/REPORT_UID"
}
POST /admin/users/{uid}/suspend - Suspend an account
Suspend an account for a fixed period with a stated reason.
Minimal role: Admin
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
uid | path | string | yes | User UID. |
reason | form | string | no | Reason shown to the user. |
duration_hours | form | integer | no | Suspension length in hours. |
A suspended account can still read, still see why, and still delete itself, but cannot create content.
Sample response
{
"ok": true,
"redirect": "/admin/users"
}
POST /admin/users/{uid}/lift - Lift a restriction
Clear a suspension or ban and restore the account.
Minimal role: Admin
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
uid | path | string | yes | User UID. |
Sample response
{
"ok": true,
"redirect": "/admin/users"
}
POST /admin/users/{uid}/ban - Ban an account
Permanently close an account and revoke every credential.
Minimal role: Admin
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
uid | path | string | yes | User UID. |
reason | form | string | no | Reason shown to the user. |
Sample response
{
"ok": true,
"redirect": "/admin/users"
}
GET /auth/accept-terms - The terms acceptance page
The Terms of Service version in force and the version this account accepted.
Minimal role: Member
Sample response
{
"terms_version": "1",
"accepted_version": ""
}
POST /auth/accept-terms - Accept the terms
Record acceptance of the Terms of Service version in force.
Minimal role: Member
A member whose accepted version is behind the version in force is redirected here on any mutating request. Reading, the docs, the safety controls and account deletion are never blocked.
Sample response
{
"ok": true,
"redirect": "/feed",
"data": {
"terms_version": "1"
}
}
POST /profile/{username}/consent - Grant or withdraw a consent
Change one consent on your own account. Withdrawal takes effect at once.
Minimal role: Member
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
username | path | string | yes | Your own username. |
kind | form | enum | yes | Consent to change. Allowed: terms, privacy, aithirdparty, activityrecording, containercredentials. |
granted | form | enum | yes | 1 grants, 0 withdraws. Allowed: 1, 0. |
Withdrawing
aithirdpartymakes the AI gateway refuse every call that would send your own content to the provider, whatever the per-feature preference says.
Withdrawing
activity_recordingstops presence writes; you simply appear offline.
Only the account holder can change a consent. An administrator reads the record but never grants or withdraws it for someone else.
Sample response
{
"ok": true,
"redirect": "/profile/USERNAME?tab=privacy",
"data": {
"kind": "ai_third_party",
"state": "granted"
}
}
POST /profile/{username}/mature-content - Set the mature-content preference
Show or hide content labelled mature for your own account.
Minimal role: Member
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
username | path | string | yes | Your own username. |
matureoptin | form | enum | yes | 1 shows mature content, 0 hides it. Allowed: 1, 0. |
Only the account holder can change this preference. An administrator reads the privacy tab but never sets it for someone else.
Sample response
{
"ok": true,
"redirect": "/profile/USERNAME?tab=privacy",
"data": {
"mature_opt_in": true
}
}
GET /profile/{username}/delete - Account deletion page
What deletion removes, what is retained, and the grace window.
Minimal role: Member
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
username | path | string | yes | Your own username. |
Sample response
{
"username": "USERNAME",
"grace_hours": 24,
"removed": [
"Your account record, username, email address and password"
],
"retained": [
"Append-only audit and moderation records"
]
}
POST /profile/{username}/delete - Delete your account
Permanently delete your account and personal data.
Minimal role: Member
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
username | path | string | yes | Your own username. |
password | form | string | yes | Your account password. |
Only the account holder can delete an account; an administrator uses a ban instead.
Sessions and tokens are revoked and the profile is anonymised immediately; the deletion event is purged after the grace window.
Sample response
{
"ok": true,
"redirect": "/",
"data": {
"stamp": "2026-01-05T10:00:00+00:00",
"rows": 42,
"grace_hours": 24
}
}
GET /workspaces/index - Published workspace index
Every workspace published to the public ingress, with its link.
Minimal role: Public
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
page | query | integer | no | Page number. |
The project-derived
descriptionandproject_urlcome back empty unless you may view the workspace's project, so a private project never leaks its title or description through this public listing.
Sample response
{
"workspaces": [
{
"uid": "INSTANCE_UID",
"name": "demo",
"slug": "demo",
"owner_uid": "USER_UID",
"url": "https://devplace.net/p/demo",
"description": "A demo workspace.",
"owner": "alice",
"maturity": "general",
"project_url": "/projects/demo"
}
],
"total": 1
}