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

# Post thesis

> POST /v1/theses: post a thesis on a token in the name of one of your users. Holders only.

Posts a thesis on a token in the name of one of your users, from your backend. The thesis appears everywhere theses are shown, in the widget and on every other platform, marked `via` your app's name. A `thesis` event goes out on the [stream](/cabalspy-id/stream#thesis-events) and apps whose users granted the scope `thesis` get a `thesis.created` [webhook](/cabalspy-id/webhooks).

<Tip>
  Only holders can post. The user must hold the token in one of their verified wallets, granted your app the scope thesis, and be within your plan's user limit. Check with [eligibility](/cabalspy-id/theses-eligibility) before you show the composer.
</Tip>

Take the `csid_id` from your own session, never from the browser request: whoever controls it posts in that user's name.

## Body

Send JSON with `Content-Type: application/json`.

<ParamField body="blockchain" type="string" required>
  Chain of the token. One of solana, bnb, base, eth, rh.
</ParamField>

<ParamField body="mint" type="string" required>
  Token address: a Solana mint or a 0x contract address.
</ParamField>

<ParamField body="body" type="string" required>
  The thesis, up to 1,000 characters. At most two links. No all caps and no repeated-character spam.
</ParamField>

<ParamField body="stance" type="string">
  `bull` or `bear`. Leave it out for no stance.
</ParamField>

<ParamField body="csid_id" type="string" required>
  The author, one of your users (the sub of their ID token).
</ParamField>

<ParamField query="api_key" type="string" required>
  Your API key. Alternatively pass it as an Authorization Bearer header.
</ParamField>

## Usage Example

<CodeGroup>
  ```bash cURL 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":"3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump","body":"Strong community, still early.","stance":"bull","csid_id":"0b7c4a52-3f1e-4f43-9d0a-2b5e7c1d9a11"}'
  ```

  ```python Python theme={null}
  import requests

  r = requests.post("https://api-id.cabalspy.xyz/v1/theses",
                    headers={"Authorization": "Bearer YOUR_KEY"},
                    json={"blockchain": "solana", "mint": mint, "body": text,
                          "stance": "bull", "csid_id": session_user_sub})
  body = r.json()
  if r.status_code == 201:
      thesis = body["data"]
  else:
      show_error(body["error"]["message"])  # e.g. not_a_holder, invalid_body, rate_limited
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch("https://api-id.cabalspy.xyz/v1/theses", {
    method: "POST",
    headers: { Authorization: "Bearer YOUR_KEY", "Content-Type": "application/json" },
    body: JSON.stringify({ blockchain: "solana", mint, body: text, stance: "bull", csid_id: session.userSub }),
  });
  const out = await res.json();
  if (!out.success) showError(out.error.message);
  ```
</CodeGroup>

## Response

<ResponseField name="success" type="boolean">
  True on success. The status is 201.
</ResponseField>

<ResponseField name="data" type="object">
  The new thesis, the same object as in [List theses](/cabalspy-id/theses-list#response), with the author's position at post time.
</ResponseField>

<ResponseExample>
  ```json Response theme={null}
  {
    "success": true,
    "data": {
      "id": "8d1f5a2e-6c4b-4e0f-9a7d-1b2c3d4e5f60",
      "chain": "solana",
      "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
      "kind": "thesis",
      "parent_id": null,
      "body": "Strong community, still early.",
      "stance": "bull",
      "author": { "csid_id": "0b7c4a52-3f1e-4f43-9d0a-2b5e7c1d9a11", "username": "MazeCCC", "name": "Maze", "image_url": "https://pbs.twimg.com/profile_images/.../photo.jpg", "kol_badge": true, "profile_url": "https://id.cabalspy.xyz/u/MazeCCC" },
      "likes": 0,
      "replies": 0,
      "reactions": {},
      "my_reactions": [],
      "via": "Example Terminal",
      "created_at": "2026-10-13T09:12:44.512113+00:00",
      "is_mine": false,
      "position": {
        "at_post": { "invested_usd": 150.0, "realized_usd": 0.0, "holding_value_usd": null, "pnl_usd": null, "pnl_percent": null, "tokens_held": 5000.0, "first_buy": "2026-10-13T08:12:40+00:00" },
        "now": { "invested_usd": 150.0, "realized_usd": 0.0, "holding_value_usd": 152.1, "pnl_usd": 2.1, "pnl_percent": 1.4, "tokens_held": 5000.0, "status": "holding" }
      }
    },
    "meta": { "request_id": "req_5162c7cf7f87", "cached": false, "cache_age_seconds": 0, "version": "v1", "timestamp": "2026-10-13T09:12:44Z" }
  }
  ```
</ResponseExample>

## Errors

The messages of `not_a_holder`, `invalid_body`, `duplicate`, `rate_limited` and `posting_paused` are written for end users, you can show them as they are.

| Status | Code | Meaning |
| - | - | - |
| 400 | missing\_parameter | blockchain, mint, body or csid\_id is missing. |
| 400 | invalid\_parameter | Unknown chain, invalid token address, stance or csid\_id. |
| 400 | invalid\_body | Text empty, too long, too many links, all caps, spam or a blocked word. |
| 400 | duplicate | The user posted the same text within the last day. |
| 401 | missing\_api\_key | API key missing. |
| 403 | invalid\_api\_key | API key invalid or not enabled for CabalSpy ID. |
| 403 | consent\_required | The user has not granted your app the scope thesis, revoked it or is blocked by you. |
| 403 | plan\_user\_limit | The user is beyond your plan's user limit. |
| 403 | sandbox\_user | Sandbox test users cannot write. |
| 403 | not\_a\_holder | The user does not hold the token in a verified wallet. |
| 403 | posting\_paused | The user is paused from writing. |
| 429 | rate\_limited | The user posts too fast: 5 theses per hour, 1 per token every 10 minutes. |
| 429 | rate\_limit\_exceeded | Your API key's request rate is exceeded. |


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