> ## 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.

# Token theses — SocialFi widget & API

> Token theses: a SocialFi widget and API where only verified holders post opinions on a token, each shown with their real position and PnL, shared across platforms.

Token theses are a SocialFi widget and API for any token page. A thesis is a short opinion on a token, written by someone who actually holds it. Every thesis shows the author's real position in the token, when they posted and live now, so your users can see who has skin in the game. Theses are shared across every platform that uses CabalSpy ID: a thesis posted on your platform also shows on others, and theirs on yours. You can also show only the theses written on your own platform, see [Reach](#reach).

<Tip>
  Try it live at [id.cabalspy.xyz/try/thesis](https://id.cabalspy.xyz/try/thesis): a real login button and the thesis widget for any token.
</Tip>

## How theses work

| Rule | Details |
| - | - |
| One token per thread | A thesis belongs to a token, identified by chain and mint (token address). |
| Text | Up to 1,000 characters, optionally tagged `bull` or `bear`. At most two links. No all caps, no repeated-character spam, no duplicates of the author's own posts within a day. |
| Only holders post | One of the author's verified wallets bought the token, seen by CabalSpy ID's trade tracking, and still holds it. |
| Anyone signed in interacts | Every signed-in user can reply, react and report. Replies are one level deep. |
| Reactions | `like` and 🔥 🚀 💎 👀 😂 🤝 💀 🐂 🐻 |
| Real position | Each thesis carries the author's position at post time and now: invested, realized, holding value, PnL in USD and percent, and `sold` once the position is closed. Authors who keep their trading private show only that they hold the token. |
| Moderation | Weighted reports hide a post until a moderator reviews it. The first hidden post in 30 days is a warning, the next one a 7-day pause from posting, replying and reacting. Authors are notified and can appeal. See [Moderation and appeals](/cabalspy-id/theses-moderation). |
| Replies and reactions on every thesis | Each thesis carries its 3 newest replies (`latest_replies`) and who reacted (`reacted_by`, up to 20). |
| Shared across platforms | Every thesis shows `via`, the name of the app it was posted from. |
| Rate limits | Per user: 5 theses per hour and 1 per token every 10 minutes, 30 replies per hour, 120 reactions per hour, 20 reports per day. |

Writing needs the user's permission: the OAuth scope `thesis` ("Post theses and replies, react and report in your name when you use the app"). Request it at sign-in together with the scopes you already use.

## Reach

By default you see the theses of **all platforms** for a token. With the instance reach you see **only the theses written on your instance**: on all live apps of your registrable domain, or only on the app itself while it is in development or sandbox mode. Reach is the same on every plan.

| Where | All platforms (default) | Only your instance |
| - | - | - |
| Widget | Saved design reach "all platforms", or `data-scope="global"` | Saved design reach "only this instance", or `data-scope="instance"` (needs `data-client-id`) |
| Data API | `scope=global` | `scope=instance` on [/v1/theses](/cabalspy-id/theses-list), [/v1/theses/summary](/cabalspy-id/theses-summary) and [/v1/theses/latest](/cabalspy-id/theses-latest) |
| Theses stream | `scope=global` | `scope=instance` on [/v1/theses/stream](/cabalspy-id/theses-stream) |
| Browser API | `scope=global` | `scope=instance&client_id=csid_...` on the list, summary and websocket of the [browser API](/cabalspy-id/theses-browser-api) |

## Two ways to integrate

| | Widget | Your own UI with the Data API |
| - | - | - |
| Effort | Two lines of HTML | You build the list, composer and live updates |
| Look and feel | Designed in the dashboard, overridable per embed | Fully yours |
| Reading | Built in | [GET /v1/theses](/cabalspy-id/theses-list) and friends, with your API key |
| Writing | Users signed in on your page with scope `thesis` write directly. Everyone else gets a **Sign in to write** popup. | Your backend posts in the user's name with [POST /v1/theses](/cabalspy-id/theses-post) and their `csid_id` |
| Who can write | Any CabalSpy ID user | Your users who granted scope `thesis` and are within your plan's user limit |
| Live updates | Built in | [Theses stream](/cabalspy-id/theses-stream) (every plan) |
| Webhook | – | `thesis.created`, `thesis.reply` and `thesis.reaction` for your users' activity |

Client-side apps that do not want a backend can also call the [public browser API](/cabalspy-id/theses-browser-api) on id.cabalspy.xyz with your instance's public client\_id and the user's access token.

## Path 1: the widget

```html theme={null}
<div data-csid-thesis data-client-id="csid_..." data-chain="solana" data-mint="TOKEN_MINT"></div>
<script src="https://id.cabalspy.xyz/widget.js" async></script>
```

With `data-client-id` the widget uses the design you saved for your instance, and theses written in it are attributed to your app.

<Note>
  `data-client-id` is required. It is your instance's public client\_id (Instance ID, `csid_...`), shown in the [API Dashboard](https://apidashboard.cabalspy.xyz) under **CabalSpy ID**, **Instances** next to the API key. It is not the API key and not a secret. If the attribute is missing, `widget.js` uses the client id of a `data-csid-login` button on the same page. The widget only loads on your instance's sites: the allowed origins, the origins of your redirect URIs and your verified domain including subdomains, plus `http://localhost:*` and `http://127.0.0.1:*` while the instance is not live. The `/embed/thesis` iframe behind the widget may only be framed by these sites. See [Client ID](/cabalspy-id/theses-browser-api#client-id).
</Note>

### Design in the dashboard

Design the widget in the [API Dashboard](https://apidashboard.cabalspy.xyz): **CabalSpy ID**, **Instances**, your instance, tab **Theses**. The designer shows a live preview and the embed code, and saves:

* **Reach:** all platforms or only this instance.
* **Theme:** dark or light, with the colours background, card, text, muted, border, accent, accent text, bull and bear.
* **Font:** system, inter, mono or serif.
* **Corner radius:** 0 to 24 px.
* **Default sort:** new or top.
* **Show header**, **show the bull/bear filter**, **compact** layout.

The same tab shows thesis statistics for your instance, the most discussed tokens and the latest theses.

### Attributes

Every attribute overrides the saved design for that one embed.

| Attribute | Values |
| - | - |
| `data-chain` | Required. solana, bnb, base, eth or rh. |
| `data-mint` | Required. Token address (Solana mint or 0x contract). |
| `data-client-id` | Required. Your Instance ID (public client\_id). Loads the saved design. Also used for `data-scope="instance"`. Falls back to the client id of a `data-csid-login` button on the page. |
| `data-scope` | `global` (all platforms) or `instance` (only your instance). |
| `data-theme` | `dark` or `light`. Switching the theme resets colours that are not set on the embed. |
| `data-sort` | `new` or `top`. |
| `data-font` | `system`, `inter`, `mono` or `serif`. |
| `data-radius` | Corner radius, 0 to 24. Default 12. |
| `data-bg`, `data-card`, `data-text`, `data-muted`, `data-border`, `data-accent`, `data-accent-text`, `data-bull`, `data-bear` | Colours as hex, for example `#3ddc84`. |
| `data-show-header` | `true` or `false`. |
| `data-show-sentiment` | `true` or `false`, the bull/bear filter. |
| `data-compact` | `true` or `false`. |
| `data-height` | Starting height in pixels, default 520. The widget then grows with its content. |

Invalid values fall back to the theme's defaults.

### Writing in the widget

The same `widget.js` also renders the [login button](/cabalspy-id/login-button). How users write:

* **Browser apps** using the login button: request the scope, for example `data-scope="openid profile thesis"` on the login button. After sign-in the widget hands the access token to every thesis widget on the page automatically.
* **Server apps** or your own session: pass the user's CabalSpy ID access token yourself. It must carry the scope `thesis`.

```javascript theme={null}
CSID.thesis.setAccessToken(accessToken);  // after your sign-in, or when you refresh tokens
CSID.thesis.clear();                       // on sign-out
```

Without a token, a user who wants to write gets a **Sign in to write** popup and signs in with CabalSpy ID directly.

### Your own sign-in

Platforms with their own sign-in can take over the widget's **Sign in to write** button with `CSID.thesis.setSignInHandler(fn)`. When a handler is set, the button calls `fn` instead of the page's login button or the popup. `fn` gets a token with the scope `thesis` for the person, for example your platform's own CabalSpy ID access token fetched from your backend, passes it to `CSID.thesis.setAccessToken(token)` and resolves `true`. Resolving `false` means the person is not signed in.

```javascript theme={null}
CSID.thesis.setSignInHandler(async () => {
  const res = await fetch("/api/csid-token", { credentials: "include" }); // your backend
  if (!res.ok) return false;                 // not signed in on your platform
  const { access_token } = await res.json(); // CabalSpy ID access token with scope thesis
  CSID.thesis.setAccessToken(access_token);
  return true;
});
```

While a handler is set, the widget treats your page as the source of truth for sign-in: someone signed out on your page is signed out in the widget too.

In the [SDK](/cabalspy-id/sdks) (`cabalspy-id` 0.5.2) the same is `setThesisSignInHandler(fn)` from `cabalspy-id/widget`, or the optional `onSignIn` prop on `<CabalSpyTheses>` in React.

## Path 2: your own UI

Read with your API key, write from your backend in the name of a user. A typical **Post thesis** button works like this:

<Steps>
  <Step title="Request the scope thesis at sign-in">
    Add `thesis` to the scopes of your [authorization request](/cabalspy-id/openid-connect#authorization-request) or the login button's `data-scope`. Store the user's `sub`, it is their `csid_id`.
  </Step>

  <Step title="Check eligibility">
    Before you show the composer, ask whether the user can post on this token. Show the button only when `can_post` is true, and a reply box when `can_reply` is true.

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

    ```json theme={null}
    { "success": true, "data": { "can_post": true, "can_reply": true, "holder": true, "paused": false } }
    ```
  </Step>

  <Step title="Post from your backend">
    Your frontend sends the text to your backend, your backend posts it with your API key and the user's `csid_id`. Never trust a `csid_id` sent by the browser, take it from your own session.

    ```bash theme={null}
    curl -X POST "https://api-id.cabalspy.xyz/v1/theses" \
      -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
      -d '{"blockchain":"solana","mint":"TOKEN_MINT","body":"Strong community, still early.","stance":"bull","csid_id":"USER_SUB"}'
    ```

    The response is `201` with the new thesis, including the author's position. Errors such as `not_a_holder`, `invalid_body` or `rate_limited` carry a message you can show the user.
  </Step>

  <Step title="Render the list and go live">
    Load the token's theses with [GET /v1/theses](/cabalspy-id/theses-list) (pass the viewer's `csid_id` to get `my_reactions` and `is_mine`) and the counts with [GET /v1/theses/summary](/cabalspy-id/theses-summary). Then open the [theses stream](/cabalspy-id/theses-stream) and subscribe to the token to receive new posts, replies, reactions and removals:

    ```text theme={null}
    wss://api-id.cabalspy.xyz/v1/theses/stream?apiKey=YOUR_KEY
    → {"op": "subscribe", "tokens": [{"blockchain": "solana", "mint": "TOKEN_MINT"}]}
    ```
  </Step>
</Steps>

<Tip>
  The official [SDKs](/cabalspy-id/sdks#theses) cover every thesis endpoint, the stream events, the browser API (`ThesisClient`) and a React widget (`<CabalSpyTheses>`).
</Tip>

## Endpoints

| Method | Path | Description |
| - | - | - |
| GET | [/v1/theses](/cabalspy-id/theses-list) | Theses of a token. |
| GET | [/v1/theses/summary](/cabalspy-id/theses-summary) | Counts and token info. |
| GET | [/v1/theses/item](/cabalspy-id/theses-item) | One thesis or reply. |
| GET | [/v1/theses/replies](/cabalspy-id/theses-replies) | Replies to a thesis. |
| GET | [/v1/theses/latest](/cabalspy-id/theses-latest) | Newest theses across all tokens. |
| GET | [/v1/theses/user](/cabalspy-id/theses-user) | Theses of one user. |
| GET | [/v1/theses/eligibility](/cabalspy-id/theses-eligibility) | Can a user post or reply. |
| POST | [/v1/theses](/cabalspy-id/theses-post) | Post a thesis for a user. |
| POST | [/v1/theses/replies](/cabalspy-id/theses-reply) | Reply for a user. |
| POST | [/v1/theses/reactions](/cabalspy-id/theses-reactions) | React for a user. |
| POST | [/v1/theses/report](/cabalspy-id/theses-report) | Report for a user. |
| POST | [/v1/theses/delete](/cabalspy-id/theses-delete) | Delete a user's own post. |
| GET | [/v1/theses/notifications](/cabalspy-id/theses-moderation#get-v1-theses-notifications) | A user's moderation notifications. |
| POST | [/v1/theses/appeal](/cabalspy-id/theses-moderation#post-v1-theses-appeal) | Appeal a decision for a user. |
| WS | [/v1/theses/stream](/cabalspy-id/theses-stream) | Live theses of the tokens you subscribe to. |

Reading is open to any API key, theses are public and not limited to your instance. Writing works only for your users: they signed in to your app with the scope `thesis`, did not revoke it, are not blocked by you and are within your plan's user limit. Sandbox test users cannot write.


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