POST
/
api
/
v1
/
chat
/
reports
Report a user
curl --request POST \
  --url https://api.example.com/api/v1/chat/reports
import requests

url = "https://api.example.com/api/v1/chat/reports"

response = requests.post(url)

print(response.text)
const options = {method: 'POST'};

fetch('https://api.example.com/api/v1/chat/reports', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));
<?php

$curl = curl_init();

curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/chat/reports",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
package main

import (
"fmt"
"net/http"
"io"
)

func main() {

url := "https://api.example.com/api/v1/chat/reports"

req, _ := http.NewRequest("POST", url, nil)

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()
body, _ := io.ReadAll(res.Body)

fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.post("https://api.example.com/api/v1/chat/reports")
.asString();
require 'uri'
require 'net/http'

url = URI("https://api.example.com/api/v1/chat/reports")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)

response = http.request(request)
puts response.read_body

Overview

Records a reports row for the moderation queue. A report points at an accusedUserId and optionally at a conversationId and messageId to scope the evidence. Reports are append-only from the API; admin actions (dismiss / warn / ban) land in Phase 6. When conversationId is supplied and the caller is a participant, the service snapshots the last 200 messages of that conversation into the report’s transcriptSnapshot field. This makes the report self-contained — admins can review what was actually said without re-permissioning into private chats. The 200-message cap exists so a malicious actor cannot bloat the report row by spamming first. Reporting is independent of blocking: the client may also call block-user, but neither is implied by the other.

Authentication

Bearer <accessToken> required. requireOnboarded middleware applied.

Path parameters

None.

Query parameters

None.

Request body

FieldTypeRequiredAllowed valuesExample
accusedUserIdstringyes24-char hex ObjectId. MUST differ from the caller — self-reports return 400 VALIDATION_FAILED.66400a8f1c2b4d5e6f7a8b01
conversationIdstring | nullno24-char hex ObjectId of the conversation to attach as evidence. When provided, the caller MUST be a participant and the accused MUST be a participant.66400a8f1c2b4d5e6f7a9000
messageIdstring | nullno24-char hex ObjectId of a specific message to flag. Optional even when conversationId is provided.66400a8f1c2b4d5e6f7aa000
reasonenumyesHARASSMENT, SCAM, INCORRECT_PROPERTY, OTHER. See enum guide below.HARASSMENT
freeTextstringconditional0..2000 chars. Required when reason === "OTHER" (and must be non-empty after trim); optional for the other reasons but encouraged.Sent abusive messages calling me names

Reason enum

ValueWhen to use
HARASSMENTAbuse, threats, name-calling, sexual harassment.
SCAMAsking for money, off-platform payment, phishing, impersonation.
INCORRECT_PROPERTYListing photos / address / details don’t match the actual property.
OTHERAnything else. Requires freeText.

Example payload

{
  "accusedUserId": "66400a8f1c2b4d5e6f7a8b01",
  "conversationId": "66400a8f1c2b4d5e6f7a9000",   // optional — supplying this snapshots last 200 messages
  "messageId": "66400a8f1c2b4d5e6f7aa000",         // optional — flag a specific message
  "reason": "HARASSMENT",                          // enum: "HARASSMENT" | "SCAM" | "INCORRECT_PROPERTY" | "OTHER"
  "freeText": "Sent abusive messages calling me names"
}

Response — 201 Created

FieldTypeNotesExample
reportobjectSee Report object.

Report object

FieldTypeAllowed values / NotesExample
idstring24-char hex ObjectId of the new report.66400a8f1c2b4d5e6f7ab000
reporterIdstring24-char hex ObjectId of the caller.66400a8f1c2b4d5e6f7a8b00
accusedIdstring24-char hex ObjectId of the accused user.66400a8f1c2b4d5e6f7a8b01
conversationIdstring | nullAs supplied. null if omitted.66400a8f1c2b4d5e6f7a9000
messageIdstring | nullAs supplied. null if omitted.66400a8f1c2b4d5e6f7aa000
reasonenumHARASSMENT, SCAM, INCORRECT_PROPERTY, OTHER.HARASSMENT
freeTextstring | nullAs supplied. null if omitted (only allowed when reason ≠ OTHER).Sent abusive messages calling me names
statusenumInitial status, always OPEN on create. Admin transitions: OPEN → DISMISSED | WARNED | BANNED (Phase 6).OPEN
createdAtstringISO 8601 UTC with millisecond precision.2026-05-22T14:50:00.000Z
The transcriptSnapshot field is not returned to the caller — it’s admin-only.

Example response

{
  "report": {
    "id": "66400a8f1c2b4d5e6f7ab000",
    "reporterId": "66400a8f1c2b4d5e6f7a8b00",
    "accusedId": "66400a8f1c2b4d5e6f7a8b01",
    "conversationId": "66400a8f1c2b4d5e6f7a9000",
    "messageId": "66400a8f1c2b4d5e6f7aa000",
    "reason": "HARASSMENT",                  // enum: "HARASSMENT" | "SCAM" | "INCORRECT_PROPERTY" | "OTHER"
    "freeText": "Sent abusive messages calling me names",
    "status": "OPEN",                        // enum: "OPEN" | "DISMISSED" | "WARNED" | "BANNED"
    "createdAt": "2026-05-22T14:50:00.000Z"
  }
}

Error responses

StatusCodeMeaning
400VALIDATION_FAILEDaccusedUserId missing / not hex / equal to caller; reason not in the allowed set; freeText missing when reason === "OTHER"; accused is not a participant of the supplied conversationId. See errors[] for the failing path.
401UNAUTHENTICATEDMissing, malformed, or expired access token.
403ONBOARDING_INCOMPLETECaller has not finished onboarding.
403FORBIDDENconversationId was supplied and the caller is not a participant of it.
404NOT_FOUNDconversationId was supplied but does not exist.

Example error — 400 (OTHER without freeText)

{
  "type": "https://api.swappr.co.uk/errors/validation-failed",
  "title": "Validation failed",
  "status": 400,
  "code": "VALIDATION_FAILED",
  "detail": "Request body failed validation",
  "instance": "/api/v1/chat/reports",
  "requestId": "01HZQ7K3M4N5P6Q7R8S9T0V1W2",
  "errors": [
    { "path": "freeText", "message": "freeText is required when reason is OTHER", "code": "custom" }
  ]
}

Side effects

  • One reports row inserted with status: "OPEN" and the optional transcriptSnapshot.
  • No notifications fire to the accused — reporting is silent by design.
  • Reporting does NOT block the accused. Call block-user separately if the caller wants to sever contact.

See also

curl

curl -X POST https://api.swappr.co.uk/api/v1/chat/reports \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "accusedUserId": "66400a8f1c2b4d5e6f7a8b01",
    "conversationId": "66400a8f1c2b4d5e6f7a9000",
    "messageId": "66400a8f1c2b4d5e6f7aa000",
    "reason": "HARASSMENT",
    "freeText": "Sent abusive messages calling me names"
  }'