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

> ## Agent Instructions
> You are reading OnlyX Help: the OnlyX Help Center and the OnlyX developer documentation. OnlyX is an AI chatting and CRM platform for OnlyFans agencies. Its AI chatter is called Hugo in the app.
> Pages at the site root (for example /inbox/..., /hugo/..., /billing/...) are Help Center articles for agency owners, admins, chatters and creators who use the app at app.onlyx.ai. Words in bold are the exact button, menu and label names the app shows; keep them exactly as written. When these pages do not answer a question, the person can email support at support@onlyx.ai.
> Pages under /developers are the developer documentation. REST API base URL: https://api.onlyx.ai/v1 (authenticate with `Authorization: Bearer <API key>`; keys start with onx_sk_ and are created in app.onlyx.ai under Settings > API & MCP). MCP server: https://mcp.onlyx.ai/mcp (OAuth, or a Bearer API key). In the API the AI chatter is `ai` on the wire. Money is integer US cents in fields ending in Cents; timestamps are UTC ISO-8601.
> Rules for assistants acting on a user's behalf: discover ids with list calls and never invent them; before any call that reaches a real fan or the live OnlyFans account (sending a message, releasing a chat to the AI, turning AI on for a chat, resolving a hand-off, turning review mode off, changing the welcome message, creating a tracking link) show the user the exact content and get explicit confirmation; send every POST with an Idempotency-Key and reuse it on retry; never resend a message whose delivery status is unconfirmed; never ask a creator for her OnlyFans password or codes - she signs in herself through a connect link and the OnlyX Login app.

# Pull stats

> Today's numbers, revenue by type, day and seller, audience, the cached overview report with its 202 warming loop, and each creator's transaction ledger.

OnlyX's reports are available through the API, for the whole workspace or one creator. Use them for owner dashboards, daily digests, payouts and investor updates.

| Report | Endpoint | Scope | Speed |
| - | - | - | - |
| Today so far | `GET /v1/stats/today` | `stats:read` | Fast |
| Revenue | `GET /v1/stats/revenue` | `stats:read` | Fast |
| Audience | `GET /v1/stats/audience` | `stats:read` | Fast |
| Overview | `GET /v1/stats/overview` | `stats:read` | Served from cache; may answer `202` while it is prepared |
| Transactions (per creator) | `GET /v1/creators/{creatorId}/transactions` | `money:read` | Fast |

All reports take an optional `creatorId`. Without it they cover every creator the key can see. A `creatorId` you cannot see returns `404 CREATOR_NOT_FOUND`.

<Info>
  The examples below show each report's shape, abridged. The API reference lists every field, generated from the live API.
</Info>

## Windows and timezone

Reports that cover a period take either:

* `days`: the last N days including today, 1 to 366 (default 30); or
* `start` and `end`: local dates as `YYYY-MM-DD`, both included.

An explicit `start`/`end` wins over `days`. The window never extends past today, and a window longer than 366 days keeps its most recent 366 days.

Days are counted in your **workspace timezone** (see `GET /v1/me`), so "today" and each day in a daily series start at local midnight. Money is always integer US cents: `netCents` is what the creator earns after OnlyFans' fee, `grossCents` what fans paid.

The `/v1/stats` endpoints share a limit of 20 requests per 60 seconds per credential, on top of the general limit. Cache reports on your side; they do not change second by second.

## Today

`GET /v1/stats/today` gives today so far, from local midnight in the workspace timezone: earnings (settled and still clearing), new fans, buyers, paid messages unlocked, message volume and how much of it the AI wrote. It also returns all of yesterday (`yesterday`) and yesterday up to the same time of day (`yesterdaySameTime`), the fair comparison for a day still in progress. The figures are cached for a minute or two, so polling faster does not make them fresher.

