BetterFans Link: the OnlyFans APIBetterFans Link

Posts, vault, tracking links and mass message stats

OnlyFans vault, posts and tracking links API: reach and sales of mass messages, top posts, subscribers and revenue per link, and vault media.

Product page: OnlyFans content API

Use them to compare what a creator sends and posts, and to find media ids to attach to a message. Every route can also return the authentication errors, rate_limited and internal_error.

RouteMethod and pathPlatforms
Mass messagesGET /v1/accounts/{accountId}/mass-messagesOnlyFans
Top contentGET /v1/accounts/{accountId}/postsOnlyFans and Fansly
Tracking linksGET /v1/accounts/{accountId}/linksOnlyFans and Fansly
VaultGET /v1/accounts/{accountId}/vaultOnlyFans and Fansly

Mass messages

GET /v1/accounts/{accountId}/mass-messages

Mass messages with how many fans got, opened and bought each one, and what each earned.

Needs the read scope. Reads synced data. Works for OnlyFans accounts only. Other accounts get not_supported_on_platform.

A mass message's numbers are totals for the whole send. Never add them to per-fan message numbers: each fan's copy of a mass message is part of the same total.

revenue comes from OnlyFans' own mass message stats, not from transactions, so net is 80% of gross.

Path parameters

NameDescription
accountIdThe account's id, which is the creator's user id on its platform. Get it from GET /v1/accounts.

Query parameters

NameTypeDefaultDescription
fromdate or timestamp30 days agoStart, ISO 8601 date or timestamp (UTC).
todate or timestampnowEnd, ISO 8601 date or timestamp (UTC).
sortenumrecentrecent = newest first. One of recent or revenue.
cursorstringNonenextCursor from the previous page. Leave out for the first page.
limitinteger25Page size, 1 to 100.

Returns

200 with a list of MassMessage objects in data. See pagination.

Example request

curl "https://betterfans.link/v1/accounts/412345678/mass-messages?limit=10" \
  -H "Authorization: Bearer $BFL_KEY"

Example response

{
  "data": [
    {
      "id": "118823004",
      "sentAt": "2026-09-26T18:00:00Z",
      "text": "Weekend special: the full beach video is in your inbox",
      "price": {
        "amount": 1200,
        "currency": "USD"
      },
      "media": [
        {
          "id": "3399120045",
          "type": "video",
          "thumbnailUrl": null,
          "durationSeconds": 94
        }
      ],
      "sentCount": 1742,
      "viewedCount": 903,
      "purchasedCount": 128,
      "revenue": {
        "gross": {
          "amount": 153600,
          "currency": "USD"
        },
        "net": {
          "amount": 122880,
          "currency": "USD"
        }
      },
      "state": "sent"
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "meta": {
    "source": "synced",
    "asOf": "2026-09-28T13:58:40Z",
    "requestId": "req_7Hq2LmX9pRt4VbN8cKe3WzYa",
    "platform": "onlyfans"
  }
}

Errors

CodeStatusWhen
account_not_found404The 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.
not_supported_on_platform400The account is not on OnlyFans.
invalid_parameter400A query parameter has a value this route does not accept, or a required one is missing. error.param names it.
invalid_cursor400cursor is not a nextCursor this list returned.

Top content

GET /v1/accounts/{accountId}/posts

Posts with price, likes, comments and tips, sorted by recent or likes. revenue is null when the platform does not say which post a purchase was for. tips is null on OnlyFans accounts, which do not say which post a tip was for.

Needs the read scope. Reads synced data. Works for OnlyFans and Fansly accounts.

Path parameters

NameDescription
accountIdThe account's id, which is the creator's user id on its platform. Get it from GET /v1/accounts.

Query parameters

NameTypeDefaultDescription
fromdate or timestamp30 days agoStart, ISO 8601 date or timestamp (UTC).
todate or timestampnowEnd, ISO 8601 date or timestamp (UTC).
sortenumrecentrecent = newest first, likes = most liked first. One of recent or likes.
cursorstringNonenextCursor from the previous page. Leave out for the first page.
limitinteger25Page size, 1 to 100.

Returns

200 with a list of Post objects in data. See pagination.

Example request

curl "https://betterfans.link/v1/accounts/412345678/posts?sort=likes&limit=10" \
  -H "Authorization: Bearer $BFL_KEY"

Example response

{
  "data": [
    {
      "id": "1788201934",
      "postedAt": "2026-09-20T17:30:00Z",
      "text": "Golden hour at the beach",
      "price": null,
      "media": [
        {
          "id": "3399120101",
          "type": "photo",
          "thumbnailUrl": null,
          "durationSeconds": null
        }
      ],
      "likes": 412,
      "comments": 38,
      "tips": null,
      "revenue": null,
      "url": "https://onlyfans.com/1788201934/jessrivers"
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "meta": {
    "source": "synced",
    "asOf": "2026-09-28T13:58:40Z",
    "requestId": "req_7Hq2LmX9pRt4VbN8cKe3WzYa",
    "platform": "onlyfans"
  }
}

Errors

CodeStatusWhen
account_not_found404The 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_parameter400A query parameter has a value this route does not accept, or a required one is missing. error.param names it.
invalid_cursor400cursor is not a nextCursor this list returned.

GET /v1/accounts/{accountId}/links

Tracking and free trial links with clicks, subscribers and revenue. url is null on Fansly accounts, which do not state the address fans open.

Needs the read scope. Reads synced data. Works for OnlyFans and Fansly accounts.

Path parameters

NameDescription
accountIdThe account's id, which is the creator's user id on its platform. Get it from GET /v1/accounts.

Query parameters

NameTypeDefaultDescription
sortenumrevenuerevenue = most earned first. One of revenue, subscribers or recent.
cursorstringNonenextCursor from the previous page. Leave out for the first page.
limitinteger25Page size, 1 to 100.

Returns

200 with a list of TrackingLink objects in data. See pagination.

Example request

curl "https://betterfans.link/v1/accounts/412345678/links?sort=subscribers" \
  -H "Authorization: Bearer $BFL_KEY"

Example response

{
  "data": [
    {
      "id": "2210044",
      "kind": "tracking",
      "name": "Instagram bio",
      "url": "https://onlyfans.com/jessrivers/c12",
      "createdAt": "2026-03-02T15:00:00Z",
      "clicks": 18244,
      "subscribers": 1310,
      "revenue": {
        "gross": {
          "amount": 486000,
          "currency": "USD"
        },
        "net": {
          "amount": 388800,
          "currency": "USD"
        }
      }
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "meta": {
    "source": "synced",
    "asOf": "2026-09-28T13:58:40Z",
    "requestId": "req_7Hq2LmX9pRt4VbN8cKe3WzYa",
    "platform": "onlyfans"
  }
}

Errors

CodeStatusWhen
account_not_found404The 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_parameter400A query parameter has a value this route does not accept, or a required one is missing. error.param names it.
invalid_cursor400cursor is not a nextCursor this list returned.

Vault

GET /v1/accounts/{accountId}/vault

Media in the account's vault, with the vault lists each item is in. Put a media id in mediaIds to attach it to a message.

Needs the read scope. Reads synced data. Works for OnlyFans and Fansly accounts.

timesSent and revenue are null for every item.

A url, when set, is a public link to the file that never expires. Anyone with the URL can fetch the file, so treat it like a secret link: never publish it or put it anywhere a fan or a stranger could see it.

Path parameters

NameDescription
accountIdThe account's id, which is the creator's user id on its platform. Get it from GET /v1/accounts.

Query parameters

NameTypeDefaultDescription
folderstringNoneVault list id. Leave out for all media.
typeenumallMedia type. One of all, photo, video, audio or gif.
cursorstringNonenextCursor from the previous page. Leave out for the first page.
limitinteger25Page size, 1 to 100.

Returns

200 with a list of VaultItem objects in data. See pagination.

Example request

curl "https://betterfans.link/v1/accounts/412345678/vault?type=video&limit=10" \
  -H "Authorization: Bearer $BFL_KEY"

Example response

{
  "data": [
    {
      "id": "3399120045",
      "type": "video",
      "createdAt": "2026-09-19T16:20:00Z",
      "folders": [
        {
          "id": "771203",
          "name": "Beach shoot"
        }
      ],
      "thumbnailUrl": null,
      "url": null,
      "durationSeconds": 94,
      "timesSent": null,
      "revenue": null
    }
  ],
  "hasMore": true,
  "nextCursor": "q8ZtR2vN5xWcL7mK4pBd",
  "meta": {
    "source": "synced",
    "asOf": "2026-09-28T13:58:40Z",
    "requestId": "req_7Hq2LmX9pRt4VbN8cKe3WzYa",
    "platform": "onlyfans"
  }
}

Errors

CodeStatusWhen
account_not_found404The 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_parameter400A query parameter has a value this route does not accept, or a required one is missing. error.param names it.
invalid_cursor400cursor is not a nextCursor this list returned.

The MassMessage object

One mass message and its totals.

FieldTypeDescription
idstringMass message id.
sentAttimestampWhen it was sent.
textstringMessage text.
priceMoney or nullSet when it is a paid message (PPV).
mediaarray of MediaMedia attached.
sentCountintegerFans it was sent to.
viewedCountinteger or nullFans who opened it.
purchasedCountinteger or nullFans who bought it.
revenueobjectWhat it earned, as totals for the whole mass message.
revenue.grossMoneyGross.
revenue.netMoneyNet.
stateenumunsent means the creator took it back. One of sent or unsent.

The Post object

One post.

FieldTypeDescription
idstringPost id.
postedAttimestampWhen it was posted.
textstringPost text.
priceMoney or nullSet for a paid post.
mediaarray of MediaMedia in the post.
likesintegerLikes.
commentsintegerComments.
tipsMoney or nullTips on the post. null when the platform does not say which post a tip was for (OnlyFans).
revenueobject or nullnull when the platform does not say which post a purchase was for.
revenue.grossMoneyGross.
revenue.netMoneyNet.
urlstring or nullLink to the post on its platform.

A tracking or free trial link.

FieldTypeDescription
idstringLink id.
kindenumtracking links count clicks and subscribers, trial links give a free trial.
namestringName the creator gave the link.
urlstring or nullThe address fans open. null when the platform does not state it (Fansly links).
createdAttimestamp or nullWhen the link was made.
clicksinteger or nullClicks on the link.
subscribersintegerFans who subscribed through the link.
revenueobjectRevenue from fans who subscribed through the link.
revenue.grossMoneyGross.
revenue.netMoneyNet.

The VaultItem object

One media item in the vault.

FieldTypeDescription
idstringMedia id. Use it in mediaIds.
typeenumMedia type. One of photo, video, audio, gif or other.
createdAttimestamp or nullWhen it was added to the vault.
foldersarray of objectsVault lists the item is in.
folders[].idstringVault list id. Use it as the folder parameter.
folders[].namestringVault list name.
thumbnailUrlstring or nullPreview image URL.
urlstring or nullArchived copy; null until archived.
durationSecondsnumber or nullLength of a video or audio clip.
timesSentinteger or nullHow many times it was sent.
revenueMoney or nullWhat it earned.

On this page