Skip to content

Content reports API

The content reports API manages user-filed moderation reports against AI-generated content. Reports are produced by the Report button in the Chat view and reviewed in the Reports view (Overview › Feedback › Reports).

All endpoints require an authenticated admin session.


Listing reports

GET /admin/v1/reports

Required permission: TENANT_SETTINGS_MANAGE — held by default by an admin or tenant admin, and delegable by a tenant admin to a custom role.

The result is tenant-scoped on the server: every caller sees only reports from their own tenant, except a platform admin (the admin role), who sees reports across all tenants. Delegating TENANT_SETTINGS_MANAGE to a custom role never widens this — a custom-role holder is still confined to their own tenant. Paging (offset/limit) does not bypass the scope: a high offset returns later pages of the caller's own tenant only, never another tenant's rows.

Optional query parameters:

Parameter Description
status Filter by status: open, triaged, or dismissed.
limit Maximum reports to return. Default 100, clamped to 500.
offset Number of reports to skip, for paging. Default 0.

The response is an array of report objects with the fields listed below.


Updating a report

PATCH /admin/v1/reports/<ID>

Required permission: TENANT_SETTINGS_MANAGE.

The same tenant scope applies: a non-platform caller may update only a report that belongs to their own tenant. A PATCH targeting a report in another tenant is rejected with 403 and the row is left unchanged. A platform admin may update a report in any tenant.

Validation. status must be exactly open, triaged, or dismissed; an absent, null, or unknown value is rejected 400 {"error":"invalid status"} and nothing is written. An unknown <ID> returns 404 (report not found); a transient database fault returns 503 (retryable).

Field Type Description
status open | triaged | dismissed The new status.

When the status changes from open to triaged or dismissed, the server sets the triaged_at timestamp and the triaged_by_id field to the calling user's ID.


Filing a report

POST /admin/v1/reports

Required role: authenticated.

Field Type Required Description
reason offensive | inaccurate | unsafe | other Yes The reason flag. Rejected with 400 if missing, null, or not a recognised reason.
conversation_id string No Source conversation. Stored in a 36-char id column; a null, blank, or over-36-byte value is dropped to null (the report still files) rather than persisted or truncated to a wrong id.
message_id string No Reported message identifier. Same 36-byte cap and drop-to-null handling as conversation_id.
message_text string No Snapshot of the reported message body. Truncated to 16 000 bytes on a UTF-8 codepoint boundary (never mid-character); invalid UTF-8 bytes are scrubbed to U+FFFD; null/blank → SQL NULL.
notes string No Free-form notes from the reporter. Truncated to 2 000 bytes on a codepoint boundary; invalid UTF-8 scrubbed; null/blank → SQL NULL.
request_log_id string No Identifier of the related request-log row. Must match [A-Za-z0-9-]; truncated to 36 characters.
client_context object No Client-side context (page, build, etc.) captured by the SPA. Rejected with 400 if malformed; string values are UTF-8-scrubbed before storage.

Response: 201 { "id": "..." }

Every free-text and identifier field is sanitized at this trust boundary: a JSON null (which is truthy server-side) never persists a literal "userdata: NULL", an over-long or invalid value never reaches the database as-is, and no input path returns a 5xx.

💡 Note: Each filed report is also mirrored, best-effort, into the unified triage queue (source="content_report"). Mirroring never fails the submission and content reports are not included in the triage notification digest.


Report object fields

Field Type Description
id string Report identifier.
user_id string The reporting user.
user_email string | null Email of the reporting user.
tenant_id string | null Tenant of the source conversation.
conversation_id string | null Source conversation.
message_id string | null Reported message identifier.
message_text string | null A snapshot of the reported message body.
reason offensive | inaccurate | unsafe | other The reason flag chosen by the reporter.
notes string | null Optional free-form notes from the reporter.
status open | triaged | dismissed Current status.
created_at unix seconds Submission timestamp.
triaged_at unix seconds | null Set when the status leaves open.
triaged_by_id string | null User who triaged or dismissed the report.
request_log_id string | null The request-log entry the reported message belongs to, when known.
request_log_provider string | null Provider that served the reported message.
request_log_model string | null Model that served the reported message.
request_log_status integer | null HTTP status of the underlying request.
request_log_latency_ms integer | null Upstream latency of the underlying request, in milliseconds.
request_log_input_tokens integer | null Input token count of the underlying request.
request_log_output_tokens integer | null Output token count of the underlying request.
client_context object | null Client-supplied context captured with the report.