# The Only API — CRM endpoint reference > All 95 endpoints of the normalized CRM layer, which works for both OnlyFans and Fansly. Base URL: https://theonlyapi.com/api/crm/{crm_id}. Auth: `X-API-Key` header. --- # 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"]}' ```