Fans
Find and rank fans, read one fan in full, list fan lists and see who is online.
Find and rank fans, read one fan in full, list fan lists and see who is online. Every route can also return the authentication errors, rate_limited and internal_error.
| Route | Method and path |
|---|---|
| List fans | GET /v1/accounts/{accountId}/fans |
| Get fan | GET /v1/accounts/{accountId}/fans/{fanId} |
| Fan lists | GET /v1/accounts/{accountId}/lists |
| Online fans | GET /v1/accounts/{accountId}/online-fans |
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.
Path parameters
| Name | Description |
|---|---|
accountId | The account's id, which is the creator's OnlyFans user id. 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://app.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"
}
}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 OnlyFans.
Path parameters
| Name | Description |
|---|---|
accountId | The account's id, which is the creator's OnlyFans user id. Get it from GET /v1/accounts. |
fanId | The fan's OnlyFans user id. A chat id is the same value. |
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
fresh | boolean | false | true reads live from OnlyFans 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://app.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"
}
}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. |
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.
Path parameters
| Name | Description |
|---|---|
accountId | The account's id, which is the creator's OnlyFans user id. 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://app.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"
}
}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 OnlyFans.
Path parameters
| Name | Description |
|---|---|
accountId | The account's id, which is the creator's OnlyFans user id. Get it from GET /v1/accounts. |
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
fresh | boolean | false | true reads live from OnlyFans 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://app.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"
}
}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. |
The Fan object
A fan of one account, as lists return it.
| Field | Type | Description |
|---|---|---|
id | string | OnlyFans user id of the fan. Also the chat id. |
username | string | OnlyFans username, 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 | OnlyFans user id of the fan. Also the chat id. |
username | string | OnlyFans username, 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 OnlyFans 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 | OnlyFans user id of the fan. Also the chat id. |
username | string | OnlyFans username, 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 OnlyFans 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 | OnlyFans user id of the fan. 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. |