ofapisv1
guide

Updated

How to Authenticate an OnlyFans Account Programmatically

"Authenticate" covers two separate things here, and merging them is where integrations break. There is your credential for ofapis — a Bearer token your server holds — and the creator session, the OnlyFans credentials ofapis stores encrypted and replays on your behalf. You rotate the first; your code never touches the second.

None of this is OAuth. OnlyFans publishes no authorization server, no consent screen and no refresh token, so there is no grant to exchange and no token endpoint to poll. Linking is the substitute: a one-time interactive sign-in that produces a server-side session.

The two secrets, side by side

API token Creator session
Shape ofapis_sk_... OF app token, x-bc, x-hash, x-of-rev, user agent, cookies
Held by your server ofapis, encrypted at rest
Created by the dashboard, instantly one interactive link per creator
Replaced by revoke and reissue re-linking the account
Visible to your code always never

The right-hand column is what a browser holds after an OnlyFans login. ofapis serialises that bundle to JSON and writes it to one TEXT column through an AES-256-GCM converter (AES/GCM/NoPadding, 256-bit key, random IV per write). Outbound calls for the account are pinned to a proxy — shared pool on Free, dedicated per account on Starter, per creator on Pro — so it always egresses from the same place. More on is the OnlyFans API safe.

Step 1 — Mint an API token

Create an ofapis_sk_... token in the dashboard (start free). It is workspace-wide, not per-creator, and nothing signs requests on top of it: one Authorization header is the whole scheme. Treat it like a database password — server-side only, never in a browser bundle.

Step 2 — Prove the token works

One call settles it, and checking it from a terminal is the fastest way to run that call:

curl -i https://api.ofapis.com/api/public/v1/me \
  -H "Authorization: Bearer $OFAPIS_TOKEN"

Use -i: the headers answer two questions the body does not.

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1771459200
X-Request-Id: 6b1c0f2e-6d8a-4a11-9a77-1f0e4a2c51d2

X-RateLimit-Limit tells you which plan the token resolved to — 60 per minute on Free, 100 on Starter, 500 on Pro. X-Request-Id is echoed from your request if you send one, generated if you do not; log it next to every call.

A 401 UNAUTHENTICATED means the token is wrong, missing or revoked. A 403 NO_ACTIVE_SUBSCRIPTION means the token is valid but the workspace has no active plan. Neither costs a credit — both are refused before metering.

Linking happens in the dashboard, interactively, by the person who owns the OnlyFans account. No endpoint accepts a username and password. Once the link completes the account moves to ACTIVE and every route works.

Account ids are assigned at link time and shown in the dashboard. Keep them in your own config: /me returns the creator profile, not an inventory of linked accounts, so ids are not discoverable at runtime.

Step 4 — Check the session is healthy, not merely present

A 200 proves your half of the handshake. The session's half is in the body:

{
  "data": {
    "id": 12345678,
    "username": "creator_handle",
    "name": "Creator Name",
    "email": "[email protected]",
    "isAuth": true,
    "canChat": true,
    "canUploadMedia": true,
    "canCreateLists": true,
    "subscribersCount": 1024
  }
}

isAuth is the live signal: OnlyFans confirming the replayed session is still logged in. The can* flags are permissions on the creator account itself, worth reading before a feature depends on them — a send fails against an account where canChat is false no matter how healthy the session is.

import os, requests

BASE = "https://api.ofapis.com/api/public/v1"

def session_state(account_id: int) -> str:
    r = requests.get(
        f"{BASE}/accounts/{account_id}/me",
        headers={"Authorization": f"Bearer {os.environ['OFAPIS_TOKEN']}"},
        timeout=30,
    )
    if r.status_code == 424:
        return "relink"                       # session gone, human action needed
    if r.status_code in (404, 422):
        return "misconfigured"                # wrong id, or account not ACTIVE
    r.raise_for_status()
    return "ok" if r.json()["data"].get("isAuth") else "relink"

