The Only API docs

Connect an account

Session paste or credentials, 2FA, and the proxy requirement.

Before you can read or write anything for a creator, that account has to be connected to your panel. Each connected account occupies one slot.

There are two ways in. Session paste is more reliable and is what we recommend.

Open a browser logged into OnlyFans, then DevTools → Application → Cookies → onlyfans.com and copy sess and auth_id (and fp if present).

curl -X POST "$BASE/api/crm/$CRM/accounts/login/cookies" \
  -H "X-API-Key: $KEY" \
  -H "X-Proxy: http://user:pass@host:port" \
  -H "Content-Type: application/json" \
  -d '{
        "platform": "onlyfans",
        "sess": "<sess cookie value>",
        "auth_id": "<your OF user id>",
        "fp": "<optional fingerprint cookie>"
      }'

Strongly recommended for OnlyFans, but not enforced

Omitting X-Proxy does not return an error. The login proceeds and egresses on our shared server IP, and the saved session records no proxy — so every later request for that account also goes out direct. The failure surfaces later, as a session that keeps breaking. A malformed proxy is silently downgraded to none rather than rejected, so a typo degrades to direct egress with no signal.

OnlyFans ties sessions to the IP that created them. See Proxies.

The session is stored server-side. You never pass cookies or tokens again — from here on, the of_user_id is enough.

Option B — credentials

We run the whole login flow for you: Cloudflare init, Turnstile solve, signed login request, and a 2FA prompt if the platform asks for one.

curl -X POST "$BASE/api/crm/$CRM/accounts/login" \
  -H "X-API-Key: $KEY" \
  -H "X-Proxy: http://user:pass@host:port" \
  -H "Content-Type: application/json" \
  -d '{
        "platform": "onlyfans",
        "email": "[email protected]",
        "password": "…",
        "use_captcha": true
      }'

For platform: "fansly" the email field accepts a username or an email, X-Proxy is optional, and use_captcha is ignored — Fansly's login has no captcha step.

Two-factor authentication

If the platform demands 2FA, the response includes requires_2fa: true (Fansly additionally returns twofa_type). Submit the code to POST /accounts/login/verify-otp, passing the same platform, the same identifier you logged in with. You do not need to resend X-Proxy — the proxy stored with the parked 2FA challenge is reused, and the header is ignored here:

curl -X POST "$BASE/api/crm/$CRM/accounts/login/verify-otp" \
  -H "X-API-Key: $KEY" \
  -H "X-Proxy: http://user:pass@host:port" \
  -H "Content-Type: application/json" \
  -d '{"platform": "onlyfans", "email": "[email protected]", "otp_code": "123456"}'

The field is `otp_code`, not `code`

This route reads otp_code only — sending code returns 400 OTP code required. (The unrelated bulk-import OTP route accepts either spelling, which makes the inconsistency easy to trip over.)

Connecting is limited to 20 attempts per minute

Each of the three connection routes gets its own allowance of 20 requests per minute. They are counted separately, so in one minute you could paste 20 sessions and attempt 20 credential logins and submit 20 OTP codes.

The count is per API key, so it is your panel's budget alone — other customers cannot use it up.

Going over returns 429 until the minute rolls over. The practical consequence: do not retry a failed login in a loop. Twenty quick retries and you have spent the allowance and locked yourself out of connecting anything for the rest of that minute.

Connecting many at once

For more than a handful of accounts, paste them all and let the importer work through them — including parking rows that need a 2FA code until you supply one. See Bulk account import.

After connecting

GET /accounts lists connected accounts and their IDs:

curl "$BASE/api/crm/$CRM/accounts" -H "X-API-Key: $KEY"

Add ?include_session=true and each OnlyFans entry also carries a session block with sess, auth_id and proxy — present only when a stored session file exists; a read failure yields session_error instead. Fansly accounts never get one; use GET /accounts/{of_user_id}/fansly-credentials for those. You rarely need either — passing of_user_id and letting us resolve the session is the point.

Disconnecting

curl -X DELETE "$BASE/api/crm/$CRM/accounts/$OFUID" -H "X-API-Key: $KEY"

This frees the slot. Add ?purge=true to also delete the account's retained data rather than keeping it.

Write actions are off until you enable them

Connecting an account does not by itself allow us to act as it. Every write performed as the account is gated by the allow_of_write_actions polling setting:

curl -X PATCH "$BASE/api/crm/$CRM/accounts/$OFUID/polling" \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"allow_of_write_actions": true}'

Without it, writes return 403 with code: "WRITES_DISABLED". Match on the code, not the message.

The gate now covers every OnlyFans or Fansly write performed as the account: single DM/PPV sends (both platforms), the mass-DM real send, the send_dm automation action, payout requests, subscription-price changes, creating a campaign, the raw POST /accounts/{of_user_id}/request proxy when its body method is not GET, and any non-GET request through the /api2/v2/* passthrough. Reads are never gated.

On this page