# The Only API β complete documentation
> Everything: guides plus all 529 endpoints. Large; consider llms-guides.txt, llms-crm.txt or llms-of.txt instead.
---
# Introduction (/docs)
The Only API gives you programmatic access to OnlyFans and Fansly through a
single REST API and a single API key.
OnlyFans has no public API. Its private endpoints require **signed headers**
(`sign`, `time`, `app-token`) that rotate constantly, a live session cookie, and
a residential or datacenter proxy that matches the IP the session logged in
from. We do all of that server-side. You send one header.
Every connected account has a `platform` of either `onlyfans` or `fansly`. The
CRM routes work for both and return the same normalized shapes. The
`/api2/v2/*` passthrough is OnlyFans-only. See
[OnlyFans & Fansly](/docs/platforms).
## The shape of the API [#the-shape-of-the-api]
Your **CRM panel** is the tenant boundary. A panel has an ID (`crm_β¦`), an API
key, and a set of connected creator accounts β one slot per account, each with
its own persisted session and proxy.
Almost every path is scoped to a panel:
```
https://theonlyapi.com/api/crm/{crm_id}/β¦
```
There are two very different surfaces underneath that prefix, and knowing which
one you are on explains most surprising responses:
95 endpoints. OnlyFans and Fansly rows projected to the same shape, plus
server-side aggregations, cached reads, async jobs, webhooks, automations and
events that have no platform equivalent.
434 endpoints under `/api2/v2/*`. OnlyFans' own response body, unchanged,
wrapped in a small `{ success, status_code, data }` envelope.
Read [The two surfaces](/docs/two-surfaces) for how to choose between them.
## Start here [#start-here]
Panel β connected account β first authenticated call, in about five minutes.
Where keys live, and the difference between primary and secondary keys.
Session paste (recommended) or credentials with automatic 2FA handling.
The two envelopes, the real error shapes, and one wrapper that will confuse
you if you test without a key.
## Using these docs with an AI [#using-these-docs-with-an-ai]
Every page here has a Markdown twin: append `.md` to any URL, or use the
**Copy page** button in the page header to copy it, view it as Markdown, or open
it directly in ChatGPT or Claude. There is also an
[`llms.txt`](/llms.txt) index, per-section full-text bundles, and an MCP server
for the docs themselves.
See [Use these docs with an AI](/docs/ai-agents).
---
# Use these docs with an AI (/docs/ai-agents)
This documentation is built to be read by machines as well as people. Every page
has a plain-Markdown twin, the whole corpus is available as text bundles, and
there are two MCP servers β one for the docs, one for the API itself.
## Copy any page [#copy-any-page]
Every page header carries a **Copy Markdown** button and an **Open βΎ** menu:
| Action | What it does |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| **Copy Markdown** (button) | Puts the page's raw Markdown on your clipboard |
| **View as Markdown** | Opens the `.md` twin |
| **Open in ChatGPT / Claude / Cursor / Scira AI** | Opens that assistant pre-filled with *"Read \, I want to ask questions about it."* |
Those links carry a prompt asking the assistant to **fetch the page itself**, so they
only work if it has web access β and it will fetch the HTML, not the smaller `.md`
twin. For a guaranteed clean paste, use **Copy Markdown**.
## Markdown twins [#markdown-twins]
Append `.md` to any documentation URL:
```bash
curl https://docs.theonlyapi.com/docs/webhooks.md
curl https://docs.theonlyapi.com/docs/crm/messaging/send-a-dm-or-ppv.md
```
Content negotiation works too β request the normal URL with
`Accept: text/markdown` and you get Markdown back:
```bash
curl -H 'Accept: text/markdown' https://docs.theonlyapi.com/docs/pagination
```
The 529 endpoint pages have no prose body of their own β they are rendered from
the OpenAPI document. Their Markdown twins are generated from the same spec, so
each one arrives as a complete, self-contained brief: signature, description,
auth, parameters, request body, response shapes and a runnable `curl` example.
An agent can make a correct call from one page.
## Text bundles [#text-bundles]
| File | Contents |
| -------------------------------------- | ----------------------------------------------------- |
| [`/llms.txt`](/llms.txt) | Index of every page, with descriptions. Start here. |
| [`/llms-guides.txt`](/llms-guides.txt) | Every guide, full text. The one to paste into a chat. |
| [`/llms-crm.txt`](/llms-crm.txt) | All 95 CRM endpoints, full text. |
| [`/llms-of.txt`](/llms-of.txt) | All 434 OnlyFans passthrough endpoints, full text. |
| [`/llms-full.txt`](/llms-full.txt) | Everything. Large β for crawlers and indexers. |
| [`/agents.md`](/agents.md) | A short operating brief for coding agents. |
The split exists for a practical reason: `llms-full.txt` is over a megabyte,
which is more than most models want in one paste. `llms-guides.txt` is the file a
human should actually hand to an assistant.
```bash
# Give an assistant everything it needs to write an integration
curl https://docs.theonlyapi.com/llms-guides.txt
```
## MCP servers [#mcp-servers]
There are two, and they do different jobs. Connecting the wrong one is the usual
confusion.
`https://docs.theonlyapi.com/mcp` Β· public, no auth Β· search and read these
pages.
`https://theonlyapi.com/mcp` Β· OAuth 2.1 or a bearer API key Β· 59 tools that
act on your real panel.
### Docs MCP [#docs-mcp]
Read-only access to this documentation. No account, no key.
```bash
claude mcp add --transport http theonlyapi-docs https://docs.theonlyapi.com/mcp
```
```json
{
"mcpServers": {
"theonlyapi-docs": {
"type": "http",
"url": "https://docs.theonlyapi.com/mcp"
}
}
}
```
Tools:
| Tool | Purpose |
| ---------------- | ----------------------------------------------------------------------------------------- |
| `search_docs` | Full-text search across every page |
| `get_page` | Fetch one page as Markdown by path |
| `list_endpoints` | List API endpoints, filtered by surface, tag or method |
| `get_endpoint` | Full reference for one endpoint by method and path |
| `list_tags` | Every tag with its surface and endpoint count β cheap orientation before `list_endpoints` |
Use this when you want an agent to *learn* the API β answer questions, write
integration code, look up a parameter.
### API MCP [#api-mcp]
The product's own MCP server, which actually calls the API on your behalf: 59
tools across accounts, fans, messaging, earnings, subscribers, campaigns,
webhooks, automations and events.
```bash
claude mcp add --transport http theonlyapi https://theonlyapi.com/mcp
```
It supports OAuth 2.1 with PKCE, and clients that cannot do OAuth may send a CRM
API key as `Authorization: Bearer `.
Notable behaviours:
* **The destructive and money-moving tools require** `confirm=true` β sending a DM,
requesting a payout, changing subscription price, deleting an account, webhook or
automation, and any non-GET through the escape hatches. Lower-risk writes (tagging
a fan, creating a webhook or automation) apply immediately.
* **Non-GET proxying is off by default.** Enable `mcp_unsafe_proxy` in the
dashboard danger zone, or via
`PATCH /api/crm/{crm_id}/mcp/unsafe-proxy`, if you need the escape-hatch tools.
Note the toggle applies to static bearer-key clients only β OAuth sessions
(ChatGPT, Claude.ai) never get non-GET proxying.
* No *dedicated* vault, media-upload, mass-DM or scheduling tools exist β
`of_send_message` sends one text DM to one fan, with no price, media or recipient
list. Those surfaces are reachable only through the `of_crm_request` escape hatch,
which needs `confirm=true` for any non-GET.
Full setup instructions for ChatGPT, Claude, Cursor and Claude Code are on the
[MCP integration page](https://theonlyapi.com/integrations/chatgpt).
## The OpenAPI spec [#the-openapi-spec]
If your tooling speaks OpenAPI, skip the prose entirely:
```bash
curl https://theonlyapi.com/api/openapi.json
```
OpenAPI 3.1, 450 paths, 529 operations, every operation with a summary. This
reference is generated from it.
## Prompting suggestions [#prompting-suggestions]
* **Give it the guides, not the full bundle.** `llms-guides.txt` plus the two or
three endpoint pages you care about beats a megabyte of everything.
* **Name the surface.** "Use the CRM layer, not the `/api2/v2` passthrough" removes
most wrong answers, because the two have different response envelopes.
* **Warn it about the money rule.** Agents will happily write
`SUM(amount)` for earnings, which is wrong β see
[the chargeback trap](/docs/earnings).
* **Warn it about pagination.** The `/chats` and `/messages` quirks in
[Pagination](/docs/pagination) are not guessable; an agent will write a walker
that loses data unless it has read that page.
---
# Async jobs (/docs/async-jobs)
Anything that has to walk a platform API takes longer than a request should. Those
operations return `202 Accepted` immediately and run in the background.
## The pattern [#the-pattern]
### Start the job [#start-the-job]
```bash
curl -X POST "$BASE/api/crm/$CRM/accounts/$OFUID/subscribers/refresh" \
-H "X-API-Key: $KEY"
```
Returns `202` with the job or state payload.
### Watch it β poll or stream [#watch-it--poll-or-stream]
Poll the matching status route:
```bash
curl "$BASE/api/crm/$CRM/accounts/$OFUID/subscribers/refresh/status" \
-H "X-API-Key: $KEY"
```
The `/refresh/status` routes report **cache freshness**, not job progress β they
return `{success, cache: {β¦}}` with no `status` or `phase`. To see whether a job is
still running, use `GET /refresh/active`, or subscribe to
[server-sent events](/docs/streaming) and watch `refresh.progress` /
`refresh.complete`. The dashboard uses SSE.
### Read the result [#read-the-result]
The job writes into the cache, so the result is just the corresponding `/cached`
read β see [Cached reads](/docs/cached-reads). Exports instead produce a
downloadable ZIP.
## The jobs [#the-jobs]
| Start | Freshness check | What it does |
| -------------------------------------------------- | ------------------------------- | ----------------------------------------------------------- |
| `POST /accounts/{of_user_id}/subscribers/refresh` | `β¦/subscribers/refresh/status` | Delta-sync the subscriber cache |
| `POST /accounts/{of_user_id}/transactions/refresh` | `β¦/transactions/refresh/status` | Delta-sync the transaction cache |
| `POST /accounts/{of_user_id}/campaigns/refresh` | `β¦/campaigns/refresh/status` | Delta-sync campaigns and claimers |
| `POST /accounts/{of_user_id}/backfill` | via `/refresh/active` | Backfill historical data |
| `POST /accounts/{of_user_id}/exports` | `β¦/exports/{job_id}` | Build a data export ZIP β see [Data exports](/docs/exports) |
Two panel-wide helpers:
```
GET /refresh/active # every running refresh job in the panel
POST /accounts/{of_user_id}/refresh/{kind}/clear # clear a stuck job's state
```
## One at a time, per account per kind [#one-at-a-time-per-account-per-kind]
Starting a job while one of the same kind is already running is **not an error**.
You get `202` with `already_running: true` and the in-flight job β `state` for
refresh and backfill, `job` for exports.
```json
{ "success": true, "already_running": true, "state": { "β¦": "β¦" } }
```
Treat it as success. Retrying on it just spins.
A **stale** job is superseded. The two clocks differ, and it matters: a refresh is
abandoned after **10 minutes with no recorded progress** β a healthy long job
keeps resetting that clock, so it never looks stale β while an export is
abandoned **30 minutes after it started**, regardless of progress. The stale job
is marked failed and yours starts fresh, so a crashed worker never blocks you
permanently.
Exports behave the same way; only the payload key differs:
```json
{ "success": true, "already_running": true, "job": { "job_id": "f01152d3β¦", "status": "running" } }
```
Treat `already_running: true` as success β something is already doing the work you
wanted. No refresh or export route returns `409` for this; the `409`s that exist
are for a different class of mistake, like downloading an export before it has
finished.
## Don't poll tightly [#dont-poll-tightly]
Refresh jobs finish in seconds to minutes depending on account size; exports with
messages and media can take many minutes. Poll every few seconds at most, or use
SSE and stop polling entirely.
Status polls are **free** β they never touch the platform and carry no monthly
quota cost. They do count against the per-minute rate limit, so do not spin on
them.
---
# Authentication (/docs/authentication)
Send your key on every request:
```http
X-API-Key: YOUR_API_KEY
```
That is the whole scheme for API clients. There are no bearer tokens and no request
signing.
Two other schemes exist but are not available to you: OAuth 2.1, used only by the
[MCP server](/docs/ai-agents), and an internal `X-Service-Token` used by our own
services calling each other.
The API does not accept a key as a query parameter, even though one old error
message mentions it. Keys in URLs end up in access logs, proxy logs and browser
history.
## Keys are scoped to one panel [#keys-are-scoped-to-one-panel]
A key authenticates you as a specific CRM panel. Passing a key that is valid but
belongs to a **different** panel than the `{crm_id}` in the path is rejected the
same way an invalid key is β there is no cross-panel access.
Likewise, `{of_user_id}` must be an account connected to *your* panel:
```json
{ "error": "Account not found or does not belong to this CRM panel" }
```
Most routes answer that with a `403`, and it means the same thing whether the
account does not exist or simply is not yours.
A handful answer the same condition with `404 {"error": "Account not found"}`
instead β `DELETE /accounts/{of_user_id}`, the `/proxy` read and update, the
`/polling` read, and `/fansly-credentials`. Treat `403` and `404` on an account
path as the same thing: not yours.
## Unscoped routes [#unscoped-routes]
These seven carry no `{crm_id}` and are served by
`https://api.theonlyapi.com` β not `theonlyapi.com`, which does not route them.
"Unscoped" is not the same as "open": most still require something.
| Route | What it needs |
| ----------------------------------------- | ----------------------------------------------------------------------------- |
| `GET /health` | nothing |
| `POST /api/auth/login` | nothing (the credentials *are* the auth) |
| `POST /api/auth/start-email-verification` | nothing |
| `POST /api/auth/verify-email` | nothing |
| `GET /api/whoami` | **an API key** β it resolves a key to its panel, it is just not scoped by one |
| `POST /api/auth/register` | **a captcha** β see below |
| `POST /api/crm/register` | **a captcha** β see below |
See [Base URL & panels](/docs/base-url) for why the host differs.
The two registration routes require either a Cloudflare Turnstile token
(`captcha_token` in the body, or an `X-Captcha-Token` header) or an internal
service token. Without one they return:
```json
{ "success": false, "code": "CAPTCHA_REQUIRED",
"error": "Captcha verification is required to create an account.", "reason": "missing_token" }
```
Sign up through [the website](https://theonlyapi.com/pricing), which solves the
captcha for you β see [Get an API key](/docs/get-an-api-key). There is no need to
call either route yourself.
## Failure codes [#failure-codes]
| Code | Body | What it means |
| ----- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `403` | `{"error": "Invalid API key"}` | Key is unknown, revoked, or belongs to another panel |
| `401` | `{"error": "Missing X-API-Key headerβ¦"}` | No key sent β note **401**, while an *invalid* key is 403 |
| `403` | `{"error": "Account not found or does not belong to this CRM panel"}` | `of_user_id` is not connected to your panel |
| `403` | `{"error": "β¦"}` | A secondary key attempted a primary-key-only operation |
| `429` | `{"error": "Rate limit exceeded", "retry_after": "1000 per 1 minute"}` | Per-minute limit. `retry_after` echoes the limit that fired, **not** a number of seconds β use the `Retry-After` header for the delay |
| `429` | `{"error": "API call limit reached", "plan": β¦, "limit": N}` | Monthly quota exhausted |
| `403` | `{"code": "WRITES_DISABLED"}` | The account has `allow_of_write_actions` off β see [Connect an account](/docs/connect-an-account) |
| `403` | `{"code": "SLOT_LIMIT"}` | Connecting an account beyond your plan's slots |
| `403` | `{"code": "CAPTCHA_REQUIRED"}` | A registration route called without a captcha |
| `404` | `{"error": "Account not found"}` | Same as the `403` above, on the few routes noted earlier |
| `501` | `{"code": "platform_not_supported", "platform": β¦, "feature": β¦}` | The operation does not exist for that account's platform β see [OnlyFans & Fansly](/docs/platforms) |
Invalid, revoked and wrong-panel keys all return the same `403`. Key comparison
is constant-time, and unknown keys still run a dummy comparison, so response
timing does not reveal whether a key exists.
---
# Automations (/docs/automations)
An automation is a rule stored on your panel: when an event of a given type
arrives and every condition matches, run an action. No infrastructure needed on
your side.
```bash
curl -X POST "$BASE/api/crm/$CRM/automations" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Thank big tippers on Discord",
"trigger_event": "new_tip",
"conditions": [
{ "field": "payload.amount", "op": "gt", "value": 20 }
],
"action_type": "discord",
"action_params": {
"url": "https://discord.com/api/webhooks/β¦",
"message": "πΈ {payload.fan.username} tipped ${payload.amount}"
},
"is_active": true
}'
```
## Conditions [#conditions]
A list, joined with **AND**. Every condition must match for the action to run. An
empty list matches every event of that type.
```json
{
"conditions": [
{ "field": "payload.amount", "op": "gt", "value": 5 },
{ "field": "payload.fan.username", "op": "neq", "value": "banned_user" }
]
}
```
`field` is a dotted path into the [event envelope](/docs/events), so
`event_type`, `of_user_id`, `occurred_at` and anything under `payload.` are all
addressable.
### Operators [#operators]
| `op` | Meaning |
| ------------ | ------------------------------------------------ |
| `eq` | equal |
| `neq` | not equal |
| `gt` / `gte` | greater than / or equal |
| `lt` / `lte` | less than / or equal |
| `contains` | substring |
| `startswith` | prefix |
| `in` | the field's value appears in the list you supply |
## Actions [#actions]
| `action_type` | `action_params` |
| ------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `webhook` | `{ "url", "method"?, "headers"?, "body"? }` β **unsigned and un-retried**, unlike a [first-party webhook](/docs/webhooks) |
| `discord` | `{ "url", "message", "username"? }` |
| `slack` | `{ "url", "message" }` |
| `telegram` | `{ "bot_token", "chat_id", "message" }` |
| `send_dm` | `{ "message", "to_fan_id"? }` β defaults to `payload.fan.id` |
| `tag_fan` | `{ "tag": "vip" }` |
### Templating [#templating]
Any `message` supports `{dotted.path}` interpolation against the event, resolved at
dispatch:
```
"πΈ {payload.fan.username} tipped ${payload.amount} on {occurred_at}"
```
Templating applies to **every string** in `action_params` β `url`, `tag`,
`to_fan_id`, nested objects and arrays β not just `message`. Credential keys
(`bot_token`, `secret`, `token`, `api_key`, `password`) are deliberately excluded
and passed through byte-for-byte.
An unresolvable path renders as an **empty string**, not as literal `{β¦}`. A typo
silently leaves a hole in the message (`"πΈ tipped $"`) rather than announcing
itself β check the first delivery.
`send_dm` is an OnlyFans write and is gated per account by
`allow_of_write_actions`. Enable it via
`PATCH /accounts/{of_user_id}/polling`. Otherwise the action is logged as a
`failed` run in `GET /automations/{id}/runs` with
`error_snippet: "send_dm blocked: account has allow_of_write_actions=false"`.
There is no HTTP error to observe β automations run in the event fan-out, not in a
request. See [Connect an account](/docs/connect-an-account).
## Managing automations [#managing-automations]
```
GET /automations # list
POST /automations # create
GET /automations/{id} # read one
PATCH /automations/{id} # update (send {"is_active": false} to pause)
DELETE /automations/{id}
POST /automations/{id}/run-now # fire the action immediately, for testing
GET /automations/{id}/runs # run history
```
`run-now` validates an action without waiting for a real event β but it builds a
synthetic event from the `sample_payload` you supply, so **send one**. Without it
the payload is empty, every `payload.*` condition fails, and the run is recorded as
`skipped`.
```bash
curl -X POST "$BASE/api/crm/$CRM/automations/$ID/run-now" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"sample_payload": {"amount": 25, "fan": {"id": "987", "username": "somefan"}}}'
```
The response is `{"status": "success" | "skipped" | "failed"}`. `GET /runs`
(default 50, max 200) has the history.
`POST /automations` also accepts `of_user_id` to scope a rule to one connected
account β the per-account routing that [webhooks](/docs/webhooks) cannot do.
An unknown or misspelled `op` is accepted, then fails every comparison at dispatch,
so the automation silently never fires. Check `/runs` after creating a rule.
## Worked examples [#worked-examples]
```json
{
"name": "VIP tagging",
"trigger_event": "new_purchase",
"conditions": [{ "field": "payload.amount", "op": "gte", "value": 100 }],
"action_type": "tag_fan",
"action_params": { "tag": "vip" }
}
```
The tag then shows up in `GET /fans`, so you can segment on it later β including
as a mass-DM audience filter. See [Messaging](/docs/messaging).
```json
{
"name": "Account needs reconnecting",
"trigger_event": "polling_paused",
"conditions": [],
"action_type": "telegram",
"action_params": {
"bot_token": "123456:ABCβ¦",
"chat_id": "-1001234567890",
"message": "β οΈ Polling paused for {of_user_id} β {payload.reason} ({payload.failures} failures)"
}
}
```
This is the single most useful automation to set up first. It is the earliest
reliable signal that a session has died β see [Sessions](/docs/sessions).
```json
{
"name": "Welcome DM",
"trigger_event": "new_subscriber",
"conditions": [],
"action_type": "send_dm",
"action_params": {
"message": "Hey {payload.fan.display_name}, thanks for subscribing π",
"to_fan_id": "{payload.fan.id}"
}
}
```
Note `to_fan_id` is templated from the event, so one rule covers every new
subscriber.
## Automations, webhooks, or your own code? [#automations-webhooks-or-your-own-code]
Automations are the right tool when the reaction is a notification or a simple
one-step side effect. Once you need branching logic, external data, or state, use a
[webhook](/docs/webhooks) and do the work in your own service.
Both run from the same event emission, so you can use them together β an automation
for the Discord ping, a webhook for your database.
---
# Base URL & panels (/docs/base-url)
```
https://theonlyapi.com/api/crm/{crm_id}
```
`{crm_id}` is your panel ID β a string like `crm_0123456789abcdef`, shown in
**Dashboard β Settings**. If you only have an API key, resolve it:
```bash
curl "https://api.theonlyapi.com/api/whoami" -H "X-API-Key: $KEY"
```
## The panel is the tenant boundary [#the-panel-is-the-tenant-boundary]
A panel owns:
* its API keys (one primary, any number of secondary)
* its connected creator accounts β one **slot** per account, each with its own
persisted session and proxy
* its events, webhooks, automations, exports and cached data
Nothing crosses panels. A key for panel A cannot read panel B, and
`{of_user_id}` must belong to the panel in the path.
## Unscoped routes use a different host [#unscoped-routes-use-a-different-host]
Seven routes are not panel-scoped. They live on the **API host**, not the
marketing host:
```
https://api.theonlyapi.com/health
https://api.theonlyapi.com/api/whoami
https://api.theonlyapi.com/api/auth/register
https://api.theonlyapi.com/api/auth/login
https://api.theonlyapi.com/api/auth/start-email-verification
https://api.theonlyapi.com/api/auth/verify-email
https://api.theonlyapi.com/api/crm/register
```
`theonlyapi.com` is a Next.js app that proxies `/api/crm/{crm_id}/β¦` through to
the API. It does **not** route the unscoped paths, and two of them collide with
its own routes:
| Route on `theonlyapi.com` | What you actually get |
| ------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `GET /health` | `404` |
| `GET /api/whoami` | `404` |
| `POST /api/crm/register` | `{"error": "Unauthorized"}` |
| `POST /api/auth/*` | `Error: This action with HTTP POST is not supported by NextAuth.js` β the site's own auth handler answers first |
Use `api.theonlyapi.com` for all seven.
In this reference those endpoints are documented with their full path, and their
"Test it" server is `api.theonlyapi.com` rather than the panel-scoped base URL.
## Path anatomy [#path-anatomy]
```
https://theonlyapi.com/api/crm/crm_abc123/accounts/482687148/chats
βββββββββ origin βββββββ panel scope ββββββββ resource ββββββββββ
```
Most resources hang off a connected account:
```
/accounts/{of_user_id}/balances
/accounts/{of_user_id}/subscribers/cached
/accounts/{of_user_id}/chats/{with_user_id}/messages
```
A few are panel-wide, aggregating across every connected account:
```
/fans
/balances/summary
/earnings/summary
/events
/webhooks
/automations
```
And the passthrough mirrors OnlyFans' own paths verbatim after the prefix:
```
/api2/v2/users/me
/api2/v2/subscriptions/subscribers?limit=10&type=active
```
Despite the name, `of_user_id` identifies a connected account on **either**
platform. A Fansly account's ID is returned and used as `of_user_id` too. Get
the list from `GET /accounts`.
## Which host to use [#which-host-to-use]
| Routes | Host |
| ----------------------------------------------- | ---------------------------- |
| Everything panel-scoped β `/api/crm/{crm_id}/β¦` | `https://theonlyapi.com` |
| The seven unscoped routes above | `https://api.theonlyapi.com` |
`api.theonlyapi.com` reaches the API directly and serves the panel-scoped routes
too, so it works for both if you would rather use one host everywhere. The
panel-scoped examples in these docs use `theonlyapi.com` because that is the
documented product base URL.
---
# Bulk account import (/docs/bulk-import)
Connecting accounts one at a time is fine for a handful. For an agency onboarding
dozens, paste them all at once: the importer parses the paste, tells you exactly
what will happen before touching anything, then works through the rows in the
background β parking any that need a 2FA code until you supply one.
All eight routes are panel-scoped and authenticated like every other CRM route β
see the [Bulk Import reference](/docs/crm/bulk-import) for full request and
response schemas.
## The flow [#the-flow]
### Preview β no side effects [#preview--no-side-effects]
```bash
curl -X POST "$BASE/api/crm/$CRM/import/preview" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "creator1@example.com,hunter2,http://user:pass@host:port\ncreator2@example.com,hunter3",
"default_platform": "onlyfans"
}'
```
Nothing is written and no login is attempted. You get back what the importer
*would* do: the detected format, which rows are valid, which lane each takes, and
precisely why row 47 is bad.
```json
{
"success": true,
"format": "bare",
"delimiter": ",",
"has_header": false,
"columns": ["email", "password", "proxy"],
"total": 2,
"valid_count": 2,
"invalid_count": 0,
"rows": [
{
"row_index": 0,
"email": "creator1@example.com",
"platform": "onlyfans",
"lane": "password",
"proxy": "host:port",
"has_password": true,
"has_totp_secret": false,
"cookie_fields": [],
"valid": true,
"errors": []
}
]
}
```
Passwords and TOTP secrets come back as booleans (`has_password`,
`has_totp_secret`), cookies as a list of field *names*, and the proxy as
`host:port` only β the credentials are stripped. The paste already crossed the
network once; reflecting it back would double the exposure for no benefit.
Always preview first. It is free, it costs no platform requests, and it is the
only chance to catch a malformed paste before logins start.
### Create the job [#create-the-job]
Same body, plus an optional `source`:
```bash
curl -X POST "$BASE/api/crm/$CRM/import/jobs" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"text": "β¦", "default_platform": "onlyfans", "source": "paste"}'
```
Returns `202` with the job, and dispatches a background worker. Up to
**1,000 rows** per job by default.
### Watch it [#watch-it]
Progress arrives on the [event stream](/docs/streaming) as `import.progress`
(coalesced to at most one per second) and finishes with `import.complete`:
```bash
curl -N "$BASE/api/crm/$CRM/events/stream" -H "X-API-Key: $KEY"
```
Or poll:
```bash
curl "$BASE/api/crm/$CRM/import/jobs/$JOB_ID" -H "X-API-Key: $KEY" # one job
curl "$BASE/api/crm/$CRM/import/jobs" -H "X-API-Key: $KEY" # history
```
### Answer the 2FA prompts [#answer-the-2fa-prompts]
Rows whose platform demands a code **park** rather than fail, with status
`needs_2fa`. Supply the code and that row finishes:
```bash
curl -X POST "$BASE/api/crm/$CRM/import/jobs/$JOB_ID/rows/$ROW_ID/otp" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"code": "123456"}'
```
This call is **synchronous** β one platform call, and the response carries the
real outcome rather than "queued", because someone is waiting on it.
## Parked rows outlive the job [#parked-rows-outlive-the-job]
```bash
curl "$BASE/api/crm/$CRM/import/pending-2fa?limit=200" -H "X-API-Key: $KEY"
```
Panel-wide, across every job. This is what lets a dashboard say "3 accounts need
a 2FA code" long after whoever started the import has closed the tab β a per-job
query could not answer that.
## Retry and cancel [#retry-and-cancel]
```bash
# Retry one failed row
curl -X POST "$BASE/api/crm/$CRM/import/jobs/$JOB_ID/rows/$ROW_ID/retry" \
-H "X-API-Key: $KEY"
# Stop the whole job
curl -X POST "$BASE/api/crm/$CRM/import/jobs/$JOB_ID/cancel" -H "X-API-Key: $KEY"
```
## Lanes [#lanes]
Every valid row is routed down one of two lanes, decided by what you supplied:
| Lane | Triggered by | Behaviour |
| ---------- | -------------------------------------- | ----------------------------------------------- |
| `cookie` | Session cookies or a Fansly auth token | Session paste β no login attempt, most reliable |
| `password` | An email/username and password | Full login flow, may park on 2FA |
Prefer `cookie` rows where you have them, for the same reasons given in
[Connect an account](/docs/connect-an-account).
## Row statuses [#row-statuses]
| Status | Meaning |
| ------------------- | ---------------------------------------------------------------------------------- |
| `pending` | Parsed and accepted, not yet claimed |
| `running` | Login in flight |
| `needs_2fa` | Parked β waiting for you to post a code |
| `needs_2fa_expired` | The 2FA window closed and the credential was destroyed; `retry` restarts the login |
| `success` | Account connected |
| `failed` | Gave up; see the row's error |
| `invalid` | Failed parse validation β never attempted |
| `skipped` | That email is already connected to this panel |
| `slot_exhausted` | Plan account limit reached |
| `canceled` | Job canceled before this row ran |
There is no `queued` and no `complete` β success is `success`. The authoritative
list comes back as `row_states` on `GET /import/jobs/{job_id}`.
## Endpoints [#endpoints]
| Endpoint | What it does |
| ----------------------------------------------------------------------------------------------------------- | --------------------------------- |
| [`POST /import/preview`](/docs/crm/bulk-import/preview-an-import-paste) | Parse + validate, no side effects |
| [`POST /import/jobs`](/docs/crm/bulk-import/start-a-bulk-import) | Create a job β `202` |
| [`GET /import/jobs`](/docs/crm/bulk-import/list-import-jobs) | Job history |
| [`GET /import/jobs/{job_id}`](/docs/crm/bulk-import/get-an-import-job) | One job, with rows |
| [`POST /import/jobs/{job_id}/cancel`](/docs/crm/bulk-import/cancel-an-import-job) | Stop a running job |
| [`POST /import/jobs/{job_id}/rows/{row_id}/otp`](/docs/crm/bulk-import/supply-a-2fa-code-for-an-import-row) | Supply a 2FA code (synchronous) |
| [`POST /import/jobs/{job_id}/rows/{row_id}/retry`](/docs/crm/bulk-import/retry-an-import-row) | Retry one row |
| [`GET /import/pending-2fa`](/docs/crm/bulk-import/list-rows-waiting-on-a-2fa-code) | Panel-wide parked rows |
All writes are on the 100/minute sensitive-route limit.
Each connected account consumes a slot. A row that hits the cap ends as
`slot_exhausted` (not `failed`) with `Account limit reached (N). Buy a slot to
add another account.` and `code: "SLOT_LIMIT"`.
The check is a read, not a reservation, so up to the lane concurrency (10 cookie /
6 password) rows can all observe the same last free slot and proceed β a panel can
end up slightly over its cap. Check `GET /api/crm/{crm_id}/usage` first.
---
# Cached reads (/docs/cached-reads)
Every live read reaches OnlyFans or Fansly and counts against **their** per-account
rate limits β the ones that get accounts flagged. The `/cached` routes serve from
our own store instead and cost **zero** platform requests.
For anything you read repeatedly β dashboards, reports, reconciliation, any loop β
use the cached route.
## The cached routes [#the-cached-routes]
| Route | Returns |
| -------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `GET /accounts/{of_user_id}/subscribers/cached` | Subscriber snapshot, paginated |
| `GET /accounts/{of_user_id}/subscribers/new` | Newly seen subscribers |
| `GET /accounts/{of_user_id}/subscribers/stats` | Aggregated counts by `granularity` (`hour`/`day`/`week`/`month`) |
| `GET /accounts/{of_user_id}/transactions/cached` | Transaction rows, paginated |
| `GET /accounts/{of_user_id}/fans/{fan_id}/transactions/cached` | One fan's transactions |
| `GET /accounts/{of_user_id}/campaigns/{campaign_id}/claimers/cached` | Campaign claimers |
All of them except `/subscribers/stats` use offset pagination with a trustworthy
`total` β convention 1 in [Pagination](/docs/pagination). Note the rows come back
under a generic `list` key, not one named after the resource.
`/subscribers/stats` is not paginated at all: it returns a zero-filled `buckets`
array plus `total_in_window`.
```bash
curl "$BASE/api/crm/$CRM/accounts/$OFUID/subscribers/cached?limit=100&offset=0" \
-H "X-API-Key: $KEY"
```
## Keeping the cache fresh [#keeping-the-cache-fresh]
Two things update it.
**Background polling.** Connected accounts are polled on a schedule. This is what
generates [events](/docs/events) β new tips, subscribers, messages, balance
changes β and it keeps the caches moving without you asking.
**Explicit refresh.** When you need current data right now, kick off an
[async refresh job](/docs/async-jobs):
```bash
curl -X POST "$BASE/api/crm/$CRM/accounts/$OFUID/subscribers/refresh" \
-H "X-API-Key: $KEY"
# 202 β poll .../subscribers/refresh/status
```
Refreshes are **delta syncs** β they fetch what changed rather than re-walking
everything, so they are cheap enough to run on a schedule.
For history that predates the account being connected, use
`POST /accounts/{of_user_id}/backfill`.
## A sensible read pattern [#a-sensible-read-pattern]
```python
# 1. Trigger a refresh only when your data is older than you can tolerate.
if cache_age_seconds > 300:
session.post(f"{BASE}/api/crm/{CRM}/accounts/{OFUID}/subscribers/refresh",
headers={"X-API-Key": KEY})
# 2. Read from the cache regardless β it always answers, and costs nothing
# against the platform. The refresh will land shortly.
rows = list(walk(session,
f"{BASE}/api/crm/{CRM}/accounts/{OFUID}/subscribers/cached",
KEY, item_key="list"))
```
Do not block on the refresh finishing before reading. Read the cache, and let
either the next refresh or the background poller catch you up. If you need to know
the moment new data lands, subscribe to [events](/docs/streaming) instead of
polling status.
`GET` and `PATCH /accounts/{of_user_id}/polling` control what we poll for an
account, and how often. The same object holds `allow_of_write_actions`, the gate
on OnlyFans write operations β see [Connect an account](/docs/connect-an-account).
## When to read live instead [#when-to-read-live-instead]
Reach for the live route when staleness is genuinely unacceptable β confirming a
balance right before a payout, or checking whether a specific message landed. Live
routes are `GET /accounts/{of_user_id}/subscribers`, `/balances`, `/earnings`,
`/purchases`, `/chats`, and the whole `/api2/v2/*` passthrough.
Even then, do it once, not in a loop.
---
# Connect an account (/docs/connect-an-account)
Before you can read or write anything for a creator, that account has to be
connected to your panel. Each connected account occupies one **slot**.
There are two ways in. Session paste is more reliable and is what we recommend.
## Option A β session paste (recommended) [#option-a--session-paste-recommended]
Open a browser logged into OnlyFans, then **DevTools β Application β Cookies β onlyfans.com** and copy `sess` and `auth_id` (and `fp` if present).
```bash
curl -X POST "$BASE/api/crm/$CRM/accounts/login/cookies" \
-H "X-API-Key: $KEY" \
-H "X-Proxy: http://user:pass@host:port" \
-H "Content-Type: application/json" \
-d '{
"platform": "onlyfans",
"sess": "",
"auth_id": "",
"fp": ""
}'
```
Omitting `X-Proxy` does **not** return an error. The login proceeds and egresses on
our shared server IP, and the saved session records **no** proxy β so every later
request for that account also goes out direct. The failure surfaces later, as a
session that keeps breaking. A malformed proxy is silently downgraded to none rather
than rejected, so a typo degrades to direct egress with no signal.
OnlyFans ties sessions to the IP that created them. See [Proxies](/docs/proxies).
Paste an auth token rather than cookies. `X-Proxy` is optional.
```bash
curl -X POST "$BASE/api/crm/$CRM/accounts/login/cookies" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"platform": "fansly",
"auth_token": "",
"fansly_session_id": "",
"fansly_client_id": ""
}'
```
The session is stored server-side. You never pass cookies or tokens again β from
here on, the `of_user_id` is enough.
## Option B β credentials [#option-b--credentials]
We run the whole login flow for you: Cloudflare init, Turnstile solve, signed
login request, and a 2FA prompt if the platform asks for one.
```bash
curl -X POST "$BASE/api/crm/$CRM/accounts/login" \
-H "X-API-Key: $KEY" \
-H "X-Proxy: http://user:pass@host:port" \
-H "Content-Type: application/json" \
-d '{
"platform": "onlyfans",
"email": "creator@example.com",
"password": "β¦",
"use_captcha": true
}'
```
For `platform: "fansly"` the `email` field accepts a **username or an email**,
`X-Proxy` is optional, and `use_captcha` is ignored β Fansly's login has no captcha
step.
### Two-factor authentication [#two-factor-authentication]
If the platform demands 2FA, the response includes `requires_2fa: true` (Fansly
additionally returns `twofa_type`). Submit the code to
`POST /accounts/login/verify-otp`, passing the **same** `platform`, the **same**
identifier you logged in with. You do **not** need to resend `X-Proxy` β the proxy
stored with the parked 2FA challenge is reused, and the header is ignored here:
```bash
curl -X POST "$BASE/api/crm/$CRM/accounts/login/verify-otp" \
-H "X-API-Key: $KEY" \
-H "X-Proxy: http://user:pass@host:port" \
-H "Content-Type: application/json" \
-d '{"platform": "onlyfans", "email": "creator@example.com", "otp_code": "123456"}'
```
This route reads `otp_code` only β sending `code` returns
`400 OTP code required`. (The unrelated
[bulk-import](/docs/bulk-import) OTP route accepts either spelling, which makes
the inconsistency easy to trip over.)
Each of the three connection routes gets its own allowance of **20 requests per minute**. They are counted separately, so in one minute you could paste 20 sessions
*and* attempt 20 credential logins *and* submit 20 OTP codes.
The count is per API key, so it is your panel's budget alone β other customers
cannot use it up.
Going over returns `429` until the minute rolls over. The practical consequence: do
not retry a failed login in a loop. Twenty quick retries and you have spent the
allowance and locked yourself out of connecting anything for the rest of that minute.
## Connecting many at once [#connecting-many-at-once]
For more than a handful of accounts, paste them all and let the importer work
through them β including parking rows that need a 2FA code until you supply one.
See [Bulk account import](/docs/bulk-import).
## After connecting [#after-connecting]
`GET /accounts` lists connected accounts and their IDs:
```bash
curl "$BASE/api/crm/$CRM/accounts" -H "X-API-Key: $KEY"
```
Add `?include_session=true` and each **OnlyFans** entry also carries a `session`
block with `sess`, `auth_id` and `proxy` β present only when a stored session file
exists; a read failure yields `session_error` instead. Fansly accounts never get one;
use `GET /accounts/{of_user_id}/fansly-credentials` for those. You rarely need either β
passing `of_user_id` and letting us resolve the session is the point.
## Disconnecting [#disconnecting]
```bash
curl -X DELETE "$BASE/api/crm/$CRM/accounts/$OFUID" -H "X-API-Key: $KEY"
```
This frees the slot. Add `?purge=true` to also delete the account's retained
data rather than keeping it.
## Write actions are off until you enable them [#write-actions-are-off-until-you-enable-them]
Connecting an account does not by itself allow us to *act* as it. Every write
performed as the account is gated by the `allow_of_write_actions` polling setting:
```bash
curl -X PATCH "$BASE/api/crm/$CRM/accounts/$OFUID/polling" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"allow_of_write_actions": true}'
```
Without it, writes return `403` with `code: "WRITES_DISABLED"`. Match on the code,
not the message.
The gate now covers **every OnlyFans or Fansly write performed as the account**:
single DM/PPV sends (both platforms), the mass-DM real send, the `send_dm`
automation action, payout requests, subscription-price changes, creating a campaign,
the raw `POST /accounts/{of_user_id}/request` proxy when its body `method` is not
`GET`, and **any non-GET request through the `/api2/v2/*` passthrough**. Reads are
never gated.
---
# CRM API (/docs/crm)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Endpoints on this surface return **normalized** data: OnlyFans and Fansly rows
are projected to the same shape, and several routes are server-side aggregations
with no platform equivalent at all.
Base URL `https://theonlyapi.com/api/crm/{crm_id}`, authenticated with an
`X-API-Key` header. Responses put data in a sibling key alongside `success`
rather than nesting it β see [Responses & errors](/docs/responses).
This is the surface to build on. Reach for the
[OnlyFans passthrough](/docs/of-api) only when you need something here does not
cover.
Connect a creator account (OnlyFans or Fansly) and complete 2FA. All three routes accept `platform: "onlyfans" | "fansly"` and are rate limited to 10 requests/minute.
Manage connected creator accounts (OnlyFans and Fansly), their polling settings, proxies, and slots.
Panel signup, health, keyβpanel resolution, and plan/usage counters. Works for OnlyFans and Fansly panels alike.
Primary + secondary API key management. `POST /api-keys` and `DELETE /api-keys/{key_id}` are PRIMARY-KEY ONLY (a secondary key gets 403). The primary `Default` key cannot be revoked β rotate it via `POST /rotate-key`.
Revenue & earnings data, including the cross-account `GET /earnings/summary` aggregation. Fansly-aware.
Payout & purchase transactions, live and cached.
Withdrawal requests and payout account details. Creating a withdrawal is OnlyFans only.
OnlyFans referral programme β who the account referred, what it earned, and referral payout history. **OnlyFans only**: Fansly accounts get `501 platform_not_supported` (`feature: "referrals"`). Live reads, no cache; one quota call per request. OnlyFans' referral response bodies are passed through with minimal normalization β only the `list`/`hasMore`/`marker` envelope keys are read, and the raw body is echoed under `data`. Fields *inside* a referral row are unverified: do not depend on them without checking against a live account.
Subscriber reads. Live routes hit the platform; `/subscribers/cached`, `/subscribers/new`, and `/subscribers/stats` cost zero platform requests and normalize OnlyFans + Fansly rows to the same shape.
Fan CRM across all accounts in the panel β tags, notes, per-fan cached transactions, profile refresh. Fansly-aware.
Chats, single DMs/PPVs, mass DMs, and PPV performance stats. Fansly-aware.
Media upload. Runs OnlyFans' four-stage signed-S3 pipeline server-side and returns a reference you attach to a post, message or story via `mediaFiles`. OnlyFans only.
Tracking link campaigns, claimers, campaign tags, and the tracked-campaign allowlist.
Account notifications. Fansly-aware.
Async refresh, backfill, and job-status routes that populate the local cache. Refresh routes return 202 β poll the matching `/refresh/status` route or listen on `GET /events/stream`.
Real-time event feed β `GET /events` for polling, `GET /events/stream` for Server-Sent Events (rate-limit exempt).
Outbound webhook subscriptions, test fires, and delivery history.
Event-triggered automations, manual runs, and run history.
Async data export jobs producing a downloadable ZIP. Progress streams over `GET /events/stream`.
Third-party integrations. One Telegram channel per panel: pair it once, then matching events are delivered to that chat. The bot token is encrypted at rest and is never returned by any route.
Account settings, subscription price & proxy management
Generic authenticated OnlyFans API proxy (`POST /accounts/{of_user_id}/request`). OnlyFans only.
Hosted MCP server support. `GET /api/whoami` turns a bearer token into a tenant; `PATCH /mcp/unsafe-proxy` toggles non-GET tool access (off by default).
Connect many creator accounts from one pasted list. `POST /import/preview` validates without side effects; `POST /import/jobs` runs the import in the background and streams `import.progress` / `import.complete` over `GET /events/stream`. Rows that hit a 2FA prompt park as `needs_2fa` until you supply a code.
*116 endpoints, generated from the [OpenAPI 3.1 spec](https://theonlyapi.com/api/openapi.json).*
---
# Accounts (/docs/crm/accounts)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Manage connected creator accounts (OnlyFans and Fansly), their polling settings, proxies, and slots.
| Method | Endpoint | Path |
| -------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| `GET` | [List connected accounts](/docs/crm/accounts/list-connected-accounts) | `/accounts` |
| `DELETE` | [Disconnect an account](/docs/crm/accounts/disconnect-an-account) | `/accounts/{of_user_id}` |
| `GET` | [Read a Fansly account's stored session](/docs/crm/accounts/read-a-fansly-account-s-stored-session) | `/accounts/{of_user_id}/fansly-credentials` |
| `GET` | [Get polling settings](/docs/crm/accounts/get-polling-settings) | `/accounts/{of_user_id}/polling` |
| `PATCH` | [Update polling settings](/docs/crm/accounts/update-polling-settings) | `/accounts/{of_user_id}/polling` |
| `POST` | [Tag a connected account](/docs/crm/accounts/tag-a-connected-account) | `/accounts/{of_user_id}/tags` |
| `DELETE` | [Remove an account tag](/docs/crm/accounts/remove-an-account-tag) | `/accounts/{of_user_id}/tags/{tag}` |
---
# Disconnect an account (/docs/crm/accounts/disconnect-an-account)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}`
Delete/disconnect a creator account from this CRM panel, freeing its slot. Works for OnlyFans and Fansly accounts. This is the only way to release a paid slot.
**Guide:** [Connect an account](/docs/connect-an-account)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Responses
### 200 β Account disconnected and its slot released
- `success` (boolean) β e.g. `true`.
### 404 β Account not found in this panel
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}" \
-H "X-API-Key: "
```
---
# Get polling settings (/docs/crm/accounts/get-polling-settings)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/polling`
Read the background polling configuration for an account β whether polling is enabled, the interval, and whether write actions are allowed.
**Guide:** [Connect an account](/docs/connect-an-account)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Responses
### 200 β Polling settings
- `success` (boolean)
- `polling` (object)
- `enabled` (boolean)
- `interval_seconds` (integer)
- `allow_of_write_actions` (boolean)
### 404 β Account not found
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/polling" \
-H "X-API-Key: "
```
---
# List connected accounts (/docs/crm/accounts/list-connected-accounts)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts`
Returns every creator account connected to this CRM panel β OnlyFans and Fansly. Each entry carries `of_user_id`, `username`, and `platform` (`"onlyfans"` or `"fansly"`). Use `of_user_id` as the `{of_user_id}` path parameter and as the `user-id` header elsewhere in this API.
**Guide:** [Connect an account](/docs/connect-an-account)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `include_session` (boolean) β Include the per-account session block (`sess`, `auth_id`, `proxy`) in each entry. Default `false`.
## Responses
### 200 β Account list
- `success` (boolean)
- `count` (integer)
- `accounts` (object[])
- `of_user_id` (string)
- `username` (string)
- `platform` ("onlyfans" | "fansly")
- `session` (object) β Only present when `include_session=true`.
- `sess` (string)
- `auth_id` (string)
- `proxy` (string)
Example response:
```json
{
"success": true,
"count": 2,
"accounts": [
{
"of_user_id": "482687148",
"username": "creator_one",
"platform": "onlyfans"
},
{
"of_user_id": "739104882",
"username": "creator_two",
"platform": "fansly"
}
]
}
```
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts" \
-H "X-API-Key: "
```
---
# Read a Fansly account's stored session (/docs/crm/accounts/read-a-fansly-account-s-stored-session)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/fansly-credentials`
Return the **current** Fansly session for one account you own, so an external worker can pick up refreshed tokens instead of holding a stale copy. We stay the credential authority: we are the thing that re-logs in and rewrites these tokens, so a caller that re-reads here each run is always current.
Read-only β it mutates nothing, and is gated by the same API key and ownership checks as every other account route. **Fansly only**; an OnlyFans account returns `400`.
**Guide:** [Connect an account](/docs/connect-an-account)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Responses
### 200
Current session material: `auth_token`, `session_id`, `client_id`, and the account's `proxy`.
- `auth_token` (any, required)
- `client_id` (any, required)
- `of_user_id` (string, required)
- `platform` (string, required)
- `proxy` (string, required)
- `session_id` (any, required)
- `success` (boolean, required)
- `username` (string, required)
### 400 β Not a Fansly account.
### 403 β Account does not belong to this panel.
### 404
Account not found, or no Fansly session is stored yet β connect it first.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/fansly-credentials" \
-H "X-API-Key: "
```
---
# Remove an account tag (/docs/crm/accounts/remove-an-account-tag)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/tags/{tag}`
Remove a tag from a connected account. Pass the tag exactly as the API returned it β the stored form is escaped once on the way in, so re-escaping it here would look for a different string and silently delete nothing. `success: false` means there was no such tag.
**Guide:** [Connect an account](/docs/connect-an-account)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `tag` (string, required) β The tag, exactly as returned by the API.
## Responses
### 200 β `success` reports whether a tag was actually removed.
- `success` (boolean, required)
### 400 β Missing tag, or longer than 40 characters.
### 403 β Account does not belong to this panel.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/tags/{tag}" \
-H "X-API-Key: "
```
---
# Tag a connected account (/docs/crm/accounts/tag-a-connected-account)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/tags`
Add a tag to a connected account, for grouping creators inside the panel. Tags are panel-local β they are never sent to the platform. Max 40 characters; re-adding an existing tag is a no-op.
**Guide:** [Connect an account](/docs/connect-an-account)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Request body
Content type: `application/json` (required)
- `tag` (string, required) β e.g. `"vip"`.
## Responses
### 200 β The stored tag
Send this exact string back when deleting it.
- `success` (boolean, required)
- `tag` (any, required)
### 400 β Missing tag, or longer than 40 characters.
### 403 β Account does not belong to this panel.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/tags" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"tag":"vip"}'
```
---
# Update polling settings (/docs/crm/accounts/update-polling-settings)
`PATCH https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/polling`
Enable/disable background polling for an account, set the interval, and control whether automations may perform write actions on the platform.
**Guide:** [Connect an account](/docs/connect-an-account)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Request body
Content type: `application/json` (required)
- `enabled` (boolean)
- `interval_seconds` (integer) β Polling interval in seconds (60β3600).
- `allow_of_write_actions` (boolean) β Allow automations to send DMs and other writes for this account.
## Responses
### 200 β Polling settings updated
- `polling` (object, required)
- `allow_of_write_actions` (integer)
- `last_polled_at` (string)
- `of_user_id` (string)
- `polling_enabled` (integer)
- `polling_failure_count` (integer)
- `polling_interval_seconds` (integer)
- `success` (boolean, required)
- `warning` (string)
### 400 β interval_seconds out of range (60β3600)
## Example
```bash
curl -X PATCH "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/polling" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{}'
```
---
# API Keys (/docs/crm/api-keys)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Primary + secondary API key management. `POST /api-keys` and `DELETE /api-keys/{key_id}` are PRIMARY-KEY ONLY (a secondary key gets 403). The primary `Default` key cannot be revoked β rotate it via `POST /rotate-key`.
| Method | Endpoint | Path |
| -------- | --------------------------------------------------------------------------- | -------------------------- |
| `GET` | [List API keys](/docs/crm/api-keys/list-api-keys) | `/api-keys` |
| `POST` | [Create a secondary API key](/docs/crm/api-keys/create-a-secondary-api-key) | `/api-keys` |
| `DELETE` | [Revoke a secondary API key](/docs/crm/api-keys/revoke-a-secondary-api-key) | `/api-keys/{key_id}` |
| `GET` | [Per-key usage detail](/docs/crm/api-keys/per-key-usage-detail) | `/api-keys/{key_id}/usage` |
| `POST` | [Rotate your API key](/docs/crm/api-keys/rotate-your-api-key) | `/rotate-key` |
---
# Create a secondary API key (/docs/crm/api-keys/create-a-secondary-api-key)
`POST https://theonlyapi.com/api/crm/your-crm-id/api-keys`
Mint a new secondary API key. **PRIMARY-KEY ONLY** β calling this with a secondary key returns `403`. The full key is returned **exactly once**, in this response, and is never retrievable again.
**Guide:** [Get an API key](/docs/get-an-api-key)
## Authentication
- `X-API-Key` header (apiKey)
## Request body
Content type: `application/json`
- `name` (string) β Default `"Untitled key"`. e.g. `"CI pipeline"`.
## Responses
### 201 β Key created β `api_key` is shown only here
- `success` (boolean)
- `id` (integer)
- `name` (string)
- `prefix` (string)
- `api_key` (string)
- `created_at` (string)
### 403 β Not the primary key
### 404 β CRM panel not found
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api-keys" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"name":"CI pipeline"}'
```
---
# List API keys (/docs/crm/api-keys/list-api-keys)
`GET https://theonlyapi.com/api/crm/your-crm-id/api-keys`
List all API keys for the panel (active + revoked), each with this-month request count and a 30-day series. Full key bodies are never returned β only a prefix.
**Guide:** [Get an API key](/docs/get-an-api-key)
## Authentication
- `X-API-Key` header (apiKey)
## Responses
### 200 β Key list
- `success` (boolean)
- `keys` (object[])
- `id` (integer)
- `name` (string)
- `prefix` (string)
- `is_primary` (boolean)
- `created_at` (string)
- `last_used_at` (string)
- `revoked_at` (string)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api-keys" \
-H "X-API-Key: "
```
---
# Per-key usage detail (/docs/crm/api-keys/per-key-usage-detail)
`GET https://theonlyapi.com/api/crm/your-crm-id/api-keys/{key_id}/usage`
30/90-day usage series, month and all-time totals, last-used timestamp, and this month's endpoint breakdown for one key.
**Guide:** [Get an API key](/docs/get-an-api-key)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `key_id` (integer, required)
## Query parameters
- `days` (integer) β Default `30`.
## Responses
### 200 β Usage detail
- `success` (boolean)
- `key` (object)
- `totals` (object)
- `series` (object[])
- `breakdown` (object[])
### 404 β Key not found
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api-keys/{key_id}/usage" \
-H "X-API-Key: "
```
---
# Revoke a secondary API key (/docs/crm/api-keys/revoke-a-secondary-api-key)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api-keys/{key_id}`
Revoke a secondary key. **PRIMARY-KEY ONLY** β calling this with a secondary key returns `403`. The primary `Default` key is non-revocable; rotate it via `POST /rotate-key` instead.
**Guide:** [Get an API key](/docs/get-an-api-key)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `key_id` (integer, required)
## Responses
### 200 β Key revoked
- `success` (boolean, required)
### 403 β Not the primary key
### 404 β Key not found, already revoked, or non-revocable (primary)
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api-keys/{key_id}" \
-H "X-API-Key: "
```
---
# Rotate your API key (/docs/crm/api-keys/rotate-your-api-key)
`POST https://theonlyapi.com/api/crm/your-crm-id/rotate-key`
Rotate (regenerate) the caller's own API key. Authenticate with your **current** key. The old key dies immediately and the new key is returned once in the response body β store it before discarding the response.
**Guide:** [Get an API key](/docs/get-an-api-key)
## Authentication
- `X-API-Key` header (apiKey)
## Responses
### 200 β Key rotated
- `success` (boolean)
- `api_key` (string) β The new key. Returned only here.
### 404 β CRM panel not found
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/rotate-key" \
-H "X-API-Key: "
```
---
# Auth (/docs/crm/auth)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Connect a creator account (OnlyFans or Fansly) and complete 2FA. All three routes accept `platform: "onlyfans" | "fansly"` and are rate limited to 10 requests/minute.
| Method | Endpoint | Path |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| `POST` | [Connect an account with credentials (OnlyFans or Fansly)](/docs/crm/auth/connect-an-account-with-credentials-onlyfans-or-fansly) | `/accounts/login` |
| `POST` | [Connect an account with an existing session (OnlyFans or Fansly)](/docs/crm/auth/connect-an-account-with-an-existing-session-onlyfans-or-fansly) | `/accounts/login/cookies` |
| `POST` | [Verify 2FA OTP code (OnlyFans or Fansly)](/docs/crm/auth/verify-2fa-otp-code-onlyfans-or-fansly) | `/accounts/login/verify-otp` |
---
# Connect an account with an existing session (OnlyFans or Fansly) (/docs/crm/auth/connect-an-account-with-an-existing-session-onlyfans-or-fansly)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/login/cookies`
Connect a creator account using credentials you already hold, rather than a password login.
- **OnlyFans** β session cookie paste. Body: `{ sess, auth_id, fp? }`. `sess` and `auth_id` are **required for OnlyFans only**. **`X-Proxy` is REQUIRED.**
- **Fansly** β auth-token paste. Body: `{ platform: "fansly", auth_token, fansly_session_id, fansly_client_id? }`. `auth_token` and `fansly_session_id` are **required for Fansly**. `X-Proxy` is optional.
Where to find the OnlyFans cookies: open onlyfans.com while logged in β **DevTools β Application β Cookies β onlyfans.com** and copy `sess` and `auth_id`.
Consumes one account slot. Rate limited to 10 requests/minute.
**Guide:** [Connect an account](/docs/connect-an-account)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `platform` ("onlyfans" | "fansly") β Which platform to connect. Defaults to `onlyfans` when omitted. Default `"onlyfans"`.
- `sess` (string) β The `sess` cookie value. **Required for OnlyFans**, unused for Fansly.
- `auth_id` (string) β The `auth_id` cookie value (OnlyFans user ID). **Required for OnlyFans**, unused for Fansly.
- `fp` (string) β The `fp` fingerprint cookie (OnlyFans, optional β used as `x-bc`).
- `auth_token` (string) β Fansly bearer/auth token. **Required for Fansly**, unused for OnlyFans.
- `fansly_session_id` (string) β Fansly session ID. **Required for Fansly**, unused for OnlyFans.
- `fansly_client_id` (string) β Fansly device/client ID (optional, Fansly only).
## Responses
### 200 β Account connected
- `success` (boolean)
- `of_user_id` (string)
- `username` (string)
- `platform` ("onlyfans" | "fansly")
- `login_method` (string) β e.g. `"cookies"`.
### 401 β Invalid or expired session credentials
## Example
**OnlyFans β cookie paste (X-Proxy required)**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/login/cookies" \
-H "X-API-Key: " \
-H "X-Proxy: http://user:pass@host:port" \
-H "Content-Type: application/json" \
-d '{"platform":"onlyfans","sess":"","auth_id":"482687148","fp":""}'
```
**Fansly β auth-token paste (X-Proxy optional)**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/login/cookies" \
-H "X-API-Key: " \
-H "X-Proxy: http://user:pass@host:port" \
-H "Content-Type: application/json" \
-d '{"platform":"fansly","auth_token":"","fansly_session_id":"","fansly_client_id":""}'
```
---
# Connect an account with credentials (OnlyFans or Fansly) (/docs/crm/auth/connect-an-account-with-credentials-onlyfans-or-fansly)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/login`
Connect a new creator account via email/username + password. Set `platform` to `"onlyfans"` (default) or `"fansly"`.
- **X-Proxy header is REQUIRED for OnlyFans**, optional for Fansly. You may pass `proxy` in the body as an alternative.
- For `platform: "fansly"` the `email` field accepts a **username or an email**.
- Supports 2FA: if the account has 2FA enabled the response includes `requires_2fa: true` (Fansly also returns `twofa_type`), and you must call `POST /accounts/login/verify-otp` next with the same `platform` and identifier.
Consumes one account slot. Rate limited to 10 requests/minute.
**Guide:** [Connect an account](/docs/connect-an-account)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `platform` ("onlyfans" | "fansly") β Which platform to connect. Defaults to `onlyfans` when omitted. Default `"onlyfans"`.
- `email` (string, required) β Account identifier. An email for OnlyFans; a **username or an email** for Fansly. e.g. `"user@example.com"`.
- `password` (string, required) β e.g. `"your_password"`.
- `use_captcha` (boolean) β Use captcha solver for login. Default `true`.
- `proxy` (string) β Proxy for this account β alternative to the `X-Proxy` header. Required for OnlyFans (via header or here), optional for Fansly. e.g. `"http://user:pass@host:port"`.
## Responses
### 200 β Login successful or 2FA required
- `about` (string)
- `avatar` (string)
- `email` (string, required)
- `expires_in_seconds` (integer)
- `of_user_id` (string)
- `otp_methods` (any[])
- `otp_state` (string)
- `platform` (string)
- `proxy` (string)
- `requires_2fa` (boolean)
- `success` (boolean, required)
- `twofa_type` (string)
- `username` (string)
- `x_bc` (string)
Successful login:
```json
{
"success": true,
"of_user_id": "123456789",
"username": "example_user",
"email": "user@example.com",
"avatar": "https://..."
}
```
2FA required:
```json
{
"success": false,
"requires_2fa": true,
"otp_state": "...",
"email": "user@example.com"
}
```
### 500 β Login failed
## Example
**OnlyFans (X-Proxy header required)**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/login" \
-H "X-API-Key: " \
-H "X-Proxy: http://user:pass@host:port" \
-H "Content-Type: application/json" \
-d '{"platform":"onlyfans","email":"user@example.com","password":"your_password","use_captcha":true}'
```
**Fansly (username accepted, proxy optional)**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/login" \
-H "X-API-Key: " \
-H "X-Proxy: http://user:pass@host:port" \
-H "Content-Type: application/json" \
-d '{"platform":"fansly","email":"creator_username","password":"your_password"}'
```
---
# Verify 2FA OTP code (OnlyFans or Fansly) (/docs/crm/auth/verify-2fa-otp-code-onlyfans-or-fansly)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/login/verify-otp`
Complete a 2FA login started by `POST /accounts/login`.
- Pass the same `platform` and the same identifier you logged in with. For `platform: "fansly"` the `email` field may be a **username**.
- The Fansly 2FA challenge returned by `POST /accounts/login` carries a `twofa_type` field indicating the delivery channel.
- **The `X-Proxy` header is required.**
Rate limited to 10 requests/minute.
**Guide:** [Connect an account](/docs/connect-an-account)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `X-Proxy` (string, required) β Proxy URL β **required** on this route. Use the same proxy the login attempt was made through.
## Request body
Content type: `application/json` (required)
- `platform` ("onlyfans" | "fansly") β Must match the `platform` used on `POST /accounts/login`. Default `"onlyfans"`.
- `email` (string, required) β The same identifier used on `POST /accounts/login`. An email for OnlyFans; a **username or an email** for Fansly. e.g. `"user@example.com"`.
- `otp_code` (string, required) β 6-digit OTP code. e.g. `"123456"`.
## Responses
### 200 β OTP verified, account connected
- `about` (string, required)
- `avatar` (string, required)
- `email` (string, required)
- `of_user_id` (string, required)
- `proxy` (string, required)
- `success` (boolean, required)
- `username` (string, required)
- `x_bc` (string, required)
### 404 β 2FA session not found or expired β start the login again
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/login/verify-otp" \
-H "X-API-Key: " \
-H "X-Proxy: http://user:pass@host:port" \
-H "Content-Type: application/json" \
-d '{"platform":"onlyfans","email":"user@example.com","otp_code":"123456"}'
```
---
# Automations (/docs/crm/automations)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Event-triggered automations, manual runs, and run history.
| Method | Endpoint | Path |
| -------- | ------------------------------------------------------------------------------------ | -------------------------------------- |
| `GET` | [List automations](/docs/crm/automations/list-automations) | `/automations` |
| `POST` | [Create an automation](/docs/crm/automations/create-an-automation) | `/automations` |
| `GET` | [Get an automation](/docs/crm/automations/get-an-automation) | `/automations/{automation_id}` |
| `PATCH` | [Update an automation](/docs/crm/automations/update-an-automation) | `/automations/{automation_id}` |
| `DELETE` | [Delete an automation](/docs/crm/automations/delete-an-automation) | `/automations/{automation_id}` |
| `POST` | [Run an automation immediately](/docs/crm/automations/run-an-automation-immediately) | `/automations/{automation_id}/run-now` |
| `GET` | [List automation runs](/docs/crm/automations/list-automation-runs) | `/automations/{automation_id}/runs` |
---
# Create an automation (/docs/crm/automations/create-an-automation)
`POST https://theonlyapi.com/api/crm/your-crm-id/automations`
Run an action whenever a panel event fires. `trigger_event` must be a known event type (not `*`) and `action_type` must be a supported action. Rate limited to 120 requests/minute.
**Guide:** [Automations](/docs/automations)
## Authentication
- `X-API-Key` header (apiKey)
## Request body
Content type: `application/json` (required)
- `name` (string, required) β e.g. `"Welcome DM"`.
- `trigger_event` (string, required) β A known event type. `*` is not allowed. e.g. `"new_subscriber"`.
- `action_type` (string, required) β A supported action type. e.g. `"send_dm"`.
- `conditions` (object[]) β Default `[]`.
- `action_params` (object) β Default `{}`.
- `of_user_id` (string) β Restrict to one connected account. Omit to apply panel-wide.
## Responses
### 200 β Automation created
- `automation` (any, required)
- `success` (boolean, required)
### 400 β Invalid trigger_event or action_type
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/automations" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"name":"Welcome DM","trigger_event":"new_subscriber","action_type":"send_dm","conditions":[],"action_params":{}}'
```
---
# Delete an automation (/docs/crm/automations/delete-an-automation)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/automations/{automation_id}`
**Guide:** [Automations](/docs/automations)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `automation_id` (integer, required)
## Responses
### 200 β Automation deleted
- `success` (boolean, required)
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/automations/{automation_id}" \
-H "X-API-Key: "
```
---
# Get an automation (/docs/crm/automations/get-an-automation)
`GET https://theonlyapi.com/api/crm/your-crm-id/automations/{automation_id}`
**Guide:** [Automations](/docs/automations)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `automation_id` (integer, required)
## Responses
### 200 β Automation
- `automation` (any, required)
- `success` (boolean, required)
### 404 β Not found
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/automations/{automation_id}" \
-H "X-API-Key: "
```
---
# List automation runs (/docs/crm/automations/list-automation-runs)
`GET https://theonlyapi.com/api/crm/your-crm-id/automations/{automation_id}/runs`
Execution history for one automation.
**Guide:** [Automations](/docs/automations)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `automation_id` (integer, required)
## Query parameters
- `limit` (integer) β Maximum number of rows to return. Default `100`.
## Responses
### 200 β Run history
- `runs` (object[], required)
- `automation_id` (integer)
- `created_at` (string)
- `error_snippet` (string)
- `event_id` (integer)
- `id` (integer)
- `status` (string)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/automations/{automation_id}/runs" \
-H "X-API-Key: "
```
---
# List automations (/docs/crm/automations/list-automations)
`GET https://theonlyapi.com/api/crm/your-crm-id/automations`
**Guide:** [Automations](/docs/automations)
## Authentication
- `X-API-Key` header (apiKey)
## Responses
### 200 β Automation list
- `automations` (object[], required)
- `action_params` (object)
- `action_type` (string)
- `conditions` (any[])
- `created_at` (string)
- `crm_id` (string)
- `id` (integer)
- `is_active` (integer)
- `last_run_at` (string)
- `name` (string)
- `of_user_id` (string)
- `run_count` (integer)
- `trigger_event` (string)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/automations" \
-H "X-API-Key: "
```
---
# Run an automation immediately (/docs/crm/automations/run-an-automation-immediately)
`POST https://theonlyapi.com/api/crm/your-crm-id/automations/{automation_id}/run-now`
**Guide:** [Automations](/docs/automations)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `automation_id` (integer, required)
## Responses
### 200 β Run triggered
- `result` (any, required)
- `success` (boolean, required)
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/automations/{automation_id}/run-now" \
-H "X-API-Key: "
```
---
# Update an automation (/docs/crm/automations/update-an-automation)
`PATCH https://theonlyapi.com/api/crm/your-crm-id/automations/{automation_id}`
**Guide:** [Automations](/docs/automations)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `automation_id` (integer, required)
## Request body
Content type: `application/json` (required)
- `name` (string)
- `enabled` (boolean)
- `conditions` (object[])
- `action_params` (object)
## Responses
### 200 β Automation updated
- `automation` (any, required)
- `success` (boolean, required)
## Example
```bash
curl -X PATCH "https://theonlyapi.com/api/crm/your-crm-id/automations/{automation_id}" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Bulk Import (/docs/crm/bulk-import)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Connect many creator accounts from one pasted list. `POST /import/preview` validates without side effects; `POST /import/jobs` runs the import in the background and streams `import.progress` / `import.complete` over `GET /events/stream`. Rows that hit a 2FA prompt park as `needs_2fa` until you supply a code.
| Method | Endpoint | Path |
| ------ | ------------------------------------------------------------------------------------------------ | ------------------------------------------- |
| `GET` | [List import jobs](/docs/crm/bulk-import/list-import-jobs) | `/import/jobs` |
| `POST` | [Start a bulk import](/docs/crm/bulk-import/start-a-bulk-import) | `/import/jobs` |
| `GET` | [Get an import job](/docs/crm/bulk-import/get-an-import-job) | `/import/jobs/{job_id}` |
| `POST` | [Cancel an import job](/docs/crm/bulk-import/cancel-an-import-job) | `/import/jobs/{job_id}/cancel` |
| `POST` | [Supply a 2FA code for an import row](/docs/crm/bulk-import/supply-a-2fa-code-for-an-import-row) | `/import/jobs/{job_id}/rows/{row_id}/otp` |
| `POST` | [Retry an import row](/docs/crm/bulk-import/retry-an-import-row) | `/import/jobs/{job_id}/rows/{row_id}/retry` |
| `GET` | [List rows waiting on a 2FA code](/docs/crm/bulk-import/list-rows-waiting-on-a-2fa-code) | `/import/pending-2fa` |
| `POST` | [Preview an import paste](/docs/crm/bulk-import/preview-an-import-paste) | `/import/preview` |
---
# Cancel an import job (/docs/crm/bulk-import/cancel-an-import-job)
`POST https://theonlyapi.com/api/crm/your-crm-id/import/jobs/{job_id}/cancel`
Stop an import. Cancellation is cooperative: `pending` rows are canceled at once and nothing further is claimed, but a row already mid-login runs to completion β we cannot un-send a login attempt β and records its real outcome. Expect a few more successes after cancelling.
Only a `queued` or `running` job can be canceled.
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `job_id` (string, required) β Import job ID (32-character hex) returned by `POST /import/jobs`.
## Responses
### 200
Canceled. `canceled_rows` is how many pending rows were dropped.
- `canceled_rows` (any, required)
- `counts` (any, required)
- `job` (any, required)
- `success` (boolean, required)
### 404 β No such job in this panel.
### 409
Job is already finished β the body reports its actual status.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/import/jobs/{job_id}/cancel" \
-H "X-API-Key: "
```
---
# Get an import job (/docs/crm/bulk-import/get-an-import-job)
`GET https://theonlyapi.com/api/crm/your-crm-id/import/jobs/{job_id}`
One job with its rows and counts. This is the fallback whenever an SSE event is missed β the stored row is authoritative, the events are only a live nudge.
`counts` is zero-filled across every state, so a missing key never has to be read as "unknown". `row_states` echoes the full state vocabulary: `pending`, `running`, `success`, `needs_2fa`, `needs_2fa_expired`, `failed`, `canceled`, `invalid`, `skipped`, `slot_exhausted`.
Rows never carry credentials β each has `has_password` / `has_totp_secret` flags instead, and a row parked on 2FA also reports `two_fa_remaining_seconds`.
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `job_id` (string, required) β Import job ID (32-character hex) returned by `POST /import/jobs`.
## Query parameters
- `limit` (integer) β Rows per page (1β1000). Default `1000`.
- `offset` (integer) β Row offset for pagination. Default `0`.
- `status` ("pending" | "running" | "success" | "needs_2fa" | "needs_2fa_expired" | "failed" | "canceled" | "invalid" | "skipped" | "slot_exhausted") β Return only rows in this state. An unrecognised value is a `400`, not an empty page.
## Responses
### 200 β Job, counts, and one page of rows.
- `counts` (any, required)
- `job` (any, required)
- `row_states` (any[], required)
- `rows` (any[], required)
- `success` (boolean, required)
- `total` (integer, required)
### 400 β Unknown `status` filter.
### 404 β No such job in this panel.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/import/jobs/{job_id}" \
-H "X-API-Key: "
```
---
# List import jobs (/docs/crm/bulk-import/list-import-jobs)
`GET https://theonlyapi.com/api/crm/your-crm-id/import/jobs`
Import history for the panel, newest first, each job carrying its per-status row counts.
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `limit` (integer) β Jobs per page (1β200). Default `50`.
- `offset` (integer) β Row offset for pagination. Default `0`.
## Responses
### 200 β Job history with `total` for pagination.
- `jobs` (any[], required)
- `success` (boolean, required)
- `total` (integer, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/import/jobs" \
-H "X-API-Key: "
```
---
# List rows waiting on a 2FA code (/docs/crm/bulk-import/list-rows-waiting-on-a-2fa-code)
`GET https://theonlyapi.com/api/crm/your-crm-id/import/pending-2fa`
Every parked 2FA row on the panel, across **all** jobs β `needs_2fa` first, soonest to expire first.
This is deliberately panel-wide rather than per-job: an operator closes the importer and the dashboard still has to be able to say "9 accounts need a 2FA code". `count` is how many are still answerable, `expired_count` how many need a retry instead, and `expiry_seconds` is the window length each one gets.
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `limit` (integer) β Maximum rows to return (1β1000). Default `200`.
## Responses
### 200 β Parked rows across every job in the panel.
- `count` (integer, required)
- `expired_count` (integer, required)
- `expiry_seconds` (any, required)
- `rows` (any[], required)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/import/pending-2fa" \
-H "X-API-Key: "
```
---
# Preview an import paste (/docs/crm/bulk-import/preview-an-import-paste)
`POST https://theonlyapi.com/api/crm/your-crm-id/import/preview`
Parse and validate a pasted account list **without side effects** β nothing is written, no login is attempted, and no slot is consumed. Use it to show which rows are valid, which lane each one will take (`cookie` or `password`), and exactly why a bad row is bad, before committing to an import.
Secrets are never echoed back: passwords and TOTP secrets are reduced to `has_password` / `has_totp_secret`, cookies to a `cookie_fields` name list, and a proxy URL to `host:port` with its credentials stripped.
Accepted formats are auto-detected β CSV, TSV, colon- or pipe-separated, with or without a header row. Limits are 1,000 rows and 2 MiB per paste; exceeding either is a rejection, never a silent truncation.
## Authentication
- `X-API-Key` header (apiKey)
## Request body
Content type: `application/json` (required)
- `text` (string, required) β The pasted account list, one account per line. e.g. `"email,password,proxy\ncreator1@example.com,pw1,http://user:pass@host:1080\ncreator2@example.com,pw2,"`.
- `default_platform` ("onlyfans" | "fansly") β Platform for rows that do not name one themselves. Also accepted as `platform`. Default `"onlyfans"`.
## Responses
### 200 β Parse result
`rows[]` is redacted; `lanes` and `platforms` count the valid rows by lane and platform.
- `columns` (any[], required)
- `delimiter` (string, required)
- `format` (string, required)
- `has_header` (boolean, required)
- `invalid_count` (integer, required)
- `lanes` (any, required)
- `limits` (object, required)
- `platforms` (any, required)
- `rows` (any[])
- `success` (boolean, required)
- `total` (integer, required)
- `valid_count` (integer, required)
### 400
`text` missing, `default_platform` not one of `onlyfans`/`fansly`, or the paste could not be parsed (`code: "IMPORT_PARSE"`).
### 413 β Paste exceeds `max_rows` (1,000) or `max_bytes` (2 MiB)
Returns `code: "IMPORT_LIMIT"` with a `limits` object. Nothing is imported β split the paste and retry.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/import/preview" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"text":"email,password,proxy\ncreator1@example.com,pw1,http://user:pass@host:1080\ncreator2@example.com,pw2,","default_platform":"onlyfans"}'
```
---
# Retry an import row (/docs/crm/bulk-import/retry-an-import-row)
`POST https://theonlyapi.com/api/crm/your-crm-id/import/jobs/{job_id}/rows/{row_id}/retry`
Re-queue one row and restart the job's worker. A `needs_2fa_expired` row re-runs the login **from scratch** β the parked challenge is dead, so replaying it would only earn another rejection.
A row cannot be retried once its stored credential has been destroyed (which happens when a password-lane row reaches a terminal state or ages out after 24 hours); re-import it instead.
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `job_id` (string, required) β Import job ID (32-character hex) returned by `POST /import/jobs`.
- `row_id` (integer, required) β Row ID within the import job β the `id` field of a row from `GET /import/jobs/{job_id}`, not its `row_index`.
## Responses
### 202 β Row re-queued; the job is running again.
- `success` (boolean, required)
- `row` (object, required)
- `id` (integer) β Row ID β this is the `{row_id}` for the OTP and retry routes.
- `job_id` (string)
- `row_index` (integer) β Zero-based position within the paste.
- `source_line` (integer) β Line number in the original paste, for error display.
- `lane` ("cookie" | "password") β How this row connects: pasted session (`cookie`) or credentials (`password`).
- `platform` ("onlyfans" | "fansly")
- `email` (string)
- `label` (string) β Optional per-row label from the paste.
- `proxy` (string)
- `status` ("pending" | "running" | "success" | "needs_2fa" | "needs_2fa_expired" | "failed" | "canceled" | "invalid" | "skipped" | "slot_exhausted")
- `has_password` (boolean)
- `has_totp_secret` (boolean)
- `two_fa_expires_at` (string) β When a parked 2FA challenge stops being answerable.
- `two_fa_remaining_seconds` (integer) β Seconds left on the 2FA window; `null` unless the row is parked.
- `of_user_id` (string) β Set once the account connects.
- `username` (string)
- `error` (string)
- `error_reason` (string) β Machine-readable failure cause; match on this, not `error`.
- `permanent` (boolean) β True when retrying cannot help.
- `attempts` (integer)
- `started_at` (string)
- `finished_at` (string)
- `created_at` (string)
- `updated_at` (string)
### 404 β No such row in this job.
### 409
Not retryable: already queued or running, already connected, never parsed (`invalid`), or its credential is gone.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/import/jobs/{job_id}/rows/{row_id}/retry" \
-H "X-API-Key: "
```
---
# Start a bulk import (/docs/crm/bulk-import/start-a-bulk-import)
`POST https://theonlyapi.com/api/crm/your-crm-id/import/jobs`
Create an import job and start working through the rows in the background. Returns `202` immediately with the job and its per-status counts.
Progress streams over [`GET /events/stream`](#tag/events-and-streaming) as `import.progress` (coalesced to at most one per second) and finishes with `import.complete`. The job row is the source of truth β if you miss an event, `GET /import/jobs/{job_id}` still has the current state.
Rows that hit a 2FA prompt do not fail: they park as `needs_2fa` holding the challenge, and wait for a code via `POST /import/jobs/{job_id}/rows/{row_id}/otp`. Parked rows expire after 10 minutes (retry them for a fresh login) and their stored credential is destroyed after 24 hours regardless.
Invalid rows are reported but never attempted. If *no* row is valid the request fails with `400` and the per-row reasons rather than creating a job that is 100% failures.
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `X-User-Email` (string) β Optional label recorded as the job's `requested_by`, for panels where several operators share a key.
## Request body
Content type: `application/json` (required)
- `text` (string, required) β The pasted account list, one account per line.
- `default_platform` ("onlyfans" | "fansly") β Platform for rows that do not name one themselves. Also accepted as `platform`. Default `"onlyfans"`.
- `source` (string) β Free-text label for where the list came from, stored on the job. Default `"paste"`.
## Responses
### 202 β Job created and running
`warning` is present only when some rows failed validation and will not be attempted.
- `columns` (any[], required)
- `counts` (any, required)
- `delimiter` (string, required)
- `format` (string, required)
- `has_header` (boolean, required)
- `invalid_count` (integer, required)
- `job` (any, required)
- `lanes` (any, required)
- `limits` (object, required)
- `platforms` (any, required)
- `success` (boolean, required)
- `total` (integer, required)
- `valid_count` (integer, required)
- `warning` (string)
### 400 β No valid rows
The body carries the same `rows[]` and counts as `POST /import/preview` so you can show what to fix.
### 413
Paste exceeds `max_rows` or `max_bytes` (`code: "IMPORT_LIMIT"`).
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/import/jobs" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"text":"string","default_platform":"onlyfans","source":"paste"}'
```
---
# Supply a 2FA code for an import row (/docs/crm/bulk-import/supply-a-2fa-code-for-an-import-row)
`POST https://theonlyapi.com/api/crm/your-crm-id/import/jobs/{job_id}/rows/{row_id}/otp`
Complete one row parked on `needs_2fa` by submitting the code the creator received.
Synchronous by design: it is a single platform call and an operator is watching, so the response carries the real outcome rather than "queued". `success: true` means the account is connected.
The field is `code` (`otp_code` is also accepted here), 4β12 characters, letters, digits and hyphens. This route is on the standard sensitive tier (100/minute), not the login tier β clearing a queue of parked rows back to back is the expected workflow, and the code is verified by the platform rather than by us.
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `job_id` (string, required) β Import job ID (32-character hex) returned by `POST /import/jobs`.
- `row_id` (integer, required) β Row ID within the import job β the `id` field of a row from `GET /import/jobs/{job_id}`, not its `row_index`.
## Request body
Content type: `application/json` (required)
- `code` (string, required) β The 2FA code. Also accepted as `otp_code`. e.g. `"123456"`.
## Responses
### 200 β Connected.
- `success` (boolean, required)
- `row` (object, required)
- `id` (integer) β Row ID β this is the `{row_id}` for the OTP and retry routes.
- `job_id` (string)
- `row_index` (integer) β Zero-based position within the paste.
- `source_line` (integer) β Line number in the original paste, for error display.
- `lane` ("cookie" | "password") β How this row connects: pasted session (`cookie`) or credentials (`password`).
- `platform` ("onlyfans" | "fansly")
- `email` (string)
- `label` (string) β Optional per-row label from the paste.
- `proxy` (string)
- `status` ("pending" | "running" | "success" | "needs_2fa" | "needs_2fa_expired" | "failed" | "canceled" | "invalid" | "skipped" | "slot_exhausted")
- `has_password` (boolean)
- `has_totp_secret` (boolean)
- `two_fa_expires_at` (string) β When a parked 2FA challenge stops being answerable.
- `two_fa_remaining_seconds` (integer) β Seconds left on the 2FA window; `null` unless the row is parked.
- `of_user_id` (string) β Set once the account connects.
- `username` (string)
- `error` (string)
- `error_reason` (string) β Machine-readable failure cause; match on this, not `error`.
- `permanent` (boolean) β True when retrying cannot help.
- `attempts` (integer)
- `started_at` (string)
- `finished_at` (string)
- `created_at` (string)
- `updated_at` (string)
### 400 β Malformed code, or the platform rejected it
The row stays parked when the window is still open, so a mistyped code can be resubmitted.
### 404 β No such row in this job.
### 409
Row is not waiting for a code β either it never was, or its 10-minute window closed (`status: "needs_2fa_expired"`). Retry the row to start a fresh login.
### 503 β The platform's OTP verification is temporarily unavailable
The row stays parked; try again.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/import/jobs/{job_id}/rows/{row_id}/otp" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"code":"123456"}'
```
---
# Cache & Sync (/docs/crm/cache-and-sync)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Async refresh, backfill, and job-status routes that populate the local cache. Refresh routes return 202 β poll the matching `/refresh/status` route or listen on `GET /events/stream`.
| Method | Endpoint | Path |
| ------ | ------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `POST` | [Backfill historical data (async)](/docs/crm/cache-and-sync/backfill-historical-data-async) | `/accounts/{of_user_id}/backfill` |
| `POST` | [Clear a stuck refresh job](/docs/crm/cache-and-sync/clear-a-stuck-refresh-job) | `/accounts/{of_user_id}/refresh/{kind}/clear` |
| `GET` | [List active refresh jobs](/docs/crm/cache-and-sync/list-active-refresh-jobs) | `/refresh/active` |
---
# Backfill historical data (async) (/docs/crm/cache-and-sync/backfill-historical-data-async)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/backfill`
Kick off a historical backfill for this account, walking further back than the routine refresh. Returns **202** β track progress via the refresh-status routes or `GET /events/stream`.
**Guide:** [Cached reads](/docs/cached-reads)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 202 β Backfill job accepted and running
- `already_running` (boolean)
- `days` (integer)
- `platform` (string)
- `state` (any)
- `success` (boolean, required)
### 409 β A job is already in progress
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/backfill" \
-H "X-API-Key: "
```
---
# Clear a stuck refresh job (/docs/crm/cache-and-sync/clear-a-stuck-refresh-job)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/refresh/{kind}/clear`
Force a wedged refresh job to a terminal state so a new one can start.
The refresh routes already supersede a stale job automatically, but that is a timer β you have to wait it out, and it cannot help at all with a job that keeps re-stamping its progress while making none. This is the escape hatch for that case.
Makes no platform calls, so it costs no quota. Always returns `200`; `cleared` says whether there was in fact something to clear, and `state` is the job's final state (or `null`).
**Guide:** [Cached reads](/docs/cached-reads)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `kind` ("subs" | "tx" | "campaigns", required) β Which refresh job to act on: `subs` (subscribers), `tx` (transactions), or `campaigns` (campaign claimers).
## Responses
### 200
`cleared: true` when a job was terminated, `false` when none was running.
- `cleared` (boolean, required)
- `state` (any, required)
- `success` (boolean, required)
### 400 β `kind` is not one of `subs`, `tx`, `campaigns`.
### 403 β Account does not belong to this panel.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/refresh/{kind}/clear" \
-H "X-API-Key: "
```
---
# List active refresh jobs (/docs/crm/cache-and-sync/list-active-refresh-jobs)
`GET https://theonlyapi.com/api/crm/your-crm-id/refresh/active`
All refresh/backfill jobs currently running across the panel. **Zero platform requests.**
**Guide:** [Cached reads](/docs/cached-reads)
## Authentication
- `X-API-Key` header (apiKey)
## Responses
### 200 β Active job list
- `jobs` (any[], required)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/refresh/active" \
-H "X-API-Key: "
```
---
# Campaigns (/docs/crm/campaigns)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Tracking link campaigns, claimers, campaign tags, and the tracked-campaign allowlist.
| Method | Endpoint | Path |
| -------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `GET` | [List campaign tags](/docs/crm/campaigns/list-campaign-tags) | `/accounts/{of_user_id}/campaign-tags` |
| `GET` | [List campaigns](/docs/crm/campaigns/list-campaigns) | `/accounts/{of_user_id}/campaigns` |
| `POST` | [Create campaign](/docs/crm/campaigns/create-campaign) | `/accounts/{of_user_id}/campaigns` |
| `GET` | [Get campaign claimers (live)](/docs/crm/campaigns/get-campaign-claimers-live) | `/accounts/{of_user_id}/campaigns/{campaign_id}/claimers` |
| `GET` | [Get cached campaign claimers](/docs/crm/campaigns/get-cached-campaign-claimers) | `/accounts/{of_user_id}/campaigns/{campaign_id}/claimers/cached` |
| `POST` | [Add a tag to a campaign](/docs/crm/campaigns/add-a-tag-to-a-campaign) | `/accounts/{of_user_id}/campaigns/{campaign_id}/tags` |
| `DELETE` | [Remove a tag from a campaign](/docs/crm/campaigns/remove-a-tag-from-a-campaign) | `/accounts/{of_user_id}/campaigns/{campaign_id}/tags/{tag}` |
| `GET` | [Campaign earnings breakdown](/docs/crm/campaigns/campaign-earnings-breakdown) | `/accounts/{of_user_id}/campaigns/earnings` |
| `POST` | [Refresh the campaign cache (async)](/docs/crm/campaigns/refresh-the-campaign-cache-async) | `/accounts/{of_user_id}/campaigns/refresh` |
| `GET` | [Campaign refresh status](/docs/crm/campaigns/campaign-refresh-status) | `/accounts/{of_user_id}/campaigns/refresh/status` |
| `GET` | [Get the tracked-campaign allowlist](/docs/crm/campaigns/get-the-tracked-campaign-allowlist) | `/accounts/{of_user_id}/tracked-campaigns` |
| `PUT` | [Set the tracked-campaign allowlist](/docs/crm/campaigns/set-the-tracked-campaign-allowlist) | `/accounts/{of_user_id}/tracked-campaigns` |
---
# Add a tag to a campaign (/docs/crm/campaigns/add-a-tag-to-a-campaign)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/{campaign_id}/tags`
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `campaign_id` (string, required) β Tracking-link campaign ID.
## Request body
Content type: `application/json` (required)
- `tag` (string, required) β e.g. `"reddit"`.
## Responses
### 200 β Tag added
- `success` (boolean, required)
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/{campaign_id}/tags" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"tag":"reddit"}'
```
---
# Campaign earnings breakdown (/docs/crm/campaigns/campaign-earnings-breakdown)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/earnings`
Earnings attributed to each tracking-link campaign, computed from cached claimers joined with cached transactions. **Zero platform requests.**
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Responses
### 200 β Per-campaign earnings
- `cache` (object, required)
- `campaigns` (integer)
- `claimers` (integer)
- `consecutive_failures` (integer)
- `last_refreshed_at` (any)
- `last_row_synced_at` (any)
- `last_spend_refreshed_at` (any)
- `earnings` (object[], required)
- `campaign_id` (string)
- `canon_messages` (any)
- `canon_posts` (any)
- `canon_streams` (any)
- `canon_subs` (any)
- `canon_tips` (any)
- `canonical_spent` (any)
- `fan_of_user_id` (string)
- `messages` (any)
- `posts` (any)
- `spent_messages` (any)
- `spent_posts` (any)
- `spent_streams` (any)
- `spent_subscriptions` (any)
- `spent_tips` (any)
- `streams` (any)
- `subs` (any)
- `tips` (any)
- `tx_messages` (any)
- `tx_posts` (any)
- `tx_spent` (any)
- `tx_streams` (any)
- `tx_subs` (any)
- `tx_tips` (any)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/earnings" \
-H "X-API-Key: "
```
---
# Campaign refresh status (/docs/crm/campaigns/campaign-refresh-status)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/refresh/status`
Progress/state of the current or last campaign refresh job. **Zero platform requests.**
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Responses
### 200 β Refresh job status
- `cache` (object, required)
- `campaigns` (integer)
- `claimers` (integer)
- `consecutive_failures` (integer)
- `last_refreshed_at` (any)
- `last_row_synced_at` (any)
- `last_spend_refreshed_at` (any)
- `live` (boolean)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/refresh/status" \
-H "X-API-Key: "
```
---
# Create campaign (/docs/crm/campaigns/create-campaign)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns`
Create a new tracking link campaign.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `name` (string, required) β e.g. `"My Campaign"`.
## Responses
### 200 β Campaign created
- `campaigns` (any[])
- `hasMore` (boolean)
- `name` (string)
- `success` (boolean)
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"name":"My Campaign"}'
```
---
# Get cached campaign claimers (/docs/crm/campaigns/get-cached-campaign-claimers)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/{campaign_id}/claimers/cached`
Cached claimers for one campaign, already joined with the subscriber cache so each row carries `total_spent`, its breakdown, and `mapped_spent`. **Zero platform requests.**
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `campaign_id` (string, required) β Tracking-link campaign ID.
## Query parameters
- `limit` (integer) β Maximum number of rows to return. Default `100`.
- `offset` (integer) β Row offset for pagination. Default `0`.
## Responses
### 200 β Cached claimer list
- `count` (integer, required)
- `hasMore` (boolean, required)
- `limit` (integer, required)
- `list` (any[], required)
- `offset` (integer, required)
- `success` (boolean, required)
- `total` (integer, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/{campaign_id}/claimers/cached" \
-H "X-API-Key: "
```
---
# Get campaign claimers (live) (/docs/crm/campaigns/get-campaign-claimers-live)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/{campaign_id}/claimers`
Fetch subscribers who converted through a specific campaign.
**OnlyFans only** β rejected for Fansly accounts. Fetched live from the platform, so it consumes platform requests. For a zero-platform-request read, use `GET /accounts/{of_user_id}/campaigns/{campaign_id}/claimers/cached`.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `campaign_id` (string, required)
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Claimer list
- `success` (boolean)
- `claimers` (object[])
- `hasMore` (boolean)
- `count` (integer)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/{campaign_id}/claimers" \
-H "X-API-Key: "
```
---
# Get the tracked-campaign allowlist (/docs/crm/campaigns/get-the-tracked-campaign-allowlist)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/tracked-campaigns`
The allowlist of tracking-link campaigns (names/codes) kept synced for this account. When set, scheduled and backfill claimer syncs walk only these β so a huge link (20k+ subscribers) is never walked on a timer.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Responses
### 200 β Tracked campaign allowlist
- `success` (boolean, required)
- `tracked_campaigns` (any, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/tracked-campaigns" \
-H "X-API-Key: "
```
---
# List campaign tags (/docs/crm/campaigns/list-campaign-tags)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaign-tags`
All campaign tags in use for this account.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Responses
### 200 β Campaign tag list
- `all_tags` (any[], required)
- `success` (boolean, required)
- `tags` (object[], required)
- `campaign_id` (string)
- `tag` (string)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaign-tags" \
-H "X-API-Key: "
```
---
# List campaigns (/docs/crm/campaigns/list-campaigns)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns`
Get tracking link campaigns with statistics and pagination.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Campaign list
- `success` (boolean)
- `campaigns` (object[])
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns" \
-H "X-API-Key: "
```
---
# Refresh the campaign cache (async) (/docs/crm/campaigns/refresh-the-campaign-cache-async)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/refresh`
Start an async campaign + claimer sync. Returns **202** β poll `GET /accounts/{of_user_id}/campaigns/refresh/status` or listen on `GET /events/stream`. Honours the `/tracked-campaigns` allowlist when one is set.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 202 β Refresh job accepted and running
- `already_running` (boolean)
- `message` (string)
- `not_needed` (boolean)
- `state` (any)
- `success` (boolean, required)
### 409 β A refresh is already in progress
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/refresh" \
-H "X-API-Key: "
```
---
# Remove a tag from a campaign (/docs/crm/campaigns/remove-a-tag-from-a-campaign)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/{campaign_id}/tags/{tag}`
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `campaign_id` (string, required) β Tracking-link campaign ID.
- `tag` (string, required)
## Responses
### 200 β Tag removed
- `success` (boolean, required)
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/campaigns/{campaign_id}/tags/{tag}" \
-H "X-API-Key: "
```
---
# Set the tracked-campaign allowlist (/docs/crm/campaigns/set-the-tracked-campaign-allowlist)
`PUT https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/tracked-campaigns`
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Request body
Content type: `application/json` (required)
- `campaigns` (string[]) β Campaign names or codes to keep synced.
## Responses
### 200 β Allowlist updated
- `success` (boolean, required)
- `tracked_campaigns` (any, required)
## Example
```bash
curl -X PUT "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/tracked-campaigns" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Content (/docs/crm/content)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Media upload. Runs OnlyFans' four-stage signed-S3 pipeline server-side and returns a reference you attach to a post, message or story via `mediaFiles`. OnlyFans only.
| Method | Endpoint | Path |
| ------ | ------------------------------------------------------------------------ | ------------------------------ |
| `POST` | [Upload media to the vault](/docs/crm/content/upload-media-to-the-vault) | `/accounts/{of_user_id}/media` |
---
# Upload media to the vault (/docs/crm/content/upload-media-to-the-vault)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/media`
Upload a photo, video, gif or audio file to the account's vault, and get back a reference you can attach to a post, message or story.
**Two ways to call it:**
1. `multipart/form-data` with a `file` part β raw bytes.
2. `application/json` with `{"source_url": "https://β¦"}` β we fetch the file server-side. Use this when your media already lives somewhere public (Drive, S3, a CDN); it saves you building a multipart request at all.
**What happens under the hood.** OnlyFans has no single upload endpoint. The web client runs a four-stage pipeline and so do we: `POST /api2/v2/upload/signed/create` β `PUT` the bytes straight to the returned pre-signed S3 URL (5 MiB parts for files β₯ 5 MiB) β `POST /api2/v2/upload/signed/finish` β hand the S3 descriptor to OnlyFans' converter host. You do not have to orchestrate any of that.
**Using the result.** Put the returned `media` object into the post/message's **`mediaFiles`** array β not `media`, which OnlyFans silently ignores for freshly uploaded files. Pass the object through whole; trimming it to just `processId` also silently attaches nothing:
```json
{ "text": "new set π₯",
"mediaFiles": [ { "processId": "0ifuovβ¦", "host": "convert4.onlyfans.com", "thumbId": 1, "name": "IMG_2676.HEIC", "extra": "β¦" } ],
"isScheduled": 1, "scheduledDate": "2026-08-20T12:00:00+00:00" }
```
β οΈ Two things that fail *silently*, both confirmed on a live account:
* Using `media` instead of `mediaFiles`, or trimming the object down to `{processId}`, returns `200` with **no media attached**.
* **`postedAt` does not schedule anything.** In either format (`β¦Z` or `β¦+00:00`) OnlyFans ignores it and publishes immediately β verified live. Scheduling requires **two** fields instead:
```json
{ "isScheduled": 1, "scheduledDate": "2026-08-20T12:00:00+00:00" }
```
Always confirm the post actually queued by checking `GET /api2/v2/schedules` for its id β a post that published instead of queueing looks identical in the create response apart from `postedAt`.
Media already in the vault is still referenced by plain integer id β list those with `GET /api2/v2/vault/media`.
**An upload does not by itself create a vault item, and that matches OnlyFans exactly.** OnlyFans has no upload-to-vault endpoint: its own vault page is browse/organise only (list, hide, folders, attach), and the uploader is wired solely into the post/message composer. The vault entry is created when a post, message or story consumes the `processId`. So immediately after this call the file will *not* appear in `GET /api2/v2/vault/media`, nor in `GET /api2/v2/vault/media/processing`, nor under `GET /api2/v2/vault/media/hash?h={md5}&size={bytes}` β that last one is the lookup OnlyFans' own client uses to decide whether a file is already in the vault, and it answers `404 Media Not Found`. Publish or schedule with the `processId` and the vault row appears. This is the same behaviour you get clicking upload in the OnlyFans web app.
**Working example:** a runnable Python client covering upload, immediate posting and scheduling β including every silent-failure case above β is at [theonlyapi-posting-example.zip](https://docs.theonlyapi.com/theonlyapi-posting-example.zip).
**Requires writes to be enabled** for the account (`PATCH /accounts/{of_user_id}/polling {"allow_of_write_actions": true}`), because uploading acts as the creator.
**OnlyFans only.** The Fansly upload pipeline is not wired yet; a Fansly account returns 501.
*(This replaces the long-documented `POST /api2/v2/media`, which never existed.)*
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `multipart/form-data` (required)
- `file` (string, required) β The media file to upload.
- `secure` (boolean) β Upload to the secure (DRM) path instead of the standard one. Default `false`.
## Responses
### 200 β Upload complete
- `success` (boolean)
- `media` (object) β Ready to drop into a post/message `media` array.
- `processId` (string)
- `host` (string)
- `name` (string)
- `extra` (string)
- `data` (object) β Full converter response β thumbs, sourceUrl, duration, plus the echoed key/etag/contentType/size.
### 400
No file and no source_url, an unsafe source_url, or OnlyFans rejected the upload descriptor.
### 403
Writes are disabled for this account (`code: WRITES_DISABLED`).
### 413 β File exceeds MEDIA_UPLOAD_MAX_BYTES.
### 501 β Fansly account β not supported yet.
### 502
A stage failed upstream (S3 PUT, the converter, or fetching source_url). The `stage` field says which.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/media" \
-H "X-API-Key: "
```
---
# Earnings (/docs/crm/earnings)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Revenue & earnings data, including the cross-account `GET /earnings/summary` aggregation. Fansly-aware.
| Method | Endpoint | Path |
| ------ | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| `GET` | [Get account balances](/docs/crm/earnings/get-account-balances) | `/accounts/{of_user_id}/balances` |
| `GET` | [Get earnings chart data](/docs/crm/earnings/get-earnings-chart-data) | `/accounts/{of_user_id}/earnings` |
| `GET` | [Verify cached earnings against the platform](/docs/crm/earnings/verify-cached-earnings-against-the-platform) | `/accounts/{of_user_id}/earnings/verify` |
| `GET` | [Get panel-wide payout totals](/docs/crm/earnings/get-panel-wide-payout-totals) | `/balances/summary` |
| `GET` | [Aggregated earnings across all accounts](/docs/crm/earnings/aggregated-earnings-across-all-accounts) | `/earnings/summary` |
---
# Aggregated earnings across all accounts (/docs/crm/earnings/aggregated-earnings-across-all-accounts)
`GET https://theonlyapi.com/api/crm/your-crm-id/earnings/summary`
Server-side earnings aggregation across every account in the panel β OnlyFans and Fansly combined into one normalized response.
The response is cached in memory per (panel, period) with a 2β15 minute TTL depending on the period, and auto-invalidates when an earnings-changing event (`new_tip`, `new_purchase`, `balance_increased`) fires for any account in the panel.
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `period` ("today" | "week" | "month") β Default `week`.
## Responses
### 200 β Aggregated earnings
- `total` (number)
- `prev_total` (number)
- `by_category` (object)
- `subscriptions` (number)
- `posts` (number)
- `messages` (number)
- `tips` (number)
- `streams` (number)
- `referrals` (number)
- `chart` (number[]) β Daily net values.
- `accounts_count` (integer)
- `transactions_counted` (integer)
- `transactions_capped` (boolean)
- `period` (string)
- `cached` (boolean)
- `cached_age_seconds` (integer)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/earnings/summary" \
-H "X-API-Key: "
```
---
# Get account balances (/docs/crm/earnings/get-account-balances)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/balances`
Fetch current payout balance for a connected account.
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Balance data
- `success` (boolean)
- `balances` (object)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/balances" \
-H "X-API-Key: "
```
---
# Get earnings chart data (/docs/crm/earnings/get-earnings-chart-data)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/earnings`
Fetch earnings data for a date range with chart-ready breakdown.
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `startDate` (string, required) β Start date (ISO format).
- `endDate` (string) β End date (ISO format).
- `withTotal` (string) β Default `true`.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Earnings data with chart arrays
- `success` (boolean)
- `earnings` (object)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/earnings" \
-H "X-API-Key: "
```
---
# Get panel-wide payout totals (/docs/crm/earnings/get-panel-wide-payout-totals)
`GET https://theonlyapi.com/api/crm/your-crm-id/balances/summary`
Available and pending payout totals across every connected account, in **one** query and **zero** platform calls.
These are last-known values, not live ones. `payoutAvailable` is only knowable by asking the platform, so a live panel-wide total would cost one round trip per account β the honest trade is to serve the last sample and say how old it is. `GET /accounts/{of_user_id}/balances` stamps a fresh sample each time it runs, so normal dashboard use keeps the figures warm at no extra cost.
Label the number with `oldest_sample_at` / `newest_sample_at`, and treat `accounts_never_sampled` as "not counted yet" rather than zero. `currency` is the panel's most common currency.
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Responses
### 200 β Panel totals with sample-freshness metadata
Amounts are rounded to 2 decimal places.
- `accounts` (integer, required)
- `accounts_never_sampled` (any, required)
- `accounts_with_balance` (any, required)
- `currency` (any, required)
- `newest_sample_at` (any, required)
- `oldest_sample_at` (any, required)
- `success` (boolean, required)
- `total_available` (number, required)
- `total_pending` (number, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/balances/summary" \
-H "X-API-Key: "
```
---
# Verify cached earnings against the platform (/docs/crm/earnings/verify-cached-earnings-against-the-platform)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/earnings/verify`
Cross-check one account's cached earnings against OnlyFans' own chart for the same window, and report both numbers plus the difference.
`GET /earnings/summary` sums the local transaction cache rather than asking the platform, because the live path cannot converge at panel scale. The cost of that choice is a trust question, and this is the answer to it: same period, both sources, side by side.
**Per-account on purpose.** It makes real upstream calls and sits on the sensitive rate-limit tier so it can never become something a dashboard fans out over hundreds of accounts.
If the platform is unreachable, `live_available` is `false` and `live_total`/`difference` are `null` β that is "could not check", not "matches".
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `period` ("today" | "week" | "month") β Window to compare. Ignored when `startDate` and `endDate` are both supplied. Default `week`.
- `startDate` (string) β Custom range start. Requires `endDate`.
- `endDate` (string) β Custom range end. Requires `startDate`.
## Responses
### 200 β Both totals and their difference
`matches` is the verdict for this window.
- `cached_total` (number, required)
- `cached_transactions` (any, required)
- `difference` (number, required)
- `end` (any, required)
- `last_synced_at` (string, required)
- `live_available` (boolean, required)
- `live_total` (any, required)
- `matches` (boolean, required)
- `of_user_id` (string, required)
- `period` (any, required)
- `start` (any, required)
- `success` (boolean, required)
### 400
`period` not one of `today`/`week`/`month`, or an unparseable `startDate`/`endDate`.
### 404 β Account not found in this panel.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/earnings/verify" \
-H "X-API-Key: "
```
---
# Events & Streaming (/docs/crm/events-and-streaming)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Real-time event feed β `GET /events` for polling, `GET /events/stream` for Server-Sent Events (rate-limit exempt).
| Method | Endpoint | Path |
| ------ | ----------------------------------------------------------------------- | ---------------- |
| `GET` | [List events](/docs/crm/events-and-streaming/list-events) | `/events` |
| `GET` | [Stream events (SSE)](/docs/crm/events-and-streaming/stream-events-sse) | `/events/stream` |
---
# List events (/docs/crm/events-and-streaming/list-events)
`GET https://theonlyapi.com/api/crm/your-crm-id/events`
Poll the panel's event feed. **Zero platform requests.** Covers OnlyFans and Fansly accounts alike.
**Guide:** [Events](/docs/events)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `types` (string) β Comma-separated event types, e.g. `new_subscriber,new_tip`. An unknown type returns 400.
- `of_user_id` (string) β Restrict to one connected account.
- `since` (string) β ISO timestamp lower bound.
- `until` (string) β ISO timestamp upper bound.
- `limit` (integer) β Default `100`.
## Responses
### 200 β Event list
- `success` (boolean)
- `events` (object[])
### 400 β Unknown event type
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/events" \
-H "X-API-Key: "
```
---
# Stream events (SSE) (/docs/crm/events-and-streaming/stream-events-sse)
`GET https://theonlyapi.com/api/crm/your-crm-id/events/stream`
Long-lived **Server-Sent Events** stream of panel events. **Rate-limit exempt.** Use this instead of polling to observe refresh-job completion and `export.progress` / `export.complete`.
Optional server-side filtering: `?types=new_subscriber,new_tip` (`*` or absent = everything). An unknown type returns 400.
**Guide:** [Events](/docs/events)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `types` (string) β Comma-separated event types, or `*` for everything.
## Responses
### 200 β SSE stream
### 400 β Unknown event type
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/events/stream" \
-H "X-API-Key: "
```
---
# Exports (/docs/crm/exports)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Async data export jobs producing a downloadable ZIP. Progress streams over `GET /events/stream`.
| Method | Endpoint | Path |
| -------- | ---------------------------------------------------------------------------- | -------------------------------------------------- |
| `GET` | [List export jobs](/docs/crm/exports/list-export-jobs) | `/accounts/{of_user_id}/exports` |
| `POST` | [Create an export job](/docs/crm/exports/create-an-export-job) | `/accounts/{of_user_id}/exports` |
| `GET` | [Get export job status](/docs/crm/exports/get-export-job-status) | `/accounts/{of_user_id}/exports/{job_id}` |
| `DELETE` | [Delete an export job](/docs/crm/exports/delete-an-export-job) | `/accounts/{of_user_id}/exports/{job_id}` |
| `POST` | [Cancel a running export job](/docs/crm/exports/cancel-a-running-export-job) | `/accounts/{of_user_id}/exports/{job_id}/cancel` |
| `GET` | [Download an export ZIP](/docs/crm/exports/download-an-export-zip) | `/accounts/{of_user_id}/exports/{job_id}/download` |
---
# Cancel a running export job (/docs/crm/exports/cancel-a-running-export-job)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/exports/{job_id}/cancel`
**Guide:** [Data exports](/docs/exports)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `job_id` (string, required)
## Responses
### 200 β Job cancelled
- `job` (any, required)
- `success` (boolean, required)
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/exports/{job_id}/cancel" \
-H "X-API-Key: "
```
---
# Create an export job (/docs/crm/exports/create-an-export-job)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/exports`
Kick off an async data export for one account. Returns **202** with the created job.
Progress streams over `GET /events/stream` as `export.progress`; completion fires `export.complete`. The generated ZIP is downloadable from `GET /accounts/{of_user_id}/exports/{job_id}/download` for the configured retention window. Fansly-aware.
**Guide:** [Data exports](/docs/exports)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Request body
Content type: `application/json` (required)
- `data_types` ("subscribers" | "transactions" | "fans" | "earnings" | "messages" | "account"[]) β e.g. `["subscribers","transactions"]`.
- `since` (string) β YYYY-MM-DD lower bound. e.g. `"2026-01-01"`.
- `until` (string) β YYYY-MM-DD upper bound. e.g. `"2026-06-30"`.
- `include_media` (boolean) β OnlyFans only in v1. Default `false`.
## Responses
### 202 β Export job created
- `already_running` (boolean)
- `job` (any, required)
- `success` (boolean, required)
- `warning` (string)
### 400 β Invalid data_types or date range
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/exports" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"data_types":["subscribers","transactions"],"since":"2026-01-01","until":"2026-06-30","include_media":false}'
```
---
# Delete an export job (/docs/crm/exports/delete-an-export-job)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/exports/{job_id}`
Delete the job and its generated ZIP.
**Guide:** [Data exports](/docs/exports)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `job_id` (string, required)
## Responses
### 200 β Job deleted
- `success` (boolean, required)
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/exports/{job_id}" \
-H "X-API-Key: "
```
---
# Download an export ZIP (/docs/crm/exports/download-an-export-zip)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/exports/{job_id}/download`
Download the generated archive. Available until the retention window expires.
**Guide:** [Data exports](/docs/exports)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `job_id` (string, required)
## Responses
### 200 β ZIP archive
### 404 β Job not found, not finished, or expired
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/exports/{job_id}/download" \
-H "X-API-Key: "
```
---
# Get export job status (/docs/crm/exports/get-export-job-status)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/exports/{job_id}`
**Guide:** [Data exports](/docs/exports)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `job_id` (string, required)
## Responses
### 200 β Export job
- `job` (any, required)
- `success` (boolean, required)
### 404 β Job not found
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/exports/{job_id}" \
-H "X-API-Key: "
```
---
# List export jobs (/docs/crm/exports/list-export-jobs)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/exports`
**Guide:** [Data exports](/docs/exports)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Responses
### 200 β Export job list
- `jobs` (any[], required)
- `success` (boolean, required)
- `total` (integer, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/exports" \
-H "X-API-Key: "
```
---
# Fans (/docs/crm/fans)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Fan CRM across all accounts in the panel β tags, notes, per-fan cached transactions, profile refresh. Fansly-aware.
| Method | Endpoint | Path |
| -------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `POST` | [Refresh a fan profile from the platform](/docs/crm/fans/refresh-a-fan-profile-from-the-platform) | `/accounts/{of_user_id}/fans/{fan_id}/refresh-profile` |
| `GET` | [Get a fan's cached transactions](/docs/crm/fans/get-a-fan-s-cached-transactions) | `/accounts/{of_user_id}/fans/{fan_id}/transactions/cached` |
| `GET` | [List fans](/docs/crm/fans/list-fans) | `/fans` |
| `PUT` | [Set a fan note](/docs/crm/fans/set-a-fan-note) | `/fans/{fan_of_user_id}/note` |
| `POST` | [Add a tag to a fan](/docs/crm/fans/add-a-tag-to-a-fan) | `/fans/{fan_of_user_id}/tags` |
| `DELETE` | [Remove a tag from a fan](/docs/crm/fans/remove-a-tag-from-a-fan) | `/fans/{fan_of_user_id}/tags/{tag}` |
---
# Add a tag to a fan (/docs/crm/fans/add-a-tag-to-a-fan)
`POST https://theonlyapi.com/api/crm/your-crm-id/fans/{fan_of_user_id}/tags`
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `fan_of_user_id` (string, required) β Fan's platform user ID β the same value returned as `of_user_id` on a row from `GET /fans`, not an internal database id.
## Request body
Content type: `application/json` (required)
- `tag` (string, required) β e.g. `"whale"`.
## Responses
### 200 β Tag added
- `success` (boolean, required)
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/fans/{fan_of_user_id}/tags" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"tag":"whale"}'
```
---
# Get a fan's cached transactions (/docs/crm/fans/get-a-fan-s-cached-transactions)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/fans/{fan_id}/transactions/cached`
Every cached transaction attributed to one fan on this account. **Zero platform requests.** Fansly-aware.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `fan_id` (string, required) β Fan's platform user ID.
## Query parameters
- `limit` (integer) β Maximum number of rows to return. Default `100`.
- `offset` (integer) β Row offset for pagination. Default `0`.
## Responses
### 200 β Cached transaction list
- `count` (integer, required)
- `hasMore` (boolean, required)
- `limit` (integer, required)
- `list` (any[], required)
- `mapped_spent` (number, required)
- `offset` (integer, required)
- `success` (boolean, required)
- `total` (integer, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/fans/{fan_id}/transactions/cached" \
-H "X-API-Key: "
```
---
# List fans (/docs/crm/fans/list-fans)
`GET https://theonlyapi.com/api/crm/your-crm-id/fans`
Fan CRM across the whole panel β every fan seen on any connected account, OnlyFans or Fansly, with tags, notes, spend, and activity counters. **Zero platform requests.**
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `of_user_id` (string) β Restrict to fans of one connected account.
- `limit` (integer) β Default `100`.
- `offset` (integer) β Row offset for pagination. Default `0`.
- `sort` ("last_seen" | "first_seen" | "tips" | "spend" | "events") β Default `last_seen`.
- `search` (string) β Free-text search over fan name/username.
- `tag` (string) β Filter to fans carrying this tag.
## Responses
### 200 β Fan list
- `success` (boolean)
- `fans` (object[])
- `count` (integer)
### 400 β Invalid sort value
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/fans" \
-H "X-API-Key: "
```
---
# Refresh a fan profile from the platform (/docs/crm/fans/refresh-a-fan-profile-from-the-platform)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/fans/{fan_id}/refresh-profile`
Re-fetch one fan's profile from the platform and update the cache. Consumes a platform request.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `fan_id` (string, required) β Fan's platform user ID.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Fan profile refreshed
- `fan` (any, required)
- `success` (boolean, required)
- `updated_cache` (boolean, required)
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/fans/{fan_id}/refresh-profile" \
-H "X-API-Key: "
```
---
# Remove a tag from a fan (/docs/crm/fans/remove-a-tag-from-a-fan)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/fans/{fan_of_user_id}/tags/{tag}`
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `fan_of_user_id` (string, required) β Fan's platform user ID β the same value returned as `of_user_id` on a row from `GET /fans`, not an internal database id.
- `tag` (string, required)
## Responses
### 200 β Tag removed
- `success` (boolean, required)
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/fans/{fan_of_user_id}/tags/{tag}" \
-H "X-API-Key: "
```
---
# Set a fan note (/docs/crm/fans/set-a-fan-note)
`PUT https://theonlyapi.com/api/crm/your-crm-id/fans/{fan_of_user_id}/note`
Replace the free-text CRM note stored against a fan. Send an empty string to clear it.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `fan_of_user_id` (string, required) β Fan's platform user ID β the same value returned as `of_user_id` on a row from `GET /fans`, not an internal database id.
## Request body
Content type: `application/json` (required)
- `note` (string) β e.g. `"Prefers PPV over tips."`.
## Responses
### 200 β Note saved
- `success` (boolean, required)
## Example
```bash
curl -X PUT "https://theonlyapi.com/api/crm/your-crm-id/fans/{fan_of_user_id}/note" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"note":"Prefers PPV over tips."}'
```
---
# Integrations (/docs/crm/integrations)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Third-party integrations. One Telegram channel per panel: pair it once, then matching events are delivered to that chat. The bot token is encrypted at rest and is never returned by any route.
| Method | Endpoint | Path |
| -------- | ----------------------------------------------------------------------------------------- | ------------------------------- |
| `GET` | [Get the Telegram integration](/docs/crm/integrations/get-the-telegram-integration) | `/integrations/telegram` |
| `PATCH` | [Update the Telegram integration](/docs/crm/integrations/update-the-telegram-integration) | `/integrations/telegram` |
| `DELETE` | [Remove the Telegram integration](/docs/crm/integrations/remove-the-telegram-integration) | `/integrations/telegram` |
| `POST` | [Register Telegram groups](/docs/crm/integrations/register-telegram-groups) | `/integrations/telegram/groups` |
| `POST` | [Start Telegram pairing](/docs/crm/integrations/start-telegram-pairing) | `/integrations/telegram/pair` |
| `POST` | [Send a Telegram test message](/docs/crm/integrations/send-a-telegram-test-message) | `/integrations/telegram/test` |
---
# Get the Telegram integration (/docs/crm/integrations/get-the-telegram-integration)
`GET https://theonlyapi.com/api/crm/your-crm-id/integrations/telegram`
The panel's Telegram channel, or `integration: null` if none is configured.
`shared_bot_available` says whether this deployment has our shared bot configured β check it before offering "use the Xcelerate bot", so you never present a button that cannot work. `event_types` lists every event type you may subscribe to.
The bot token is never returned by this or any other route; `has_custom_token` only reports whether one is stored.
**Guide:** [Integrations](/docs/integrations)
## Authentication
- `X-API-Key` header (apiKey)
## Responses
### 200
Integration state, plus the deployment's shared-bot availability and the allowed event types.
- `event_types` (any[], required)
- `integration` (object, required)
- `bot_mode` (any)
- `bot_username` (any)
- `chat_id` (any)
- `chat_title` (any)
- `chat_type` (any)
- `consecutive_failures` (any)
- `created_at` (string)
- `event_types` (any)
- `has_custom_token` (boolean)
- `is_active` (boolean)
- `is_paired` (boolean)
- `last_delivery_at` (any)
- `last_error` (any)
- `paired_at` (any)
- `updated_at` (string)
- `shared_bot_available` (boolean, required)
- `shared_bot_username` (any, required)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/integrations/telegram" \
-H "X-API-Key: "
```
---
# Register Telegram groups (/docs/crm/integrations/register-telegram-groups)
`POST https://theonlyapi.com/api/crm/your-crm-id/integrations/telegram/groups`
Register Telegram group targets so automations and webhooks can deliver notifications there.
**Guide:** [Integrations](/docs/integrations)
## Authentication
- `X-API-Key` header (apiKey)
## Request body
Content type: `application/json` (required)
- `groups` (object[]) β Telegram group identifiers to register.
## Responses
### 200 β Groups registered
- `groups` (any[], required)
- `success` (boolean, required)
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/integrations/telegram/groups" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Remove the Telegram integration (/docs/crm/integrations/remove-the-telegram-integration)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/integrations/telegram`
Unpair the channel and delete the stored configuration, including any custom bot token.
**Guide:** [Integrations](/docs/integrations)
## Authentication
- `X-API-Key` header (apiKey)
## Responses
### 200 β Removed.
- `success` (boolean, required)
### 404 β No Telegram integration configured.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/integrations/telegram" \
-H "X-API-Key: "
```
---
# Send a Telegram test message (/docs/crm/integrations/send-a-telegram-test-message)
`POST https://theonlyapi.com/api/crm/your-crm-id/integrations/telegram/test`
Post a test message to the paired chat, so you can confirm the whole path end to end. Returns `400` with the failure reason β and the current integration state, including `last_error` β if Telegram refuses it.
**Guide:** [Integrations](/docs/integrations)
## Authentication
- `X-API-Key` header (apiKey)
## Responses
### 200 β Test message delivered.
- `integration` (object, required)
- `bot_mode` (any)
- `bot_username` (any)
- `chat_id` (any)
- `chat_title` (any)
- `chat_type` (any)
- `consecutive_failures` (any)
- `created_at` (string)
- `event_types` (any)
- `has_custom_token` (boolean)
- `is_active` (boolean)
- `is_paired` (boolean)
- `last_delivery_at` (any)
- `last_error` (any)
- `paired_at` (any)
- `updated_at` (string)
### 400 β Not paired, inactive, or Telegram rejected the send
The body carries the reason.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/integrations/telegram/test" \
-H "X-API-Key: "
```
---
# Start Telegram pairing (/docs/crm/integrations/start-telegram-pairing)
`POST https://theonlyapi.com/api/crm/your-crm-id/integrations/telegram/pair`
Begin (or restart) pairing and get a `t.me` deep link. Open the link, and the chat that the bot lands in becomes the panel's channel.
Two modes:
- `bot_mode: "shared"` β use our bot. Nothing else to supply. Returns `409` if this deployment has no shared bot configured, rather than handing back a link that can never resolve.
- `bot_mode: "custom"` β use your own bot, passing its `bot_token`. We verify the token with Telegram before storing it, encrypt it at rest, and clear any existing webhook on that bot (otherwise our polling would conflict with it and the deep link would silently never resolve).
The response carries the deep link and its expiry, never a token. Calling this again replaces any pending code.
**Guide:** [Integrations](/docs/integrations)
## Authentication
- `X-API-Key` header (apiKey)
## Request body
Content type: `application/json` (required)
- `bot_mode` ("shared" | "custom") β Default `"shared"`.
- `bot_token` (string) β Required when `bot_mode` is `custom`. Stored encrypted and never returned. e.g. `"123456789:AAβ¦"`.
- `event_types` (string[]) β Which events to deliver. Defaults to every type.
## Responses
### 200 β Pairing started
Open `deep_link` before `expires_at` (`ttl_seconds` from now).
- `bot_username` (any, required)
- `deep_link` (any, required)
- `expires_at` (any, required)
- `integration` (object, required)
- `bot_mode` (any)
- `bot_username` (any)
- `chat_id` (any)
- `chat_title` (any)
- `chat_type` (any)
- `consecutive_failures` (any)
- `created_at` (string)
- `event_types` (any)
- `has_custom_token` (boolean)
- `is_active` (boolean)
- `is_paired` (boolean)
- `last_delivery_at` (any)
- `last_error` (any)
- `paired_at` (any)
- `updated_at` (string)
- `success` (boolean, required)
- `ttl_seconds` (any, required)
### 400
`bot_mode` invalid, an event type is not recognised, the token is malformed, Telegram rejected it, or it is the shared token (use `bot_mode: "shared"`).
### 409
`bot_mode: "shared"` requested but this deployment has no shared bot. Connect your own bot instead.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/integrations/telegram/pair" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"bot_mode":"shared","bot_token":"123456789:AAβ¦"}'
```
---
# Update the Telegram integration (/docs/crm/integrations/update-the-telegram-integration)
`PATCH https://theonlyapi.com/api/crm/your-crm-id/integrations/telegram`
Change which events are delivered, or pause delivery without unpairing. Send only the fields you want to change.
**Guide:** [Integrations](/docs/integrations)
## Authentication
- `X-API-Key` header (apiKey)
## Request body
Content type: `application/json` (required)
- `event_types` (string[]) β Event types to deliver. `["*"]` means every type. e.g. `["new_subscriber","new_tip"]`.
- `is_active` (boolean) β Set `false` to stop delivery while keeping the pairing.
## Responses
### 200 β Updated integration.
- `integration` (object, required)
- `bot_mode` (any)
- `bot_username` (any)
- `chat_id` (any)
- `chat_title` (any)
- `chat_type` (any)
- `consecutive_failures` (any)
- `created_at` (string)
- `event_types` (any)
- `has_custom_token` (boolean)
- `is_active` (boolean)
- `is_paired` (boolean)
- `last_delivery_at` (any)
- `last_error` (any)
- `paired_at` (any)
- `updated_at` (string)
- `success` (boolean, required)
### 400 β An event type is not recognised.
### 404 β No Telegram integration configured.
## Example
```bash
curl -X PATCH "https://theonlyapi.com/api/crm/your-crm-id/integrations/telegram" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"event_types":["new_subscriber","new_tip"]}'
```
---
# MCP (/docs/crm/mcp)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Hosted MCP server support. `GET /api/whoami` turns a bearer token into a tenant; `PATCH /mcp/unsafe-proxy` toggles non-GET tool access (off by default).
| Method | Endpoint | Path |
| ------- | -------------------------------------------------------------------------------- | ------------------- |
| `PATCH` | [Toggle MCP non-GET proxy access](/docs/crm/mcp/toggle-mcp-non-get-proxy-access) | `/mcp/unsafe-proxy` |
---
# Toggle MCP non-GET proxy access (/docs/crm/mcp/toggle-mcp-non-get-proxy-access)
`PATCH https://theonlyapi.com/api/crm/your-crm-id/mcp/unsafe-proxy`
Toggle whether the hosted MCP server may issue non-GET requests through the generic OF proxy tool. **Off by default.** Use with care: enabling it lets a model POST/PATCH/DELETE arbitrary OnlyFans endpoints on your behalf.
**Guide:** [Use these docs with an AI](/docs/ai-agents)
## Authentication
- `X-API-Key` header (apiKey)
## Request body
Content type: `application/json` (required)
- `enabled` (boolean) β Default `false`.
## Responses
### 200 β Toggle updated
- `success` (boolean)
- `mcp_unsafe_proxy` (boolean)
## Example
```bash
curl -X PATCH "https://theonlyapi.com/api/crm/your-crm-id/mcp/unsafe-proxy" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"enabled":false}'
```
---
# Messaging (/docs/crm/messaging)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Chats, single DMs/PPVs, mass DMs, and PPV performance stats. Fansly-aware.
| Method | Endpoint | Path |
| ------ | ------------------------------------------------------------------ | ------------------------------------------------------ |
| `GET` | [List chats](/docs/crm/messaging/list-chats) | `/accounts/{of_user_id}/chats` |
| `GET` | [Get message history](/docs/crm/messaging/get-message-history) | `/accounts/{of_user_id}/chats/{with_user_id}/messages` |
| `POST` | [Send a DM or PPV](/docs/crm/messaging/send-a-dm-or-ppv) | `/accounts/{of_user_id}/chats/{with_user_id}/messages` |
| `POST` | [Send a mass DM](/docs/crm/messaging/send-a-mass-dm) | `/accounts/{of_user_id}/messages/mass` |
| `GET` | [PPV performance stats](/docs/crm/messaging/ppv-performance-stats) | `/accounts/{of_user_id}/ppv-stats` |
---
# Get message history (/docs/crm/messaging/get-message-history)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/chats/{with_user_id}/messages`
Message history for one conversation, newest-first by default. Fansly-aware.
**Guide:** [Messaging & mass DM](/docs/messaging)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `with_user_id` (string, required) β The fan's user ID.
## Query parameters
- `limit` (integer) β Default `100`.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Message list
- `hasMore` (boolean, required)
- `messages` (any[], required)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/chats/{with_user_id}/messages" \
-H "X-API-Key: "
```
---
# List chats (/docs/crm/messaging/list-chats)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/chats`
Conversation list for a connected account. Fansly-aware.
**Guide:** [Messaging & mass DM](/docs/messaging)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `limit` (integer) β Default `100`.
- `offset` (integer) β Row offset for pagination. Default `0`.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Chat list
- `chats` (any[], required)
- `hasMore` (boolean, required)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/chats" \
-H "X-API-Key: "
```
---
# PPV performance stats (/docs/crm/messaging/ppv-performance-stats)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/ppv-stats`
Purchase/unlock statistics for PPV messages sent from this account, computed from cached transactions. **Zero platform requests.** Fansly-aware.
**Guide:** [Messaging & mass DM](/docs/messaging)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `since` (string) β ISO date lower bound.
- `until` (string) β ISO date upper bound.
## Responses
### 200 β PPV statistics
- `stats` (object, required)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/ppv-stats" \
-H "X-API-Key: "
```
---
# Send a DM or PPV (/docs/crm/messaging/send-a-dm-or-ppv)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/chats/{with_user_id}/messages`
Send a single DM (or PPV) to one fan. Works for OnlyFans and Fansly.
- `price > 0` makes the message a **PPV**.
- `mediaFiles` locks vault media behind the price.
- OnlyFans sends are gated per account by the `allow_of_write_actions` polling setting (`PATCH /accounts/{of_user_id}/polling`).
Rate limited to 120 requests/minute.
**Guide:** [Messaging & mass DM](/docs/messaging)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
- `with_user_id` (string, required) β The fan's user ID.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `text` (string, required) β e.g. `"Hey! New drop is up π"`.
- `price` (number) β Above 0 makes this a PPV message. Default `0`.
- `mediaFiles` (string[]) β Vault media IDs to attach.
## Responses
### 200 β Message sent
- `message` (string, required)
- `success` (boolean, required)
### 403 β Write actions disabled for this account
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/chats/{with_user_id}/messages" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"text":"Hey! New drop is up π","price":0}'
```
---
# Send a mass DM (/docs/crm/messaging/send-a-mass-dm)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/messages/mass`
Send one message to many fans at once. Supports PPV pricing and vault media, and can target a subscriber segment. Rate limited to 120 requests/minute. Fansly-aware.
**Guide:** [Messaging & mass DM](/docs/messaging)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `text` (string, required) β e.g. `"New set just dropped!"`.
- `price` (number) β Above 0 makes this a mass PPV. Default `0`.
- `mediaFiles` (string[]) β Vault media IDs to attach.
- `userLists` (string[]) β Target segment(s), e.g. `["active"]` or specific list IDs.
- `excludedLists` (string[]) β Segment(s) to exclude.
## Responses
### 200 β Mass DM queued/sent
- `dry_run` (boolean)
- `failed` (integer)
- `note` (string)
- `recipients` (integer, required)
- `results` (any[])
- `sample` (any[])
- `sent` (integer, required)
- `success` (boolean, required)
### 403 β Write actions disabled for this account
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/messages/mass" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"text":"New set just dropped!","price":0}'
```
---
# Notifications (/docs/crm/notifications)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Account notifications. Fansly-aware.
| Method | Endpoint | Path |
| ------ | -------------------------------------------------------------- | -------------------------------------- |
| `GET` | [Get notifications](/docs/crm/notifications/get-notifications) | `/accounts/{of_user_id}/notifications` |
---
# Get notifications (/docs/crm/notifications/get-notifications)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/notifications`
Fetch notifications for a connected account.
**Guide:** [Events](/docs/events)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `limit` (integer) β Default `20`.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Notification list
- `success` (boolean)
- `count` (integer)
- `notifications` (object[])
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/notifications" \
-H "X-API-Key: "
```
---
# Panel & Usage (/docs/crm/panel-and-usage)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Panel signup, health, keyβpanel resolution, and plan/usage counters. Works for OnlyFans and Fansly panels alike.
| Method | Endpoint | Path |
| ------ | -------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `POST` | [Dashboard user login](/docs/crm/panel-and-usage/dashboard-user-login) | `/api/auth/login` |
| `POST` | [Register a dashboard user](/docs/crm/panel-and-usage/register-a-dashboard-user) | `/api/auth/register` |
| `POST` | [Send an email verification code](/docs/crm/panel-and-usage/send-an-email-verification-code) | `/api/auth/start-email-verification` |
| `POST` | [Verify an email code](/docs/crm/panel-and-usage/verify-an-email-code) | `/api/auth/verify-email` |
| `POST` | [Create a CRM panel](/docs/crm/panel-and-usage/create-a-crm-panel) | `/api/crm/register` |
| `GET` | [Resolve an API key to a panel](/docs/crm/panel-and-usage/resolve-an-api-key-to-a-panel) | `/api/whoami` |
| `GET` | [Service health check](/docs/crm/panel-and-usage/service-health-check) | `/health` |
| `GET` | [Get request metrics for your panel](/docs/crm/panel-and-usage/get-request-metrics-for-your-panel) | `/metrics/requests` |
| `GET` | [Get plan usage](/docs/crm/panel-and-usage/get-plan-usage) | `/usage` |
---
# Create a CRM panel (/docs/crm/panel-and-usage/create-a-crm-panel)
`POST https://api.theonlyapi.com/api/crm/register`
Create a CRM panel and receive its `crm_id` plus the primary `Default` API key. The key is returned once β store it.
**Guide:** [Base URL & panels](/docs/base-url)
## Authentication
None β this endpoint is public.
## Responses
### 200 β Panel created
- `success` (boolean)
- `crm_id` (string)
- `api_key` (string)
## Example
```bash
curl -X POST "https://api.theonlyapi.com/api/crm/register"
```
---
# Dashboard user login (/docs/crm/panel-and-usage/dashboard-user-login)
`POST https://api.theonlyapi.com/api/auth/login`
Authenticate a dashboard user. Rate limited to **6 requests/minute** β the strictest limit in the API.
**Guide:** [Base URL & panels](/docs/base-url)
## Authentication
None β this endpoint is public.
## Request body
Content type: `application/json` (required)
- `email` (string, required)
- `password` (string, required)
## Responses
### 200 β Authenticated
- `crm_id` (string, required)
- `email` (string, required)
- `is_admin` (any, required)
- `name` (string, required)
- `success` (boolean, required)
- `user_id` (any, required)
### 401 β Invalid credentials
### 429 β Rate limit exceeded (6/minute)
## Example
```bash
curl -X POST "https://api.theonlyapi.com/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"string","password":"string"}'
```
---
# Get plan usage (/docs/crm/panel-and-usage/get-plan-usage)
`GET https://theonlyapi.com/api/crm/your-crm-id/usage`
Current plan and consumption for this panel.
Plans: **Free** β $0/mo, 1 connected account, 1,000 API calls/month. **Slots** β $20 per slot per month ($15 per slot at 15+ slots), 1 slot = 1 connected account, unlimited API calls. **Enterprise** β custom pricing and limits.
An `api_calls_limit` of `-1` means unlimited.
**Guide:** [Base URL & panels](/docs/base-url)
## Authentication
- `X-API-Key` header (apiKey)
## Responses
### 200 β Usage statistics
- `crm_id` (string)
- `plan` (string) β e.g. `"only-api-free"`.
- `api_calls_used` (integer)
- `api_calls_limit` (integer) β `1000` on Free, `-1` (unlimited) on paid plans.
- `accounts_used` (integer)
- `accounts_limit` (integer)
- `month` (string) β e.g. `"2026-07"`.
Example response:
```json
{
"crm_id": "crm_abc123",
"plan": "only-api-free",
"api_calls_used": 214,
"api_calls_limit": 1000,
"accounts_used": 1,
"accounts_limit": 1,
"month": "2026-07"
}
```
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/usage" \
-H "X-API-Key: "
```
---
# Get request metrics for your panel (/docs/crm/panel-and-usage/get-request-metrics-for-your-panel)
`GET https://theonlyapi.com/api/crm/your-crm-id/metrics/requests`
Request outcomes for your own panel over a time window: volume, status classes, error rate, latency, and the slowest and most error-prone routes. This is what the dashboard Overview charts.
The current partial bucket is included, so the numbers are current rather than up to a minute stale. `top_tenants` is always empty here β it exists only so one component can render this and the platform-wide admin view interchangeably.
**Guide:** [Base URL & panels](/docs/base-url)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `hours` (integer) β Window to report, in hours (1β2160, i.e. up to 90 days). Default `24`.
- `granularity` ("5m" | "15m" | "1h" | "6h" | "1d") β Bucket size for `series`. Defaults to a sensible size for `hours`. Also accepted as `bucket`.
- `limit` (integer) β How many rows in each top-N breakdown (1β50). Default `10`.
## Responses
### 200
`metrics` carries `range`, `totals`, `series`, `status_codes`, `top_routes`, `slowest_routes`, `top_error_routes`, `top_tenants` (empty) and `collector`.
- `metrics` (any, required)
- `success` (boolean, required)
### 400
`hours` or `limit` out of range, or an unknown `granularity`.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/metrics/requests" \
-H "X-API-Key: "
```
---
# Register a dashboard user (/docs/crm/panel-and-usage/register-a-dashboard-user)
`POST https://api.theonlyapi.com/api/auth/register`
Create a dashboard account. This **also creates your CRM panel** and returns its `crm_id` and primary `api_key` in the same response β there is no second step. Do *not* follow it with `POST /api/crm/register`: that creates a separate panel with no dashboard user attached, which you cannot log into or manage.
**Guide:** [Base URL & panels](/docs/base-url)
## Authentication
None β this endpoint is public.
## Request body
Content type: `application/json` (required)
- `email` (string, required)
- `password` (string, required)
## Responses
### 200 β User registered
- `crm_id` (string, required)
- `email` (string, required)
- `email_verified` (boolean, required)
- `name` (string, required)
- `success` (boolean, required)
- `user_id` (any, required)
### 400 β Invalid payload or email already in use
## Example
```bash
curl -X POST "https://api.theonlyapi.com/api/auth/register" \
-H "Content-Type: application/json" \
-d '{"email":"string","password":"string"}'
```
---
# Resolve an API key to a panel (/docs/crm/panel-and-usage/resolve-an-api-key-to-a-panel)
`GET https://api.theonlyapi.com/api/whoami`
Resolves the API key sent in `X-API-Key` to its `crm_id`. This is how the hosted MCP server turns a bearer token into a tenant. Returns only public-safe fields (panel id, plan info).
**Guide:** [Base URL & panels](/docs/base-url)
## Authentication
- `X-API-Key` header (apiKey)
## Responses
### 200 β Panel resolved
- `crm_id` (string)
- `plan` (string) β e.g. `"only-api-free"`.
### 401 β Missing or invalid API key
## Example
```bash
curl -X GET "https://api.theonlyapi.com/api/whoami" \
-H "X-API-Key: "
```
---
# Send an email verification code (/docs/crm/panel-and-usage/send-an-email-verification-code)
`POST https://api.theonlyapi.com/api/auth/start-email-verification`
**Guide:** [Base URL & panels](/docs/base-url)
## Authentication
None β this endpoint is public.
## Request body
Content type: `application/json` (required)
- `email` (string, required)
## Responses
### 200 β Verification email sent
- `expires_at` (any, required)
- `success` (boolean, required)
- `token` (any, required)
## Example
```bash
curl -X POST "https://api.theonlyapi.com/api/auth/start-email-verification" \
-H "Content-Type: application/json" \
-d '{"email":"string"}'
```
---
# Service health check (/docs/crm/panel-and-usage/service-health-check)
`GET https://api.theonlyapi.com/health`
Liveness probe. Exempt from rate limiting and requires no API key.
**Guide:** [Base URL & panels](/docs/base-url)
## Authentication
None β this endpoint is public.
## Responses
### 200 β Service is healthy
- `service` (string, required)
- `status` (string, required)
## Example
```bash
curl -X GET "https://api.theonlyapi.com/health"
```
---
# Verify an email code (/docs/crm/panel-and-usage/verify-an-email-code)
`POST https://api.theonlyapi.com/api/auth/verify-email`
**Guide:** [Base URL & panels](/docs/base-url)
## Authentication
None β this endpoint is public.
## Request body
Content type: `application/json` (required)
- `email` (string, required)
- `code` (string, required) β e.g. `"123456"`.
## Responses
### 200 β Email verified
- `api_key` (string, required)
- `crm_id` (string, required)
- `email` (string, required)
- `name` (string, required)
- `success` (boolean, required)
- `user_id` (any, required)
### 400 β Invalid or expired code
## Example
```bash
curl -X POST "https://api.theonlyapi.com/api/auth/verify-email" \
-H "Content-Type: application/json" \
-d '{"email":"string","code":"123456"}'
```
---
# Payouts (/docs/crm/payouts)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Withdrawal requests and payout account details. Creating a withdrawal is OnlyFans only.
| Method | Endpoint | Path |
| ------ | ---------------------------------------------------------------------- | ---------------------------------------- |
| `GET` | [Get payout account](/docs/crm/payouts/get-payout-account) | `/accounts/{of_user_id}/payout-account` |
| `GET` | [List withdrawal requests](/docs/crm/payouts/list-withdrawal-requests) | `/accounts/{of_user_id}/payout-requests` |
| `POST` | [Create payout request](/docs/crm/payouts/create-payout-request) | `/accounts/{of_user_id}/payout-requests` |
---
# Create payout request (/docs/crm/payouts/create-payout-request)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/payout-requests`
Create a withdrawal request for a connected account.
**OnlyFans only** β rejected for Fansly accounts. Requires an `X-Proxy` header (or a proxy saved on the account).
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `withdrawal_amount` (number, required) β Amount to withdraw in USD. e.g. `100`.
## Responses
### 200 β Payout request created
- `data` (any, required)
- `request` (any, required)
- `success` (boolean, required)
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/payout-requests" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"withdrawal_amount":100}'
```
---
# Get payout account (/docs/crm/payouts/get-payout-account)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/payout-account`
Payout/withdrawal account details for a connected account.
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Payout account details
- `account` (any, required)
- `balances` (any[], required)
- `blockers` (any[], required)
- `can_withdraw` (any, required)
- `check_receive` (any, required)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/payout-account" \
-H "X-API-Key: "
```
---
# List withdrawal requests (/docs/crm/payouts/list-withdrawal-requests)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/payout-requests`
Withdrawal request history for the account. For OnlyFans this is read live from the platform; for Fansly it is served from the synced wallet ledger.
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `limit` (integer) β Maximum number of rows to return. Default `100`.
- `offset` (integer) β Row offset for pagination. Default `0`.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Withdrawal request history
- `success` (boolean)
- `payout_requests` (object[])
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/payout-requests" \
-H "X-API-Key: "
```
---
# Proxy (/docs/crm/proxy)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Generic authenticated OnlyFans API proxy (`POST /accounts/{of_user_id}/request`). OnlyFans only.
| Method | Endpoint | Path |
| ------ | ------------------------------------------------------------ | -------------------------------- |
| `POST` | [Generic OF API proxy](/docs/crm/proxy/generic-of-api-proxy) | `/accounts/{of_user_id}/request` |
---
# Generic OF API proxy (/docs/crm/proxy/generic-of-api-proxy)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/request`
Send any authenticated request to the OnlyFans API. Use this to access any OF endpoint not covered by the dedicated CRM routes. Auto-relogins on session expiry.
**OnlyFans only** β rejected for Fansly accounts. For Fansly, use the platform-neutral CRM data routes.
**Guide:** [Proxies](/docs/proxies)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `path` (string, required) β OnlyFans API path. e.g. `"/api2/v2/users/me"`.
- `method` ("GET" | "POST" | "PUT" | "PATCH" | "DELETE") β Default `"GET"`.
- `body` (object) β Request body (for POST/PUT/PATCH).
## Responses
### 200 β Proxied response
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/request" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"path":"/api2/v2/users/me","method":"GET"}'
```
---
# Referrals (/docs/crm/referrals)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
OnlyFans referral programme β who the account referred, what it earned, and referral payout history. **OnlyFans only**: Fansly accounts get `501 platform_not_supported` (`feature: "referrals"`). Live reads, no cache; one quota call per request. OnlyFans' referral response bodies are passed through with minimal normalization β only the `list`/`hasMore`/`marker` envelope keys are read, and the raw body is echoed under `data`. Fields *inside* a referral row are unverified: do not depend on them without checking against a live account.
| Method | Endpoint | Path |
| ------ | ---------------------------------------------------------------------------------- | -------------------------------------------------- |
| `GET` | [List referred users](/docs/crm/referrals/list-referred-users) | `/accounts/{of_user_id}/referrals` |
| `GET` | [Referral balance + chart](/docs/crm/referrals/referral-balance-chart) | `/accounts/{of_user_id}/referrals/earnings` |
| `GET` | [List referral payout requests](/docs/crm/referrals/list-referral-payout-requests) | `/accounts/{of_user_id}/referrals/payout-requests` |
---
# List referral payout requests (/docs/crm/referrals/list-referral-payout-requests)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/referrals/payout-requests`
Referral payout history β the referral twin of `GET /accounts/{of_user_id}/payout-requests`, shaped identically.
The row shape inside `requests` is **unverified**; rows pass through untouched and the raw upstream body is echoed under `data`.
**OnlyFans only** β Fansly returns `501`.
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `startDate` (string) β Range start, `YYYY-MM-DD` or `YYYY-MM-DD HH:MM:SS`.
- `endDate` (string) β Range end, same format as startDate.
- `offset` (integer)
- `marker` (string) β Pagination cursor returned by OnlyFans.
- `limit` (integer)
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Referral payout requests
- `success` (boolean)
- `requests` (object[]) β Rows exactly as OnlyFans returned them β item fields unverified.
- `count` (integer)
- `marker` (any) β Upstream pagination cursor; pass back as `marker` for the next page.
- `data` (any) β Raw upstream body, verbatim.
### 400 β Invalid startDate/endDate format
### 403 β Account does not belong to this panel
### 501
Fansly account β referrals are not available on this platform
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/referrals/payout-requests" \
-H "X-API-Key: "
```
---
# List referred users (/docs/crm/referrals/list-referred-users)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/referrals`
The creators/users this account referred, read live from OnlyFans.
**OnlyFans only** β a Fansly account returns `501` with `code: "platform_not_supported"` and `feature: "referrals"`.
Query params are forwarded upstream **only when supplied** β no default date window is invented, because a wrong default silently truncates money figures.
The row shape inside `referrals` is **unverified** (OnlyFans' item fields were never captured); rows pass through untouched and the raw upstream body is echoed under `data`.
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `startDate` (string) β Range start, `YYYY-MM-DD` or `YYYY-MM-DD HH:MM:SS`.
- `endDate` (string) β Range end, same format as startDate.
- `offset` (integer)
- `marker` (string) β Pagination cursor returned by OnlyFans.
- `onlyPerformers` (string) β Restrict to referred creators/performers.
- `limit` (integer)
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Referred users
- `success` (boolean)
- `referrals` (object[]) β Rows exactly as OnlyFans returned them β item fields unverified.
- `count` (integer)
- `hasMore` (boolean)
- `data` (any) β Raw upstream body, verbatim.
### 400 β Invalid startDate/endDate format
### 403 β Account does not belong to this panel
### 501
Fansly account β referrals are not available on this platform
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/referrals" \
-H "X-API-Key: "
```
---
# Referral balance + chart (/docs/crm/referrals/referral-balance-chart)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/referrals/earnings`
Referral money summary in one response, fanned out to two OnlyFans endpoints (still one quota call).
`balance` and `chart` hold the **raw** upstream bodies β no field mapping. The two documentation sources disagree about the chart body, and the balance endpoint appears in no capture at all, so mapping fields would fabricate a contract.
Partial failure is tolerated: a source that fails comes back `null` and `sources` records its status. If **both** fail, the route returns the upstream status with `success: false` β an expired session must not be indistinguishable from "no referral earnings".
**OnlyFans only** β Fansly returns `501`.
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `startDate` (string) β Chart range start (forwarded to the chart call).
- `endDate` (string) β Chart range end.
- `withTotal` (string) β Include totals. Defaults to the value the OnlyFans web client sends. Default `1`.
- `withChart` (string) β Include the chart series. Defaults to the value the OnlyFans web client sends. Default `true`.
- `filter` (string) β Chart filter; forwarded verbatim when supplied.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Referral earnings (one source may be null)
- `success` (boolean)
- `balance` (any) β Raw body of the OnlyFans referral balance endpoint, or null if it failed. Shape unverified.
- `chart` (any) β Raw body of the OnlyFans referral chart endpoint, or null if it failed. Shape unverified.
- `sources` (object) β Per-source outcome, so a null above is explainable.
- `balance` (object)
- `ok` (boolean)
- `status` (integer)
- `chart` (object)
- `ok` (boolean)
- `status` (integer)
### 400 β Invalid startDate/endDate format
### 403 β Account does not belong to this panel
### 501
Fansly account β referrals are not available on this platform
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/referrals/earnings" \
-H "X-API-Key: "
```
---
# Settings (/docs/crm/settings)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Account settings, subscription price & proxy management
| Method | Endpoint | Path |
| ------- | ------------------------------------------------------------------------- | ------------------------------------------- |
| `GET` | [Get account proxy](/docs/crm/settings/get-account-proxy) | `/accounts/{of_user_id}/proxy` |
| `PATCH` | [Update account proxy](/docs/crm/settings/update-account-proxy) | `/accounts/{of_user_id}/proxy` |
| `GET` | [Get subscription price](/docs/crm/settings/get-subscription-price) | `/accounts/{of_user_id}/subscription-price` |
| `PATCH` | [Update subscription price](/docs/crm/settings/update-subscription-price) | `/accounts/{of_user_id}/subscription-price` |
| `POST` | [Test a proxy](/docs/crm/settings/test-a-proxy) | `/proxy/test` |
---
# Get account proxy (/docs/crm/settings/get-account-proxy)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/proxy`
Get the current proxy configured for an account.
**Guide:** [Cached reads](/docs/cached-reads)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Responses
### 200 β Proxy info
- `success` (boolean)
- `proxy` (string)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/proxy" \
-H "X-API-Key: "
```
---
# Get subscription price (/docs/crm/settings/get-subscription-price)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscription-price`
Read the account's current subscription price live from `/users/me`. Consumes a platform request.
**Guide:** [Cached reads](/docs/cached-reads)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Current subscription price
- `success` (boolean)
- `subscribePrice` (number) β e.g. `9.99`.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscription-price" \
-H "X-API-Key: "
```
---
# Test a proxy (/docs/crm/settings/test-a-proxy)
`POST https://theonlyapi.com/api/crm/your-crm-id/proxy/test`
Check that a proxy URL is reachable and usable before connecting an account with it. HTTP and SOCKS5 are both accepted.
**Guide:** [Cached reads](/docs/cached-reads)
## Authentication
- `X-API-Key` header (apiKey)
## Request body
Content type: `application/json` (required)
- `proxy` (string, required) β e.g. `"http://user:pass@host:port"`.
## Responses
### 200 β Proxy test result
- `geo` (any, required)
- `ip` (any, required)
- `latency_ms` (integer, required)
- `ok` (boolean, required)
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/proxy/test" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"proxy":"http://user:pass@host:port"}'
```
---
# Update account proxy (/docs/crm/settings/update-account-proxy)
`PATCH https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/proxy`
Update or remove the proxy for an account. Set to `null` to remove.
**Guide:** [Cached reads](/docs/cached-reads)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Request body
Content type: `application/json` (required)
- `proxy` (string) β e.g. `"http://user:pass@host:port"`.
## Responses
### 200 β Proxy updated
- `message` (string)
- `proxy` (string, required)
- `success` (boolean, required)
## Example
```bash
curl -X PATCH "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/proxy" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"proxy":"http://user:pass@host:port"}'
```
---
# Update subscription price (/docs/crm/settings/update-subscription-price)
`PATCH https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscription-price`
Change the subscription price for a connected OnlyFans account.
**Guide:** [Cached reads](/docs/cached-reads)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `subscribePrice` (number, required) β New subscription price in USD. e.g. `9.99`.
## Responses
### 200 β Price updated
- `data` (any)
- `isFree` (boolean)
- `subscribePrice` (number, required)
- `success` (boolean, required)
## Example
```bash
curl -X PATCH "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscription-price" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"subscribePrice":9.99}'
```
---
# Subscribers (/docs/crm/subscribers)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Subscriber reads. Live routes hit the platform; `/subscribers/cached`, `/subscribers/new`, and `/subscribers/stats` cost zero platform requests and normalize OnlyFans + Fansly rows to the same shape.
| Method | Endpoint | Path |
| ------ | ------------------------------------------------------------------------------------------------ | --------------------------------------------------- |
| `GET` | [List subscribers](/docs/crm/subscribers/list-subscribers) | `/accounts/{of_user_id}/subscribers` |
| `GET` | [List cached subscribers](/docs/crm/subscribers/list-cached-subscribers) | `/accounts/{of_user_id}/subscribers/cached` |
| `GET` | [List newly-seen subscribers](/docs/crm/subscribers/list-newly-seen-subscribers) | `/accounts/{of_user_id}/subscribers/new` |
| `POST` | [Refresh the subscriber cache (async)](/docs/crm/subscribers/refresh-the-subscriber-cache-async) | `/accounts/{of_user_id}/subscribers/refresh` |
| `GET` | [Subscriber refresh status](/docs/crm/subscribers/subscriber-refresh-status) | `/accounts/{of_user_id}/subscribers/refresh/status` |
| `GET` | [Subscriber statistics](/docs/crm/subscribers/subscriber-statistics) | `/accounts/{of_user_id}/subscribers/stats` |
---
# List cached subscribers (/docs/crm/subscribers/list-cached-subscribers)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscribers/cached`
Read subscribers from the local cache, populated by the background refresh job. **Zero platform requests.**
Platform-neutral: OnlyFans and Fansly rows are normalized to the same shape. Each row carries the raw platform payload merged with the flat spend/lifecycle projection (total spent, breakdown), which for Fansly is backfilled from the wallet ledger.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `limit` (integer) β Default `100`.
- `offset` (integer) β Row offset for pagination. Default `0`.
- `type` ("all" | "active" | "expired") β Default `all`.
## Responses
### 200 β Cached subscriber list
- `success` (boolean)
- `subscribers` (object[])
- `count` (integer)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscribers/cached" \
-H "X-API-Key: "
```
---
# List newly-seen subscribers (/docs/crm/subscribers/list-newly-seen-subscribers)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscribers/new`
Subscribers first seen within a recent window, read from the cache. **Zero platform requests.** Fansly-aware.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `since` (string) β ISO timestamp or date lower bound.
- `limit` (integer) β Maximum number of rows to return. Default `100`.
## Responses
### 200 β New subscriber list
- `count` (integer, required)
- `hasMore` (boolean, required)
- `limit` (integer, required)
- `offset` (integer, required)
- `subscribers` (any[], required)
- `success` (boolean, required)
- `total` (integer, required)
- `window` (object, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscribers/new" \
-H "X-API-Key: "
```
---
# List subscribers (/docs/crm/subscribers/list-subscribers)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscribers`
Get subscribers with pagination and type filtering. Includes total spent data.
**Paging:** advance with the `nextOffset` from the response, and stop when `hasMore` is `false` β not when `list` is empty. `offset` does not mean the same thing on both platforms: OnlyFans filters by type server-side so its offset counts rows *returned*, while Fansly filters client-side so its offset counts rows *consumed*, which can exceed `count`. Computing `offset + list.length` yourself silently skips subscribers on Fansly, and a page whose rows were all filtered out comes back with an empty `list` and more still behind it. `nextOffset` + `hasMore` are correct on both.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
- `type` ("all" | "active" | "expired") β Default `all`.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Subscriber list
- `success` (boolean)
- `list` (object[])
- `hasMore` (boolean) β The stop signal. An empty `list` is not β a page can filter to zero rows and still have more behind it.
- `count` (integer) β Length of `list` after type filtering. A page-size readout, not a cursor.
- `offset` (integer)
- `nextOffset` (integer) β The offset to pass for the next page. Always follow this rather than computing offset + list.length.
- `limit` (integer)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscribers" \
-H "X-API-Key: "
```
---
# Refresh the subscriber cache (async) (/docs/crm/subscribers/refresh-the-subscriber-cache-async)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscribers/refresh`
Start an async subscriber sync for this account. Returns **202** immediately β poll `GET /accounts/{of_user_id}/subscribers/refresh/status` or listen on `GET /events/stream` for completion. Works for OnlyFans and Fansly.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 202 β Refresh job accepted and running
- `already_running` (boolean)
- `state` (any, required)
- `success` (boolean, required)
### 409 β A refresh is already in progress
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscribers/refresh" \
-H "X-API-Key: "
```
---
# Subscriber refresh status (/docs/crm/subscribers/subscriber-refresh-status)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscribers/refresh/status`
Progress/state of the current or last subscriber refresh job. **Zero platform requests.**
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Responses
### 200 β Refresh job status
- `cache` (object, required)
- `active` (integer)
- `breakdown` (object)
- `consecutive_failures` (integer)
- `expired` (integer)
- `last_refreshed_at` (any)
- `last_row_synced_at` (any)
- `spenders` (integer)
- `total` (integer)
- `total_spent_sum` (number)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscribers/refresh/status" \
-H "X-API-Key: "
```
---
# Subscriber statistics (/docs/crm/subscribers/subscriber-statistics)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscribers/stats`
Time-bucketed counts of incoming subscriptions, plus the cache summary. **Zero platform requests.** Fansly-aware.
Query: `granularity` = `hour|day|week|month` (default `day`), `since`/`until` = inclusive ISO-8601 bounds on `subscribed_at`.
Buckets are ascending, contiguous and **zero-filled server-side** β a quiet day comes back as `count: 0`, not as a missing key, so the array plots directly. With both `since` and `until` the axis spans exactly that window even if it holds no subscriptions; otherwise it spans the first to the last bucket with data. Week buckets are Monday-start ISO dates.
`zero_filled` is `false` when the requested span exceeded the server's bucket ceiling (e.g. `granularity=hour` over an unbounded window): the series is then sparse and you must fill the gaps yourself. Narrow the window or coarsen the granularity.
**Guide:** [Subscribers & fans](/docs/subscribers)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Responses
### 200 β Subscriber statistics
- `buckets` (any, required)
- `cache` (object, required)
- `active` (integer)
- `breakdown` (object)
- `consecutive_failures` (integer)
- `expired` (integer)
- `last_refreshed_at` (any)
- `last_row_synced_at` (any)
- `spenders` (integer)
- `total` (integer)
- `total_spent_sum` (number)
- `granularity` (string, required)
- `success` (boolean, required)
- `total_in_window` (any, required)
- `window` (object, required)
- `zero_filled` (any, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/subscribers/stats" \
-H "X-API-Key: "
```
---
# Transactions (/docs/crm/transactions)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Payout & purchase transactions, live and cached.
| Method | Endpoint | Path |
| ------ | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `GET` | [Get purchase transactions](/docs/crm/transactions/get-purchase-transactions) | `/accounts/{of_user_id}/purchases` |
| `GET` | [List cached transactions](/docs/crm/transactions/list-cached-transactions) | `/accounts/{of_user_id}/transactions/cached` |
| `POST` | [Refresh the transaction cache (async)](/docs/crm/transactions/refresh-the-transaction-cache-async) | `/accounts/{of_user_id}/transactions/refresh` |
| `GET` | [Transaction refresh status](/docs/crm/transactions/transaction-refresh-status) | `/accounts/{of_user_id}/transactions/refresh/status` |
---
# Get purchase transactions (/docs/crm/transactions/get-purchase-transactions)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/purchases`
Fetch payout transactions with marker-based pagination.
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `startDate` (string)
- `marker` (string) β Pagination marker (use nextMarker from response).
- `limit` (integer) β Default `100`.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Transaction list
- `success` (boolean)
- `purchases` (object[])
- `hasMore` (boolean)
- `nextMarker` (string)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/purchases" \
-H "X-API-Key: "
```
---
# List cached transactions (/docs/crm/transactions/list-cached-transactions)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/transactions/cached`
Read transactions from the local cache. **Zero platform requests.** OnlyFans and Fansly rows are normalized to the same shape.
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Query parameters
- `limit` (integer) β Maximum number of rows to return. Default `100`.
- `offset` (integer) β Row offset for pagination. Default `0`.
- `since` (string) β ISO date lower bound.
- `until` (string) β ISO date upper bound.
## Responses
### 200 β Cached transaction list
- `cache` (object, required)
- `by_type` (any)
- `consecutive_failures` (integer)
- `last_refreshed_at` (any)
- `last_tx_marker` (any)
- `newest` (any)
- `oldest` (any)
- `total` (integer)
- `total_amount` (number)
- `total_net` (number)
- `count` (integer, required)
- `hasMore` (boolean, required)
- `limit` (integer, required)
- `list` (any[], required)
- `offset` (integer, required)
- `success` (boolean, required)
- `total` (integer, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/transactions/cached" \
-H "X-API-Key: "
```
---
# Refresh the transaction cache (async) (/docs/crm/transactions/refresh-the-transaction-cache-async)
`POST https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/transactions/refresh`
Start an async transaction sync. Returns **202** β poll `GET /accounts/{of_user_id}/transactions/refresh/status` or listen on `GET /events/stream`. Works for OnlyFans and Fansly.
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Header parameters
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 202 β Refresh job accepted and running
- `already_running` (boolean)
- `state` (any, required)
- `success` (boolean, required)
### 409 β A refresh is already in progress
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/transactions/refresh" \
-H "X-API-Key: "
```
---
# Transaction refresh status (/docs/crm/transactions/transaction-refresh-status)
`GET https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/transactions/refresh/status`
Progress/state of the current or last transaction refresh job. **Zero platform requests.**
**Guide:** [Earnings & transactions](/docs/earnings)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `of_user_id` (string, required) β Creator account ID of the connected account (OnlyFans or Fansly). Use `GET /accounts` to list connected accounts and their IDs.
## Responses
### 200 β Refresh job status
- `cache` (object, required)
- `by_type` (any)
- `consecutive_failures` (integer)
- `last_refreshed_at` (any)
- `last_tx_marker` (any)
- `newest` (any)
- `oldest` (any)
- `total` (integer)
- `total_amount` (number)
- `total_net` (number)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/accounts/{of_user_id}/transactions/refresh/status" \
-H "X-API-Key: "
```
---
# Webhooks (/docs/crm/webhooks)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Outbound webhook subscriptions, test fires, and delivery history.
| Method | Endpoint | Path |
| -------- | --------------------------------------------------------------------- | ----------------------------------- |
| `GET` | [List webhooks](/docs/crm/webhooks/list-webhooks) | `/webhooks` |
| `POST` | [Create a webhook](/docs/crm/webhooks/create-a-webhook) | `/webhooks` |
| `GET` | [Get a webhook](/docs/crm/webhooks/get-a-webhook) | `/webhooks/{webhook_id}` |
| `PATCH` | [Update a webhook](/docs/crm/webhooks/update-a-webhook) | `/webhooks/{webhook_id}` |
| `DELETE` | [Delete a webhook](/docs/crm/webhooks/delete-a-webhook) | `/webhooks/{webhook_id}` |
| `GET` | [List webhook deliveries](/docs/crm/webhooks/list-webhook-deliveries) | `/webhooks/{webhook_id}/deliveries` |
| `POST` | [Send a test delivery](/docs/crm/webhooks/send-a-test-delivery) | `/webhooks/{webhook_id}/test` |
---
# Create a webhook (/docs/crm/webhooks/create-a-webhook)
`POST https://theonlyapi.com/api/crm/your-crm-id/webhooks`
Subscribe an HTTPS endpoint to panel events. Rate limited to 120 requests/minute.
**Guide:** [Webhooks](/docs/webhooks)
## Authentication
- `X-API-Key` header (apiKey)
## Request body
Content type: `application/json` (required)
- `url` (string, required) β e.g. `"https://example.com/hooks/theonlyapi"`.
- `event_types` (string[], required) β Event types to deliver. **Required and non-empty** β omitting it returns `400 event_types must be a non-empty list`. Use `["*"]` for every type. e.g. `["new_subscriber","new_tip"]`.
- `description` (string) β Optional label for your own reference.
## Responses
### 200 β Webhook created
- `success` (boolean, required)
- `webhook` (any, required)
### 400 β Invalid URL or event type
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/webhooks" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/hooks/theonlyapi","event_types":["new_subscriber","new_tip"]}'
```
---
# Delete a webhook (/docs/crm/webhooks/delete-a-webhook)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/webhooks/{webhook_id}`
**Guide:** [Webhooks](/docs/webhooks)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `webhook_id` (integer, required)
## Responses
### 200 β Webhook deleted
- `success` (boolean, required)
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/webhooks/{webhook_id}" \
-H "X-API-Key: "
```
---
# Get a webhook (/docs/crm/webhooks/get-a-webhook)
`GET https://theonlyapi.com/api/crm/your-crm-id/webhooks/{webhook_id}`
**Guide:** [Webhooks](/docs/webhooks)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `webhook_id` (integer, required)
## Responses
### 200 β Webhook
- `success` (boolean, required)
- `webhook` (any, required)
### 404 β Not found
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/webhooks/{webhook_id}" \
-H "X-API-Key: "
```
---
# List webhook deliveries (/docs/crm/webhooks/list-webhook-deliveries)
`GET https://theonlyapi.com/api/crm/your-crm-id/webhooks/{webhook_id}/deliveries`
Delivery history with response status codes, for debugging failures.
**Guide:** [Webhooks](/docs/webhooks)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `webhook_id` (integer, required)
## Query parameters
- `limit` (integer) β Maximum number of rows to return. Default `100`.
## Responses
### 200 β Delivery history
- `deliveries` (object[], required)
- `attempt` (integer)
- `completed_at` (string)
- `created_at` (string)
- `event_id` (integer)
- `id` (integer)
- `next_retry_at` (string)
- `response_code` (integer)
- `response_snippet` (string)
- `status` (string)
- `webhook_id` (integer)
- `success` (boolean, required)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/webhooks/{webhook_id}/deliveries" \
-H "X-API-Key: "
```
---
# List webhooks (/docs/crm/webhooks/list-webhooks)
`GET https://theonlyapi.com/api/crm/your-crm-id/webhooks`
**Guide:** [Webhooks](/docs/webhooks)
## Authentication
- `X-API-Key` header (apiKey)
## Responses
### 200 β Webhook list
- `success` (boolean, required)
- `webhooks` (object[], required)
- `consecutive_failures` (integer)
- `created_at` (string)
- `crm_id` (string)
- `description` (string)
- `event_types` (any[])
- `id` (integer)
- `is_active` (integer)
- `last_delivery_at` (string)
- `last_status_code` (integer)
- `reject_reason` (string)
- `reviewed_at` (string)
- `status` (string)
- `url` (string)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/webhooks" \
-H "X-API-Key: "
```
---
# Send a test delivery (/docs/crm/webhooks/send-a-test-delivery)
`POST https://theonlyapi.com/api/crm/your-crm-id/webhooks/{webhook_id}/test`
Fire a synthetic event at the webhook so you can verify the endpoint and signature handling. The delivery carries X-OnlyAPI-Signature (sha256=), X-OnlyAPI-Timestamp, X-OnlyAPI-Event and X-OnlyAPI-Delivery-Id; the digest is HMAC-SHA256 over "{X-OnlyAPI-Timestamp}." + the raw body.
**Guide:** [Webhooks](/docs/webhooks)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `webhook_id` (integer, required)
## Responses
### 200 β Test delivery result
- `success` (boolean, required)
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/webhooks/{webhook_id}/test" \
-H "X-API-Key: "
```
---
# Update a webhook (/docs/crm/webhooks/update-a-webhook)
`PATCH https://theonlyapi.com/api/crm/your-crm-id/webhooks/{webhook_id}`
**Guide:** [Webhooks](/docs/webhooks)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `webhook_id` (integer, required)
## Request body
Content type: `application/json` (required)
- `url` (string)
- `event_types` (string[]) β Event types to deliver. **Required and non-empty** β omitting it returns `400 event_types must be a non-empty list`. Use `["*"]` for every type. e.g. `["new_subscriber","new_tip"]`.
- `description` (string) β Optional label for your own reference.
- `is_active` (boolean) β Enable or disable the webhook. Setting it true also resets the consecutive-failure counter, which is how you revive a webhook that was auto-deactivated.
## Responses
### 200 β Webhook updated
- `success` (boolean, required)
- `webhook` (any, required)
## Example
```bash
curl -X PATCH "https://theonlyapi.com/api/crm/your-crm-id/webhooks/{webhook_id}" \
-H "X-API-Key: " \
-H "Content-Type: application/json" \
-d '{"event_types":["new_subscriber","new_tip"]}'
```
---
# Earnings & transactions (/docs/earnings)
## Balances and earnings [#balances-and-earnings]
```bash
# One account, live
curl "$BASE/api/crm/$CRM/accounts/$OFUID/balances" -H "X-API-Key: $KEY"
curl "$BASE/api/crm/$CRM/accounts/$OFUID/earnings" -H "X-API-Key: $KEY"
# Whole panel, aggregated server-side across every connected account
curl "$BASE/api/crm/$CRM/balances/summary" -H "X-API-Key: $KEY"
curl "$BASE/api/crm/$CRM/earnings/summary" -H "X-API-Key: $KEY"
```
The two `/summary` routes are the ones to reach for when building a dashboard β
they aggregate in one request instead of N live calls, and they work across both
platforms. See
[`GET /balances/summary`](/docs/crm/earnings/get-panel-wide-payout-totals) and
[`GET /earnings/summary`](/docs/crm/earnings/aggregated-earnings-across-all-accounts)
for the full field lists.
`/balances/summary` serves last-known samples rather than live values β a live
panel-wide payout total would cost one platform round trip per account. Label it
with the `oldest_sample_at` / `newest_sample_at` it returns, and treat
`accounts_never_sampled` as "not counted yet" rather than zero. Each call to
`GET /accounts/{of_user_id}/balances` refreshes that account's sample.
## Transactions [#transactions]
```bash
# Cached β zero platform requests, trustworthy total
curl "$BASE/api/crm/$CRM/accounts/$OFUID/transactions/cached?limit=100&offset=0" \
-H "X-API-Key: $KEY"
# One fan's history
curl "$BASE/api/crm/$CRM/accounts/$OFUID/fans/$FANID/transactions/cached" \
-H "X-API-Key: $KEY"
# Live walk (cursor-paginated β carry the marker forward)
curl "$BASE/api/crm/$CRM/accounts/$OFUID/purchases?limit=100" -H "X-API-Key: $KEY"
```
Keep the cache current with
`POST /accounts/{of_user_id}/transactions/refresh` (see
[Async jobs](/docs/async-jobs)), and use
`POST /accounts/{of_user_id}/backfill` for history predating the connection.
## The chargeback trap [#the-chargeback-trap]
This is the single most important thing on this page. **Do not compute earnings with a plain sum over transaction amounts.**
A chargeback is the **original row relabelled** β `status` flips to `undo`, `tx_type`
becomes `chargeback`, and `amount`/`net` stay positive. There is no reversing entry.
That means the correct treatment differs by what you are computing:
| Computing | Treatment |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **Period revenue** (`/earnings/summary`) | **Exclude** `chargeback` rows. A naive sum overstates by one chargeback; subtracting would double-count the reversal |
| **Per-fan lifetime spend** | **Subtract** them β the platform's canonical lifetime total still contains the original sale, so the delta has to reverse it out |
For per-fan spend, sign each row by status:
| `status` | Meaning | Contribution |
| ------------- | ---------------------------------------------------- | --------------- |
| `done` | Cleared; withdrawable | `+net` |
| `loading` | Within the payout pending window (7 days by default) | `+net` |
| `undo` | Chargeback or refund | `βnet` |
| anything else | Unknown | `0` β ignore it |
```python
def signed_total(rows):
total = 0.0
for row in rows:
status = row.get("status")
# `net` is what the creator received (gross minus the platform fee).
# `amount` is what the fan paid. The canonical lifetime total is net, so
# summing `amount` here inflates the result by roughly 1.25x.
if status in ("done", "loading"):
total += row["net"]
elif status == "undo":
total -= row["net"]
# Unknown statuses contribute nothing. Deliberately not treated as
# positive β if the platform adds a status, you want a visible
# discrepancy rather than a silently wrong number.
return total
```
Treating an unrecognised status as income hides the problem. Contributing zero
makes a new status show up as a divergence you can notice and fix.
Deleted fans still count. The money was real.
## Per-fan lifetime spend [#per-fan-lifetime-spend]
`GET /fans` reports `total_spend` as `MAX(platform lifetime total, sum of captured
tip/purchase events)` β a hybrid that favours whichever source is higher. No signed
delta, no chargeback correction. It also returns `spend_known`: when that is `0`, the
spend is **unknown**, not zero. `total` appears only if you pass `?with_total=true`.
For the chargeback-corrected per-fan figure, read `mapped_spent` from
`GET /accounts/{of_user_id}/fans/{fan_id}/transactions/cached`.
The canonical model below is how per-fan spend is defined:
```
current_spent(fan) = canonical(fan) + signed_delta(fan, since = last subscriber sync)
```
* **canonical** is the platform's own lifetime total for that fan, as of the last
subscriber-cache sync.
* **signed\_delta** is the status-signed sum of that fan's transactions *after*
that sync timestamp.
The cutoff is strictly greater-than, so a transaction landing in the same second
as the sync stays inside `canonical` and is not double-counted.
A fan with transactions but no subscriber row yet gets `canonical = 0` and is
flagged `source: "tx-only"` in the result, so you can tell a genuinely small
spender from one we have not fully synced.
## Money scale differs by platform [#money-scale-differs-by-platform]
OnlyFans reports money as decimal dollars, and the CRM routes pass those through
untouched β `/api2/v2/*` and the CRM money routes agree, so mixing them is safe.
**Fansly reports integer tenths of a cent.** `497496` is `$497.50`. The CRM routes
divide by 1000 on read and multiply by 1000 on write.
Fansly amounts are not cents β they are tenths of a cent. Sending a raw dollar value
to a Fansly write produces a PPV priced 1000x too low. The `/api2/v2/*` passthrough
rejects Fansly accounts outright, so raw Fansly integers are not reachable there.
## Why two caches [#why-two-caches]
Subscriber rows carry the platform's **lifetime** total per fan but are only as
fresh as the last sync. Transactions are fresh and attributable per event but only
cover a recent window, not all history. Combining them gives both lifetime
accuracy and up-to-the-minute freshness β which is exactly what the formula above
does.
If a figure looks wrong, refresh both caches before investigating anything else:
```bash
curl -X POST "$BASE/api/crm/$CRM/accounts/$OFUID/subscribers/refresh" -H "X-API-Key: $KEY"
curl -X POST "$BASE/api/crm/$CRM/accounts/$OFUID/transactions/refresh" -H "X-API-Key: $KEY"
```
## Payouts [#payouts]
```
GET /accounts/{of_user_id}/payout-account # payout destination
GET /accounts/{of_user_id}/payout-requests # history
POST /accounts/{of_user_id}/payout-requests # request a payout β OnlyFans only
```
Requesting a payout is OnlyFans-only (`501` on Fansly) and sits on the sensitive
rate tier. It **is** behind `allow_of_write_actions`, like every other write performed as the
account. Body: `{"withdrawal_amount": 20}`. The route pre-flights the platform's own
payout eligibility, so a blocked account gets a `400` with a `blockers[]` array
rather than an opaque platform error.
## Referrals are separate money [#referrals-are-separate-money]
`GET /earnings/summary` sums the transaction cache, and referral payouts never land
there β so `by_category.referrals` is reported but is always `0`. Add referral money
explicitly if you need a true total β see
[CRM API β Referrals](/docs/crm/referrals):
```
GET /accounts/{of_user_id}/referrals
GET /accounts/{of_user_id}/referrals/earnings
GET /accounts/{of_user_id}/referrals/payout-requests
```
## Verifying a figure [#verifying-a-figure]
[`GET /accounts/{of_user_id}/earnings/verify`](/docs/crm/earnings/verify-cached-earnings-against-the-platform)
cross-checks the computed total against the platform's own chart for the same
window, and returns both numbers plus the difference. That tells you whether a
discrepancy is in the cache or in your aggregation.
It makes real upstream calls, so it is per-account and on the 100/minute sensitive
tier β do not fan it out across a panel. If the platform is unreachable,
`live_available` is `false` and `live_total` is `null`: that is "could not check",
not "matches".
---
# Events (/docs/events)
Polling is **opt-in per account**. Once you enable it β
`PATCH /accounts/{of_user_id}/polling` with `{"enabled": true}` β we poll that
account in the background, diff the results, and emit typed events. Those events are stored, broadcast over
[SSE](/docs/streaming), delivered to your [webhooks](/docs/webhooks), and
evaluated by your [automations](/docs/automations) β all from the same emission.
## Event types [#event-types]
| `event_type` | Emitted when | Key payload fields |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `new_subscriber` | A new subscription appears | `fan`, `price`, `subscribed_at`, `expire_at` |
| `renewed_subscriber` | An existing subscription renews | `fan`, `price`, `subscribed_at`, `expire_at` |
| `expired_subscriber` | The subscriber count drops (sampled every 10th poll) | `previous_total`, `new_total`, `delta` β **negative** |
| `new_tip` | A `tip` row appears in the transactions ledger | `fan`, `amount`, `net`, `currency`, `tx_type`, `status`, `description` |
| `new_message` | A new-message notification arrives | `fan`, `text` β the only type with a `text` field |
| `new_purchase` | A paid-content row appears β `tx_type` is `message`, `post`, `stream` or `chargeback` | `fan`, `amount`, `net`, `currency`, `tx_type`, `status`, `description` |
| `balance_increased` | **Fansly only** β the wallet balance rises. Never emitted for OnlyFans; the money signal there is `new_tip`/`new_purchase` | `delta`, `available`, `previous_balance`, `new_balance`, `currency` |
| `polling_paused` | Polling auto-pauses after 5 consecutive failures | `reason`, `failures` |
| `payout_completed` | Reserved β **not currently emitted** | β |
Background jobs additionally emit progress events on the SSE stream, which are not
stored or delivered to webhooks:
| Event | Emitted by |
| --------------------------------------- | --------------------------------------------------------------------------------------------- |
| `export.progress` / `export.complete` | [Data exports](/docs/exports) |
| `refresh.progress` / `refresh.complete` | Cache refreshes and backfills, including the automatic backfill when you first enable polling |
| `import.progress` / `import.complete` | [Bulk account import](/docs/bulk-import) β progress is coalesced to at most one per second |
Webhook subscriptions also accept `*` as a wildcard for every type. `*` is a
subscription filter only β it is never the `event_type` of a delivered event.
## Envelope [#envelope]
```json
{
"id": 12345,
"event_type": "new_tip",
"crm_id": "crm_0123456789abcdef",
"of_user_id": "1234567",
"occurred_at": "2026-04-18T12:34:56.789",
"payload": {
"fan": {
"id": "987",
"username": "somefan",
"display_name": "Some Fan",
"avatar": "https://β¦"
},
"amount": 10.0,
"net": 8.0,
"currency": "USD",
"tx_type": "tip",
"status": "done",
"description": "Tip from somefan",
"created_at": "2026-04-18T12:34:50+00:00"
}
}
```
## Reading past events [#reading-past-events]
```bash
curl "$BASE/api/crm/$CRM/events?types=new_tip,new_subscriber&limit=100" \
-H "X-API-Key: $KEY"
```
| Parameter | Purpose |
| ----------------- | ------------------------------------------------------------------------------------------------------------ |
| `types` | Comma-separated event types |
| `of_user_id` | Restrict to one connected account |
| `since` / `until` | ISO bounds on `created_at` (when we recorded it), not `occurred_at`. `since` is exclusive, `until` inclusive |
| `limit` | Default 100, max 500 |
This route is panel-wide and takes no `offset` β narrow with `since`/`until`
rather than paging.
## Behaviours worth knowing [#behaviours-worth-knowing]
`new_subscriber` and `renewed_subscriber` are derived exclusively from the
platform's subscriber feed, distinguished by whether the entry is a subscribe or a
renewal. Notifications are not used for this β they carry no reliable renewal
indicator, so using them would double-count renewals as new subscribers.
`new_tip` and `new_purchase` take `amount` and `net` **directly from the transactions ledger** β real numbers. The exception is `new_message`, whose `amount`
is best-effort-parsed out of a formatted display string (`"$25.00"`) and is often
`null`. Reconcile money against [cached transactions](/docs/earnings) regardless.
This event is derived from a drop in the total subscriber count, sampled only on
every 10th poll β roughly every 20 minutes at the default interval, so several
expiries collapse into one event. `delta` is `new_total - previous_total`, so it is
**negative** (`-3` means three expired), and it carries no fan identity.
To recover *who*, read `GET /subscribers/cached?type=expired`, which names them with
each row's `expired_at`.
Events are unique on `(crm_id, of_user_id, event_type, source_event_id)`.
`source_event_id` is a key we derive per source β the transaction id, the fan id plus
subscribe timestamp, and so on β not always the platform's own id. A poll that
re-walks the same record does not emit twice. Your handler should still be idempotent β webhook *delivery*
retries on failure, so the same event can arrive more than once.
## Choosing a delivery mechanism [#choosing-a-delivery-mechanism]
Best for servers. Signed, retried, with a delivery log. Survives your process
restarting.
Best for dashboards and live UI. No retry β if you disconnect, you miss what
happened while you were gone.
No infrastructure at all. Match a condition, fire a Discord/Slack/Telegram
message, a DM, or a fan tag.
Simplest, and the right fallback for batch reconciliation.
---
# Data exports (/docs/exports)
An async, per-account export. You start a job, it walks the selected data β cheap
cached tables first, expensive live platform walks last β packages **CSV + JSON into a ZIP**, and reports progress live over [SSE](/docs/streaming). The ZIP stays
downloadable for **7 days**.
## Endpoints [#endpoints]
| Method | Path | Purpose |
| -------- | -------------------------------------------------- | -------------------------- |
| `POST` | `/accounts/{of_user_id}/exports` | Start a job β `202` |
| `GET` | `/accounts/{of_user_id}/exports` | History, paginated |
| `GET` | `/accounts/{of_user_id}/exports/{job_id}` | One job's status |
| `GET` | `/accounts/{of_user_id}/exports/{job_id}/download` | Download the ZIP |
| `POST` | `/accounts/{of_user_id}/exports/{job_id}/cancel` | Stop a running job |
| `DELETE` | `/accounts/{of_user_id}/exports/{job_id}` | Delete the job and its ZIP |
## Start an export [#start-an-export]
```bash
curl -X POST "$BASE/api/crm/$CRM/accounts/$OFUID/exports" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"data_types": ["account","subscribers","transactions","fans","earnings","messages"],
"since": "2026-06-01",
"until": null,
"include_media": false
}'
```
| Field | Type | Notes |
| --------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data_types` | `string[]` | Non-empty subset of `account`, `subscribers`, `transactions`, `fans`, `earnings`, `messages` |
| `since` | `"YYYY-MM-DD"` \| `null` | Lower bound |
| `until` | `"YYYY-MM-DD"` \| `null` | Upper bound. Both null means all-time |
| `include_media` | `boolean` | Download message media. **Required:** `messages` must be in `data_types`, or this is silently ignored. OnlyFans only β a Fansly export records a warning and lists attachment URLs in the message JSON instead |
The `202` response includes a warning worth heeding:
```json
{
"success": true,
"job": { "job_id": "f01152d3β¦", "status": "queued", β¦ },
"warning": "Messages and media are fetched live from the platform β this can take several minutes and counts against your API quota."
}
```
`account`, `subscribers`, `fans` and (on OnlyFans) `transactions` are cached reads
and nearly free. **`earnings` costs 1β2 live calls.** Fansly `transactions` falls
back to a live wallet walk of up to 20 pages when its cache is cold. `messages` β
and especially `include_media` β is the expensive one. Export messages when you need
them, not by default.
## One export at a time [#one-export-at-a-time]
A second request while one is in flight returns the **existing** job rather than an
error:
```json
{ "already_running": true, "job": { β¦ } }
```
Unless that job is stale β no terminal status for over **30 minutes** β in which
case it is marked failed and yours starts fresh.
## Track progress [#track-progress]
Poll:
```bash
curl "$BASE/api/crm/$CRM/accounts/$OFUID/exports/$JOB_ID" -H "X-API-Key: $KEY"
```
Or stream, which is what the dashboard does:
```bash
curl -N "$BASE/api/crm/$CRM/events/stream" -H "X-API-Key: $KEY"
```
```
event: export.progress
data: {"job_id":"β¦","status":"running","phase":"messages","phase_index":6,
"phase_total":7,"counts":{β¦},"updated_at":"β¦"}
event: export.complete
data: {"job_id":"β¦","status":"complete",β¦}
```
`phase_index` / `phase_total` give you a real progress bar; `phase` names what is
running. Because messages come last, a job can sit at high progress for a long
time β that is the live walk, not a stall.
## Download [#download]
```bash
curl -L -o export.zip \
"$BASE/api/crm/$CRM/accounts/$OFUID/exports/$JOB_ID/download" \
-H "X-API-Key: $KEY"
```
Responds `application/zip`. What each type produces differs:
| Type | Files |
| ------------------------------------- | --------------------------------------------------------------- |
| `subscribers`, `transactions`, `fans` | `.json` **and** `.csv` |
| `account`, `earnings` | `.json` only |
| `messages` | one JSON per conversation, plus `messages.csv` and `index.json` |
Available for 7 days, then cleaned up. `DELETE` the job to remove it sooner.
Statuses are `queued`, `running`, `complete`, `failed`, `canceled`, `expired` β the
terminal success value is `complete`, not `completed`. A poll loop waiting for
`"completed"` never fires.
## Cancelling [#cancelling]
```bash
curl -X POST "$BASE/api/crm/$CRM/accounts/$OFUID/exports/$JOB_ID/cancel" \
-H "X-API-Key: $KEY"
```
Worth doing if you started a media export by accident β it will otherwise keep
consuming quota until it finishes.
## There is a UI for this [#there-is-a-ui-for-this]
`Dashboard β Export` drives exactly these endpoints. If a human needs a one-off
download rather than an integration, send them there.
---
# FAQ (/docs/faq)
The risk is real and it comes almost entirely from **request pacing**, not from the
API itself. We keep sessions stable, sign requests with the current algorithm, and
route each account through the proxy you supplied β the things that get accounts
flagged when done wrong. (If you supply no proxy, traffic egresses on our server IP;
see below.)
What is left to you: pace your live calls. As a reference point, our own background
poller runs at roughly 3β5 requests per *two minutes* per account, and internal
sweeps self-throttle to about 1 req/s β that is the shape of traffic the platforms
are comfortable with.
The [cached routes](/docs/cached-reads) cost zero platform requests, so read from
those and refresh them on a schedule rather than polling live endpoints in a loop.
See [Rate limits & quotas](/docs/rate-limits).
For OnlyFans, in practice yes β but it is **not enforced**. Omit it and the session
is created from our server's IP and pinned there, which is what gets accounts flagged
later. A malformed `X-Proxy` is silently ignored rather than rejected, so a typo
degrades to direct egress without any error.
Use one sticky residential or ISP proxy per account, and keep the exit location
stable. Rotating proxies defeat the purpose. See [Proxies](/docs/proxies).
Only requests that actually reach the platform: live reads, refreshes, backfills,
payout calls and every `/api2/v2/*` call β about 30 of \~145 routes. **Cached reads, async-job status polls and background polling are free**, in quota as well as in
platform requests.
`GET /health` and `GET /events/stream` are also exempt from the per-minute limit.
Check consumption at `GET /api/crm/{crm_id}/usage`; `api_calls_limit: -1` means
unlimited.
The normalized CRM routes are Fansly-aware and return the same shapes for both
platforms. Not supported for Fansly: the `/api2/v2/*` passthrough (OnlyFans-only by
definition), payout requests, campaign claimers, mass DM, campaign *creation*,
subscription-price *updates*, PPV stats, the three referrals routes, and real-time
WebSocket (Fansly is polling-only). Most return `501` with
`code: "platform_not_supported"` plus `platform` and `feature`.
Full matrix in [OnlyFans & Fansly](/docs/platforms).
You are not talking to the API. `_notice` comes from our honeypot layer on **decoy paths** β most commonly `https://theonlyapi.com/api/health`, which is bait and
returns fabricated data. The selection is by path, not by whether you authenticated.
Real API paths are panel-scoped: `https://theonlyapi.com/api/crm/{crm_id}/β¦`. A
missing or wrong key there returns a plain `401`/`403` JSON error with no `_notice`.
Details in [Responses & errors](/docs/responses).
It does not. There is no User-Agent check anywhere on the API β a `curl` request and
a browser-UA request get byte-identical responses, and `-A "Mozilla/5.0"` changes
nothing. If you are seeing decoy data, you are on a decoy **path** (see the previous
answer).
OnlyFans write operations are gated per account, off by default:
```bash
curl -X PATCH "$BASE/api/crm/$CRM/accounts/$OFUID/polling" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"allow_of_write_actions": true}'
```
The gate now covers **every OnlyFans or Fansly write performed as the account**:
single DM/PPV sends (both platforms), the mass-DM real send, the `send_dm`
automation action, payout requests, subscription-price changes, the raw
`POST /accounts/{of_user_id}/request` proxy, and **any non-GET request through the `/api2/v2/*` passthrough**. Reads are never gated.
Match on `code: "WRITES_DISABLED"` rather than the message text.
Almost certainly the pagination quirks. `/chats` ignores `limit` and returns 10β14
conversations regardless, and a short page from `/messages` does **not** mean the end
of the conversation.
Note the CRM `/messages` route is **offset**-paged; the `id=` cursor works only on the
`/api2/v2/chats/{fan_id}/messages` passthrough, which is the reliable way to walk a
full history. See [Pagination](/docs/pagination).
Two likely causes. You are summing `amount` (the gross the fan paid) instead of
`net` (what the creator received) β that inflates the total by roughly 1.25x. And you
are counting chargebacks: a reversal is the **original row relabelled** to
`status: "undo"` / `tx_type: "chargeback"` with the amount still positive.
For period revenue, **exclude** chargeback rows β a naive sum overstates by one
chargeback, and subtracting double-counts the reversal. For per-fan lifetime spend,
subtract them. See [the chargeback trap](/docs/earnings).
First check `status` β a webhook on an unapproved domain is `pending` and delivers
nothing at all. Otherwise: five consecutive failed deliveries (each an exhausted
retry ladder) deactivates it until you re-enable it with `PATCH /webhooks/{id}` and
`{"is_active": true}`. Check
`GET /webhooks/{id}/deliveries` for the failures, fix the endpoint, re-enable, then
backfill the gap from `GET /events?since=β¦`.
See [Webhooks](/docs/webhooks).
The session probably died. After five consecutive failures, polling for that
account is paused and a `polling_paused` event is emitted. Reconnecting clears the
session block but **not** the pause β re-enable polling explicitly with
`PATCH /accounts/{of_user_id}/polling` and `{"enabled": true}`.
Set up a `polling_paused` [automation](/docs/automations) so you hear about this
before your users do. See [Sessions & re-login](/docs/sessions).
Not with your API key. A key grants full access to the panel, so it must never reach
client-side JavaScript β a browser cannot keep a secret.
Cross-origin requests to `/api/*` are restricted to an allowlist, so a page on
another domain cannot read your panel even if it has a key.
Proxy through your own backend and attach the key server-side. For live events this
is required regardless, because the browser `EventSource` API cannot set headers β
there is a worked example in [Server-sent events](/docs/streaming).
Not yet. The API is plain REST with a single header, and there is an
[OpenAPI 3.1 spec](https://theonlyapi.com/api/openapi.json) you can point a
generator at.
For AI-assisted work, the [MCP server](/docs/ai-agents) is usually faster than
generating a client.
Three differences to plan for:
1. **Panel-scoped paths.** Every route carries your `crm_id`:
`https://theonlyapi.com/api/crm/{crm_id}/β¦`. Seven unscoped routes are the
exception, and they live on `api.theonlyapi.com` β see
[Base URL & panels](/docs/base-url).
2. **Two surfaces.** Providers that only proxy OnlyFans map onto our
`/api2/v2/*` passthrough. The normalized CRM layer β cached reads,
aggregations, events, exports β has no equivalent and is where the value is.
See [The two surfaces](/docs/two-surfaces).
3. **Slot pricing, not per-call metering.** Paid plans have unlimited API calls;
you pay per connected account. So caching is about protecting the *platform's*
limits, not your bill.
Sessions do not transfer. Reconnect each account with
[session paste](/docs/connect-an-account) β it takes about a minute per account.
[Contact us](https://theonlyapi.com/contact). For a `500`, include the
`correlation_id` from the response body β it maps to the full server-side
traceback, which is deliberately not returned to callers.
---
# Get an API key (/docs/get-an-api-key)
Every request needs an `X-API-Key` header. Keys are scoped to a single CRM
panel.
## Get your first key [#get-your-first-key]
Sign up on the website. Your key already exists by the time you land in the
dashboard.
Registration happens through the website form. Creating the account also
creates your CRM panel and mints its first API key, in the same step.
Your `crm_id` and key are here, alongside per-key usage and the controls to
mint or revoke additional keys.
There is no separate "create a panel" step and no API call to make. A panel is
the tenant your key belongs to, and one is provisioned for you at signup β
`crm_id`, primary key, and the user record linking them are all written together.
The rest of this page assumes you are signed in and looking at
**Dashboard β API Keys**.
## Primary and secondary keys [#primary-and-secondary-keys]
A panel has exactly one **primary** key (named `Default`) plus any number of
**secondary** keys.
| | Primary | Secondary |
| ---------------------------------------------------- | ---------------------- | --------- |
| Read and write panel data | yes | yes |
| Create a secondary key (`POST /api-keys`) | yes | **403** |
| Revoke a secondary key (`DELETE /api-keys/{key_id}`) | yes | **403** |
| Can be revoked | no β rotate it instead | yes |
Use secondary keys to give each service, environment or teammate its own
credential, so you can revoke one without disrupting everything else. Per-key
usage is tracked separately.
`POST /api-keys` returns the full key in that response and never again. List
endpoints only ever return a `prefix`. If you lose a secondary key, revoke it
and mint a new one.
```bash
curl -X POST "$BASE/api/crm/$CRM/api-keys" \
-H "X-API-Key: $PRIMARY_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "production-worker"}'
```
## Rotate a key [#rotate-a-key]
Rotation replaces the key you are calling with. The old key dies **immediately**
and the new one is returned once in the response body.
```bash
curl -X POST "$BASE/api/crm/$CRM/rotate-key" \
-H "X-API-Key: $KEY"
```
The primary `Default` key cannot be revoked β rotate it.
## Resolve a key to a panel [#resolve-a-key-to-a-panel]
If you have a key but not its `crm_id`:
```bash
curl "https://api.theonlyapi.com/api/whoami" \
-H "X-API-Key: $KEY"
```
This is also how the hosted MCP server turns a bearer token into a tenant.
## Check your usage [#check-your-usage]
```bash
curl "$BASE/api/crm/$CRM/usage" -H "X-API-Key: $KEY"
```
Returns `plan`, `api_calls_used`, `api_calls_limit`, `accounts_used` and
`accounts_limit`. An `api_calls_limit` of `-1` means unlimited. Per-key detail β
30/90-day series, month and all-time totals, endpoint breakdown β is at
`GET /api-keys/{key_id}/usage`.
## Keeping keys safe [#keeping-keys-safe]
* Send keys in the `X-API-Key` **header** only. The API deliberately does not
read a key from a query parameter, so keys never land in access logs.
* Some keys in circulation are **canary tokens**. Using a key you found rather
than one you were issued raises an alert while still failing as
`Invalid API key`.
[Authentication](/docs/authentication) covers what each failure code means.
---
# Integrations (/docs/integrations)
Panel-scoped and authenticated exactly like every other CRM route. Each endpoint
below links to its full reference page.
## Telegram notifications [#telegram-notifications]
A first-class Telegram integration, separate from the `telegram` action available
in [automations](/docs/automations). Pairing is done through a deep link, so a bot
token never has to be sent to a chat.
| Endpoint | What it does |
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| [`GET /integrations/telegram`](/docs/crm/integrations/get-the-telegram-integration) | Current integration + whether a shared bot is available |
| [`POST /integrations/telegram/pair`](/docs/crm/integrations/start-telegram-pairing) | Start or restart pairing β returns a `t.me` deep link |
| [`PATCH /integrations/telegram`](/docs/crm/integrations/update-the-telegram-integration) | Update event types or pause delivery (404 if none configured) |
| [`POST /integrations/telegram/test`](/docs/crm/integrations/send-a-telegram-test-message) | Send a test notification |
| [`DELETE /integrations/telegram`](/docs/crm/integrations/remove-the-telegram-integration) | Remove the integration |
| [`POST /integrations/telegram/groups`](/docs/crm/integrations/register-telegram-groups) | **List** the groups your own bot can see |
### Pairing [#pairing]
```bash
curl -X POST "$BASE/api/crm/$CRM/integrations/telegram/pair" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"bot_mode": "shared",
"event_types": ["new_tip", "new_subscriber"]
}'
```
| Field | Notes |
| ------------- | -------------------------------------------------------------------- |
| `bot_mode` | `"shared"` to use the platform's own bot, or `"custom"` for your own |
| `bot_token` | Required for `"custom"` only |
| `event_types` | Optional filter; omit for everything |
The response is a `t.me` deep link β **never a token**. Open it and start the bot
to complete pairing.
`GET /integrations/telegram` reports `shared_bot_available`, so a UI can hide the
shared-bot option on a deployment where no shared bot is configured rather than
offering a button that fails.
### Read, test, remove [#read-test-remove]
```bash
curl "$BASE/api/crm/$CRM/integrations/telegram" -H "X-API-Key: $KEY"
curl -X POST "$BASE/api/crm/$CRM/integrations/telegram/test" -H "X-API-Key: $KEY"
curl -X DELETE "$BASE/api/crm/$CRM/integrations/telegram" -H "X-API-Key: $KEY"
```
`PATCH` returns `404 No Telegram integration configured` if you have not paired
yet β pair first, then update.
`POST /integrations/telegram/groups` **lists** the group chats your bot can see so a
UI can offer a picker β it registers nothing. It requires `{"bot_token": "β¦"}` and is
custom-bot only; passing the shared platform token is refused with `400`, because
that token is a single-consumer stream.
Writes here are on the 100/minute sensitive-route limit; the `GET` is on the
default 100/minute.
## Account tags [#account-tags]
Tag connected accounts (distinct from [fan tags](/docs/subscribers)) β useful for
grouping creators by manager, niche or status.
| Endpoint | What it does |
| -------------------------------------------------------------------------------------- | ----------------------------------------- |
| [`POST /accounts/{of_user_id}/tags`](/docs/crm/accounts/tag-a-connected-account) | Add a tag β body `{"tag": "vip-creator"}` |
| [`DELETE /accounts/{of_user_id}/tags/{tag}`](/docs/crm/accounts/remove-an-account-tag) | Remove a tag |
```bash
curl -X POST "$BASE/api/crm/$CRM/accounts/$OFUID/tags" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"tag": "managed-by-alex"}'
```
Tags are capped at 40 characters. The full set for the panel comes back as
`all_tags` on `GET /accounts`.
Tag values are HTML-escaped once when stored. The `DELETE` path segment is
deliberately **not** re-escaped, so pass the tag exactly as it was stored β a
double-escaped value silently matches nothing and deletes nothing.
## Referrals [#referrals]
Full reference: [CRM API β Referrals](/docs/crm/referrals).
Referral money is separate from subscription and tip revenue, so it is not
included in `GET /earnings/summary` β add it explicitly if your reporting needs a
true total. See [Earnings & transactions](/docs/earnings).
## Earnings verification [#earnings-verification]
```
GET /accounts/{of_user_id}/earnings/verify
```
Cross-checks the computed earnings figures against the platform's own totals.
Useful when a number looks wrong and you want to know whether the discrepancy is
in the cache or in your own aggregation β see
[the chargeback trap](/docs/earnings) for the most common cause.
## Request metrics [#request-metrics]
[`GET /metrics/requests`](/docs/crm/panel-and-usage/get-request-metrics-for-your-panel)
returns your panel's own request outcomes over a window β volume, status classes,
error rate, latency, and the slowest and most error-prone routes. It is what the
dashboard Overview charts, and it includes the current partial bucket so the
numbers are not up to a minute stale.
## Related guides [#related-guides]
| Endpoint | Guide |
| ------------------------------------------------------------------------------------------------------------ | ----------------------------------------- |
| The eight [`/import/*`](/docs/crm/bulk-import) routes | [Bulk account import](/docs/bulk-import) |
| [`GET /balances/summary`](/docs/crm/earnings/get-panel-wide-payout-totals) | [Earnings & transactions](/docs/earnings) |
| [`GET /accounts/{of_user_id}/fansly-credentials`](/docs/crm/accounts/read-a-fansly-account-s-stored-session) | [OnlyFans & Fansly](/docs/platforms) |
| [`POST /accounts/{of_user_id}/refresh/{kind}/clear`](/docs/crm/cache-and-sync/clear-a-stuck-refresh-job) | [Async jobs](/docs/async-jobs) |
Discord, Slack and OnlyFans DM notifications are delivered through
[automation actions](/docs/automations) β you configure them as part of a rule
rather than as a standalone integration. Only Telegram has dedicated endpoints,
because of the pairing handshake.
---
# Messaging & mass DM (/docs/messaging)
## Read a conversation [#read-a-conversation]
```bash
# List conversations
curl "$BASE/api/crm/$CRM/accounts/$OFUID/chats" -H "X-API-Key: $KEY"
# Message history with one fan
curl "$BASE/api/crm/$CRM/accounts/$OFUID/chats/$FANID/messages" -H "X-API-Key: $KEY"
```
`/chats` largely ignores `limit` and returns 10β14 conversations regardless, and
`/messages` uses an `id=` cursor where a short page does **not** mean the end. A
walker that gets this wrong loses most of a message history. Read
[Pagination](/docs/pagination) before writing one.
## Send a DM or PPV [#send-a-dm-or-ppv]
```bash
curl -X POST "$BASE/api/crm/$CRM/accounts/$OFUID/chats/$FANID/messages" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Hey! New drop is up π",
"price": 0,
"mediaFiles": []
}'
```
* `price > 0` makes the message a **PPV**.
* `mediaFiles` takes vault media IDs, locked behind the price.
* Works for OnlyFans and Fansly.
Rate limited to 100 requests/minute.
OnlyFans sends are gated per account by `allow_of_write_actions`. Without it you
get a `403` with `code: "WRITES_DISABLED"` (match on the code, not the message).
This applies to Fansly sends too:
```bash
curl -X PATCH "$BASE/api/crm/$CRM/accounts/$OFUID/polling" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"allow_of_write_actions": true}'
```
## Mass DM [#mass-dm]
**OnlyFans only** β Fansly returns `501` with
`{"code": "PLATFORM_NOT_SUPPORTED"}`.
```bash
curl -X POST "$BASE/api/crm/$CRM/accounts/$OFUID/messages/mass" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "New set just dropped π₯",
"price": 12,
"mediaFiles": ["1234567"],
"previews": ["1234567"],
"audience": {
"min_spent": 20,
"since": "2026-01-01T00:00:00Z"
},
"dry_run": true
}'
```
A message needs `text`, `mediaFiles`, or both β an empty send is rejected with
`400 message requires text or mediaFiles`.
### `dry_run` defaults to true [#dry_run-defaults-to-true]
This is the important detail. `dry_run` is **`true` unless you explicitly set it to `false`**. In dry-run mode the API resolves the audience, counts the recipients and
returns a sample β **without sending anything**.
```json
{
"success": true,
"dry_run": true,
"recipients": 418,
"sent": 0,
"sample": [ { "fan_of_user_id": "987", "username": "somefan" } ],
"note": "Preview only β no messages were sent."
}
```
`recipients` is the resolved count; `sample` is the first **10** only.
This is the "who will this actually reach?" preview, and it costs nothing. Check
the count and the sample, then re-send the identical body with
`"dry_run": false`.
A mass DM cannot be recalled once sent.
### Audience filters [#audience-filters]
| Field | Effect |
| ----------------- | --------------------------------------------------------------------------- |
| `type` | `all` \| `active` \| `expired` β **defaults to** `active` |
| `fan_ids` | Explicit id list. **Overrides every other filter.** Truncated at 5,000 |
| `min_spent` | Only fans who have spent at least this much |
| `since` / `until` | Inclusive bounds on the fan's `subscribed_at` |
Omitting `type` reaches only **current** subscribers, not everyone.
There is no `tag` field β the audience is resolved from the subscriber cache, which
carries no tags, so a `tag` key is silently ignored. To target a segment, resolve it
yourself with `GET /fans`, then pass the ids as `audience.fan_ids`.
## PPV performance [#ppv-performance]
```bash
curl "$BASE/api/crm/$CRM/accounts/$OFUID/ppv-stats" -H "X-API-Key: $KEY"
```
Unlock rates and revenue per PPV message, so you can tell which sends actually
earned.
## Vault media [#vault-media]
Media IDs for `mediaFiles` and `previews` come from the vault, which lives on the
[passthrough surface](/docs/two-surfaces) β see the **Content** tag in the
[OnlyFans passthrough reference](/docs/of-api). Upload and vault management are not
exposed on the CRM layer.
`previews` should be a subset of `mediaFiles`: those items stay unlocked as a teaser
while the rest sit behind the price.
## Pacing [#pacing]
Sends are OnlyFans writes and hit the platform directly. See
[Rate limits](/docs/rate-limits).
The real send is **not** dispatched to a background worker and is **not** paced. It
loops through recipients synchronously inside the HTTP request β up to 5,000 sends
on one connection. Keep audiences small enough that the request completes, and pace
by splitting the audience with `since`/`until` rather than relying on server-side
throttling.
---
# OnlyFans Passthrough (/docs/of-api)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
A thin, signed proxy over OnlyFans' own `/api2/v2` API. We load the account's
saved session, route through its proxy, generate fresh signed headers, and return
the response.
Every request needs an `X-API-Key` header **and** a `user-id` header naming
the connected account. Every response is wrapped:
```json
{ "success": true, "status_code": 200, "data": { "β¦OnlyFans' own payloadβ¦" } }
```
A `user-id` belonging to a Fansly account is rejected on this surface. Use the
platform-neutral [CRM routes](/docs/crm) instead β they are Fansly-aware.
Direct proxy to OnlyFans user profile & settings endpoints. **OnlyFans only.** Requires `X-API-Key` + `user-id` headers.
OnlyFans subscriber endpoints (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
OnlyFans subscription management (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
OnlyFans chats & messages (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
OnlyFans posts, vault, labels (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
OnlyFans stories (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
OnlyFans live streams (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
OnlyFans campaign endpoints (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
OnlyFans promotions & trials (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
OnlyFans user lists (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
OnlyFans payout & payment endpoints (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
OnlyFans notification endpoints (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
OnlyFans helper/manager accounts (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
Other OnlyFans endpoints. **OnlyFans only** β Fansly accounts are rejected on this surface.
*433 endpoints, generated from the [OpenAPI 3.1 spec](https://theonlyapi.com/api/openapi.json).*
---
# Campaigns (/docs/of-api/campaigns)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
OnlyFans campaign endpoints (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
| Method | Endpoint | Path |
| ------ | --------------------------------------------------------------------------- | ------------------------------------------- |
| `GET` | [List campaigns](/docs/of-api/campaigns/list-campaigns-get) | `/api2/v2/campaigns` |
| `POST` | [Create campaign](/docs/of-api/campaigns/create-campaign-post) | `/api2/v2/campaigns` |
| `GET` | [Get campaign conversions](/docs/of-api/campaigns/get-campaign-conversions) | `/api2/v2/campaigns/{campaign_id}/claimers` |
---
# Create campaign (/docs/of-api/campaigns/create-campaign-post)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/campaigns`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `name` (string) β e.g. `"My Campaign"`.
## Responses
### 200 β Campaign created
- `id` (integer) β e.g. `12345`.
- `campaignName` (string) β e.g. `"My Campaign"`.
- `campaignCode` (string) β Short code / slug used in the tracking URL.
- `countTransitions` (integer | object) β Number of link clicks. May be an integer or `{ count, date }` object when stats=true.
- `countSubscribers` (integer | object) β Number of subscribers gained through this campaign. May be an integer or `{ count, date }` object.
- `createdAt` (string)
- `sharedWith` (object[]) β Users this campaign link is shared with.
- `id` (integer)
- `name` (string)
- `username` (string)
- `displayName` (string)
- `about` (string)
- `avatar` (string) β Avatar URL.
- `avatarThumbs` (object)
- `c50` (string)
- `c144` (string)
- `header` (string) β Profile header/banner URL.
- `email` (string)
- `isVerified` (boolean)
- `isPerformer` (boolean)
- `canEarn` (boolean)
- `tipsEnabled` (boolean)
- `subscribedBy` (boolean) β Whether you are subscribed to this user.
- `subscribersCount` (integer)
- `subscribesCount` (integer)
- `postsCount` (integer)
- `photosCount` (integer)
- `videosCount` (integer)
- `audiosCount` (integer)
- `friendsCount` (integer)
- `joinDate` (string)
- `firstPublishedPostDate` (string)
- `subscribePrice` (number)
- `chatMessagesCount` (integer) β Unread chat messages.
- `countPriorityChat` (integer)
- `countPinnedChat` (integer)
- `hasPurchasedPosts` (boolean)
- `paidFeed` (boolean)
- `openseaInfo` (object)
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/campaigns" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"name":"My Campaign"}'
```
---
# Get campaign conversions (/docs/of-api/campaigns/get-campaign-conversions)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/campaigns/{campaign_id}/claimers`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `campaign_id` (string, required)
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Claimer list
- `list` (object[])
- `id` (integer)
- `name` (string)
- `username` (string)
- `displayName` (string)
- `about` (string)
- `avatar` (string) β Avatar URL.
- `avatarThumbs` (object)
- `c50` (string)
- `c144` (string)
- `header` (string) β Profile header/banner URL.
- `email` (string)
- `isVerified` (boolean)
- `isPerformer` (boolean)
- `canEarn` (boolean)
- `tipsEnabled` (boolean)
- `subscribedBy` (boolean) β Whether you are subscribed to this user.
- `subscribersCount` (integer)
- `subscribesCount` (integer)
- `postsCount` (integer)
- `photosCount` (integer)
- `videosCount` (integer)
- `audiosCount` (integer)
- `friendsCount` (integer)
- `joinDate` (string)
- `firstPublishedPostDate` (string)
- `subscribePrice` (number)
- `chatMessagesCount` (integer) β Unread chat messages.
- `countPriorityChat` (integer)
- `countPinnedChat` (integer)
- `hasPurchasedPosts` (boolean)
- `paidFeed` (boolean)
- `openseaInfo` (object)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/campaigns/{campaign_id}/claimers" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# List campaigns (/docs/of-api/campaigns/list-campaigns-get)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/campaigns`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
- `pagination` (integer) β Default `1`.
- `stats` (string) β Default `true`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Campaign list
- `list` (object[])
- `id` (integer) β e.g. `12345`.
- `campaignName` (string) β e.g. `"My Campaign"`.
- `campaignCode` (string) β Short code / slug used in the tracking URL.
- `countTransitions` (integer | object) β Number of link clicks. May be an integer or `{ count, date }` object when stats=true.
- `countSubscribers` (integer | object) β Number of subscribers gained through this campaign. May be an integer or `{ count, date }` object.
- `createdAt` (string)
- `sharedWith` (object[]) β Users this campaign link is shared with.
- `id` (integer)
- `name` (string)
- `username` (string)
- `displayName` (string)
- `about` (string)
- `avatar` (string) β Avatar URL.
- `avatarThumbs` (object)
- `header` (string) β Profile header/banner URL.
- `email` (string)
- `isVerified` (boolean)
- `isPerformer` (boolean)
- `canEarn` (boolean)
- `tipsEnabled` (boolean)
- `subscribedBy` (boolean) β Whether you are subscribed to this user.
- `subscribersCount` (integer)
- `subscribesCount` (integer)
- `postsCount` (integer)
- `photosCount` (integer)
- `videosCount` (integer)
- `audiosCount` (integer)
- `friendsCount` (integer)
- `joinDate` (string)
- `firstPublishedPostDate` (string)
- `subscribePrice` (number)
- `chatMessagesCount` (integer) β Unread chat messages.
- `countPriorityChat` (integer)
- `countPinnedChat` (integer)
- `hasPurchasedPosts` (boolean)
- `paidFeed` (boolean)
- `openseaInfo` (object)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/campaigns" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Content (/docs/of-api/content)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
OnlyFans posts, vault, labels (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
| Method | Endpoint | Path |
| -------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `DELETE` | [Delete a comment](/docs/of-api/content/delete-a-comment) | `/api2/v2/comments/{comment_id}` |
| `POST` | [Like a comment](/docs/of-api/content/like-a-comment) | `/api2/v2/comments/{comment_id}/like` |
| `DELETE` | [Unlike a comment](/docs/of-api/content/unlike-a-comment) | `/api2/v2/comments/{comment_id}/like` |
| `POST` | [Pin a comment](/docs/of-api/content/pin-a-comment) | `/api2/v2/comments/{comment_id}/pin` |
| `DELETE` | [Unpin a comment](/docs/of-api/content/unpin-a-comment) | `/api2/v2/comments/{comment_id}/pin` |
| `GET` | [Get user labels](/docs/of-api/content/get-user-labels) | `/api2/v2/labels` |
| `GET` | [Get posts feed](/docs/of-api/content/get-posts-feed) | `/api2/v2/posts` |
| `POST` | [Create a new post](/docs/of-api/content/create-a-new-post) | `/api2/v2/posts` |
| `GET` | [Get specific post](/docs/of-api/content/get-specific-post) | `/api2/v2/posts/{post_id}` |
| `POST` | [Bookmark a post](/docs/of-api/content/bookmark-a-post) | `/api2/v2/posts/{post_id}/bookmarks` |
| `DELETE` | [Remove post from bookmarks](/docs/of-api/content/remove-post-from-bookmarks) | `/api2/v2/posts/{post_id}/bookmarks` |
| `GET` | [Get post comments](/docs/of-api/content/get-post-comments) | `/api2/v2/posts/{post_id}/comments` |
| `POST` | [Like / favorite a post](/docs/of-api/content/like-favorite-a-post) | `/api2/v2/posts/{post_id}/favorites` |
| `POST` | [Add post to favorites](/docs/of-api/content/add-post-to-favorites) | `/api2/v2/posts/{post_id}/favorites/{author_id}` |
| `GET` | [Count fundraising contributors](/docs/of-api/content/count-fundraising-contributors) | `/api2/v2/posts/{post_id}/fund-raising-contributors/count` |
| `PUT` | [Hide a post](/docs/of-api/content/hide-a-post) | `/api2/v2/posts/{post_id}/hide` |
| `POST` | [Vote on a post poll](/docs/of-api/content/vote-on-a-post-poll) | `/api2/v2/posts/{post_id}/vote` |
| `GET` | [Get bookmarked posts](/docs/of-api/content/get-bookmarked-posts) | `/api2/v2/posts/bookmarks` |
| `GET` | [Get bookmark categories](/docs/of-api/content/get-bookmark-categories) | `/api2/v2/posts/bookmarks/categories` |
| `PATCH` | [Rename bookmark category](/docs/of-api/content/rename-bookmark-category) | `/api2/v2/posts/bookmarks/categories/{category_id}` |
| `DELETE` | [Delete a bookmark category](/docs/of-api/content/delete-a-bookmark-category) | `/api2/v2/posts/bookmarks/categories/{category_id}` |
| `POST` | [Add post to bookmark category](/docs/of-api/content/add-post-to-bookmark-category) | `/api2/v2/posts/bookmarks/categories/{category_id}/{post_id}` |
| `POST` | [Sort bookmark categories](/docs/of-api/content/sort-bookmark-categories) | `/api2/v2/posts/bookmarks/categories/sort` |
| `POST` | [Sort pinned paid posts](/docs/of-api/content/sort-pinned-paid-posts) | `/api2/v2/posts/paid/pin/sort` |
| `POST` | [Reorder pinned posts](/docs/of-api/content/reorder-pinned-posts) | `/api2/v2/posts/pinned/sort` |
| `POST` | [Report post view/interaction stats](/docs/of-api/content/report-post-view-interaction-stats) | `/api2/v2/posts/stats-collect` |
| `POST` | [Create release form link](/docs/of-api/content/create-release-form-link) | `/api2/v2/release-form-links` |
| `POST` | [Attach release form](/docs/of-api/content/attach-release-form) | `/api2/v2/release-forms/attach` |
| `GET` | [Get scheduled posts](/docs/of-api/content/get-scheduled-posts) | `/api2/v2/schedules` |
| `PUT` | [Publish a scheduled item](/docs/of-api/content/publish-a-scheduled-item) | `/api2/v2/schedules/{schedule_id}/publish` |
| `GET` | [Get scheduled-post counts per day](/docs/of-api/content/get-scheduled-post-counts-per-day) | `/api2/v2/schedules/counters` |
| `POST` | [Create signed media upload](/docs/of-api/content/create-signed-media-upload) | `/api2/v2/upload/signed/create` |
| `POST` | [Finish signed upload](/docs/of-api/content/finish-signed-upload) | `/api2/v2/upload/signed/finish` |
| `GET` | [Get DRM certificate](/docs/of-api/content/get-drm-certificate) | `/api2/v2/users/media/drm/certificate` |
| `GET` | [Get 'on this day' posts](/docs/of-api/content/get-on-this-day-posts) | `/api2/v2/users/posts/on-this-day` |
| `GET` | [Get vault lists (folders)](/docs/of-api/content/get-vault-lists-folders) | `/api2/v2/vault/lists` |
| `GET` | [Get vault media list](/docs/of-api/content/get-vault-media-list) | `/api2/v2/vault/lists/{list_id}` |
| `PATCH` | [Rename a vault list](/docs/of-api/content/rename-a-vault-list) | `/api2/v2/vault/lists/{list_id}` |
| `DELETE` | [Delete vault list](/docs/of-api/content/delete-vault-list) | `/api2/v2/vault/lists/{list_id}` |
| `POST` | [Add media to vault list](/docs/of-api/content/add-media-to-vault-list) | `/api2/v2/vault/lists/{list_id}/media` |
| `POST` | [Sort vault lists](/docs/of-api/content/sort-vault-lists) | `/api2/v2/vault/lists/sort` |
| `GET` | [Get vault media](/docs/of-api/content/get-vault-media) | `/api2/v2/vault/media` |
| `GET` | [Get vault media item](/docs/of-api/content/get-vault-media-item) | `/api2/v2/vault/media/{media_id}` |
| `PUT` | [Attach vault media to post/message](/docs/of-api/content/attach-vault-media-to-post-message) | `/api2/v2/vault/media/{media_id}/attach` |
| `GET` | [Get posts using a vault media item](/docs/of-api/content/get-posts-using-a-vault-media-item) | `/api2/v2/vault/media/{media_id}/posts` |
| `GET` | [Get vault media release forms](/docs/of-api/content/get-vault-media-release-forms) | `/api2/v2/vault/media/{media_id}/release-forms` |
| `GET` | [Find vault media by MD5 (dedupe)](/docs/of-api/content/find-vault-media-by-md5-dedupe) | `/api2/v2/vault/media/hash` |
| `PUT` | [Hide vault media](/docs/of-api/content/hide-vault-media) | `/api2/v2/vault/media/hidden` |
| `GET` | [Get vault media being processed](/docs/of-api/content/get-vault-media-being-processed) | `/api2/v2/vault/media/processing` |
| `GET` | [Get vault media types present](/docs/of-api/content/get-vault-media-types-present) | `/api2/v2/vault/media/types` |
---
# Add media to vault list (/docs/of-api/content/add-media-to-vault-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/lists/{list_id}/media`
Add one or more media items to a vault list (folder) for organization.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (integer, required) β Vault list ID.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `mediaIds` (integer[], required) β Array of media IDs to add to the list. e.g. `[12345,67890]`.
## Responses
### 200 β Media added to list
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/lists/{list_id}/media" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"mediaIds":[12345,67890]}'
```
---
# Add post to bookmark category (/docs/of-api/content/add-post-to-bookmark-category)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/bookmarks/categories/{category_id}/{post_id}`
Adds a post to a bookmark category. Called as ({categoryId, postId}). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `category_id` (string, required) β ID of the bookmark category.
- `post_id` (string, required) β ID of the post to add.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/bookmarks/categories/{category_id}/{post_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Add post to favorites (/docs/of-api/content/add-post-to-favorites)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/favorites/{author_id}`
Marks the given post (by the specified author) as a favorite for the current user. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `post_id` (string, required) β ID of the post to favorite.
- `author_id` (string, required) β User ID of the post's author.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/favorites/{author_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Attach release form (/docs/of-api/content/attach-release-form)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/release-forms/attach`
Attaches a release form (content consent document) to content. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/release-forms/attach" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Attach vault media to post/message (/docs/of-api/content/attach-vault-media-to-post-message)
`PUT https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/{media_id}/attach`
Attach a media item from the vault to an existing post or message. The media must already be uploaded.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `media_id` (integer, required) β Vault media ID.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `postId` (integer) β Post ID to attach media to.
- `messageId` (integer) β Message ID to attach media to.
## Responses
### 200 β Media attached
## Example
```bash
curl -X PUT "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/{media_id}/attach" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Bookmark a post (/docs/of-api/content/bookmark-a-post)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/bookmarks`
Adds a post to bookmarks; an optional chat_group_id body targets a specific bookmark group. DELETE on the same path removes the bookmark. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `post_id` (string, required) β ID of the post to bookmark.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `chat_group_id` (string) β Optional bookmark/chat group id; basic call sends no body.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/bookmarks" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Count fundraising contributors (/docs/of-api/content/count-fundraising-contributors)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/fund-raising-contributors/count`
Returns the number of contributors to a post's fundraising campaign. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `post_id` (string, required) β ID of the post.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/fund-raising-contributors/count" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Create a new post (/docs/of-api/content/create-a-new-post)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts`
Create a new post on your profile. Supports text, media attachments, PPV pricing, polls, scheduled publishing (`isScheduled` + `scheduledDate`), and expiration dates.
**Attaching a freshly uploaded file β use `mediaFiles`, not `media`.** `POST /accounts/{of_user_id}/media` returns a `media` object; put it in a **`mediaFiles`** array here, unchanged and complete.
Verified live 2026-08-06, and the failure mode is silent β OnlyFans returns `200` and simply attaches nothing if you get this wrong:
| body | result |
|---|---|
| `mediaFiles: [{processId, host, thumbId, name, extra}]` | media attached β
|
| `media: [{β¦same objectβ¦}]` | post created, **0 media** |
| `mediaFiles: [{processId}]` only | post created, **0 media** |
So pass the whole object through β dropping `thumbId` or `name` silently loses the attachment.
The vault row for the media is created by this call and appears in `GET /api2/v2/vault/media` roughly 10β15 seconds later. It survives deletion of the post.
Media already in the vault is referenced by its integer ID from `GET /api2/v2/vault/media`.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `text` (string) β Post text / caption. e.g. `"New exclusive content! π₯"`.
- `rawText` (string) β Raw text without formatting (used internally for search/indexing).
- `price` (number) β PPV price in USD. If set, the post is locked behind a paywall. e.g. `15`.
- `lockedText` (boolean) β If true, text content is hidden until PPV is purchased. Default `false`.
- `media` (integer[]) β Media to attach: integer vault IDs (from `GET /api2/v2/vault/media`), and/or the `media` object returned by `POST /accounts/{of_user_id}/media` for a file you just uploaded. e.g. `[12345,67890]`.
- `preview` (integer[]) β Preview media IDs visible before purchasing PPV content.
- `postedAt` (string) β Schedule date for the post (ISO 8601). If in the future, the post is scheduled. e.g. `"2025-06-15T14:00:00.000Z"`.
- `postedAtPrecise` (string) β Precise timestamp string (unix ms). Used internally for ordering.
- `expiredAt` (string) β Expiration date β post auto-deletes after this time. e.g. `"2025-07-15T14:00:00.000Z"`.
- `isPublishedWithPeriod` (boolean) β Whether the post is published for a specific time period (used with expiredAt). Default `false`.
- `voting` (string[]) β Poll options. Each item is a poll choice string. Adds a poll/vote to the post. e.g. `["Option A","Option B","Option C"]`.
- `linkedPosts` (integer[]) β Array of post IDs to link to this post (related content).
- `releaseForms` (integer[]) β Release form IDs for compliance when media features other people.
- `streamId` (integer) β Link this post to a live stream by stream ID.
## Responses
### 200 β Post created
- `id` (integer)
- `text` (string)
- `rawText` (string)
- `price` (number)
- `isOpened` (boolean)
- `isPinned` (boolean)
- `postedAt` (string)
- `postedAtPrecise` (string)
- `expiredAt` (string)
- `commentsCount` (integer)
- `favoritesCount` (integer)
- `tipsAmount` (number)
- `media` (object[])
- `id` (integer) β Stable OnlyFans media ID β the only persistent identifier for the item.
- `type` ("photo" | "video" | "audio" | "gif")
- `createdAt` (string)
- `duration` (integer) β Duration in seconds (video/audio; 0 for photos).
- `convertedToVideo` (boolean)
- `canView` (boolean)
- `isReady` (boolean) β False while OnlyFans is still processing/transcoding the upload.
- `hasError` (boolean)
- `hasPosts` (boolean) β Whether the media is attached to any post.
- `hasCustomPreview` (boolean)
- `counters` (object) β Engagement counters (present on the vault view).
- `buyersCount` (integer)
- `likesCount` (integer)
- `tipsSumm` (number)
- `listStates` (object[]) β Which vault lists (folders) this media belongs to.
- `id` (integer) β Vault list ID.
- `name` (string) β Vault list name.
- `hasMedia` (boolean)
- `canAddMedia` (boolean)
- `files` (object) β Signed, time-limited CDN URLs at several resolutions. `size` is frequently `0` β do not rely on it.
- `full` (object)
- `preview` (object)
- `thumb` (object)
- `squarePreview` (object)
- `videoSources` (object) β Per-resolution video URLs (e.g. `240`, `720`); entries are null when not applicable.
- `author` (object)
- `canComment` (boolean)
- `canEdit` (boolean)
- `hasVoting` (boolean)
- `voting` (object[])
- `id` (integer)
- `text` (string)
- `votesCount` (integer)
- `isSelected` (boolean)
## Example
**Simple text post**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"text":"Happy Monday everyone! β€οΈ"}'
```
**Post with media**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"text":"New photoset just dropped!","media":[12345,67890,11111]}'
```
**Pay-per-view post**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"text":"Unlock to see the full set π","media":[12345,67890],"preview":[12345],"price":25,"lockedText":false}'
```
**Post with a poll**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"text":"What should I post next?","voting":["Beach photoshoot","Gym content","Behind the scenes"]}'
```
**Scheduled post**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"text":"Coming soon...","media":[12345],"postedAt":"2025-06-20T18:00:00.000Z"}'
```
---
# Create release form link (/docs/of-api/content/create-release-form-link)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/release-form-links`
Creates a release-form link (content consent document link) from the supplied data. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/release-form-links" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Create signed media upload (/docs/of-api/content/create-signed-media-upload)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/upload/signed/create`
Stage 1 of OnlyFans' media upload. Returns pre-signed S3 URL(s) that you then PUT the bytes to.
**Body** (verified live 2026-08-06):
```json
{ "key": "upload/{uuid4}/{nonce}/{url-encoded-lowercased-filename}", "parts": 1, "contentType": "image/jpeg", "secure": false }
```
The `key` prefix comes from `GET /api2/v2/init` β `upload.s3.uploadPath` (`upload/`, or `upload/secure/` when `secure` is true). `parts` is `floor(size / 5242880) + 1` for files β₯ 5 MiB, else `1`.
**A wrong or missing `key` is what produces `400 {"error":{"message":"Bad key"}}`** β the field really is named `key`, and it must be a path under the account's upload prefix.
Response: `{ keys: [{putUrl}], uploadId, putUrl, getUrl }`. Multipart when `keys[]` is populated and the file is β₯ 5 MiB; otherwise PUT the whole file to `putUrl`.
**You normally don't call this directly** β `POST /accounts/{of_user_id}/media` runs all four stages for you.
**OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `key` (string, required) β Storage PATH, not a filename β `{uploadPath}{uuid4}/{nonce}/{url-encoded lowercased filename}`. `uploadPath` comes from GET /api2/v2/init β `upload.s3.uploadPath` (`upload/`, or `upload/secure/` when secure=true). A missing or malformed value is what returns `400 Bad key`. e.g. `"upload/0e683d4d-1726-4506-a04d-2a5d05f5b9e1/1031326954680/photo.jpg"`.
- `parts` (integer, required) β Number of 5 MiB S3 parts: `floor(size / 5242880) + 1` for files β₯ 5 MiB, otherwise 1. e.g. `1`.
- `contentType` (string, required) β MIME type of the file. `.heic` must be sent as `image/heic`. e.g. `"image/jpeg"`.
- `secure` (boolean) β Use the secure (DRM) upload prefix instead of the standard one. Default `false`.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/upload/signed/create" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"key":"upload/0e683d4d-1726-4506-a04d-2a5d05f5b9e1/1031326954680/photo.jpg","parts":1,"contentType":"image/jpeg","secure":false}'
```
---
# Delete a bookmark category (/docs/of-api/content/delete-a-bookmark-category)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/bookmarks/categories/{category_id}`
Deletes a post-bookmark category by id. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `category_id` (string, required) β Id of the bookmark category to delete.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/bookmarks/categories/{category_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Delete a comment (/docs/of-api/content/delete-a-comment)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/comments/{comment_id}`
Deletes a specific comment by its id. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `comment_id` (string, required) β Id of the comment to delete.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/comments/{comment_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Delete vault list (/docs/of-api/content/delete-vault-list)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/lists/{list_id}`
Deletes a vault media list, optionally also clearing its media. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the vault media list.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/lists/{list_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Find vault media by MD5 (dedupe) (/docs/of-api/content/find-vault-media-by-md5-dedupe)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/hash`
**Deduplication / fileβvault-item lookup.** Given the MD5 of an original file, returns the matching vault media if that exact byte content was previously uploaded, or `404 Media Not Found` if not.
This is the ONLY content-based way to correlate a local file with an existing vault item β OnlyFans stores no filename or hash on the media object. The hash is the MD5 of the **original uploaded bytes**: hashing a downloaded/CDN copy will NOT match (the served file differs from the original), and a file that was re-encoded/re-exported after upload will not match either. In that case, capture the media `id` at upload time and keep your own `source β id` map.
Response is wrapped in the passthrough envelope. **OnlyFans only.**
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `h` (string, required) β MD5 hex digest of the ORIGINAL uploaded file bytes. Note the parameter is `h`, not `hash` β this was documented as `hash` in error until 2026-08-06. For a single-part upload the S3 ETag returned by `POST /accounts/{of_user_id}/media` is exactly this MD5, so you can feed it straight back in.
- `size` (integer, required) β Size of the original file in bytes. OnlyFans' own client always sends this alongside `h`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Matching media, wrapped in the passthrough envelope
On no match: `success:false`, `status_code:404`, and `data.error.message = "Media Not Found"`.
- `success` (boolean)
- `status_code` (integer)
- `data` (object) β Media item (photo/video/audio/gif). Returned by the vault endpoints and embedded in posts, messages, and stories. **No source fingerprint exists.** OnlyFans does NOT expose an original filename, content hash, upload source ID, or any immutable content fingerprint on this object β `id` is the only stable identifier. To correlate a local file with an existing vault item, use `GET /api2/v2/vault/media/hash?hash={md5}`. `files.*.size` is frequently `0` and must never be used as a fingerprint.
- `id` (integer) β Stable OnlyFans media ID β the only persistent identifier for the item.
- `type` ("photo" | "video" | "audio" | "gif")
- `createdAt` (string)
- `duration` (integer) β Duration in seconds (video/audio; 0 for photos).
- `convertedToVideo` (boolean)
- `canView` (boolean)
- `isReady` (boolean) β False while OnlyFans is still processing/transcoding the upload.
- `hasError` (boolean)
- `hasPosts` (boolean) β Whether the media is attached to any post.
- `hasCustomPreview` (boolean)
- `counters` (object) β Engagement counters (present on the vault view).
- `buyersCount` (integer)
- `likesCount` (integer)
- `tipsSumm` (number)
- `listStates` (object[]) β Which vault lists (folders) this media belongs to.
- `id` (integer) β Vault list ID.
- `name` (string) β Vault list name.
- `hasMedia` (boolean)
- `canAddMedia` (boolean)
- `files` (object) β Signed, time-limited CDN URLs at several resolutions. `size` is frequently `0` β do not rely on it.
- `full` (object)
- `preview` (object)
- `thumb` (object)
- `squarePreview` (object)
- `videoSources` (object) β Per-resolution video URLs (e.g. `240`, `720`); entries are null when not applicable.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/hash" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Finish signed upload (/docs/of-api/content/finish-signed-upload)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/upload/signed/finish`
Stage 3 of OnlyFans' media upload β completes an S3 **multipart** upload after every part has been PUT.
**Body** (verified live 2026-08-06):
```json
{ "key": "", "parts": [{ "ETag": "\"abcβ¦\"", "PartNumber": 1 }], "uploadId": "", "secure": false }
```
Returns `{ "ETag": "β¦" }` for the assembled object.
Single-part uploads (< 5 MiB) **skip this call** β the ETag comes straight off the S3 PUT response.
Note that neither this nor `create` puts anything in the vault; a fourth stage hands the S3 descriptor to OnlyFans' converter host (`GET /api2/v2/init` β `upload.geoUploadHosts`). `POST /accounts/{of_user_id}/media` does all of it for you.
**OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `key` (string, required) β The same `key` passed to upload/signed/create. e.g. `"upload/0e683d4d-1726-4506-a04d-2a5d05f5b9e1/1031326954680/photo.jpg"`.
- `parts` (object[], required) β One entry per uploaded S3 part, in order. `ETag` is the quoted value from that part's PUT response header.
- `ETag` (string, required) β e.g. `"\"5623a10f3404a04d201a8e38aea195d6\""`.
- `PartNumber` (integer, required) β e.g. `1`.
- `uploadId` (string, required) β The `uploadId` returned by upload/signed/create.
- `secure` (boolean) β Must match the value used on create. Default `false`.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/upload/signed/finish" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"key":"upload/0e683d4d-1726-4506-a04d-2a5d05f5b9e1/1031326954680/photo.jpg","parts":[{"ETag":"\"5623a10f3404a04d201a8e38aea195d6\"","PartNumber":1}],"uploadId":"2~abc123","secure":false}'
```
---
# Get bookmark categories (/docs/of-api/content/get-bookmark-categories)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/bookmarks/categories`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Categories list
- `[]` array of:
- `id` (string) β 'all' for all bookmarks, or numeric ID.
- `name` (string)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/bookmarks/categories" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get bookmarked posts (/docs/of-api/content/get-bookmarked-posts)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/bookmarks`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Bookmarked posts
- `list` (object[])
- `id` (integer)
- `text` (string)
- `rawText` (string)
- `price` (number)
- `isOpened` (boolean)
- `isPinned` (boolean)
- `postedAt` (string)
- `postedAtPrecise` (string)
- `expiredAt` (string)
- `commentsCount` (integer)
- `favoritesCount` (integer)
- `tipsAmount` (number)
- `media` (object[])
- `id` (integer) β Stable OnlyFans media ID β the only persistent identifier for the item.
- `type` ("photo" | "video" | "audio" | "gif")
- `createdAt` (string)
- `duration` (integer) β Duration in seconds (video/audio; 0 for photos).
- `convertedToVideo` (boolean)
- `canView` (boolean)
- `isReady` (boolean) β False while OnlyFans is still processing/transcoding the upload.
- `hasError` (boolean)
- `hasPosts` (boolean) β Whether the media is attached to any post.
- `hasCustomPreview` (boolean)
- `counters` (object) β Engagement counters (present on the vault view).
- `listStates` (object[]) β Which vault lists (folders) this media belongs to.
- `files` (object) β Signed, time-limited CDN URLs at several resolutions. `size` is frequently `0` β do not rely on it.
- `videoSources` (object) β Per-resolution video URLs (e.g. `240`, `720`); entries are null when not applicable.
- `author` (object)
- `canComment` (boolean)
- `canEdit` (boolean)
- `hasVoting` (boolean)
- `voting` (object[])
- `id` (integer)
- `text` (string)
- `votesCount` (integer)
- `isSelected` (boolean)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/bookmarks" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get DRM certificate (/docs/of-api/content/get-drm-certificate)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/users/media/drm/certificate`
Retrieves the DRM certificate used for protected media playback. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
β **Verified live** against a real OnlyFans account (2026-07-30); the `data` schema below is the real response shape.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response β verified live; `data` is the real shape.
- `success` (boolean)
- `status_code` (integer)
- `data` (string)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/users/media/drm/certificate" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get 'on this day' posts (/docs/of-api/content/get-on-this-day-posts)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/users/posts/on-this-day`
Retrieves the current user's posts from this date in previous years ('on this day' memories). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
β **Verified live** against a real OnlyFans account (2026-07-30); the `data` schema below is the real response shape.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response β verified live; `data` is the real shape.
- `success` (boolean)
- `status_code` (integer)
- `data` (any[])
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/users/posts/on-this-day" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get post comments (/docs/of-api/content/get-post-comments)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/comments`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `post_id` (string, required)
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Comments list
- `list` (object[])
- `id` (integer)
- `text` (string)
- `author` (object) β Full OnlyFans user profile object.
- `id` (integer)
- `name` (string)
- `username` (string)
- `displayName` (string)
- `about` (string)
- `avatar` (string) β Avatar URL.
- `avatarThumbs` (object)
- `header` (string) β Profile header/banner URL.
- `email` (string)
- `isVerified` (boolean)
- `isPerformer` (boolean)
- `canEarn` (boolean)
- `tipsEnabled` (boolean)
- `subscribedBy` (boolean) β Whether you are subscribed to this user.
- `subscribersCount` (integer)
- `subscribesCount` (integer)
- `postsCount` (integer)
- `photosCount` (integer)
- `videosCount` (integer)
- `audiosCount` (integer)
- `friendsCount` (integer)
- `joinDate` (string)
- `firstPublishedPostDate` (string)
- `subscribePrice` (number)
- `chatMessagesCount` (integer) β Unread chat messages.
- `countPriorityChat` (integer)
- `countPinnedChat` (integer)
- `hasPurchasedPosts` (boolean)
- `paidFeed` (boolean)
- `openseaInfo` (object)
- `createdAt` (string)
- `likesCount` (integer)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/comments" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get posts feed (/docs/of-api/content/get-posts-feed)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
- `format` (string) β Default `infinite`.
- `skip_users` (string) β Default `all`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Posts list
- `list` (object[])
- `id` (integer)
- `text` (string)
- `rawText` (string)
- `price` (number)
- `isOpened` (boolean)
- `isPinned` (boolean)
- `postedAt` (string)
- `postedAtPrecise` (string)
- `expiredAt` (string)
- `commentsCount` (integer)
- `favoritesCount` (integer)
- `tipsAmount` (number)
- `media` (object[])
- `id` (integer) β Stable OnlyFans media ID β the only persistent identifier for the item.
- `type` ("photo" | "video" | "audio" | "gif")
- `createdAt` (string)
- `duration` (integer) β Duration in seconds (video/audio; 0 for photos).
- `convertedToVideo` (boolean)
- `canView` (boolean)
- `isReady` (boolean) β False while OnlyFans is still processing/transcoding the upload.
- `hasError` (boolean)
- `hasPosts` (boolean) β Whether the media is attached to any post.
- `hasCustomPreview` (boolean)
- `counters` (object) β Engagement counters (present on the vault view).
- `listStates` (object[]) β Which vault lists (folders) this media belongs to.
- `files` (object) β Signed, time-limited CDN URLs at several resolutions. `size` is frequently `0` β do not rely on it.
- `videoSources` (object) β Per-resolution video URLs (e.g. `240`, `720`); entries are null when not applicable.
- `author` (object)
- `canComment` (boolean)
- `canEdit` (boolean)
- `hasVoting` (boolean)
- `voting` (object[])
- `id` (integer)
- `text` (string)
- `votesCount` (integer)
- `isSelected` (boolean)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get posts using a vault media item (/docs/of-api/content/get-posts-using-a-vault-media-item)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/{media_id}/posts`
List posts that use a specific vault media item.
Response is wrapped in the passthrough envelope. **OnlyFans only.**
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `media_id` (integer, required) β Vault media ID.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200
Posts referencing the media, wrapped in the passthrough envelope.
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
- `list` (object[])
- `id` (integer)
- `text` (string)
- `rawText` (string)
- `price` (number)
- `isOpened` (boolean)
- `isPinned` (boolean)
- `postedAt` (string)
- `postedAtPrecise` (string)
- `expiredAt` (string)
- `commentsCount` (integer)
- `favoritesCount` (integer)
- `tipsAmount` (number)
- `media` (object[])
- `author` (object)
- `canComment` (boolean)
- `canEdit` (boolean)
- `hasVoting` (boolean)
- `voting` (object[])
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/{media_id}/posts" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get scheduled-post counts per day (/docs/of-api/content/get-scheduled-post-counts-per-day)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/schedules/counters`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Scheduled-post counts, wrapped in the passthrough envelope
`data.list` is an OBJECT keyed by ISO date (e.g. "2026-07-28") whose value is `{ post: }` β it is NOT an array.
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
- `list` (object) β Keyed by ISO date string.
- `syncInProcess` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/schedules/counters" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get scheduled posts (/docs/of-api/content/get-scheduled-posts)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/schedules`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Scheduled posts
- `list` (object[])
- `id` (integer)
- `text` (string)
- `rawText` (string)
- `price` (number)
- `isOpened` (boolean)
- `isPinned` (boolean)
- `postedAt` (string)
- `postedAtPrecise` (string)
- `expiredAt` (string)
- `commentsCount` (integer)
- `favoritesCount` (integer)
- `tipsAmount` (number)
- `media` (object[])
- `id` (integer) β Stable OnlyFans media ID β the only persistent identifier for the item.
- `type` ("photo" | "video" | "audio" | "gif")
- `createdAt` (string)
- `duration` (integer) β Duration in seconds (video/audio; 0 for photos).
- `convertedToVideo` (boolean)
- `canView` (boolean)
- `isReady` (boolean) β False while OnlyFans is still processing/transcoding the upload.
- `hasError` (boolean)
- `hasPosts` (boolean) β Whether the media is attached to any post.
- `hasCustomPreview` (boolean)
- `counters` (object) β Engagement counters (present on the vault view).
- `listStates` (object[]) β Which vault lists (folders) this media belongs to.
- `files` (object) β Signed, time-limited CDN URLs at several resolutions. `size` is frequently `0` β do not rely on it.
- `videoSources` (object) β Per-resolution video URLs (e.g. `240`, `720`); entries are null when not applicable.
- `author` (object)
- `canComment` (boolean)
- `canEdit` (boolean)
- `hasVoting` (boolean)
- `voting` (object[])
- `id` (integer)
- `text` (string)
- `votesCount` (integer)
- `isSelected` (boolean)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/schedules" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get specific post (/docs/of-api/content/get-specific-post)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `post_id` (string, required)
## Query parameters
- `skip_users` (string) β Default `all`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Post object
- `id` (integer)
- `text` (string)
- `rawText` (string)
- `price` (number)
- `isOpened` (boolean)
- `isPinned` (boolean)
- `postedAt` (string)
- `postedAtPrecise` (string)
- `expiredAt` (string)
- `commentsCount` (integer)
- `favoritesCount` (integer)
- `tipsAmount` (number)
- `media` (object[])
- `id` (integer) β Stable OnlyFans media ID β the only persistent identifier for the item.
- `type` ("photo" | "video" | "audio" | "gif")
- `createdAt` (string)
- `duration` (integer) β Duration in seconds (video/audio; 0 for photos).
- `convertedToVideo` (boolean)
- `canView` (boolean)
- `isReady` (boolean) β False while OnlyFans is still processing/transcoding the upload.
- `hasError` (boolean)
- `hasPosts` (boolean) β Whether the media is attached to any post.
- `hasCustomPreview` (boolean)
- `counters` (object) β Engagement counters (present on the vault view).
- `buyersCount` (integer)
- `likesCount` (integer)
- `tipsSumm` (number)
- `listStates` (object[]) β Which vault lists (folders) this media belongs to.
- `id` (integer) β Vault list ID.
- `name` (string) β Vault list name.
- `hasMedia` (boolean)
- `canAddMedia` (boolean)
- `files` (object) β Signed, time-limited CDN URLs at several resolutions. `size` is frequently `0` β do not rely on it.
- `full` (object)
- `preview` (object)
- `thumb` (object)
- `squarePreview` (object)
- `videoSources` (object) β Per-resolution video URLs (e.g. `240`, `720`); entries are null when not applicable.
- `author` (object)
- `canComment` (boolean)
- `canEdit` (boolean)
- `hasVoting` (boolean)
- `voting` (object[])
- `id` (integer)
- `text` (string)
- `votesCount` (integer)
- `isSelected` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get user labels (/docs/of-api/content/get-user-labels)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Labels list
- `list` (object[])
- `id` (integer)
- `name` (string)
- `postsCount` (integer)
- `type` (string)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get vault lists (folders) (/docs/of-api/content/get-vault-lists-folders)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/lists`
List the vault folders for an account.
**`view=main` is required** β omitting it makes OnlyFans respond `400 Bad view param` (passed through verbatim). The response is wrapped in the standard passthrough envelope (`{ success, status_code, data }`); the OnlyFans body is under `data`, where `data.list` is the folder array and `data.all` carries global counts across the whole vault.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `view` ("main", required) β Required by OnlyFans. Use `main` β it returns the folder list plus the `all` bucket of global counts. Omitting it returns `400 Bad view param`. Default `main`.
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Vault folders, wrapped in the passthrough envelope.
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
- `all` (object) β Pseudo-folder holding global counts across the entire vault.
- `photosCount` (integer)
- `videosCount` (integer)
- `gifsCount` (integer)
- `audiosCount` (integer)
- `list` (object[])
- `id` (integer)
- `name` (string)
- `photosCount` (integer)
- `videosCount` (integer)
- `gifsCount` (integer)
- `audiosCount` (integer)
- `hasMedia` (boolean)
- `canUpdate` (boolean)
- `canDelete` (boolean)
- `medias` (object[]) β A few thumbnail previews (type + url) used as the folder cover.
- `hasMore` (boolean)
- `order` (string)
- `sort` (string)
- `canCreateVaultLists` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/lists" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get vault media (/docs/of-api/content/get-vault-media)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media`
List vault media items. This is how you obtain the integer media IDs referenced by post creation, story creation, and messages.
**Uploading new media:** use `POST /accounts/{of_user_id}/media`. A freshly uploaded file will **not** show up in this list right away β OnlyFans creates the vault entry when a post/message/story consumes the upload's `processId`, not at upload time. That is OnlyFans' own behaviour, not a limitation of this API: there is no upload-to-vault endpoint anywhere in the OnlyFans web client. (A `POST /api2/v2/media` upload route was documented in error until 2026-08-06 β it never existed and OnlyFans answers it with 404.)
**Filtering (supported, but previously undocumented):** `list={list_id}` restricts results to one folder, `field=recent` chooses the ordering field, and `sort=asc|desc` the direction β combine with `limit`/`offset` for paging. The response is wrapped in the standard passthrough envelope; the OnlyFans body (`{ list, hasMore }`) is under `data`.
**Matching a local file to an existing item:** the media object carries no filename or hash β use `GET /api2/v2/vault/media/hash?h={md5}&size={bytes}` instead.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `list` (integer) β Restrict results to a single vault folder (a list ID from GET /vault/lists).
- `field` ("recent") β Ordering field. `recent` orders by upload time. Default `recent`.
- `sort` ("asc" | "desc") β Sort direction. Default `desc`.
- `limit` (integer) β Default `24`.
- `offset` (integer) β Default `0`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Media items, wrapped in the passthrough envelope.
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
- `list` (object[])
- `id` (integer) β Stable OnlyFans media ID β the only persistent identifier for the item.
- `type` ("photo" | "video" | "audio" | "gif")
- `createdAt` (string)
- `duration` (integer) β Duration in seconds (video/audio; 0 for photos).
- `convertedToVideo` (boolean)
- `canView` (boolean)
- `isReady` (boolean) β False while OnlyFans is still processing/transcoding the upload.
- `hasError` (boolean)
- `hasPosts` (boolean) β Whether the media is attached to any post.
- `hasCustomPreview` (boolean)
- `counters` (object) β Engagement counters (present on the vault view).
- `listStates` (object[]) β Which vault lists (folders) this media belongs to.
- `files` (object) β Signed, time-limited CDN URLs at several resolutions. `size` is frequently `0` β do not rely on it.
- `videoSources` (object) β Per-resolution video URLs (e.g. `240`, `720`); entries are null when not applicable.
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get vault media being processed (/docs/of-api/content/get-vault-media-being-processed)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/processing`
List media still being processed/transcoded by OnlyFans (items whose `isReady` is false).
`data` carries its OWN `success` flag alongside `list` β distinct from the outer envelope's `success`.
Response is wrapped in the passthrough envelope. **OnlyFans only.**
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200
Media currently processing, wrapped in the passthrough envelope.
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
- `is_processing` (boolean)
- `success` (boolean) β OnlyFans' own flag, nested inside data.
- `list` (object[])
- `id` (integer) β Stable OnlyFans media ID β the only persistent identifier for the item.
- `type` ("photo" | "video" | "audio" | "gif")
- `createdAt` (string)
- `duration` (integer) β Duration in seconds (video/audio; 0 for photos).
- `convertedToVideo` (boolean)
- `canView` (boolean)
- `isReady` (boolean) β False while OnlyFans is still processing/transcoding the upload.
- `hasError` (boolean)
- `hasPosts` (boolean) β Whether the media is attached to any post.
- `hasCustomPreview` (boolean)
- `counters` (object) β Engagement counters (present on the vault view).
- `listStates` (object[]) β Which vault lists (folders) this media belongs to.
- `files` (object) β Signed, time-limited CDN URLs at several resolutions. `size` is frequently `0` β do not rely on it.
- `videoSources` (object) β Per-resolution video URLs (e.g. `240`, `720`); entries are null when not applicable.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/processing" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get vault media item (/docs/of-api/content/get-vault-media-item)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/{media_id}`
Get a single vault media item by ID.
Response is wrapped in the passthrough envelope; the OnlyFans media object is under `data`. **OnlyFans only.**
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `media_id` (integer, required) β Vault media ID.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β The media item, wrapped in the passthrough envelope.
- `success` (boolean)
- `status_code` (integer)
- `data` (object) β Media item (photo/video/audio/gif). Returned by the vault endpoints and embedded in posts, messages, and stories. **No source fingerprint exists.** OnlyFans does NOT expose an original filename, content hash, upload source ID, or any immutable content fingerprint on this object β `id` is the only stable identifier. To correlate a local file with an existing vault item, use `GET /api2/v2/vault/media/hash?hash={md5}`. `files.*.size` is frequently `0` and must never be used as a fingerprint.
- `id` (integer) β Stable OnlyFans media ID β the only persistent identifier for the item.
- `type` ("photo" | "video" | "audio" | "gif")
- `createdAt` (string)
- `duration` (integer) β Duration in seconds (video/audio; 0 for photos).
- `convertedToVideo` (boolean)
- `canView` (boolean)
- `isReady` (boolean) β False while OnlyFans is still processing/transcoding the upload.
- `hasError` (boolean)
- `hasPosts` (boolean) β Whether the media is attached to any post.
- `hasCustomPreview` (boolean)
- `counters` (object) β Engagement counters (present on the vault view).
- `buyersCount` (integer)
- `likesCount` (integer)
- `tipsSumm` (number)
- `listStates` (object[]) β Which vault lists (folders) this media belongs to.
- `id` (integer) β Vault list ID.
- `name` (string) β Vault list name.
- `hasMedia` (boolean)
- `canAddMedia` (boolean)
- `files` (object) β Signed, time-limited CDN URLs at several resolutions. `size` is frequently `0` β do not rely on it.
- `full` (object)
- `preview` (object)
- `thumb` (object)
- `squarePreview` (object)
- `videoSources` (object) β Per-resolution video URLs (e.g. `240`, `720`); entries are null when not applicable.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/{media_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get vault media list (/docs/of-api/content/get-vault-media-list)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/lists/{list_id}`
Retrieves a single vault media list by id. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the vault list.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/lists/{list_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get vault media release forms (/docs/of-api/content/get-vault-media-release-forms)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/{media_id}/release-forms`
Returns the release forms attached to a vault media item identified by media_id. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `media_id` (string, required) β ID of the vault media item.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/{media_id}/release-forms" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get vault media types present (/docs/of-api/content/get-vault-media-types-present)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/types`
Report which media types exist in the vault. `data` is a flat object of booleans (NOT counts) β one per type.
Response is wrapped in the passthrough envelope. **OnlyFans only.**
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200
Booleans for which media types are present, wrapped in the passthrough envelope.
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
- `hasPhoto` (boolean)
- `hasVideo` (boolean)
- `hasGif` (boolean)
- `hasAudio` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/types" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Hide a post (/docs/of-api/content/hide-a-post)
`PUT https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/hide`
Hides the specified post. Defined alongside post pin, favorite, vote and delete calls. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `post_id` (string, required) β ID of the post to hide.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X PUT "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/hide" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Hide vault media (/docs/of-api/content/hide-vault-media)
`PUT https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/hidden`
Marks the given vault media items as hidden. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `mediaIds` (any[]) β Ids of the vault media to hide.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X PUT "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/media/hidden" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Like a comment (/docs/of-api/content/like-a-comment)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/comments/{comment_id}/like`
Adds a like to the specified comment. Paired with a DELETE on the same path to unlike, plus comment pin/delete calls. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `comment_id` (string, required) β ID of the comment to like.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/comments/{comment_id}/like" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Like / favorite a post (/docs/of-api/content/like-favorite-a-post)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/favorites`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `post_id` (string, required)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Post liked
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/favorites" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Pin a comment (/docs/of-api/content/pin-a-comment)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/comments/{comment_id}/pin`
Pins the specified comment. Paired with a DELETE on the same path to unpin. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `comment_id` (string, required) β ID of the comment to pin.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/comments/{comment_id}/pin" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Publish a scheduled item (/docs/of-api/content/publish-a-scheduled-item)
`PUT https://theonlyapi.com/api/crm/your-crm-id/api2/v2/schedules/{schedule_id}/publish`
Publishes a scheduled entity (e.g. queued post/stream) immediately by its schedule id. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `schedule_id` (string, required) β ID of the scheduled item to publish.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X PUT "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/schedules/{schedule_id}/publish" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Remove post from bookmarks (/docs/of-api/content/remove-post-from-bookmarks)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/bookmarks`
Removes the given post from the user's bookmarks; an optional chat_group_id can scope the removal to a specific bookmark group. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `post_id` (string, required) β Id of the post to un-bookmark.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/bookmarks" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Rename a vault list (/docs/of-api/content/rename-a-vault-list)
`PATCH https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/lists/{list_id}`
Renames a media vault list identified by list id. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the vault list.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `name` (string) β New name for the vault list.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X PATCH "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/lists/{list_id}" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Rename bookmark category (/docs/of-api/content/rename-bookmark-category)
`PATCH https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/bookmarks/categories/{category_id}`
Renames a post-bookmark category (DELETE removes it, POST creates one). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `category_id` (string, required) β ID of the bookmark category.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `name` (string) β New category name.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X PATCH "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/bookmarks/categories/{category_id}" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Reorder pinned posts (/docs/of-api/content/reorder-pinned-posts)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/pinned/sort`
Change the display order of your pinned posts on your profile.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `order` (integer[], required) β Ordered array of pinned post IDs. First ID appears first on profile. e.g. `[999,888,777]`.
## Responses
### 200 β Pin order updated
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/pinned/sort" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"order":[999,888,777]}'
```
---
# Report post view/interaction stats (/docs/of-api/content/report-post-view-interaction-stats)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/stats-collect`
Submit post view and interaction analytics. Used by the client to report which posts were viewed, scrolled past, or interacted with.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `actions` (object[], required) β Array of post interaction events.
- `postId` (integer)
- `type` (string) β Action type (e.g. 'view', 'scroll', 'click').
## Responses
### 200 β Stats collected
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/stats-collect" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"actions":[{}]}'
```
---
# Sort bookmark categories (/docs/of-api/content/sort-bookmark-categories)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/bookmarks/categories/sort`
Sets the sort order of post bookmark categories. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/bookmarks/categories/sort" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Sort pinned paid posts (/docs/of-api/content/sort-pinned-paid-posts)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/paid/pin/sort`
Reorders the creator's pinned paid posts. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/paid/pin/sort" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Sort vault lists (/docs/of-api/content/sort-vault-lists)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/lists/sort`
Reorders the creator's vault media lists. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/vault/lists/sort" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Unlike a comment (/docs/of-api/content/unlike-a-comment)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/comments/{comment_id}/like`
Removes the current user's like from a comment (POST on the same path adds a like). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `comment_id` (string, required) β ID of the comment to unlike.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/comments/{comment_id}/like" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Unpin a comment (/docs/of-api/content/unpin-a-comment)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/comments/{comment_id}/pin`
Removes the pinned status from a comment. The paired POST /comments/{id}/pin pins it. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `comment_id` (string, required) β ID of the comment to unpin.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/comments/{comment_id}/pin" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Vote on a post poll (/docs/of-api/content/vote-on-a-post-poll)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/vote`
Submit a vote on a post that has a poll attached. Get the `optionId` from the post's `voting` array.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `post_id` (string, required)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `optionId` (integer, required) β The poll option ID to vote for (from the post's voting array).
## Responses
### 200 β Vote recorded
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/{post_id}/vote" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"optionId":0}'
```
---
# Helpers (/docs/of-api/helpers)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
OnlyFans helper/manager accounts (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
| Method | Endpoint | Path |
| ------ | --------------------------------------------------------------------- | ------------------------------ |
| `GET` | [List account helpers](/docs/of-api/helpers/list-account-helpers) | `/api2/v2/helpers` |
| `GET` | [Get helper permissions](/docs/of-api/helpers/get-helper-permissions) | `/api2/v2/helpers/permissions` |
---
# Get helper permissions (/docs/of-api/helpers/get-helper-permissions)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/helpers/permissions`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Permissions config
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/helpers/permissions" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# List account helpers (/docs/of-api/helpers/list-account-helpers)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/helpers`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Helper list
- `[]` array of:
- `userId` (integer)
- `user` (object) β Full OnlyFans user profile object.
- `id` (integer)
- `name` (string)
- `username` (string)
- `displayName` (string)
- `about` (string)
- `avatar` (string) β Avatar URL.
- `avatarThumbs` (object)
- `c50` (string)
- `c144` (string)
- `header` (string) β Profile header/banner URL.
- `email` (string)
- `isVerified` (boolean)
- `isPerformer` (boolean)
- `canEarn` (boolean)
- `tipsEnabled` (boolean)
- `subscribedBy` (boolean) β Whether you are subscribed to this user.
- `subscribersCount` (integer)
- `subscribesCount` (integer)
- `postsCount` (integer)
- `photosCount` (integer)
- `videosCount` (integer)
- `audiosCount` (integer)
- `friendsCount` (integer)
- `joinDate` (string)
- `firstPublishedPostDate` (string)
- `subscribePrice` (number)
- `chatMessagesCount` (integer) β Unread chat messages.
- `countPriorityChat` (integer)
- `countPinnedChat` (integer)
- `hasPurchasedPosts` (boolean)
- `paidFeed` (boolean)
- `openseaInfo` (object)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/helpers" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Lists (/docs/of-api/lists)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
OnlyFans user lists (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
| Method | Endpoint | Path |
| -------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `GET` | [Get a label by ID](/docs/of-api/lists/get-a-label-by-id) | `/api2/v2/labels/{label_id}` |
| `PATCH` | [Rename a label](/docs/of-api/lists/rename-a-label) | `/api2/v2/labels/{label_id}` |
| `DELETE` | [Delete a label](/docs/of-api/lists/delete-a-label) | `/api2/v2/labels/{label_id}` |
| `DELETE` | [Remove post from label](/docs/of-api/lists/remove-post-from-label) | `/api2/v2/labels/{label_id}/post/{post_id}` |
| `POST` | [Add posts to label](/docs/of-api/lists/add-posts-to-label) | `/api2/v2/labels/{label_id}/posts` |
| `DELETE` | [Remove all posts from label](/docs/of-api/lists/remove-all-posts-from-label) | `/api2/v2/labels/{label_id}/posts` |
| `POST` | [Sort labels](/docs/of-api/lists/sort-labels) | `/api2/v2/labels/sort` |
| `GET` | [Get user lists](/docs/of-api/lists/get-user-lists) | `/api2/v2/lists` |
| `POST` | [Create a new list](/docs/of-api/lists/create-a-new-list) | `/api2/v2/lists` |
| `GET` | [Get a user list](/docs/of-api/lists/get-a-user-list) | `/api2/v2/lists/{list_id}` |
| `PATCH` | [Update a list](/docs/of-api/lists/update-a-list) | `/api2/v2/lists/{list_id}` |
| `DELETE` | [Delete a list](/docs/of-api/lists/delete-a-list) | `/api2/v2/lists/{list_id}` |
| `POST` | [Sort users in list](/docs/of-api/lists/sort-users-in-list) | `/api2/v2/lists/{list_id}/sort` |
| `GET` | [Get users in a list](/docs/of-api/lists/get-users-in-a-list) | `/api2/v2/lists/{list_id}/users` |
| `POST` | [Add claimers to list](/docs/of-api/lists/add-claimers-to-list) | `/api2/v2/lists/{list_id}/users/{type}/{id}/claims` |
| `POST` | [Add user to list](/docs/of-api/lists/add-user-to-list) | `/api2/v2/lists/{list_id}/users/{user_id}` |
| `DELETE` | [Remove user from list](/docs/of-api/lists/remove-user-from-list) | `/api2/v2/lists/{list_id}/users/{user_id}` |
| `POST` | [Pin user in list](/docs/of-api/lists/pin-user-in-list) | `/api2/v2/lists/{list_id}/users/{user_id}/pin` |
| `POST` | [Add award-winning users to list](/docs/of-api/lists/add-award-winning-users-to-list) | `/api2/v2/lists/{list_id}/users/awards/{year}/{month}` |
| `POST` | [Add campaign claimers to list](/docs/of-api/lists/add-campaign-claimers-to-list) | `/api2/v2/lists/{list_id}/users/campaign/{campaign_id}/claimers` |
| `POST` | [Add media buyers to list](/docs/of-api/lists/add-media-buyers-to-list) | `/api2/v2/lists/{list_id}/users/media/{media_id}/buyers` |
| `POST` | [Sort pinned list users](/docs/of-api/lists/sort-pinned-list-users) | `/api2/v2/lists/{list_id}/users/pinned/sort` |
| `POST` | [Add queue buyers to list](/docs/of-api/lists/add-queue-buyers-to-list) | `/api2/v2/lists/{list_id}/users/queue/{queue_id}/buyers` |
| `POST` | [Add story viewers to list](/docs/of-api/lists/add-story-viewers-to-list) | `/api2/v2/lists/{list_id}/users/story/{story_id}/{type}` |
| `POST` | [Add stream audience to list](/docs/of-api/lists/add-stream-audience-to-list) | `/api2/v2/lists/{list_id}/users/stream/{stream_id}/{type}` |
| `DELETE` | [Remove stream users from list](/docs/of-api/lists/remove-stream-users-from-list) | `/api2/v2/lists/{list_id}/users/stream/{stream_id}/{type}` |
| `POST` | [Add subscribers to list](/docs/of-api/lists/add-subscribers-to-list) | `/api2/v2/lists/{list_id}/users/subscribers` |
| `POST` | [Add top subscribers to list](/docs/of-api/lists/add-top-subscribers-to-list) | `/api2/v2/lists/{list_id}/users/top-subscribers` |
| `GET` | [Check list membership](/docs/of-api/lists/check-list-membership) | `/api2/v2/lists/check/{list_id}/{user_id}` |
| `POST` | [Sort user lists](/docs/of-api/lists/sort-user-lists) | `/api2/v2/lists/sort` |
| `POST` | [Add users to lists](/docs/of-api/lists/add-users-to-lists) | `/api2/v2/lists/users` |
---
# Add award-winning users to list (/docs/of-api/lists/add-award-winning-users-to-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/awards/{year}/{month}`
Adds users who received awards in the given year/month to the specified list. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the list.
- `year` (string, required) β Year of the awards period.
- `month` (string, required) β Month of the awards period.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/awards/{year}/{month}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Add campaign claimers to list (/docs/of-api/lists/add-campaign-claimers-to-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/campaign/{campaign_id}/claimers`
Adds the users who claimed a campaign to a custom list. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the list.
- `campaign_id` (string, required) β ID of the campaign whose claimers are added.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/campaign/{campaign_id}/claimers" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Add claimers to list (/docs/of-api/lists/add-claimers-to-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/{type}/{id}/claims`
Adds users who claimed a given entity (identified by type and id) to the specified list. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the target list.
- `type` (string, required) β Claim entity type.
- `id` (string, required) β ID of the entity of the given type.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/{type}/{id}/claims" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Add media buyers to list (/docs/of-api/lists/add-media-buyers-to-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/media/{media_id}/buyers`
Adds users who purchased a given media item to a list (bulk add by media). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the target list.
- `media_id` (string, required) β ID of the media whose buyers are added.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/media/{media_id}/buyers" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Add posts to label (/docs/of-api/lists/add-posts-to-label)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/{label_id}/posts`
Adds one or more posts to a label. Called as (labelId, posts[]). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `label_id` (string, required) β ID of the label.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `posts` (any[]) β IDs of the posts to add to the label.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/{label_id}/posts" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Add queue buyers to list (/docs/of-api/lists/add-queue-buyers-to-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/queue/{queue_id}/buyers`
Adds users who bought from a given queue to a list (bulk add by queue). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the target list.
- `queue_id` (string, required) β ID of the queue whose buyers are added.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/queue/{queue_id}/buyers" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Add story viewers to list (/docs/of-api/lists/add-story-viewers-to-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/story/{story_id}/{type}`
Adds users who interacted with a given story (by interaction type) to the specified list. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the target list.
- `story_id` (string, required) β ID of the story.
- `type` (string, required) β Interaction type used to select users.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/story/{story_id}/{type}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Add stream audience to list (/docs/of-api/lists/add-stream-audience-to-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/stream/{stream_id}/{type}`
Adds users from a live stream (matching the given type/criteria) to a list, optionally filtered by tip/subscription thresholds. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β Id of the target list.
- `stream_id` (string, required) β Id of the stream whose audience is added.
- `type` (string, required) β Audience segment/type key (e.g. viewers/tippers).
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `tippedOver` (number) β Only include users who tipped over this amount.
- `subscribedOver` (number) β Only include users subscribed over this threshold.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/stream/{stream_id}/{type}" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Add subscribers to list (/docs/of-api/lists/add-subscribers-to-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/subscribers`
Adds subscribers to the list identified by list_id. Enclosing fn is addSubscribersToList. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the list to add subscribers to.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/subscribers" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Add top subscribers to list (/docs/of-api/lists/add-top-subscribers-to-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/top-subscribers`
Adds the account's top subscribers to the given list (addTopSubscribersToList). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the list to add top subscribers to.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/top-subscribers" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Add user to list (/docs/of-api/lists/add-user-to-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/{user_id}`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required)
- `user_id` (string, required)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β User added
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/{user_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Add users to lists (/docs/of-api/lists/add-users-to-lists)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/users`
Adds users to one or more lists in bulk. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/users" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Check list membership (/docs/of-api/lists/check-list-membership)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/check/{list_id}/{user_id}`
Checks whether a given user belongs to a specific list. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the list.
- `user_id` (string, required) β ID of the user to check.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/check/{list_id}/{user_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Create a new list (/docs/of-api/lists/create-a-new-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `name` (string) β e.g. `"My List"`.
## Responses
### 200 β List created
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"name":"My List"}'
```
---
# Delete a label (/docs/of-api/lists/delete-a-label)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/{label_id}`
Deletes a label identified by label id. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `label_id` (string, required) β ID of the label to delete.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/{label_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Delete a list (/docs/of-api/lists/delete-a-list)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}`
Deletes a user list by id (GET retrieves it, PATCH renames/updates it). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the list to delete.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get a label by ID (/docs/of-api/lists/get-a-label-by-id)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/{label_id}`
Retrieves a single content label by its ID. Sibling calls create, rename, sort and delete labels. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `label_id` (string, required) β ID of the label to retrieve.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/{label_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get a user list (/docs/of-api/lists/get-a-user-list)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}`
Retrieves a single user list identified by list_id. Enclosing fn is getUsersList. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the list to retrieve.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get user lists (/docs/of-api/lists/get-user-lists)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Lists
- `list` (object[])
- `id` (integer)
- `name` (string)
- `type` (string) β List type (e.g. 'archived', 'private_archived', or standard).
- `postsCount` (integer)
- `usersCount` (integer)
- `customOrderEnabled` (boolean)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get users in a list (/docs/of-api/lists/get-users-in-a-list)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required)
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β User list
- `list` (object[])
- `id` (integer)
- `name` (string)
- `username` (string)
- `displayName` (string)
- `about` (string)
- `avatar` (string) β Avatar URL.
- `avatarThumbs` (object)
- `c50` (string)
- `c144` (string)
- `header` (string) β Profile header/banner URL.
- `email` (string)
- `isVerified` (boolean)
- `isPerformer` (boolean)
- `canEarn` (boolean)
- `tipsEnabled` (boolean)
- `subscribedBy` (boolean) β Whether you are subscribed to this user.
- `subscribersCount` (integer)
- `subscribesCount` (integer)
- `postsCount` (integer)
- `photosCount` (integer)
- `videosCount` (integer)
- `audiosCount` (integer)
- `friendsCount` (integer)
- `joinDate` (string)
- `firstPublishedPostDate` (string)
- `subscribePrice` (number)
- `chatMessagesCount` (integer) β Unread chat messages.
- `countPriorityChat` (integer)
- `countPinnedChat` (integer)
- `hasPurchasedPosts` (boolean)
- `paidFeed` (boolean)
- `openseaInfo` (object)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Pin user in list (/docs/of-api/lists/pin-user-in-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/{user_id}/pin`
Pins a user to the top of a custom list. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the list.
- `user_id` (string, required) β ID of the user to pin.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/{user_id}/pin" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Remove all posts from label (/docs/of-api/lists/remove-all-posts-from-label)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/{label_id}/posts`
Removes all posts from the specified label. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `label_id` (string, required) β Id of the label to clear.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/{label_id}/posts" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Remove post from label (/docs/of-api/lists/remove-post-from-label)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/{label_id}/post/{post_id}`
Removes a single post from the specified label. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `label_id` (string, required) β ID of the label.
- `post_id` (string, required) β ID of the post to remove from the label.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/{label_id}/post/{post_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Remove stream users from list (/docs/of-api/lists/remove-stream-users-from-list)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/stream/{stream_id}/{type}`
Removes stream-derived users (e.g. viewers who tipped or subscribed over a threshold) of a given type from a list. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the list.
- `stream_id` (string, required) β ID of the stream.
- `type` (string, required) β User selection type (passed as the trailing path segment).
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/stream/{stream_id}/{type}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Remove user from list (/docs/of-api/lists/remove-user-from-list)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/{user_id}`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required)
- `user_id` (string, required)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β User removed
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/{user_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Rename a label (/docs/of-api/lists/rename-a-label)
`PATCH https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/{label_id}`
Renames the label identified by label_id to the provided name. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `label_id` (string, required) β ID of the label to rename.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `name` (string) β New label name.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X PATCH "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/{label_id}" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Sort labels (/docs/of-api/lists/sort-labels)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/sort`
Reorders the user's labels according to the supplied order. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/labels/sort" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Sort pinned list users (/docs/of-api/lists/sort-pinned-list-users)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/pinned/sort`
Reorders the pinned users within a list identified by list_id, per the provided order. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the list whose pinned users are sorted.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `order` (any[]) β Desired ordering of pinned user IDs.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/users/pinned/sort" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Sort user lists (/docs/of-api/lists/sort-user-lists)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/sort`
Persists a new ordering of the user's lists. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/sort" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Sort users in list (/docs/of-api/lists/sort-users-in-list)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/sort`
Sorts the users within a list. Called as sortListUsers({listId, data}). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the list.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}/sort" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Update a list (/docs/of-api/lists/update-a-list)
`PATCH https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}`
Updates the given user list (getUsersList/updateList module). Sibling calls create, delete and sort lists. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `list_id` (string, required) β ID of the list to update.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X PATCH "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/lists/{list_id}" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Messaging (/docs/of-api/messaging)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
OnlyFans chats & messages (via proxy). **OnlyFans only** β Fansly accounts are rejected on this surface.
| Method | Endpoint | Path |
| -------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| `GET` | [List recent chats](/docs/of-api/messaging/list-recent-chats) | `/api2/v2/chats` |
| `POST` | [Mark chat as read](/docs/of-api/messaging/mark-chat-as-read) | `/api2/v2/chats/{user_id}/mark-as-read` |
| `GET` | [Get chat messages](/docs/of-api/messaging/get-chat-messages) | `/api2/v2/chats/{user_id}/messages` |
| `POST` | [Send a message](/docs/of-api/messaging/send-a-message) | `/api2/v2/chats/{user_id}/messages` |
| `GET` | [Get single chat message](/docs/of-api/messaging/get-single-chat-message) | `/api2/v2/chats/{user_id}/messages/{message_id}` |
| `GET` | [Search messages in chat](/docs/of-api/messaging/search-messages-in-chat) | `/api2/v2/chats/{user_id}/messages/search` |
| `POST` | [Mark chats as read](/docs/of-api/messaging/mark-chats-as-read) | `/api2/v2/chats/mark-as-read` |
| `PUT` | [Hide a message](/docs/of-api/messaging/hide-a-message) | `/api2/v2/messages/{message_id}/hide` |
| `POST` | [Like / react to a message](/docs/of-api/messaging/like-react-to-a-message) | `/api2/v2/messages/{message_id}/like` |
| `GET` | [Get queued mass messages](/docs/of-api/messaging/get-queued-mass-messages) | `/api2/v2/messages/queue` |
| `POST` | [Create a mass message](/docs/of-api/messaging/create-a-mass-message) | `/api2/v2/messages/queue` |
| `PUT` | [Update queued message](/docs/of-api/messaging/update-queued-message) | `/api2/v2/messages/queue/{queue_id}` |
| `DELETE` | [Delete queued message](/docs/of-api/messaging/delete-queued-message) | `/api2/v2/messages/queue/{queue_id}` |
| `POST` | [Calculate mass message audience size](/docs/of-api/messaging/calculate-mass-message-audience-size) | `/api2/v2/messages/queue/size` |
| `GET` | [Get message templates](/docs/of-api/messaging/get-message-templates) | `/api2/v2/messages/templates` |
| `DELETE` | [Delete message template](/docs/of-api/messaging/delete-message-template) | `/api2/v2/messages/templates/{template_id}` |
| `POST` | [Set reply-on-subscribe template](/docs/of-api/messaging/set-reply-on-subscribe-template) | `/api2/v2/messages/templates/reply_on_subscribe` |
---
# Calculate mass message audience size (/docs/of-api/messaging/calculate-mass-message-audience-size)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/queue/size`
Preview how many users would receive a mass message given the buyer filter criteria.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `queueBuyers` (object[]) β Same filter array as used in POST /messages/queue. Empty = all subscribers. e.g. `[]`.
## Responses
### 200 β Audience size estimate
- `size` (integer) β Number of users who would receive the message.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/queue/size" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"queueBuyers":[]}'
```
---
# Create a mass message (/docs/of-api/messaging/create-a-mass-message)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/queue`
Queue a mass message to be sent to multiple subscribers. You can target all subscribers, specific lists, or exclude certain users.
**Attaching media:** `POST /accounts/{of_user_id}/media` (raw bytes or a `source_url` we fetch for you) returns a `media` object β pass it through here. Media already in the vault is referenced by its integer ID from `GET /api2/v2/vault/media`.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `text` (string) β Message text. e.g. `"Special offer just for you! π"`.
- `mediaFiles` (integer[]) β Media IDs from vault.
- `price` (number) β PPV price for locked content.
- `lockedText` (boolean) β Default `false`.
- `releaseForms` (integer[])
- `queueBuyers` (object[]) β Target audience filters. Empty array = all subscribers. Can specify list IDs or user groups. e.g. `[]`.
## Responses
### 200 β Mass message queued successfully
## Example
**Mass message to all subscribers**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/queue" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"text":"Happy weekend! Check your DMs for a surprise π","mediaFiles":[12345],"price":5,"queueBuyers":[]}'
```
---
# Delete message template (/docs/of-api/messaging/delete-message-template)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/templates/{template_id}`
Deletes a saved message template. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `template_id` (string, required) β ID of the message template to delete.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/templates/{template_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Delete queued message (/docs/of-api/messaging/delete-queued-message)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/queue/{queue_id}`
Deletes a queued (scheduled) mass message by its queue ID. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `queue_id` (string, required) β ID of the queued/scheduled message.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/queue/{queue_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get chat messages (/docs/of-api/messaging/get-chat-messages)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/{user_id}/messages`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `user_id` (string, required)
## Query parameters
- `limit` (integer) β Default `20`.
- `order` (string) β Default `desc`.
- `id` (integer) β Message ID cursor for pagination. Returns messages before/after this ID depending on order.
- `skip_users` (string) β Default `all`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Message list
- `list` (object[])
- `id` (integer)
- `text` (string)
- `price` (number)
- `isFromQueue` (boolean)
- `isOpened` (boolean)
- `isNew` (boolean)
- `createdAt` (string)
- `changedAt` (string)
- `isFree` (boolean)
- `media` (object[])
- `id` (integer) β Stable OnlyFans media ID β the only persistent identifier for the item.
- `type` ("photo" | "video" | "audio" | "gif")
- `createdAt` (string)
- `duration` (integer) β Duration in seconds (video/audio; 0 for photos).
- `convertedToVideo` (boolean)
- `canView` (boolean)
- `isReady` (boolean) β False while OnlyFans is still processing/transcoding the upload.
- `hasError` (boolean)
- `hasPosts` (boolean) β Whether the media is attached to any post.
- `hasCustomPreview` (boolean)
- `counters` (object) β Engagement counters (present on the vault view).
- `listStates` (object[]) β Which vault lists (folders) this media belongs to.
- `files` (object) β Signed, time-limited CDN URLs at several resolutions. `size` is frequently `0` β do not rely on it.
- `videoSources` (object) β Per-resolution video URLs (e.g. `240`, `720`); entries are null when not applicable.
- `fromUser` (object)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/{user_id}/messages" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get message templates (/docs/of-api/messaging/get-message-templates)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/templates`
Get saved message templates for quick replies.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Template list
- `[]` array of:
- `id` (integer)
- `name` (string)
- `content` (string)
- `createdAt` (string)
- `updatedAt` (string)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/templates" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get queued mass messages (/docs/of-api/messaging/get-queued-mass-messages)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/queue`
List all scheduled/queued mass messages. These are messages waiting to be sent to multiple users.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Queue list
- `list` (object[])
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/queue" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get single chat message (/docs/of-api/messaging/get-single-chat-message)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/{user_id}/messages/{message_id}`
Retrieves a specific message within the chat with a given user. Called as getMessage({userId, groupId}). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `user_id` (string, required) β ID of the user the chat is with.
- `message_id` (string, required) β ID of the message (passed as groupId in the caller).
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/{user_id}/messages/{message_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Hide a message (/docs/of-api/messaging/hide-a-message)
`PUT https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/{message_id}/hide`
Hides a chat message identified by message_id. The request body carries hide options. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `message_id` (string, required) β ID of the message to hide.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X PUT "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/{message_id}/hide" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Like / react to a message (/docs/of-api/messaging/like-react-to-a-message)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/{message_id}/like`
Send a like reaction to a specific message in a chat.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `message_id` (integer, required)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `withUserId` (integer) β The user ID of the chat partner (recipient of the like notification).
## Responses
### 200 β Message liked
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/{message_id}/like" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# List recent chats (/docs/of-api/messaging/list-recent-chats)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `limit` (integer) β Default `10`.
- `offset` (integer) β Default `0`.
- `order` (string) β Default `recent`.
- `skip_users` (string) β Default `all`.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Chat list
- `list` (object[])
- `id` (integer)
- `withUser` (object) β Full OnlyFans user profile object.
- `id` (integer)
- `name` (string)
- `username` (string)
- `displayName` (string)
- `about` (string)
- `avatar` (string) β Avatar URL.
- `avatarThumbs` (object)
- `header` (string) β Profile header/banner URL.
- `email` (string)
- `isVerified` (boolean)
- `isPerformer` (boolean)
- `canEarn` (boolean)
- `tipsEnabled` (boolean)
- `subscribedBy` (boolean) β Whether you are subscribed to this user.
- `subscribersCount` (integer)
- `subscribesCount` (integer)
- `postsCount` (integer)
- `photosCount` (integer)
- `videosCount` (integer)
- `audiosCount` (integer)
- `friendsCount` (integer)
- `joinDate` (string)
- `firstPublishedPostDate` (string)
- `subscribePrice` (number)
- `chatMessagesCount` (integer) β Unread chat messages.
- `countPriorityChat` (integer)
- `countPinnedChat` (integer)
- `hasPurchasedPosts` (boolean)
- `paidFeed` (boolean)
- `openseaInfo` (object)
- `lastMessage` (object) β An OnlyFans chat message object.
- `id` (integer)
- `text` (string)
- `price` (number)
- `isFromQueue` (boolean)
- `isOpened` (boolean)
- `isNew` (boolean)
- `createdAt` (string)
- `changedAt` (string)
- `isFree` (boolean)
- `media` (object[])
- `fromUser` (object)
- `unreadMessagesCount` (integer)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Mark chat as read (/docs/of-api/messaging/mark-chat-as-read)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/{user_id}/mark-as-read`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `user_id` (string, required)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Marked as read
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/{user_id}/mark-as-read" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Mark chats as read (/docs/of-api/messaging/mark-chats-as-read)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/mark-as-read`
Marks one or more chats as read for the current user. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/mark-as-read" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Search messages in chat (/docs/of-api/messaging/search-messages-in-chat)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/{user_id}/messages/search`
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `user_id` (string, required)
## Query parameters
- `query` (string, required)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Search results
- `list` (object[])
- `id` (integer)
- `text` (string)
- `price` (number)
- `isFromQueue` (boolean)
- `isOpened` (boolean)
- `isNew` (boolean)
- `createdAt` (string)
- `changedAt` (string)
- `isFree` (boolean)
- `media` (object[])
- `id` (integer) β Stable OnlyFans media ID β the only persistent identifier for the item.
- `type` ("photo" | "video" | "audio" | "gif")
- `createdAt` (string)
- `duration` (integer) β Duration in seconds (video/audio; 0 for photos).
- `convertedToVideo` (boolean)
- `canView` (boolean)
- `isReady` (boolean) β False while OnlyFans is still processing/transcoding the upload.
- `hasError` (boolean)
- `hasPosts` (boolean) β Whether the media is attached to any post.
- `hasCustomPreview` (boolean)
- `counters` (object) β Engagement counters (present on the vault view).
- `listStates` (object[]) β Which vault lists (folders) this media belongs to.
- `files` (object) β Signed, time-limited CDN URLs at several resolutions. `size` is frequently `0` β do not rely on it.
- `videoSources` (object) β Per-resolution video URLs (e.g. `240`, `720`); entries are null when not applicable.
- `fromUser` (object)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/{user_id}/messages/search" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Send a message (/docs/of-api/messaging/send-a-message)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/{user_id}/messages`
Send a direct message to a user. Supports text, media attachments (uploaded via the media upload endpoint first), PPV pricing, and locked text. Media must be uploaded to the vault first, then referenced by ID in the `mediaFiles` array.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `user_id` (string, required)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json` (required)
- `text` (string) β Message text content. e.g. `"Hello! Check out this exclusive content π₯"`.
- `mediaFiles` (integer[]) β Media to attach. Two accepted forms: * an integer vault ID of media already in the vault (list them with `GET /api2/v2/vault/media`) * the `media` object returned by `POST /accounts/{of_user_id}/media` β a freshly uploaded file is referenced by `processId`, not a vault ID Upload new files with `POST /accounts/{of_user_id}/media` (raw bytes, or a `source_url` we fetch for you). e.g. `[12345,67890]`.
- `price` (number) β PPV price in USD. If set, the message content is locked behind a paywall. e.g. `9.99`.
- `lockedText` (boolean) β If true, the text is hidden until the user pays the PPV price. Default `false`.
- `releaseForms` (integer[]) β Release form IDs for compliance. Required if media contains other people.
- `replyToMessage` (integer) β Message ID to reply to (creates a threaded reply).
## Responses
### 200 β Message sent successfully
- `id` (integer)
- `text` (string)
- `price` (number)
- `isFromQueue` (boolean)
- `isOpened` (boolean)
- `isNew` (boolean)
- `createdAt` (string)
- `changedAt` (string)
- `isFree` (boolean)
- `media` (object[])
- `id` (integer) β Stable OnlyFans media ID β the only persistent identifier for the item.
- `type` ("photo" | "video" | "audio" | "gif")
- `createdAt` (string)
- `duration` (integer) β Duration in seconds (video/audio; 0 for photos).
- `convertedToVideo` (boolean)
- `canView` (boolean)
- `isReady` (boolean) β False while OnlyFans is still processing/transcoding the upload.
- `hasError` (boolean)
- `hasPosts` (boolean) β Whether the media is attached to any post.
- `hasCustomPreview` (boolean)
- `counters` (object) β Engagement counters (present on the vault view).
- `buyersCount` (integer)
- `likesCount` (integer)
- `tipsSumm` (number)
- `listStates` (object[]) β Which vault lists (folders) this media belongs to.
- `id` (integer) β Vault list ID.
- `name` (string) β Vault list name.
- `hasMedia` (boolean)
- `canAddMedia` (boolean)
- `files` (object) β Signed, time-limited CDN URLs at several resolutions. `size` is frequently `0` β do not rely on it.
- `full` (object)
- `preview` (object)
- `thumb` (object)
- `squarePreview` (object)
- `videoSources` (object) β Per-resolution video URLs (e.g. `240`, `720`); entries are null when not applicable.
- `fromUser` (object)
## Example
**Simple text message**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/{user_id}/messages" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"text":"Hey! How are you?"}'
```
**Message with media attachments**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/{user_id}/messages" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"text":"Here's your exclusive content!","mediaFiles":[12345,67890]}'
```
**Pay-per-view message**
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/chats/{user_id}/messages" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{"text":"Unlock to see π","mediaFiles":[12345],"price":15,"lockedText":true}'
```
---
# Set reply-on-subscribe template (/docs/of-api/messaging/set-reply-on-subscribe-template)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/templates/reply_on_subscribe`
Creates or updates the automatic welcome message template sent when a fan subscribes. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/templates/reply_on_subscribe" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Update queued message (/docs/of-api/messaging/update-queued-message)
`PUT https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/queue/{queue_id}`
Updates a queued/scheduled message identified by its queue id with new message data. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `queue_id` (string, required) β ID of the queued/scheduled message.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X PUT "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/queue/{queue_id}" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Misc (/docs/of-api/misc)
{/* Generated by scripts/gen-api.ts β edit that script, not this file. */}
Other OnlyFans endpoints. **OnlyFans only** β Fansly accounts are rejected on this surface.
| Method | Endpoint | Path |
| -------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| `POST` | [Record address statistics](/docs/of-api/misc/record-address-statistics) | `/api2/v2/address/stat` |
| `POST` | [Submit consent form](/docs/of-api/misc/submit-consent-form) | `/api2/v2/consent-form` |
| `POST` | [Expand address for country](/docs/of-api/misc/expand-address-for-country) | `/api2/v2/countries/{country_id}/address/expand` |
| `GET` | [List states for a country](/docs/of-api/misc/list-states-for-a-country) | `/api2/v2/countries/{country_id}/states` |
| `GET` | [Get guest details](/docs/of-api/misc/get-guest-details) | `/api2/v2/guests/{guest_id}` |
| `POST` | [Assign a guest](/docs/of-api/misc/assign-a-guest) | `/api2/v2/guests/{guest_id}/assign` |
| `GET` | [App initialization data](/docs/of-api/misc/app-initialization-data) | `/api2/v2/init` |
| `GET` | [Get client IP address](/docs/of-api/misc/get-client-ip-address) | `/api2/v2/ip` |
| `GET` | [List states for country](/docs/of-api/misc/list-states-for-country) | `/api2/v2/iso/countries/{country_id}/states` |
| `POST` | [Submit a legal inquiry](/docs/of-api/misc/submit-a-legal-inquiry) | `/api2/v2/legal-inquiry` |
| `GET` | [Get legal inquiry](/docs/of-api/misc/get-legal-inquiry) | `/api2/v2/legal-inquiry/{inquiry_id}` |
| `POST` | [Submit legal inquiry response](/docs/of-api/misc/submit-legal-inquiry-response) | `/api2/v2/legal-inquiry/{inquiry_id}` |
| `GET` | [Get legal inquiry history](/docs/of-api/misc/get-legal-inquiry-history) | `/api2/v2/legal-inquiry/{inquiry_id}/history` |
| `GET` | [Get legal inquiry update](/docs/of-api/misc/get-legal-inquiry-update) | `/api2/v2/legal-inquiry/{inquiry_id}/update/{hash}` |
| `POST` | [Update a legal inquiry](/docs/of-api/misc/update-a-legal-inquiry) | `/api2/v2/legal-inquiry/{inquiry_id}/update/{hash}` |
| `POST` | [Submit legal inquiry by counsel](/docs/of-api/misc/submit-legal-inquiry-by-counsel) | `/api2/v2/legal-inquiry/by-counsel` |
| `POST` | [Change legal inquiry status](/docs/of-api/misc/change-legal-inquiry-status) | `/api2/v2/legal-inquiry/change-status/{inquiry_id}` |
| `GET` | [Get legal inquiry form params](/docs/of-api/misc/get-legal-inquiry-form-params) | `/api2/v2/legal-inquiry/params` |
| `POST` | [Send legal inquiry notification](/docs/of-api/misc/send-legal-inquiry-notification) | `/api2/v2/legal-inquiry/send-notification/{inquiry_id}` |
| `POST` | [Submit client-side log entry](/docs/of-api/misc/submit-client-side-log-entry) | `/api2/v2/log` |
| `GET` | [Get messages earnings chart](/docs/of-api/misc/get-messages-earnings-chart) | `/api2/v2/messages/queue/chart` |
| `POST` | [Submit contact form](/docs/of-api/misc/submit-contact-form) | `/api2/v2/pages/contacts` |
| `GET` | [Get payouts chart stats](/docs/of-api/misc/get-payouts-chart-stats) | `/api2/v2/payouts/chart` |
| `GET` | [Get VAT payouts chart stats](/docs/of-api/misc/get-vat-payouts-chart-stats) | `/api2/v2/payouts/vat/chart` |
| `GET` | [Get posts earnings chart](/docs/of-api/misc/get-posts-earnings-chart) | `/api2/v2/posts/chart` |
| `GET` | [Get top posts stats](/docs/of-api/misc/get-top-posts-stats) | `/api2/v2/posts/top` |
| `GET` | [Get promotions statistics chart](/docs/of-api/misc/get-promotions-statistics-chart) | `/api2/v2/promotions/chart` |
| `POST` | [Upload release form document](/docs/of-api/misc/upload-release-form-document) | `/api2/v2/release-form-documents` |
| `POST` | [Start release form link](/docs/of-api/misc/start-release-form-link) | `/api2/v2/release-form-links/{link_id}/start` |
| `POST` | [Generate release form link URL](/docs/of-api/misc/generate-release-form-link-url) | `/api2/v2/release-form-links/{link_id}/url` |
| `POST` | [Submit release form proof](/docs/of-api/misc/submit-release-form-proof) | `/api2/v2/release-form-proof` |
| `GET` | [Get partner release forms](/docs/of-api/misc/get-partner-release-forms) | `/api2/v2/release-forms/partner/{partner_id}` |
| `PATCH` | [Rename a release form](/docs/of-api/misc/rename-a-release-form) | `/api2/v2/release-forms/rename` |
| `PATCH` | [Toggle release form visibility](/docs/of-api/misc/toggle-release-form-visibility) | `/api2/v2/release-forms/toggle-show` |
| `GET` | [List content report reasons](/docs/of-api/misc/list-content-report-reasons) | `/api2/v2/reports/reasons` |
| `GET` | [Get report reason detail options](/docs/of-api/misc/get-report-reason-detail-options) | `/api2/v2/reports/reasons/{reason_id}/details-options` |
| `DELETE` | [Delete Shopify store](/docs/of-api/misc/delete-shopify-store) | `/api2/v2/shopify/stores/{store_id}` |
| `GET` | [Get stories statistics chart](/docs/of-api/misc/get-stories-statistics-chart) | `/api2/v2/stories/chart` |
| `GET` | [Get top stories stats](/docs/of-api/misc/get-top-stories-stats) | `/api2/v2/stories/top` |
| `GET` | [Get streaks over date range](/docs/of-api/misc/get-streaks-over-date-range) | `/api2/v2/streaks` |
| `GET` | [Get top streaks](/docs/of-api/misc/get-top-streaks) | `/api2/v2/streaks/top` |
| `GET` | [Get streams stats chart](/docs/of-api/misc/get-streams-stats-chart) | `/api2/v2/streams/chart` |
| `GET` | [Get top streams stats](/docs/of-api/misc/get-top-streams-stats) | `/api2/v2/streams/top` |
| `GET` | [Get subscribers statistics chart](/docs/of-api/misc/get-subscribers-statistics-chart) | `/api2/v2/subscriptions/subscribers/chart` |
| `GET` | [Get latest subscribers](/docs/of-api/misc/get-latest-subscribers) | `/api2/v2/subscriptions/subscribers/latest` |
| `GET` | [Get top subscribers stats](/docs/of-api/misc/get-top-subscribers-stats) | `/api2/v2/subscriptions/subscribers/top` |
| `PUT` | [Update a localization text](/docs/of-api/misc/update-a-localization-text) | `/api2/v2/texts/{code}` |
| `POST` | [Search localization texts](/docs/of-api/misc/search-localization-texts) | `/api2/v2/texts/search` |
| `GET` | [Get trials chart stats](/docs/of-api/misc/get-trials-chart-stats) | `/api2/v2/trials/chart` |
| `GET` | [Get trial link statistics](/docs/of-api/misc/get-trial-link-statistics) | `/api2/v2/trials/stats` |
| `POST` | [Report user click statistics](/docs/of-api/misc/report-user-click-statistics) | `/api2/v2/users/clicks-stats` |
| `POST` | [Submit license form](/docs/of-api/misc/submit-license-form) | `/api2/v2/users/license_form` |
| `GET` | [List referrals](/docs/of-api/misc/list-referrals) | `/api2/v2/users/me/referrals` |
| `GET` | [Get messages statistics by type](/docs/of-api/misc/get-messages-statistics-by-type) | `/api2/v2/users/me/stats/messages/{type}` |
| `GET` | [Get top fans stats](/docs/of-api/misc/get-top-fans-stats) | `/api2/v2/users/me/stats/top/fan` |
| `GET` | [Get top messages stats](/docs/of-api/misc/get-top-messages-stats) | `/api2/v2/users/me/stats/top/message` |
| `GET` | [Get top posts statistics](/docs/of-api/misc/get-top-posts-statistics) | `/api2/v2/users/me/stats/top/post` |
| `GET` | [Get top stories stats](/docs/of-api/misc/get-top-stories-stats-get) | `/api2/v2/users/me/stats/top/story` |
| `GET` | [Get top streams stats](/docs/of-api/misc/get-top-streams-stats-get) | `/api2/v2/users/me/stats/top/stream` |
| `POST` | [Confirm terms acceptance](/docs/of-api/misc/confirm-terms-acceptance) | `/api2/v2/users/terms/confirm` |
| `POST` | [Create a support ticket](/docs/of-api/misc/create-a-support-ticket) | `/api2/v2/users/tickets` |
| `GET` | [Get support ticket](/docs/of-api/misc/get-support-ticket) | `/api2/v2/users/tickets/{ticket_id}` |
| `PUT` | [Mark support ticket as read](/docs/of-api/misc/mark-support-ticket-as-read) | `/api2/v2/users/tickets/{ticket_id}/read` |
| `PUT` | [Reopen a support ticket](/docs/of-api/misc/reopen-a-support-ticket) | `/api2/v2/users/tickets/{ticket_id}/reopen` |
| `POST` | [Reply to support ticket](/docs/of-api/misc/reply-to-support-ticket) | `/api2/v2/users/tickets/{ticket_id}/reply` |
| `POST` | [Mark support ticket solved](/docs/of-api/misc/mark-support-ticket-solved) | `/api2/v2/users/tickets/{ticket_id}/solve` |
| `POST` | [Check support ticket allowed](/docs/of-api/misc/check-support-ticket-allowed) | `/api2/v2/users/tickets/allowed` |
| `GET` | [Get support ticket subjects](/docs/of-api/misc/get-support-ticket-subjects) | `/api2/v2/users/tickets/form_subjects` |
| `GET` | [Get state for a ZIP code](/docs/of-api/misc/get-state-for-a-zip-code) | `/api2/v2/zip/{zip_code}/state` |
---
# App initialization data (/docs/of-api/misc/app-initialization-data)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/init`
Returns comprehensive initialization data including authenticated user profile, feature flags, chat counts, and app configuration.
**OnlyFans only.** Requests made with a `user-id` belonging to a Fansly account are rejected β use the platform-neutral CRM data routes (`/notifications`, `/balances`, `/chats`, `/purchases`, `/subscribers/cached`, ...) which are Fansly-aware.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β Init payload
- `user` (object) β Full OnlyFans user profile object.
- `id` (integer)
- `name` (string)
- `username` (string)
- `displayName` (string)
- `about` (string)
- `avatar` (string) β Avatar URL.
- `avatarThumbs` (object)
- `c50` (string)
- `c144` (string)
- `header` (string) β Profile header/banner URL.
- `email` (string)
- `isVerified` (boolean)
- `isPerformer` (boolean)
- `canEarn` (boolean)
- `tipsEnabled` (boolean)
- `subscribedBy` (boolean) β Whether you are subscribed to this user.
- `subscribersCount` (integer)
- `subscribesCount` (integer)
- `postsCount` (integer)
- `photosCount` (integer)
- `videosCount` (integer)
- `audiosCount` (integer)
- `friendsCount` (integer)
- `joinDate` (string)
- `firstPublishedPostDate` (string)
- `subscribePrice` (number)
- `chatMessagesCount` (integer) β Unread chat messages.
- `countPriorityChat` (integer)
- `countPinnedChat` (integer)
- `hasPurchasedPosts` (boolean)
- `paidFeed` (boolean)
- `openseaInfo` (object)
- `chatMessagesCount` (integer)
- `countPriorityChat` (integer)
- `countPinnedChat` (integer)
- `hasPurchasedPosts` (boolean)
- `paidFeed` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/init" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Assign a guest (/docs/of-api/misc/assign-a-guest)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/guests/{guest_id}/assign`
Assigns a guest (co-performer/guest record) identified by guest_id. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `guest_id` (string, required) β Id of the guest to assign.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/guests/{guest_id}/assign" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Change legal inquiry status (/docs/of-api/misc/change-legal-inquiry-status)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/legal-inquiry/change-status/{inquiry_id}`
Changes the status of a legal inquiry. Part of the legal-inquiry (static-law) module. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `inquiry_id` (string, required) β ID of the legal inquiry.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/legal-inquiry/change-status/{inquiry_id}" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Check support ticket allowed (/docs/of-api/misc/check-support-ticket-allowed)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/users/tickets/allowed`
Checks whether the user is allowed to create a support ticket (part of the users/tickets support module). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/users/tickets/allowed" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Confirm terms acceptance (/docs/of-api/misc/confirm-terms-acceptance)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/users/terms/confirm`
Confirms the user's acceptance of updated terms. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/users/terms/confirm" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Create a support ticket (/docs/of-api/misc/create-a-support-ticket)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/users/tickets`
Creates a new support ticket for the current user. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/users/tickets" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Delete Shopify store (/docs/of-api/misc/delete-shopify-store)
`DELETE https://theonlyapi.com/api/crm/your-crm-id/api2/v2/shopify/stores/{store_id}`
Disconnects/removes a linked Shopify store by ID. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `store_id` (string, required) β ID of the linked Shopify store.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X DELETE "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/shopify/stores/{store_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Expand address for country (/docs/of-api/misc/expand-address-for-country)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/countries/{country_id}/address/expand`
Expands/resolves a partial address (by its hash) for a given country, optionally in Latin transliteration. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `country_id` (string, required) β ID of the country for address expansion.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Request body
Content type: `application/json`
- `hash` (string) β Address identifier/hash to expand (addressId).
- `isLatin` (boolean) β Whether to return Latin-script address.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/countries/{country_id}/address/expand" \
-H "X-API-Key: " \
-H "user-id: β¦" \
-H "Content-Type: application/json" \
-d '{}'
```
---
# Generate release form link URL (/docs/of-api/misc/generate-release-form-link-url)
`POST https://theonlyapi.com/api/crm/your-crm-id/api2/v2/release-form-links/{link_id}/url`
Generates/returns a shareable URL for a release form link identified by link_id. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `link_id` (string, required) β Id of the release form link.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X POST "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/release-form-links/{link_id}/url" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get client IP address (/docs/of-api/misc/get-client-ip-address)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/ip`
Returns the caller's IP address as seen by the API. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
β **Verified live** against a real OnlyFans account (2026-07-30); the `data` schema below is the real response shape.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response β verified live; `data` is the real shape.
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
- `ip` (string)
- `geo` (object)
- `connectionType` (string)
- `userType` (string)
- `subdivisionConfidence` (string)
- `postalConfidence` (string)
- `isp` (string)
- `domain` (string)
- `countryConfidence` (string)
- `cityConfidence` (string)
- `legitimateProxy` (string)
- `regionName` (string)
- `region` (string)
- `regionGeonameid` (string)
- `registeredCountryInEu` (string)
- `registeredCountryName` (string)
- `registeredCountryIso` (string)
- `registeredCountryGeonameid` (string)
- `postalCode` (string)
- `locationTimezone` (string)
- `locationMetrocode` (string)
- `longitude` (string)
- `latitude` (string)
- `locationAccuracyRadius` (string)
- `countryInEu` (string)
- `countryName` (string)
- `countryCode` (string)
- `countryGeonameid` (string)
- `continentName` (string)
- `continentGeonameid` (string)
- `continentCode` (string)
- `cityGeonameid` (string)
- `city` (string)
- `cityBuildDate` (string)
- `isEurope` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/ip" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get guest details (/docs/of-api/misc/get-guest-details)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/guests/{guest_id}`
Retrieves a guest record by id. Guests appear alongside release-form and guest-assign endpoints (people tagged in content). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `guest_id` (string, required) β ID of the guest.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/guests/{guest_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get latest subscribers (/docs/of-api/misc/get-latest-subscribers)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/subscriptions/subscribers/latest`
Returns the latest subscribers within a date range, grouped by the given field. The dynamic path segment is actually the querystring. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
β **Verified live** against a real OnlyFans account (2026-07-30); the `data` schema below is the real response shape.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `startDate` (string, required) β Start of the date range.
- `endDate` (string, required) β End of the date range.
- `by` (string) β Grouping/aggregation field.
- `offset` (string) β Pagination offset.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response β verified live; `data` is the real shape.
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
- `users` (object[])
- `view` (string)
- `avatar` (null)
- `avatarThumbs` (null)
- `header` (null)
- `headerSize` (null)
- `headerThumbs` (null)
- `id` (integer)
- `name` (string)
- `username` (string)
- `canLookStory` (boolean)
- `canCommentStory` (boolean)
- `hasNotViewedStory` (boolean)
- `isVerified` (boolean)
- `canPayInternal` (boolean)
- `hasScheduledStream` (boolean)
- `hasStream` (boolean)
- `hasStories` (boolean)
- `tipsEnabled` (boolean)
- `tipsTextEnabled` (boolean)
- `tipsMin` (integer)
- `tipsMinInternal` (integer)
- `tipsMax` (integer)
- `canEarn` (boolean)
- `canAddSubscriber` (boolean)
- `subscribePrice` (integer)
- `displayName` (string)
- `notice` (string)
- `isActive` (boolean)
- `isRestricted` (boolean)
- `canRestrict` (boolean)
- `subscribedBy` (boolean)
- `subscribedByExpire` (boolean)
- `subscribedByExpireDate` (string)
- `subscribedByAutoprolong` (boolean)
- `subscribedIsExpiredNow` (boolean)
- `currentSubscribePrice` (integer)
- `subscribedOn` (boolean)
- `subscribedOnExpire` (boolean)
- `subscribedOnExpiredNow` (boolean)
- `subscribedOnDuration` (string)
- `listsStates` (any[])
- `canReport` (boolean)
- `canReceiveChatMessage` (boolean)
- `hideChat` (boolean)
- `lastSeen` (string)
- `isPerformer` (boolean)
- `isRealPerformer` (boolean)
- `subscribedByData` (object)
- `subscribedOnData` (object)
- `canTrialSend` (boolean)
- `isBlocked` (boolean)
- `canUnsubscribe` (boolean)
- `isPendingAutoprolong` (boolean)
- `offset` (integer)
- `hasMore` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/subscriptions/subscribers/latest" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get legal inquiry (/docs/of-api/misc/get-legal-inquiry)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/legal-inquiry/{inquiry_id}`
Retrieves a legal inquiry (e.g. legal/DMCA request) by ID. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `inquiry_id` (string, required) β ID of the legal inquiry.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/legal-inquiry/{inquiry_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get legal inquiry form params (/docs/of-api/misc/get-legal-inquiry-form-params)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/legal-inquiry/params`
Returns the parameter/option definitions used to build the legal inquiry form. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
β **Verified live** against a real OnlyFans account (2026-07-30); the `data` schema below is the real response shape.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response β verified live; `data` is the real shape.
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
- `newReportsCount` (integer)
- `fileAllowedExtensions` (string[])
- `legalInquiry` (object)
- `matterTypes` (object[])
- `additional` (object)
- `category` (object[])
- `subject` (object[])
- `gdprOptions` (object)
- `groups` (object)
- `reports` (object)
- `highPrioritySubjects` (string[])
- `success` (boolean)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/legal-inquiry/params" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get legal inquiry history (/docs/of-api/misc/get-legal-inquiry-history)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/legal-inquiry/{inquiry_id}/history`
Returns the history/audit trail for a legal inquiry by id (from the legal/law module). **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `inquiry_id` (string, required) β ID of the legal inquiry.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/legal-inquiry/{inquiry_id}/history" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get legal inquiry update (/docs/of-api/misc/get-legal-inquiry-update)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/legal-inquiry/{inquiry_id}/update/{hash}`
Retrieves a specific update of a legal inquiry identified by inquiry_id, addressed by an update hash. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `inquiry_id` (string, required) β ID of the legal inquiry.
- `hash` (string, required) β Hash identifying the specific inquiry update.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/legal-inquiry/{inquiry_id}/update/{hash}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get messages earnings chart (/docs/of-api/misc/get-messages-earnings-chart)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/queue/chart`
Returns time-series earnings/statistics for queued messages. The dynamic path segment is a built querystring. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
β **Verified live** against a real OnlyFans account (2026-07-30); the `data` schema below is the real response shape.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `startDate` (string, required) β Start of the date range.
- `endDate` (string, required) β End of the date range.
- `withTotal` (string) β Include totals in the response.
- `filter` (string) β Metric filter (built from 'by' and 'by2' arguments).
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response β verified live; `data` is the real shape.
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
- `purchases` (object)
- `chart` (object[])
- `messages` (object)
- `chart` (object[])
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/messages/queue/chart" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get messages statistics by type (/docs/of-api/misc/get-messages-statistics-by-type)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/users/me/stats/messages/{type}`
Returns the current user's messaging statistics for a given type (default 'all') over a date range, paginated and searchable. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `type` (string, required) β Message stats type/segment (default 'all').
## Query parameters
- `startDate` (string) β Start of the date range.
- `endDate` (string) β End of the date range.
- `limit` (string) β Max number of rows.
- `query` (string) β Search term.
- `offset` (string) β Pagination offset.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/users/me/stats/messages/{type}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get partner release forms (/docs/of-api/misc/get-partner-release-forms)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/release-forms/partner/{partner_id}`
Retrieves release forms associated with a partner identified by partner id. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `partner_id` (string, required) β ID of the partner whose release forms are fetched.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/release-forms/partner/{partner_id}" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get payouts chart stats (/docs/of-api/misc/get-payouts-chart-stats)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/payouts/chart`
Returns payouts chart statistics (amount/count) over a date range. The dynamic path segment is the serialized querystring. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
β **Verified live** against a real OnlyFans account (2026-07-30); the `data` schema below is the real response shape.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `startDate` (string, required) β Range start date.
- `endDate` (string, required) β Range end date.
- `withTotal` (string) β Include totals (set true).
- `withChart` (string) β Include chart series (set true).
- `filter` (string) β Chart amount/count filter.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response β verified live; `data` is the real shape.
- `success` (boolean)
- `status_code` (integer)
- `data` (any[])
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/payouts/chart" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get posts earnings chart (/docs/of-api/misc/get-posts-earnings-chart)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/chart`
Returns time-series earnings/statistics for posts. The dynamic path segment is a built querystring, not an id. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
β **Verified live** against a real OnlyFans account (2026-07-30); the `data` schema below is the real response shape.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `startDate` (string, required) β Start of the date range.
- `endDate` (string, required) β End of the date range.
- `withTotal` (string) β Include totals in the response.
- `filter` (string) β Metric filter (built from the 'by' argument plus posts).
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response β verified live; `data` is the real shape.
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
- `posts` (object)
- `chart` (object[])
- `purchases` (object)
- `chart` (object[])
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/posts/chart" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get promotions statistics chart (/docs/of-api/misc/get-promotions-statistics-chart)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/promotions/chart`
Returns chart/statistics data for promotions over a date range. Path segment is static; the trailing token is a serialized query string. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
β **Verified live** against a real OnlyFans account (2026-07-30); the `data` schema below is the real response shape.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Query parameters
- `startDate` (string) β Start of the date range.
- `endDate` (string) β End of the date range.
- `stats` (string) β Stats flag (set to 1).
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response β verified live; `data` is the real shape.
- `success` (boolean)
- `status_code` (integer)
- `data` (object)
- `claims` (object)
- `chart` (object[])
- `total` (integer)
- `offers` (object)
- `chart` (object[])
- `total` (integer)
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/promotions/chart" \
-H "X-API-Key: " \
-H "user-id: β¦"
```
---
# Get report reason detail options (/docs/of-api/misc/get-report-reason-detail-options)
`GET https://theonlyapi.com/api/crm/your-crm-id/api2/v2/reports/reasons/{reason_id}/details-options`
Retrieves the detail options available for a specific report reason. **OnlyFans only** β Fansly accounts are rejected on `/api2/v2/*`.
**Guide:** [The two surfaces](/docs/two-surfaces)
## Authentication
- `X-API-Key` header (apiKey)
## Path parameters
- `reason_id` (string, required) β ID of the report reason.
## Header parameters
- `user-id` (string, required) β Creator account ID of the account to act as (e.g. `509955039`). **Required** β omitting it returns `400 user-id header is required`. Use `GET /accounts` to list your connected accounts. On `/api2/v2/*` the account must be an OnlyFans account; Fansly accounts are rejected there.
- `X-Proxy` (string) β Proxy URL β HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. **Required on the connection routes** (`POST /accounts/login`, `POST /accounts/login/cookies`, `POST /accounts/login/verify-otp`) for OnlyFans accounts; optional for Fansly.
## Responses
### 200 β OnlyFans response (passthrough envelope)
`data` carries OnlyFans' own response body for this endpoint, passed through unchanged.
- `success` (boolean, required) β True when OnlyFans returned a 2xx status.
- `status_code` (integer, required) β The upstream OnlyFans HTTP status code.
- `data` (any, required) β The raw OnlyFans response body (object or array).
- `relogin` (boolean) β Present and true when the session was refreshed mid-request.
## Example
```bash
curl -X GET "https://theonlyapi.com/api/crm/your-crm-id/api2/v2/reports/reasons/{reason_id}/details-options" \
-H "X-API-Key: