Content
How mass messages, posts, tracking links and vault media perform.
How mass messages, posts, tracking links and vault media perform. Every route can also return the authentication errors, rate_limited and internal_error.
| Route | Method and path |
|---|---|
| Mass messages | GET /v1/accounts/{accountId}/mass-messages |
| Top content | GET /v1/accounts/{accountId}/posts |
| Tracking links | GET /v1/accounts/{accountId}/links |
| Vault | GET /v1/accounts/{accountId}/vault |
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.
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
| 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 |
|---|---|---|---|
from | date or timestamp | 30 days ago | Start, ISO 8601 date or timestamp (UTC). |
to | date or timestamp | now | End, ISO 8601 date or timestamp (UTC). |
sort | enum | recent | recent = newest first. One of recent or revenue. |
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 MassMessage objects in data. See pagination.
Example request
curl "https://app.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"
}
}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. |
Top content
GET /v1/accounts/{accountId}/posts
Posts with price, likes and comments, sorted by recent or likes. tips and revenue are null: OnlyFans does not say which post a payment was for.
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 |
|---|---|---|---|
from | date or timestamp | 30 days ago | Start, ISO 8601 date or timestamp (UTC). |
to | date or timestamp | now | End, ISO 8601 date or timestamp (UTC). |
sort | enum | recent | recent = newest first, likes = most liked first. One of recent or likes. |
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 Post objects in data. See pagination.
Example request
curl "https://app.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"
}
}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. |
Tracking links
GET /v1/accounts/{accountId}/links
Tracking and free trial links with clicks, subscribers and revenue.
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 |
|---|---|---|---|
sort | enum | revenue | revenue = most earned first. One of revenue, subscribers or recent. |
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 TrackingLink objects in data. See pagination.
Example request
curl "https://app.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"
}
}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. |
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.
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
| 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 |
|---|---|---|---|
folder | string | None | Vault list id. Leave out for all media. |
type | enum | all | Media type. One of all, photo, video, audio or gif. |
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 VaultItem objects in data. See pagination.
Example request
curl "https://app.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"
}
}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. |
The MassMessage object
One mass message and its totals.
| Field | Type | Description |
|---|---|---|
id | string | Mass message (queue) id. |
sentAt | timestamp | When it was sent. |
text | string | Message text. |
price | Money or null | Set when it is a paid message (PPV). |
media | array of Media | Media attached. |
sentCount | integer | Fans it was sent to. |
viewedCount | integer or null | Fans who opened it. |
purchasedCount | integer or null | Fans who bought it. |
revenue | object | What it earned, as totals for the whole mass message. |
revenue.gross | Money | Gross. |
revenue.net | Money | Net. |
state | enum | unsent means the creator took it back. One of sent or unsent. |
The Post object
One post.
| Field | Type | Description |
|---|---|---|
id | string | Post id. |
postedAt | timestamp | When it was posted. |
text | string | Post text. |
price | Money or null | Set for a paid post. |
media | array of Media | Media in the post. |
likes | integer | Likes. |
comments | integer | Comments. |
tips | Money or null | null: OnlyFans does not say which post a tip was for. |
revenue | object or null | null: OnlyFans does not say which post a purchase was for. |
revenue.gross | Money | Gross. |
revenue.net | Money | Net. |
url | string or null | Link to the post on OnlyFans. |
The TrackingLink object
A tracking or free trial link.
| Field | Type | Description |
|---|---|---|
id | string | Link id. |
kind | enum | tracking links count clicks and subscribers, trial links give a free trial. One of tracking or trial. |
name | string | Name the creator gave the link. |
url | string | The link fans open. |
createdAt | timestamp or null | When the link was made. |
clicks | integer or null | Clicks on the link. |
subscribers | integer | Fans who subscribed through the link. |
revenue | object | Revenue from fans who subscribed through the link. |
revenue.gross | Money | Gross. |
revenue.net | Money | Net. |
The VaultItem object
One media item in the vault.
| Field | Type | Description |
|---|---|---|
id | string | Media id. Use it in mediaIds. |
type | enum | Media type. One of photo, video, audio, gif or other. |
createdAt | timestamp or null | When it was added to the vault. |
folders | array of objects | Vault lists the item is in. |
folders[].id | string | Vault list id. Use it as the folder parameter. |
folders[].name | string | Vault list name. |
thumbnailUrl | string or null | Preview image URL. |
url | string or null | Archived copy; null until archived. |
durationSeconds | number or null | Length of a video or audio clip. |
timesSent | integer or null | How many times it was sent. |
revenue | Money or null | What it earned. |