OnlyFans Creator Profile & User API
Two different questions, three routes. /me answers whose session am I holding and is it alive. The user lookups answer who is this id — the question every webhook, chat row and list membership leaves you with, because chat and message payloads carry bare numeric ids and nothing else.
Endpoints
GET /api/public/v1/me
GET /api/public/v1/users/{targetUserId}
GET /api/public/v1/users/by-username/{username}
GET /api/public/v1/accounts/{accountId}/me
GET /api/public/v1/accounts/{accountId}/users/{targetUserId}
GET /api/public/v1/accounts/{accountId}/users/by-username/{username}
None of these take query parameters. username is the handle without the @. targetUserId is the numeric OnlyFans user id.
One id to keep straight: accountId is the ofapis account id (the integer that identifies a linked session on your token, 42 in every example here). It is not the creator's OnlyFans id returned by /me. Mixing them up produces a 404 ACCOUNT_NOT_FOUND, not a 403.
GET /me
curl https://api.ofapis.com/api/public/v1/accounts/42/me \
-H "Authorization: Bearer ofapis_sk_..."
{
"data": {
"id": 12345678,
"username": "creator_handle",
"name": "Creator Name",
"email": "[email protected]",
"subscribersCount": 1024,
"isAuth": true
}
}
The full object carries more than the example: subscribesCount, postsCount, photosCount, videosCount, avatar and header URLs, the capability flags canChat, canCreateLists, canUploadMedia, canUploadAvatarHeader, canPayInternal, plus isVisibleOnline, watermarkText and watermarkPosition.
The capability flags are worth reading before you act rather than after. canCreateLists: false means a list write will fail upstream; canUploadMedia: false means a scheduled post with mediaFiles has nowhere to get them. Checking the flag costs the same credit as discovering it the hard way, and it turns a failed write into a skipped one.
GET /users/{targetUserId} and /users/by-username/{username}
curl https://api.ofapis.com/api/public/v1/accounts/42/users/by-username/fan_handle \
-H "Authorization: Bearer ofapis_sk_..."
{
"data": {
"id": 98765,
"username": "fan_handle",
"name": "Fan Name",
"isVerified": false,
"subscribedBy": true,
"postsCount": 42,
"photosCount": 30,
"videosCount": 12
}
}
This is the richest read in the API, and the only place several fields appear at all:
| Field | Type | What it tells you |
|---|---|---|
subscribedBy |
boolean | this user subscribes to the authenticated creator |
subscribedByExpire |
boolean | the subscription is set to lapse |
subscribedByExpireDate |
string | ISO-8601 expiry — the churn clock |
currentSubscribePrice |
integer | cents, after any promotion |
subscribePrice |
integer | cents, list price |
subscribedOn |
boolean | the creator subscribes to them |
joinDate |
string | ISO-8601, account age |
lastSeen |
string | ISO-8601, recency signal |
canReceiveChatMessage |
boolean | a DM will actually land |
tipsEnabled, tipsMin, tipsMax |
boolean, cents | tipping envelope |
about, website, location, notice |
string | profile text |
isPaywallRequired, isAdultContent, isPerformer |
boolean | account type |
isBlocked, isRestricted, isFriend |
boolean | relationship state |
subscribedByExpireDate and currentSubscribePrice are the two that make this endpoint worth a credit: neither appears on the paged rows from the subscribers API, so a renewal reminder or a win-back cohort has to come through here. Which of the reads is cheapest for a given field is worked out row by row on the fan lookup page.
Session health as a free signal
/me is the cheapest honest probe of a linked session. A 200 with isAuth: true means the stored session still works; a 422 OF_AUTH_INVALID means it expired and the account needs re-linking, and a 409 ACCOUNT_NOT_ACTIVE means it is linked but disconnected. Both of those statuses are refunded, so a failing probe costs nothing — only the healthy ones are billed. The curl audit from the shell uses the same request as its first health check.
#!/usr/bin/env bash
# Probe a roster, print the accounts that need re-linking.
for id in 42 43 44; do
body=$(curl -sS -o - -w '\n%{http_code}' \
"https://api.ofapis.com/api/public/v1/accounts/$id/me" \
-H "Authorization: Bearer $OFAPIS_TOKEN")
code=$(tail -n1 <<<"$body")
case "$code" in
200) jq -r '"ok \(.data.username) subs=\(.data.subscribersCount)"' <<<"$(sed '$d' <<<"$body")" ;;
422) echo "relink account $id — OnlyFans session expired" ;;
409) echo "offline account $id — account not active" ;;
*) echo "error account $id — HTTP $code" ;;
esac
done
Wire that to the same alert channel as your webhook endpoint health and a dead session surfaces in minutes instead of when a campaign silently stops delivering. Re-linking itself is covered in the authentication guide.
Errors
| Status | error.code |
Applies to |
|---|---|---|
| 401 | UNAUTHENTICATED |
any route — token missing or revoked |
| 402 | INSUFFICIENT_CREDITS |
any route |
| 403 | NO_ACTIVE_SUBSCRIPTION |
any route |
| 404 | ACCOUNT_NOT_FOUND |
scoped routes — accountId not on this token |
| 409 | ACCOUNT_NOT_ACTIVE |
scoped routes — linked but disconnected |
| 422 | OF_AUTH_INVALID |
scoped routes — stored session expired |
| 422 | ACCOUNT_NOT_LINKED |
unscoped routes — no active account at all; charged |
| 429 | RATE_LIMIT_EXCEEDED |
any route — honour Retry-After |
The split between 424 and 404 is diagnostic: 424 means the token has no usable account behind it, while 404 means the id you named is not one of yours. A 422 is the only one that needs a human — the creator has to re-authorise.
Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (unix seconds), and echo back any X-Request-Id you send, which is the value to quote in a support thread. Everything here is a single Bearer token with no request signing; the full scheme is on authentication.
Cost
One credit per successful lookup, charged per call with no batch form — resolving 200 ids is 200 credits. Cache aggressively: username, name and joinDate effectively never change, subscribedByExpireDate changes once a billing cycle, and lastSeen is the only field that justifies a re-fetch inside a day. Plan limits are on pricing, and the shared response headers and error codes behave as described in the API reference.
FAQ
Which linked account does the unscoped /me return?
The first account in ACTIVE status on your token. That is deterministic but implicit, so anything managing more than one creator should call /accounts/{accountId}/me and pass the id explicitly rather than relying on ordering.
Can I resolve a handle to a numeric id?
Yes — GET /users/by-username/{username} takes the handle without the @ and returns the full profile, id included. Store that id; it is stable, while a handle can be changed by its owner.
Is there a bulk user lookup?
No. Each id is one request and one credit, so a job that enriches thousands of fans needs a local cache and a pass that only fetches ids it has never seen. Fetch on first sight, refresh on a schedule you choose.
Does a failed probe cost credits?
Not for the statuses that matter here. 422, 409 and any 5xx are refunded after the fact, and 401, 402, 403 and 429 are rejected before the debit happens. A 200 is billed whether or not you liked the answer.
What is the difference between subscribedBy and subscribedOn?
subscribedBy is true when the user subscribes to the authenticated creator — the normal fan relationship. subscribedOn is the reverse: the creator subscribes to them, which is what the /following list is made of.