Run that on deploy and on a schedule you control — a Python client with retries wires the same probe into a reusable session with typed responses. It is a probe, not a keep-alive: nothing refreshes anything.

Step 5 — Address the right creator

curl https://api.ofapis.com/api/public/v1/accounts/42/subscribers \
  -H "Authorization: Bearer $OFAPIS_TOKEN"

curl https://api.ofapis.com/api/public/v1/subscribers \
  -H "Authorization: Bearer $OFAPIS_TOKEN"

The first names account 42 explicitly. The second is a back-compatibility shim resolving to whichever account is first and ACTIVE — fine for one creator, dangerous the moment a second is linked, because the target changes without your code changing. Pro allows five; see multi-account management for how that is wired, or running a creator roster if the second account belongs to someone else's business — and name the id.

Account status, and the error each one produces

An account moves through four states — PENDING, ACTIVE, DISCONNECTED, ERROR — resolved before your request reaches the handler, so the state maps straight onto a response code.

Situation Status Code What to do
Linked and working 200 — nothing
Session dead upstream 424 bare text body re-link, then resume — refunded
Stored session rejected on validation 422 bare text body re-link — and you were charged
No linked account on the unscoped route 422 bare text body link one, or scope to an id — charged
Account still PENDING or DISCONNECTED 409 bare text body finish or redo the link — refunded
Id not on this workspace 404 bare text body fix the id in config

One asymmetry: the unscoped route only considers ACTIVE accounts, so a creator in ERROR surfaces as "nothing is linked" rather than "this session expired". Scoped routes tell the truth — one more reason to name the id.

Billing differs too. A 424 is debited then refunded, so a retry storm against a dead session is free. A 422 or 404 is debited and not refunded, so a worker looping on a misconfigured id burns real credits. Full matrix on API errors, allowances on pricing.

What a 424 actually means

When OnlyFans rejects the stored session with a 401, ofapis marks that account ERROR and returns 424. The marking is the point: later calls fail fast at resolution without touching OnlyFans, so a broken session degrades into an instant labelled error rather than a slow upstream timeout. The account stays ERROR until a human re-links it.

Treat 424 as a paging condition, not a retry condition. Backing off cannot fix it. Alert, pause that account's queue, keep the others running.

Why there is no refresh cron to write

There is no refresh endpoint, no expiry timestamp to track, and nothing in your code reaching for x-bc or a cookie jar. The session lives server-side, is replayed per request, and fails with one status code. Your whole session layer is: send the token, branch on 424. Compare the cookie rotation a scraper needs in API vs scraping.

Next: read the audience

With a healthy link, the first useful read is who is subscribed — get your subscribers list is the next step in this series, and the API reference covers every route you can point the token at.

FAQ

Does OnlyFans API authentication use OAuth?

No. OnlyFans exposes no OAuth authorization server, so there is no consent redirect, no scope string and no refresh token. ofapis replaces that flow with a one-time interactive link in the dashboard that produces an encrypted server-side session, and hands your code a plain Bearer token instead.

Do I ever send an OnlyFans username or password to the API?

Never, and no endpoint would accept them. Linking is interactive, in the dashboard, under the account owner's control. The only credential your code holds is the ofapis_sk_... token.

How do I tell a healthy session from a healthy token?

Call /me and read both layers. A 200 proves the token is valid; data.isAuth being true proves the stored OnlyFans session is still logged in. A bad token returns 401 instead, and a dead session returns 424.

What happens to in-flight work when a session expires?

The affected account starts returning 424 and is marked ERROR, so further calls fail immediately rather than hanging. Those calls are debited and refunded, so they cost nothing. Other accounts on the same token keep working.

Can one token drive several creators?

Yes. A token belongs to the workspace, and you address each linked creator with /accounts/{accountId}/.... Free and Starter allow one linked account, Pro allows five; the authentication reference has the header details.

Related guides

All guides
Start free — 250 credits, no card
Generate a token and make your first call in minutes.
Get started