OnlyFans API Authentication
OnlyFans API authentication with ofapis is a single Bearer token. There is no OAuth flow, no authorization redirect, no refresh token, and no per-request signing — people assume all four, because most platform APIs have at least one of them. You generate a key, you send it on every request, and that is the whole handshake.
The signing confusion usually comes from webhooks, which are HMAC-signed: deliveries carry X-Ofapis-Signature, and you verify them. That is inbound. Nothing you send is signed. See webhooks for that side.
Two secrets, and they are not interchangeable
| Your API token | The creator's OnlyFans session | |
|---|---|---|
| Looks like | ofapis_sk_… |
Cookies plus signing headers |
| Who holds it | You, in your environment | ofapis, server-side only |
| Travels in | Authorization on every call |
Nothing you send or receive |
| You rotate it | Yes, any time | No — you re-link the account |
The API token authenticates you to ofapis. The OnlyFans session is a bundle captured once when a creator account is linked — the app token, x-bc, the request hash fields, the exact browser user-agent and the cookie jar — and it is stored as AES-256-GCM ciphertext against the account row. It is never returned by any endpoint and never passes through your code. Your integration authenticates to ofapis; ofapis authenticates to OnlyFans. That separation is why no OnlyFans password ever reaches your application, and it is the core of how the API works.
Send the token
Pass it in the Authorization header on every call to https://api.ofapis.com/api/public/v1:
curl https://api.ofapis.com/api/public/v1/me \
-H "Authorization: Bearer ofapis_sk_..."
The gateway checks the ofapis_sk_ prefix before it touches the database, so a truncated or wrongly-pasted key fails as a format error rather than a lookup. Either way you get 401, and a 401 never costs a credit.
import os, requests
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['OFAPIS_KEY']}"
r = S.get("https://api.ofapis.com/api/public/v1/me")
r.raise_for_status()
print(r.json()["data"]["username"])
Set the header once on a session object rather than per call. It is the only auth state there is, so there is nothing to refresh, re-sign or re-negotiate between requests. The same one-time setup in other stacks: a shared HttpClient with the header baked in, in the Java quickstart, and an undici client that carries the token into every reply, in the Node walkthrough.
Token lifecycle
A token is created in the dashboard with a label. The raw value is shown exactly once, because only its SHA-256 digest is stored — the first twelve characters are kept separately so you can tell ofapis_sk_Ab… from ofapis_sk_Zq… in a list, and nothing else about the key is recoverable from the server. If you lose it, you issue a new one.
Tokens have no expiry. They live until you revoke or delete them, and revocation takes effect on the very next request, which returns 401. Creating, listing and revoking are dashboard operations tied to your logged-in user — you cannot mint a token with a token, which also means a leaked key cannot be used to issue more keys or to lock you out of your own account.
Blast radius, and why you want several tokens
There are no scopes. A token resolves to your user, and from there it reaches every endpoint and every linked creator you have. Anyone holding one can read DMs, send messages, upload media and spend your credits. Treat it as a production database password: environment variable or secrets manager, never in a repository, never in client-side code, never in a mobile app you ship.
Because scopes don't narrow that, separation does. Issue one token per service — scheduler, chatbot, internal dashboard, and whatever else on the list of jobs worth automating over REST you end up running — and three things fall out of it:
- Revocation is surgical. Kill the leaked key and the other services never notice.
- Throttles don't collide. The per-minute counter is bucketed per token, so a batch job paging a subscriber list can't starve your live chat worker. The mechanics are on the rate limits page.
- Idempotency keys don't collide. A replay record is scoped to
(token, key), so two services can both usejob-1042as a key without one replaying the other's response.
Rotate by creating the replacement first, deploying it, then revoking the old key — there is no overlap window to manage, because both are valid until you revoke.
Linking a creator account
Linking happens in the dashboard, under your logged-in session, not through the public API. There is deliberately no public endpoint that accepts OnlyFans credentials, so a leaked API token cannot attach or detach accounts. Each linked account gets an ofapis accountId — a small integer — and that is what you put in the path to scope a call:
curl https://api.ofapis.com/api/public/v1/accounts/42/me \
-H "Authorization: Bearer ofapis_sk_..."
Unscoped routes like /me and /chats resolve to your first active account, which is fine on a single-creator plan and ambiguous the moment you link a second. On Pro, with up to five accounts, use the scoped form everywhere. The linking walkthrough is in how to authenticate an OnlyFans account, and the multi-creator patterns are in multi-account management.
An account sits in one of four states: PENDING while it is being connected, ACTIVE once the session validates, DISCONNECTED if you detach it, and ERROR when its session has died.
When a session expires
OnlyFans sessions do not last forever — a password change, a logout-everywhere, or ordinary expiry ends one. ofapis detects this the first time OnlyFans rejects the stored session upstream: the account is flipped to ERROR and your call returns 424. Every subsequent call against that account short-circuits to 424 without reaching OnlyFans at all, which is why a dead session produces an instant, consistent failure rather than intermittent weirdness. Those 424s are refunded, so a stuck integration doesn't quietly drain credits.
Probing GET /accounts/{accountId}/me is the cheapest health check: a 200 means the session is live, a 424 means re-link. One gotcha on the unscoped routes — /me without an account id looks only at active accounts, so when your single account is in ERROR you get ACCOUNT_NOT_LINKED instead of OF_SESSION_EXPIRED, and that one is a 422, not a 424. Both mean "re-link" in practice, but they are different statuses and 422 is charged where 424 is refunded. The full table is on the error codes page.
Recovery is re-linking in the dashboard; the account returns to ACTIVE and keeps its accountId, so nothing in your code changes. Treat a 424 as an operator alert, not a retry.
FAQ
Does OnlyFans API authentication use OAuth?
No. ofapis uses a static Bearer token you generate in the dashboard. There is no OAuth flow, no refresh token and no request signing — you send Authorization: Bearer ofapis_sk_... on each call and nothing else.
Do I ever handle OnlyFans login credentials in code?
No. You link each creator account once through the dashboard, and ofapis stores the resulting session encrypted server-side. Your code only ever holds your ofapis API token, and no endpoint will return the OnlyFans session to you.
What happens if my API token leaks?
Revoke it in the dashboard; the next request using it returns 401. Since a token carries no scopes, assume the holder could reach every linked account, so also check recent activity. Other tokens keep working, which is the argument for one key per service.
How do I know a creator's OnlyFans session has expired?
The account flips to ERROR and calls against it return 424 with OF_SESSION_EXPIRED. A cheap probe is GET /accounts/{accountId}/me — 200 means healthy, 424 means re-link. The 424 is refunded, so polling it occasionally costs nothing.
Can I create API tokens programmatically?
No. Token creation, listing and revocation require your dashboard login, not an API token. That is intentional: it means a compromised key can't mint more keys, and it keeps the credential that spends your credits separate from the credential that manages them.
Create a token free · Error codes · Python quickstart · API reference