# Fans

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.

Source: https://betterfans.link/docs/api/fans

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](https://betterfans.link/docs/errors#type-authentication), [`rate_limited`](https://betterfans.link/docs/errors#rate-limited) and [`internal_error`](https://betterfans.link/docs/errors#internal-error).

| Route                       | Method and path                             | Platforms           |
| --------------------------- | ------------------------------------------- | ------------------- |
| [List fans](#list-fans)     | `GET /v1/accounts/{accountId}/fans`         | OnlyFans and Fansly |
| [Get fan](#get-fan)         | `GET /v1/accounts/{accountId}/fans/{fanId}` | OnlyFans and Fansly |
| [Fan lists](#fan-lists)     | `GET /v1/accounts/{accountId}/lists`        | OnlyFans and Fansly |
| [Online fans](#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](https://betterfans.link/docs/api/fans#fan) objects in `data`. See [pagination](https://betterfans.link/docs/api#pagination).

### Example request

```bash
curl "https://betterfans.link/v1/accounts/412345678/fans?status=active&sort=spend&limit=10" \
  -H "Authorization: Bearer $BFL_KEY"
```

```ts
const res = await fetch("https://betterfans.link/v1/accounts/412345678/fans?status=active&sort=spend&limit=10", {
  headers: { Authorization: `Bearer ${process.env.BFL_KEY}` },
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
console.log(body.data);
```

### Example response

```json
{
  "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`](https://betterfans.link/docs/errors#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`](https://betterfans.link/docs/errors#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`](https://betterfans.link/docs/errors#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](https://betterfans.link/docs/api/fans#fandetail) object in `data`.

### Example request

```bash
curl "https://betterfans.link/v1/accounts/412345678/fans/38291045" \
  -H "Authorization: Bearer $BFL_KEY"
```

```ts
const res = await fetch("https://betterfans.link/v1/accounts/412345678/fans/38291045", {
  headers: { Authorization: `Bearer ${process.env.BFL_KEY}` },
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
console.log(body.data);
```

### Example response

```json
{
  "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`](https://betterfans.link/docs/errors#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`](https://betterfans.link/docs/errors#fan-not-found)             | 404    | No fan with this id on the account.                                                                                                   |
| [`invalid_parameter`](https://betterfans.link/docs/errors#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`](https://betterfans.link/docs/errors#account-unavailable) | 409    | Only with `fresh=true`: the account's status stops live calls. See `error.accountStatus`.                                             |
| [`onlyfans_error`](https://betterfans.link/docs/errors#onlyfans-error)           | 502    | Only with `fresh=true`: OnlyFans returned an error.                                                                                   |
| [`onlyfans_timeout`](https://betterfans.link/docs/errors#onlyfans-timeout)       | 502    | Only with `fresh=true`: OnlyFans did not answer in time.                                                                              |
| [`fansly_error`](https://betterfans.link/docs/errors#fansly-error)               | 502    | Only with `fresh=true`: Fansly returned an error.                                                                                     |
| [`fansly_timeout`](https://betterfans.link/docs/errors#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](https://betterfans.link/docs/api/fans#fanlist) objects in `data`. See [pagination](https://betterfans.link/docs/api#pagination).

### Example request

```bash
curl "https://betterfans.link/v1/accounts/412345678/lists" \
  -H "Authorization: Bearer $BFL_KEY"
```

```ts
const res = await fetch("https://betterfans.link/v1/accounts/412345678/lists", {
  headers: { Authorization: `Bearer ${process.env.BFL_KEY}` },
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
console.log(body.data);
```

### Example response

```json
{
  "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`](https://betterfans.link/docs/errors#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`](https://betterfans.link/docs/errors#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`](https://betterfans.link/docs/errors#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](https://betterfans.link/docs/api/fans#onlinefans) object in `data`.

### Example request

```bash
curl "https://betterfans.link/v1/accounts/412345678/online-fans" \
  -H "Authorization: Bearer $BFL_KEY"
```

```ts
const res = await fetch("https://betterfans.link/v1/accounts/412345678/online-fans", {
  headers: { Authorization: `Bearer ${process.env.BFL_KEY}` },
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
console.log(body.data);
```

### Example response

```json
{
  "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`](https://betterfans.link/docs/errors#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`](https://betterfans.link/docs/errors#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`](https://betterfans.link/docs/errors#account-unavailable) | 409    | Only with `fresh=true`: the account's status stops live calls. See `error.accountStatus`.                                             |
| [`onlyfans_error`](https://betterfans.link/docs/errors#onlyfans-error)           | 502    | Only with `fresh=true`: OnlyFans returned an error.                                                                                   |
| [`onlyfans_timeout`](https://betterfans.link/docs/errors#onlyfans-timeout)       | 502    | Only with `fresh=true`: OnlyFans did not answer in time.                                                                              |
| [`fansly_error`](https://betterfans.link/docs/errors#fansly-error)               | 502    | Only with `fresh=true`: Fansly returned an error.                                                                                     |
| [`fansly_timeout`](https://betterfans.link/docs/errors#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](https://betterfans.link/docs/concepts/money#money) or null | Price of the subscription.                                                                    |
| `spend`                     | object                                                             | Lifetime spend on this account.                                                               |
| `spend.total`               | [Money](https://betterfans.link/docs/concepts/money#money)         | Lifetime gross.                                                                               |
| `spend.net`                 | [Money](https://betterfans.link/docs/concepts/money#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](https://betterfans.link/docs/concepts/money#money) or null | Price of the subscription.                                                                    |
| `spend`                     | object                                                             | Lifetime spend on this account.                                                               |
| `spend.total`               | [Money](https://betterfans.link/docs/concepts/money#money)         | Lifetime gross from the fan's transactions.                                                   |
| `spend.net`                 | [Money](https://betterfans.link/docs/concepts/money#money)         | Lifetime net (about 80% of gross).                                                            |
| `spend.subscriptions`       | [Money](https://betterfans.link/docs/concepts/money#money)         | Subscription payments.                                                                        |
| `spend.tips`                | [Money](https://betterfans.link/docs/concepts/money#money)         | Tips.                                                                                         |
| `spend.messages`            | [Money](https://betterfans.link/docs/concepts/money#money)         | Paid messages (PPV).                                                                          |
| `spend.posts`               | [Money](https://betterfans.link/docs/concepts/money#money)         | Paid posts.                                                                                   |
| `spend.other`               | [Money](https://betterfans.link/docs/concepts/money#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](https://betterfans.link/docs/concepts/money#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.                                     |
