> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cabalspy.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Moderation and appeals

> How reported theses are hidden, warnings and pauses, moderator decisions, notifications and appeals, and the APIs to show them in your own UI.

Theses are moderated by the community and by CabalSpy moderators. Reports hide a post quickly, the author is told why and can appeal, and people who report in bad faith lose their influence. This page explains the rules and how to show notifications and appeals in your own UI.

## Reports

Any signed-in user can [report](/cabalspy-id/theses-report) a thesis or reply as spam, scam, abuse, impersonation or other. Reports are weighted:

| Reporter's record | Weight of a report |
| - | - |
| Normal, or fewer than 3 rejected reports, or more confirmed than rejected | 1 |
| Most of their earlier reports were rejected by moderators | 0.5 |
| A clear record of false reports | 0 |

When the weights on a post add up to **3**, the post is hidden right away and waits for a moderator. A `thesis.removed` event goes out on the streams.

People whose reports are rejected again and again cannot report for **30 days**. Their reports then fail with `reporting_paused`, and they get a `reporting_paused` notification.

## Warnings and pauses

| When | Consequence |
| - | - |
| A post is hidden after reports for the first time in 30 days | A **warning**. Nothing else changes. |
| Another post is hidden within those 30 days | A **7-day pause**: no posting, replying or reacting. Writes fail with `posting_paused`. |

Both can be appealed. A warning or pause whose post is restored is taken back, and a pause it caused is lifted.

## Moderator decisions

| Decision | Effect |
| - | - |
| Keep | The post is visible again and any warning or pause from the reports is taken back. The reports count as rejected for the reporters. |
| Remove | The post stays hidden. The reports count as confirmed. |
| Remove and pause | Removed, plus a 7-day pause. |
| Remove and ban | Removed, and the author can no longer write theses. |

## Notifications

The affected person gets a notification for every decision. Each has a `kind` and a ready-made English `text`.

| kind | Meaning | Appealable |
| - | - | - |
| `post_hidden` | A post was hidden after reports. This is a warning, the next one within 30 days pauses writing for 7 days. | Yes |
| `paused` | No posting, replying or reacting until `data.until`. | Yes |
| `banned` | A moderator removed a post, no more theses. | Yes |
| `post_removed` | A moderator removed a post. | Yes |
| `post_restored` | A moderator reviewed the reports and the post is visible again. | No |
| `appeal_accepted` | The appeal was accepted, with the moderator's note. | No |
| `appeal_rejected` | The appeal was rejected, with the moderator's note. | No |
| `reporting_paused` | No reports until `data.until`: too many reports were rejected. | No |

People see their notifications at [id.cabalspy.xyz/me/notifications](https://id.cabalspy.xyz/me/notifications), and the [thesis widget](/cabalspy-id/theses#path-1-the-widget) shows a banner with a link there when something is unread. Platforms with their own UI read them through the APIs below.

### The notification object

<ResponseField name="id" type="integer">
  Notification id, used to appeal.
</ResponseField>

<ResponseField name="kind" type="string">
  One of the kinds above.
</ResponseField>

<ResponseField name="text" type="string">
  Ready-made English text for the person.
</ResponseField>

<ResponseField name="data" type="object">
  Details: thesis\_id, chain, mint and excerpt (the first 140 characters of the post) where a post is concerned, until for pauses, note for appeal decisions.
</ResponseField>

<ResponseField name="created_at" type="string">
  ISO timestamp.
</ResponseField>

<ResponseField name="read" type="boolean">
  Whether the person has seen it.
</ResponseField>

<ResponseField name="appealable" type="boolean">
  Whether the decision can be appealed.
</ResponseField>

<ResponseField name="appeal" type="string">
  Status of an appeal against it: `pending`, `accepted`, `rejected`, or null when none was sent.
</ResponseField>

```json Notification theme={null}
{
  "id": 1042,
  "kind": "post_hidden",
  "text": "Your post was hidden after several reports. This is a warning — the next one pauses posting for 7 days.",
  "data": {
    "thesis_id": "8d1f5a2e-6c4b-4e0f-9a7d-1b2c3d4e5f60",
    "chain": "solana",
    "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
    "excerpt": "Strong community, still early.",
    "strike_id": 77,
    "until": null,
    "warning": true
  },
  "created_at": "2026-10-13T12:40:03.511020+00:00",
  "read": false,
  "appealable": true,
  "appeal": null
}
```

## Appeals

An appealable decision can be appealed within **30 days**, once per decision, with an explanation of 10 to 1,000 characters. A moderator reviews it. When an appeal is accepted, the post is visible again and the warning or pause is taken back. The person gets `appeal_accepted` or `appeal_rejected` with the moderator's note.

| Error | When |
| - | - |
| `not_found` | No such notification for this person, or it cannot be appealed. |
| `invalid_parameter` | The decision is older than 30 days. |
| `invalid_body` | The explanation is shorter than 10 or longer than 1,000 characters. |
| `duplicate` | This decision was already appealed. |

## Data API

Read and appeal in the name of one of your users, with your API key. The user must have granted your app the scope `thesis` and be within your plan's user limit, as for every [thesis write](/cabalspy-id/theses-post).

### GET /v1/theses/notifications

<ParamField query="csid_id" type="string" required>
  The user.
</ParamField>

```bash theme={null}
curl "https://api-id.cabalspy.xyz/v1/theses/notifications?csid_id=USER_SUB" \
  -H "Authorization: Bearer YOUR_KEY"
```

```json Response theme={null}
{
  "success": true,
  "data": { "unread": 1, "items": [ { "id": 1042, "kind": "post_hidden", "text": "...", "data": { }, "created_at": "...", "read": false, "appealable": true, "appeal": null } ] },
  "meta": { "request_id": "req_5162c7cf7f87", "cached": false, "cache_age_seconds": 0, "version": "v1", "timestamp": "2026-10-14T08:00:00Z" }
}
```

`items` holds the 50 newest notifications, newest first.

### POST /v1/theses/appeal

<ParamField body="csid_id" type="string" required>
  The user.
</ParamField>

<ParamField body="notification_id" type="integer" required>
  The notification to appeal.
</ParamField>

<ParamField body="message" type="string" required>
  The user's explanation, 10 to 1,000 characters.
</ParamField>

```bash theme={null}
curl -X POST "https://api-id.cabalspy.xyz/v1/theses/appeal" \
  -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"csid_id":"USER_SUB","notification_id":1042,"message":"This is my honest view on the token, not spam."}'
```

The response is `201` with `{"appealed": true}`. Besides the appeal errors above, both endpoints return the usual write errors `missing_parameter`, `consent_required`, `plan_user_limit` and `sandbox_user`.

## Browser API

With the signed-in user's access token (scope `thesis`) on the [browser API](/cabalspy-id/theses-browser-api):

| Method | Path | Description |
| - | - | - |
| GET | `/thesis/v1/me/notifications` | `{ unread, items }`, the 50 newest notifications. |
| POST | `/thesis/v1/me/notifications/read` | Marks all as read. Returns `{"read": true}`. |
| POST | `/thesis/v1/me/notifications/{id}/appeal` | Body `{"message": "..."}`. Returns `201` with `{"appealed": true}`. |

`GET /thesis/v1/tokens/{chain}/{mint}/me` also returns `notices`: `unread` (count), `latest` (the text of the newest notification, or null when nothing is unread) and `url` (`https://id.cabalspy.xyz/me/notifications`), so you can show the same banner as the widget.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.