Skip to content

Reports, blocks and bans

Users block and report each other. You and your moderators ban users and IP ranges.

  • For blocks: the policy row from the user’s role to the peer’s role has block on. Conversation.can.block shows it to a client.
  • For bans from your server: your server secret and app id, and the user’s external_id or an IP range. See Sessions and tokens.
  • Set links.support_email if you want banned users to see how to reach you.
Terminal window
export MC=https://app.mondochat.io/v1
export SESSION=<session token>
auth=(-H "Authorization: Bearer $MONDOCHAT_SERVER_SECRET" -H "Mondochat-App-Id: $MONDOCHAT_APP_ID" -H 'content-type: application/json')
  1. Block and unblock a user. user_id is the peer’s id in Peer, not the external id. Both calls answer 204.

    Terminal window
    curl -X PUT $MC/blocks/$USER_ID -H "Authorization: Bearer $SESSION"
    curl -X DELETE $MC/blocks/$USER_ID -H "Authorization: Bearer $SESSION"

    A block applies both ways for delivery: messages, calls, photos and presence stop in both directions. call_ended and purchase cards still arrive. A block also clears the blocker’s waiting state in that conversation, and sends your server the block.created or block.removed webhook with {blocker, blocked}.

  2. List what a user blocked with GET /v1/blocks?cursor=...&limit=200. It answers {items: [{user, created_at}], next_cursor}, newest first. Deleted users are left out and the Peer has no presence. Another user’s blocks are never listed.

  3. Decide what a blocked sender sees. By default the sender is not told:

    • The send succeeds for the sender, the message is stored as undelivered, and the peer receives nothing and gets no unread count.
    • A blocked reaction is stored and shown only to its author.
    • The blocker who writes to someone they blocked gets 403 forbidden with reason you_blocked.

    Set chat.refuse_blocked_send to true to refuse such sends instead, with 403 forbidden and reason undelivered, and store nothing. It applies to text, photos, videos, voice notes and reactions.

  4. Let users report. Send POST /v1/reports with {user_id, conversation_id?, reason}. It answers 202.

    Terminal window
    curl -X POST $MC/reports -H "Authorization: Bearer $SESSION" -H 'content-type: application/json' \
    -d "{\"user_id\": \"$USER_ID\", \"conversation_id\": \"$ID\", \"reason\": \"spam\"}"

    reason is free text of 1 to 1000 characters and is stored as sent. The frame offers spam, harassment, inappropriate, scam, underage and other; your own client may send any text. The reported user must be one the reporter has a conversation with, and conversation_id, when given, must be that conversation. A report about a conversation peer opens a moderation case of kind report and sends the report.created webhook with {report_id, reporter, target, reason}. A second report from the same user about the same target while the first is open is a duplicate: there is still one open report. With safety.report_reasons off, the frame asks for no reason and reports with reason other.

  5. Ban from your server with POST /v1/server/bans. Ban a user by external_id, or an IP range by cidr (IPv4 or IPv6). reason is required. expires_at is optional. Send an Idempotency-Key.

    Terminal window
    curl -X POST $MC/server/bans "${auth[@]}" -H 'Idempotency-Key: ban-carol-1' \
    -d '{"external_id": "carol", "reason": "spam", "expires_at": "2026-12-31T00:00:00Z"}'
    curl -X POST $MC/server/bans "${auth[@]}" -d '{"cidr": "203.0.113.0/24", "reason": "abuse"}'
    const res = await fetch('https://app.mondochat.io/v1/server/bans', {
    method: 'POST',
    headers: {
    authorization: `Bearer ${process.env.MONDOCHAT_SERVER_SECRET}`,
    'mondochat-app-id': process.env.MONDOCHAT_APP_ID,
    'idempotency-key': 'ban-carol-1',
    'content-type': 'application/json',
    },
    body: JSON.stringify({ external_id: 'carol', reason: 'spam' }),
    })
    const ban = await res.json()

    The answer is a Ban: {id, user_id, external_id, cidr, reason, expires_at, created_by, created_by_name, created_at, lifted_at}. For a ban made through the server API, created_by and created_by_name are null. A ban sends the ban.created webhook.

  6. Lift a ban from your server with DELETE /v1/server/bans/{id}. It answers 204 and sends ban.lifted.

    Terminal window
    curl -X DELETE $MC/server/bans/$BAN_ID "${auth[@]}"
  7. Ban from the dashboard. Moderators list bans, create them with {user_id?, cidr?, reason, expires_at?}, change expires_at (or clear it with null) and lift them. The bans list shows who made each ban, and “API” for those from your server.

  8. Know where bans bite. A ban is checked at session exchange, refresh, conversation open, message send, call start, call token and upload. A new ban on a user publishes session.revoked to that user with {reason: "banned", temporary}, so a connected client learns at once. temporary is true when the ban expires. Lookups are cached for 60 seconds per user and per IP, so a new ban can take up to a minute to reach every server.

  1. Ban a user with POST /v1/server/bans. The answer has lifted_at: null.
  2. Send a message as that user. The answer is 403 banned. With an expires_at, the problem carries reason: "temporary".
  3. Lift the ban. The same user can send again.
Answer Cause
403 banned The user or their IP is banned. detail is your links.support_email if you set one. reason is temporary for a ban with an expiry, and absent otherwise. It never carries the expiry or the kind of ban.
403 policy_denied with reason block PUT /v1/blocks/{user_id} and the policy row from the user’s role to the peer’s has block off.
404 not_found A block or report names a user that is not in your app, or a report names a user the reporter has no conversation with. A server ban names an external_id that is not a user of your app.
422 A block or report names the caller, a ban gives both or neither of external_id and cidr, or a body field is invalid.
403 forbidden with reason you_blocked The user blocked the peer they are writing to.
403 forbidden with reason undelivered The peer blocked the sender and chat.refuse_blocked_send is on.
409 idempotency_conflict The same Idempotency-Key was sent with a different body.
401 The server secret or app id is wrong.
  • An expired or lifted ban is not a ban.
  • Blocked recipients are not an error unless you turn on chat.refuse_blocked_send.
  • A repeat of a server ban call with the same Idempotency-Key within 24 hours returns the first answer with Idempotent-Replayed: true.
  • There are no automatic bans. A moderator or your server makes each one.
  • The client IP is the viewer’s address as the edge reports it. X-Forwarded-For is ignored.