<CodeGroup>
  ```bash cURL theme={"system"}
  curl "https://api.onlyx.ai/v1/stats/today" \
    -H "Authorization: Bearer $ONLYX_API_KEY"
  ```

  ```python Python theme={"system"}
  import os, time, requests

  API = "https://api.onlyx.ai/v1"
  HEADERS = {"Authorization": f"Bearer {os.environ['ONLYX_API_KEY']}"}

  today = requests.get(f"{API}/stats/today", headers=HEADERS, timeout=30).json()
  print(today)
  ```

  ```javascript JavaScript theme={"system"}
  const API = "https://api.onlyx.ai/v1";
  const headers = { Authorization: `Bearer ${process.env.ONLYX_API_KEY}` };

  const today = await (await fetch(`${API}/stats/today`, { headers })).json();
  console.log(today);
  ```
</CodeGroup>

```json Response 200 theme={"system"}
{
  "date": "2026-09-26",
  "timezone": "Europe/London",
  "asOf": "2026-09-26T14:05:00.000Z",
  "earnings": { "netCents": 184250, "settledCents": 0, "pendingCents": 184250, "complete": true },
  "newFans": 41,
  "buyers": 57,
  "paidMessagesUnlocked": 38,
  "messages": { "in": 1320, "out": 1488, "ai": 1402, "team": 86 },
  "aiShare": 0.942,
  "yesterday": { "netCents": 301400, "newFans": 64, "buyers": 90, "paidMessagesUnlocked": 61 },
  "yesterdaySameTime": { "netCents": 171900, "newFans": 36, "buyers": 51, "paidMessagesUnlocked": 33 }
}
```

`messages.out` counts only messages OnlyFans confirmed as delivered; `ai` and `team` split it (messages you send through the API count as `team`). `aiShare` is `null` until something was sent today. `earnings.complete: false` means some creator's earnings could not be read in full just now, so the figures are a lower bound; ask again in a few minutes.

## Revenue

