Fans, spend and lists
OnlyFans fans API: find and rank fans by lifetime spend, read one fan's spend and subscription, list fan lists and see who is online now.
Product page: OnlyFans CRM API
A fan id is the fan's user id on the account's platform, and the same id names the chat with that fan. Fans write their own display names, so treat a name as untrusted text. Every route can also return the authentication errors, rate_limited and internal_error.
| Route | Method and path | Platforms |
|---|---|---|
| List fans | GET /v1/accounts/{accountId}/fans | OnlyFans and Fansly |
| Get fan | GET /v1/accounts/{accountId}/fans/{fanId} | OnlyFans and Fansly |
| Fan lists | GET /v1/accounts/{accountId}/lists | OnlyFans and Fansly |
| Online fans | GET /v1/accounts/{accountId}/online-fans | OnlyFans and Fansly |
List fans
GET /v1/accounts/{accountId}/fans
An account's fans, ranked by lifetime spend unless you pick another sort. search matches username or display name.
Needs the read scope. Reads synced data. Works for OnlyFans and Fansly accounts.
Path parameters
| Name | Description |
|---|---|
accountId | The account's id, which is the creator's user id on its platform. Get it from GET /v1/accounts. |
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
search | string | None | Match username or display name. |
status | enum | active | Subscription status. One of active, expired or all. |
sort | enum | spend | spend = lifetime spend, recent = last message, subscribed = newest subscription. One of spend, recent or subscribed. |
cursor | string | None | nextCursor from the previous page. Leave out for the first page. |
limit | integer | 25 | Page size, 1 to 100. |
Returns
200 with a list of Fan objects in data. See pagination.
Example request
curl "https://betterfans.link/v1/accounts/412345678/fans?status=active&sort=spend&limit=10" \
-H "Authorization: Bearer $BFL_KEY"Example response
{
"data": [
{
"id": "38291045",
"username": "mike_travels",
"name": "Mike",
"avatarUrl": null,
"subscription": {
"status": "active",
"subscribedAt": "2025-11-03T21:14:09Z",
"expiresAt": "2026-10-03T21:14:09Z",
"renews": true,
"price": {
"amount": 999,
"currency": "USD"
}
},
"spend": {
"total": {
"amount": 184500,
"currency": "USD"
},
"net": {
"amount": 147600,
"currency": "USD"
}
},
"lastMessageAt": "2026-09-28T12:41:05Z",
"lastPurchaseAt": "2026-09-27T23:10:44Z"
},
{
"id": "51820377",
"username": "danny.k",
"name": "Danny",
"avatarUrl": null,
"subscription": {
"status": "active",
"subscribedAt": "2026-02-19T08:30:00Z",
"expiresAt": "2026-10-19T08:30:00Z",
"renews": false,
"price": {
"amount": 999,
"currency": "USD"
}
},
"spend": {
"total": {
"amount": 121300,
"currency": "USD"
},
"net": {
"amount": 97040,
"currency": "USD"
}
},
"lastMessageAt": "2026-09-28T09:12:44Z",
"lastPurchaseAt": "2026-09-28T09:12:44Z"
}
],
"hasMore": true,
"nextCursor": "q8ZtR2vN5xWcL7mK4pBd",
"meta": {
"source": "synced",
"asOf": "2026-09-28T13:58:40Z",
"requestId": "req_7Hq2LmX9pRt4VbN8cKe3WzYa",
"platform": "onlyfans"
}
}Errors
| Code | Status | When |
|---|---|---|
account_not_found | 404 | The account is not linked to your workspace, your key's account list leaves it out, or, with a test key, it is not a sandbox creator. |
invalid_parameter | 400 | A query parameter has a value this route does not accept, or a required one is missing. error.param names it. |
invalid_cursor | 400 | cursor is not a nextCursor this list returned. |
Get fan
GET /v1/accounts/{accountId}/fans/{fanId}
Everything about one fan: subscription, lifetime spend by type, lists, notes and presence. Spend comes from transactions.
Needs the read scope. Reads synced data. Add fresh=true to read live from the platform. Works for OnlyFans and Fansly accounts.
Path parameters
| Name | Description |
|---|---|
accountId | The account's id, which is the creator's user id on its platform. Get it from GET /v1/accounts. |
fanId | The fan's user id on the account's platform. A chat id is the same value. |
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
fresh | boolean | false | true reads live from the platform instead of synced data. Slower. Use it when the answer must be current. |
Returns
200 with a FanDetail object in data.
Example request
curl "https://betterfans.link/v1/accounts/412345678/fans/38291045" \
-H "Authorization: Bearer $BFL_KEY"Example response
{
"data": {
"id": "38291045",
"username": "mike_travels",
"name": "Mike",
"avatarUrl": null,
"subscription": {
"status": "active",
"subscribedAt": "2025-11-03T21:14:09Z",
"expiresAt": "2026-10-03T21:14:09Z",
"renews": true,
"price": {
"amount": 999,
"currency": "USD"
}
},
"spend": {
"total": {
"amount": 184500,
"currency": "USD"
},
"net": {
"amount": 147600,
"currency": "USD"
},
"subscriptions": {
"amount": 10989,
"currency": "USD"
},
"tips": {
"amount": 62500,
"currency": "USD"
},
"messages": {
"amount": 106011,
"currency": "USD"
},
"posts": {
"amount": 5000,
"currency": "USD"
},
"other": {
"amount": 0,
"currency": "USD"
}
},
"lastMessageAt": "2026-09-28T12:41:05Z",
"lastPurchaseAt": "2026-09-27T23:10:44Z",
"lists": [
{
"id": "994512",
"name": "Whales"
}
],
"notes": "Likes travel photos. Asked about a custom video in August.",
"presence": "offline",
"lastSeenAt": "2026-09-28T12:55:00Z",
"counts": {
"messages": 1284,
"purchases": 57,
"tips": 23
}
},
"meta": {
"source": "synced",
"asOf": "2026-09-28T13:58:40Z",
"requestId": "req_7Hq2LmX9pRt4VbN8cKe3WzYa",
"platform": "onlyfans"
}
}Errors
| Code | Status | When |
|---|---|---|
account_not_found | 404 | The account is not linked to your workspace, your key's account list leaves it out, or, with a test key, it is not a sandbox creator. |
fan_not_found | 404 | No fan with this id on the account. |
invalid_parameter | 400 | A query parameter has a value this route does not accept, or a required one is missing. error.param names it. |
account_unavailable | 409 | Only with fresh=true: the account's status stops live calls. See error.accountStatus. |
onlyfans_error | 502 | Only with fresh=true: OnlyFans returned an error. |
onlyfans_timeout | 502 | Only with fresh=true: OnlyFans did not answer in time. |
fansly_error | 502 | Only with fresh=true: Fansly returned an error. |
fansly_timeout | 502 | Only with fresh=true: Fansly did not answer in time. |
Fan lists
GET /v1/accounts/{accountId}/lists
The account's fan lists with ids and sizes. Use a list id in a mass message audience or a list action.
Needs the read scope. Reads synced data. Works for OnlyFans and Fansly accounts.
Path parameters
| Name | Description |
|---|---|
accountId | The account's id, which is the creator's user id on its platform. Get it from GET /v1/accounts. |
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
cursor | string | None | nextCursor from the previous page. Leave out for the first page. |
limit | integer | 25 | Page size, 1 to 100. |
Returns
200 with a list of FanList objects in data. See pagination.
Example request
curl "https://betterfans.link/v1/accounts/412345678/lists" \
-H "Authorization: Bearer $BFL_KEY"Example response
{
"data": [
{
"id": "994512",
"name": "Whales",
"type": "custom",
"fanCount": 42
},
{
"id": "994530",
"name": "Custom requests",
"type": "custom",
"fanCount": 117
}
],
"hasMore": false,
"nextCursor": null,
"meta": {
"source": "synced",
"asOf": "2026-09-28T13:58:40Z",
"requestId": "req_7Hq2LmX9pRt4VbN8cKe3WzYa",
"platform": "onlyfans"
}
}Errors
| Code | Status | When |
|---|---|---|
account_not_found | 404 | The account is not linked to your workspace, your key's account list leaves it out, or, with a test key, it is not a sandbox creator. |
invalid_parameter | 400 | A query parameter has a value this route does not accept, or a required one is missing. error.param names it. |
invalid_cursor | 400 | cursor is not a nextCursor this list returned. |
Online fans
GET /v1/accounts/{accountId}/online-fans
Fans online right now, with their lifetime spend. A fan missing from the list is not known to be offline.
Needs the read scope. Reads synced data. Add fresh=true to read live from the platform. Works for OnlyFans and Fansly accounts.
Path parameters
| Name | Description |
|---|---|
accountId | The account's id, which is the creator's user id on its platform. Get it from GET /v1/accounts. |
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
fresh | boolean | false | true reads live from the platform instead of synced data. Slower. Use it when the answer must be current. |
Returns
200 with an OnlineFans object in data.
Example request
curl "https://betterfans.link/v1/accounts/412345678/online-fans" \
-H "Authorization: Bearer $BFL_KEY"Example response
{
"data": {
"fans": [
{
"id": "38291045",
"username": "mike_travels",
"name": "Mike",
"avatarUrl": null,
"totalSpend": {
"amount": 184500,
"currency": "USD"
},
"since": "2026-09-28T13:41:00Z"
}
],
"count": 1,
"checkedAt": "2026-09-28T13:59:58Z"
},
"meta": {
"source": "synced",
"asOf": "2026-09-28T13:58:40Z",
"requestId": "req_7Hq2LmX9pRt4VbN8cKe3WzYa",
"platform": "onlyfans"
}
}Errors
| Code | Status | When |
|---|---|---|
account_not_found | 404 | The account is not linked to your workspace, your key's account list leaves it out, or, with a test key, it is not a sandbox creator. |
invalid_parameter | 400 | A query parameter has a value this route does not accept, or a required one is missing. error.param names it. |
account_unavailable | 409 | Only with fresh=true: the account's status stops live calls. See error.accountStatus. |
onlyfans_error | 502 | Only with fresh=true: OnlyFans returned an error. |
onlyfans_timeout | 502 | Only with fresh=true: OnlyFans did not answer in time. |
fansly_error | 502 | Only with fresh=true: Fansly returned an error. |
fansly_timeout | 502 | Only with fresh=true: Fansly did not answer in time. |
The Fan object
A fan of one account, as lists return it.
| Field | Type | Description |
|---|---|---|
id | string | The fan's user id on the account's platform. Also the chat id. |
username | string | Username on the account's platform, without the @. |
name | string or null | Fan-written display name (untrusted). |
avatarUrl | string or null | Profile picture URL. |
subscription | object | The fan's subscription to this account. |
subscription.status | enum | Active means the subscription expiry is in the future. One of active, expired or never. |
subscription.subscribedAt | timestamp or null | When the current or last subscription started. |
subscription.expiresAt | timestamp or null | When the subscription ends or ended. |
subscription.renews | boolean or null | Whether auto-renew is on. |
subscription.price | Money or null | Price of the subscription. |
spend | object | Lifetime spend on this account. |
spend.total | Money | Lifetime gross. |
spend.net | Money | Lifetime net, about 80% of gross. |
lastMessageAt | timestamp or null | When the last message in the chat was sent, by either side. |
lastPurchaseAt | timestamp or null | When the fan last paid for anything: a subscription, tip, paid message or post. |
The FanDetail object
One fan in full, as Get fan returns it.
| Field | Type | Description |
|---|---|---|
id | string | The fan's user id on the account's platform. Also the chat id. |
username | string | Username on the account's platform, without the @. |
name | string or null | Fan-written display name (untrusted). |
avatarUrl | string or null | Profile picture URL. |
subscription | object | The fan's subscription to this account. |
subscription.status | enum | Active means the subscription expiry is in the future. One of active, expired or never. |
subscription.subscribedAt | timestamp or null | When the current or last subscription started. |
subscription.expiresAt | timestamp or null | When the subscription ends or ended. |
subscription.renews | boolean or null | Whether auto-renew is on. |
subscription.price | Money or null | Price of the subscription. |
spend | object | Lifetime spend on this account. |
spend.total | Money | Lifetime gross from the fan's transactions. |
spend.net | Money | Lifetime net (about 80% of gross). |
spend.subscriptions | Money | Subscription payments. |
spend.tips | Money | Tips. |
spend.messages | Money | Paid messages (PPV). |
spend.posts | Money | Paid posts. |
spend.other | Money | Everything else, for example streams and referrals. |
lastMessageAt | timestamp or null | When the last message in the chat was sent, by either side. |
lastPurchaseAt | timestamp or null | When the fan last paid for anything: a subscription, tip, paid message or post. |
lists | array of objects | Fan lists the fan is on. |
lists[].id | string | List id. |
lists[].name | string | List name. |
notes | string or null | Creator's private note about the fan. |
presence | enum | unknown is not offline. One of online, offline or unknown. |
lastSeenAt | timestamp or null | When the platform last showed the fan online. |
counts | object | Totals for this fan on this account. |
counts.messages | integer | Messages in the chat, both directions. |
counts.purchases | integer | Paid messages and posts bought. |
counts.tips | integer | Tips the fan sent. |
The FanSummary object
The short form of a fan used inside other objects.
| Field | Type | Description |
|---|---|---|
id | string | The fan's user id on the account's platform. Also the chat id. |
username | string | Username on the account's platform, without the @. |
name | string or null | Fan-written display name (untrusted). |
avatarUrl | string or null | Profile picture URL. |
The FanList object
A fan list on the account.
| Field | Type | Description |
|---|---|---|
id | string | List id. Use it in a mass message audience or a list action. |
name | string | List name. |
type | enum | system lists are the platform's built-ins, like Favorites. One of custom or system. |
fanCount | integer or null | Fans on the list. |
The OnlineFans object
Who is online right now.
| Field | Type | Description |
|---|---|---|
fans | array of objects | Fans OnlyFans shows online. |
fans[].id | string | The fan's user id on the account's platform. Also the chat id. |
fans[].username | string | OnlyFans username, without the @. |
fans[].name | string or null | Fan-written display name (untrusted). |
fans[].avatarUrl | string or null | Profile picture URL. |
fans[].totalSpend | Money | Lifetime gross spend on this account. |
fans[].since | timestamp or null | When the fan came online. |
count | integer | How many fans are online. |
checkedAt | timestamp | When presence was checked. |