OnlyFans Revenue Analytics
OnlyFans revenue analytics is a money-shaped collect → store → compute pipeline. Unlike an engagement dashboard, the source of truth is a ledger of money events: every subscription, tip, PPV unlock and message purchase. ofapis doesn't ship a ready-made ledger endpoint — you reconstruct it from the money events and message price fields it exposes over REST, persist them, and derive the numbers a creator or operator actually cares about — MRR, ARPU, and revenue split by source.
Data sources
There is no /earnings or /transactions ledger endpoint — you reconstruct the ledger from money events. Read these account-scoped so one collector serves a single model or a whole roster:
| Signal | Endpoint | Use |
|---|---|---|
| Event feed (tips, subs, PPV) | GET /accounts/{id}/notifications |
Flags that an event happened — type + text, no amount field |
| Payment amounts | GET /accounts/{id}/chats/{chatId}/messages |
The money value lives in the message price (cents) |
| Active subscribers | GET /accounts/{id}/subscribers |
Denominator for ARPU and churn |
All paths sit under https://api.ofapis.com/api/public/v1. See the transactions and earnings pages for how each event maps to a money line.
Collect the ledger
There is no metric endpoint that hands you MRR — you build it from stored events. Paginate the money-event feed nightly and upsert by event id so re-runs are idempotent:
import os, requests
BASE = "https://api.ofapis.com/api/public/v1"
H = {"Authorization": f"Bearer {os.environ['OFAPIS_KEY']}"}
ACCOUNT = 42
offset = 0
while True:
data = requests.get(f"{BASE}/accounts/{ACCOUNT}/notifications",
headers=H, params={"type": "tips", "limit": 50, "offset": offset}).json()["data"]
for ev in data["list"]: # id, type, text, createdAt
db.events.upsert(ev["id"], ev) # amounts come from message price, joined separately
if not data.get("hasMore"):
break
offset = data.get("nextOffset", offset + 50)
Repeat per money type (tips, subscriptions, PPV) and merge. Because failed calls (expired session, upstream error) are never charged, a scheduled collector has a predictable credit cost — one credit per successful page, and nothing when an upstream blip aborts the run.
Compute what matters
Derive every headline number from stored rows, not live calls:
- MRR — sum active-subscription amounts normalised to a monthly figure.
- ARPU — trailing-30-day revenue ÷ active subscribers (join to the subscribers API).
- Revenue by source — group by
type(subscription, tip, ppv, message) for the split that tells a creator where to focus. - Whales — sum
amountperfanIdover a rolling window to rank top spenders. - Payout reconciliation — diff the ledger against payouts received to catch chargebacks and refunds.
Keep the definitions on your side — that way "revenue" means exactly what your finance view says it means.
Real-time vs. batch
Nightly pulls cover the books; webhooks cover the moment. The live event today is message.received, which is enough to update a "today's earnings" tile the instant a priced message is paid for — tip and subscription events are on the roadmap, so keep the nightly reconcile as the source of truth. For agencies, loop the same collector over each accountId and roll individual ledgers into a portfolio P&L under one token and one bill — the natural pairing with an analytics dashboard.
FAQ
Does ofapis give me pre-computed revenue metrics?
No. ofapis exposes the underlying money events (notifications) and the price field on messages over REST; you store the ledger and compute MRR, ARPU and revenue-by-source yourself, which keeps the definitions under your control.
How do I build revenue history with no history endpoint?
Paginate the notifications feed on a nightly cron and upsert by event id, joining amounts from message price. History accrues in your own store from the day the collector starts, and idempotent upserts make re-runs safe.
What does a nightly revenue collector cost?
One credit per successful call, and failed calls are free. A creator's daily ledger reconciles in a handful of paged calls, so a nightly job stays comfortably inside the Starter plan.
Can one report cover a whole agency roster?
Yes. Run the collector per accountId under a single token and aggregate the ledgers into a portfolio view. See the analytics dashboard build for the collect-and-store pattern.
Ready to build? Get a token free and start with the Python quickstart or the live playground.
Related: Tracking links & attribution · Running an agency at scale