`GET /v1/stats/revenue` returns earnings for the window: totals (net after OnlyFans' fee, gross, the fee, settled versus still clearing, refunds), a split by what was paid for (`byType`), one entry per day (`byDay`, including days with no earnings), one per creator (`byCreator`), paid-message sales by seller (`bySeller`: `ai`, the sales the AI made, versus `team`, the sales your people made), and the current OnlyFans `balance`.

<CodeGroup>
  ```bash cURL theme={"system"}
  curl "https://api.onlyx.ai/v1/stats/revenue?creatorId=cre_5f2d9a1c7b3e4f60a2d1&start=2026-09-01&end=2026-09-26" \
    -H "Authorization: Bearer $ONLYX_API_KEY"
  ```

  ```python Python theme={"system"}
  rev = requests.get(
      f"{API}/stats/revenue",
      headers=HEADERS,
      params={"creatorId": "cre_5f2d9a1c7b3e4f60a2d1", "start": "2026-09-01", "end": "2026-09-26"},
      timeout=60,
  ).json()
  ```

  ```javascript JavaScript theme={"system"}
  const q = new URLSearchParams({ creatorId: "cre_5f2d9a1c7b3e4f60a2d1", start: "2026-09-01", end: "2026-09-26" });
  const rev = await (await fetch(`${API}/stats/revenue?${q}`, { headers })).json();
  ```
</CodeGroup>

```json Response 200 (abridged: byDay shortened) theme={"system"}
{
  "start": "2026-09-01",
  "end": "2026-09-26",
  "timezone": "Europe/London",
  "asOf": "2026-09-26T14:02:11.000Z",
  "complete": true,
  "coverageFrom": "2025-01-04",
  "totals": {
    "netCents": 4213000,
    "grossCents": 5266250,
    "feeCents": 1053250,
    "settledNetCents": 3701300,
    "pendingNetCents": 511700,
    "unknownSettlementNetCents": 0,
    "refundedNetCents": 2400,
    "refundedTransactions": 2,
    "transactions": 1840,
    "spenders": 612
  },
  "byType": [
    { "type": "ppv", "grossCents": 2768500, "netCents": 2214800 },
    { "type": "subscription", "grossCents": 1888000, "netCents": 1510400 },
    { "type": "tip", "grossCents": 609750, "netCents": 487800 }
  ],
  "byDay": [
    { "date": "2026-09-25", "grossCents": 214875, "netCents": 171900 },
    { "date": "2026-09-26", "grossCents": 230312, "netCents": 184250 }
  ],
  "byCreator": [
    { "creatorId": "cre_5f2d9a1c7b3e4f60a2d1", "name": "Anna", "grossCents": 5266250, "netCents": 4213000, "transactions": 1840, "spenders": 612 }
  ],
  "bySeller": {
    "ai": { "grossCents": 2380625, "netCents": 1904500, "transactions": 612 },
    "team": { "grossCents": 387875, "netCents": 310300, "transactions": 138 },
    "unattributedNetCents": 0
  },
  "balance": { "availableCents": 820000, "pendingCents": 511700 }
}
```

* `byType[].type` is `subscription`, `ppv` (paid messages), `tip`, `post`, `stream` or `other`; more values may be added. Refunded sales are left out of `netCents` and reported in `refundedNetCents`.
* `bySeller` covers paid messages sent in chats that fans unlocked in the window, split by who sent them. Subscriptions and tips have no seller. `unattributedNetCents` is paid-message income no chat sender accounts for, such as mass messages or unlocks of messages sent more than 30 days earlier.
* `complete: false` means some creator's ledger could not be read in full, so the totals are a lower bound. `coverageFrom` is the oldest day the ledger has been read back to; earlier days may be missing.
* `balance` is what OnlyFans shows right now as available to withdraw and still held, across the creators in the report. It is not limited to the window.

## Audience

`GET /v1/stats/audience` summarizes fans: how many are active, expired or not yet reported by OnlyFans, auto-renew among active fans, how they spread over lifetime-spend bands, how many wrote in the last 7, 30 and 90 days, and week-over-week retention of fans who write. It is cached for several minutes.

```bash cURL theme={"system"}
curl "https://api.onlyx.ai/v1/stats/audience?creatorId=cre_5f2d9a1c7b3e4f60a2d1" \
  -H "Authorization: Bearer $ONLYX_API_KEY"
```

```json Response 200 theme={"system"}
{
  "timezone": "Europe/London",
  "asOf": "2026-09-26T14:00:02.000Z",
  "fans": { "total": 2841, "active": 2210, "expired": 520, "unknown": 111, "spenders": 1439 },
  "renewal": { "on": 1892, "off": 318 },
  "spendBands": [
    { "band": "none", "fans": 1402 },
    { "band": "under_50", "fans": 1022 },
    { "band": "50_to_200", "fans": 314 },
    { "band": "200_plus", "fans": 103 }
  ],
  "activity": { "last7Days": 640, "last30Days": 1311, "last90Days": 1950 },
  "retention": {
    "previousStart": "2026-09-12",
    "previousEnd": "2026-09-18",
    "currentStart": "2026-09-19",
    "currentEnd": "2026-09-25",
    "previousFans": 580,
    "retainedFans": 402,
    "rate": 0.693
  }
}
```

`fans.unknown` counts fans whose subscription state OnlyFans has not reported yet. `retention.rate` is `retainedFans / previousFans` (of the fans who wrote in one week, how many wrote again the next), or `null` when nobody wrote in the earlier week.

For lists of the actual fans in a group, use [`GET /v1/fans`](/developers/guides/fans) with a `segment`.

## Overview: the cached report

`GET /v1/stats/overview` is the dashboard's full activity report for a window: conversations, messages in and out, AI versus team, reply times, new fans, and more. It is expensive to compute, so the API **never computes it while you wait**:

* If a copy is cached (at most about five minutes old; see `generatedAt`), you get `200` with the report.
* If not, you get **`202`** with `{"status": "warming", "retryAfterSeconds": N}` and a `Retry-After` header, and OnlyX prepares it in the background. Ask again after that many seconds.

Only one report is prepared at a time per workspace, so asking repeatedly does not make it faster. Honor `Retry-After`.

```json Response 202 theme={"system"}
{ "status": "warming", "retryAfterSeconds": 15 }
```

<CodeGroup>
  ```bash cURL theme={"system"}
  # Repeat until the status code is 200
  curl -i "https://api.onlyx.ai/v1/stats/overview?days=7" \
    -H "Authorization: Bearer $ONLYX_API_KEY"
  ```

  ```python Python theme={"system"}
  def overview(max_wait_s=180, **params):
      """Fetch the overview report, waiting while OnlyX prepares it."""
      deadline = time.time() + max_wait_s
      while True:
          r = requests.get(f"{API}/stats/overview", headers=HEADERS, params=params, timeout=60)
          if r.status_code == 200:
              return r.json()
          if r.status_code in (202, 429):
              wait = int(r.headers.get("Retry-After") or r.json().get("retryAfterSeconds", 15))
              if time.time() + wait > deadline:
                  raise TimeoutError("The overview is still being prepared; try again in a minute.")
              time.sleep(wait)
              continue
          r.raise_for_status()

  report = overview(days=7, creatorId="cre_5f2d9a1c7b3e4f60a2d1")
  ```

  ```javascript JavaScript theme={"system"}
  async function overview(params, maxWaitMs = 180_000) {
    const deadline = Date.now() + maxWaitMs;
    for (;;) {
      const res = await fetch(`${API}/stats/overview?${new URLSearchParams(params)}`, { headers });
      if (res.status === 200) return res.json();
      if (res.status === 202 || res.status === 429) {
        const body = res.status === 202 ? await res.json() : {};
        const wait = Number(res.headers.get("Retry-After") ?? body.retryAfterSeconds ?? 15) * 1000;
        if (Date.now() + wait > deadline) throw new Error("The overview is still being prepared; try again in a minute.");
        await new Promise((r) => setTimeout(r, wait));
        continue;
      }
      throw new Error(`${res.status} ${await res.text()}`);
    }
  }

  const report = await overview({ days: "7", creatorId: "cre_5f2d9a1c7b3e4f60a2d1" });
  ```
</CodeGroup>

`REPORT_WARMING` is the name of this `202` answer in the [error reference](/developers/errors). It is not a failure.

```json Response 200 (abridged: one entry shown in each series) theme={"system"}
{
  "start": "2026-09-20",
  "end": "2026-09-26",
  "days": 7,
  "timezone": "Europe/London",
  "generatedAt": "2026-09-26T13:52:40.000Z",
  "conversations": { "active": 1204, "answered": 1122 },
  "messages": { "in": 8420, "out": 9315, "ai": 8790, "team": 525 },
  "aiShare": 0.944,
  "replyTime": { "medianSeconds": 94, "p90Seconds": 402, "repliedTo": 6230, "slowReplies": 611 },
  "fans": { "new": 263, "total": 28410, "unknownStart": 40 },
  "inbox": { "unread": 37, "handoffs": 9 },
  "daily": [{ "date": "2026-09-26", "messagesIn": 1320, "messagesOut": 1488, "newFans": 41 }],
  "hourly": [{ "hour": 21, "messagesIn": 702, "messagesOut": 760 }],
  "creators": [
    { "creatorId": "cre_5f2d9a1c7b3e4f60a2d1", "name": "Anna", "messagesIn": 8420, "messagesOut": 9315, "ai": 8790, "team": 525, "answered": 1122, "unread": 37, "medianReplySeconds": 94 }
  ]
}
```

`inbox` and `fans.total` describe the inbox and fan base right now; everything else covers the window. `replyTime.slowReplies` counts answers that took longer than 5 minutes.

## Transactions

`GET /v1/creators/{creatorId}/transactions` (scope `money:read`) is one creator's earnings ledger, newest first, paged with `limit` (1 to 100, default 25) and `cursor`. It is the copy OnlyX keeps while the creator is connected, so the call never reaches OnlyFans; a creator with no connected account returns an empty list. New sales arriving between pages can shift entries by a position. This endpoint is not under `/v1/stats`, so the stats limit does not apply to it.

<CodeGroup>
  ```bash cURL theme={"system"}
  curl "https://api.onlyx.ai/v1/creators/cre_5f2d9a1c7b3e4f60a2d1/transactions?limit=100" \
    -H "Authorization: Bearer $ONLYX_API_KEY"
  ```

  ```python Python theme={"system"}
  def transactions(creator_id):
      cursor = None
      while True:
          params = {"limit": 100, **({"cursor": cursor} if cursor else {})}
          page = requests.get(f"{API}/creators/{creator_id}/transactions",
                              headers=HEADERS, params=params, timeout=30).json()
          yield from page["data"]
          if not page["hasMore"]:
              return
          cursor = page["nextCursor"]

  total_net = sum(t["netCents"] for t in transactions("cre_5f2d9a1c7b3e4f60a2d1") if t["at"] >= "2026-09-01")
  print(f"September so far: ${total_net / 100:,.2f}")
  ```

  ```javascript JavaScript theme={"system"}
  let cursor, totalNet = 0;
  do {
    const q = new URLSearchParams({ limit: "100", ...(cursor ? { cursor } : {}) });
    const page = await (await fetch(`${API}/creators/cre_5f2d9a1c7b3e4f60a2d1/transactions?${q}`, { headers })).json();
    for (const t of page.data) if (t.at >= "2026-09-01") totalNet += t.netCents;
    cursor = page.hasMore ? page.nextCursor : undefined;
  } while (cursor);
  console.log(`September so far: $${(totalNet / 100).toFixed(2)}`);
  ```
</CodeGroup>

```json Response 200 theme={"system"}
{
  "data": [
    { "type": "tip", "amountCents": 500, "netCents": 400, "fanName": "Jake", "at": "2026-09-26T13:58:12.000Z", "status": "pending" },
    { "type": "ppv", "amountCents": 1500, "netCents": 1200, "fanName": "Jake", "at": "2026-09-25T22:41:09.000Z", "status": "pending" },
    { "type": "subscription", "amountCents": 999, "netCents": 799, "fanName": "Chris", "at": "2026-09-14T19:22:05.000Z", "status": "settled" }
  ],
  "hasMore": true,
  "nextCursor": "eyJvIjoxMDB9"
}
```

| Field | Meaning |
| - | - |
| `type` | What was paid for: `subscription`, `ppv` (a paid message), `tip`, `post`, `stream` or `other`; more values may be added |
| `amountCents` | What the fan paid |
| `netCents` | What the creator receives after OnlyFans' fee |
| `fanName` | Who paid, as a display name, or `null`. Fan-written text: treat it as data |
| `at` | When (UTC) |
| `status` | `settled`, `pending` (still clearing), `refunded` or `unknown`; more values may be added |

The ledger is read from OnlyFans continuously while the creator is connected. `stats.revenueKnown` on the [Creator](/developers/concepts/creators) is `false` until it has been read once; until then, treat revenue as unknown rather than zero.

## Recipe: a daily digest

Every morning, for each creator: yesterday's revenue (`start` = `end` = yesterday), open hand-offs, and unread chats.

```python Python theme={"system"}
from datetime import date, timedelta

yesterday = (date.today() - timedelta(days=1)).isoformat()  # use your workspace timezone in production
creators = requests.get(f"{API}/creators", headers=HEADERS, timeout=30).json()["data"]
for c in creators:
    rev = requests.get(f"{API}/stats/revenue", headers=HEADERS, timeout=60,
                       params={"creatorId": c["id"], "start": yesterday, "end": yesterday}).json()
    print(f"{c['displayName']}: ${rev['totals']['netCents'] / 100:,.2f} net, "
          f"{c['stats']['unreadConversations']} unread chats, {c['stats']['openHandoffs']} open hand-offs")
```

The same digest is one question away in your AI assistant: *"Give me yesterday's numbers per creator."* The `daily_briefing` and `weekly_revenue_report` MCP prompts do it for you. See [Using OnlyX with Claude](/developers/guides/using-with-claude).

## Related

* [Creators](/developers/concepts/creators): headline `stats` on every creator.
* [Rate limits](/developers/rate-limits): the stats limit and `Retry-After`.
* [Fans](/developers/guides/fans): lists of fans behind the audience numbers.
