OnlyFans Earnings API
There is no earnings endpoint. No /earnings, no /statistics, no statement object, nothing that returns a number OnlyFans would recognise as your revenue. Any API claiming to proxy one is describing a different product or making it up.
What the onlyfans earnings api means in practice is a daily figure you assemble from three unrelated sources, one of which you have to record at write time because no read gives it back. It is a useful number — good enough to compare Tuesday to Tuesday, rank chatters, notice a dead week — and categorically not an accounting figure. The reconciliation warning at the bottom is the most important section here.
Three inputs, different reliability
| Input | Where it comes from | What it establishes | What it cannot |
|---|---|---|---|
| Priced sends | your own log of POST /chats/{chatId}/messages with price > 0 |
exact cents asked, which chat, which minute | whether anyone paid |
| Tips received | isTip and price on DM messages |
gross cents, fan id, timestamp | tips outside DMs; anything already read |
| Subscription movement | subscribersCount on GET /me, day-over-day |
net headcount change | who, at what price, new vs renewal |
Only the first is exact, and only because you wrote it down. The second is a best-effort sweep; the third is a count, not money.
Log your priced sends at write time
This is the one number in the system that is not a reconstruction, and most teams throw it away. POST /accounts/{id}/chats/{chatId}/messages returns the created message object — id, price, createdAt, fromUser — and that response is the only place the intent of the send is captured cleanly. Nothing downstream tells you a given message was a deliberate $25 PPV drop rather than a fan's unrelated DM. Write it to your own store as the call returns, keyed on the message id, with the Idempotency-Key you sent. Note the call costs 2 credits, not 1; mass sends through the mailing flow are instead metered per recipient when the run executes.
The honest caveat: a logged priced send is exposure, not revenue. It records what you asked for, and nothing in the API returns a purchase event for it. isOpened is documented as whether the message has been opened or read — read state, not payment — so you cannot promote an exposure row into a revenue row from this data. Treat priced sends as a denominator, observable money as a partial numerator, and label the chart accordingly. The PPV API covers the send side.
Subscriptions give you a count, not an amount
GET /me returns subscribersCount for a credit. Snapshot it daily; the delta is clean net-subscriber movement.
That is as far as it goes. The record from the subscribers API is {id, username, name, avatar, subscribedOn} — and subscribedOn is a boolean, not a date. No start date, no renewal date, no price per subscriber, no spend field. You cannot date a subscription from it, so you cannot attribute revenue to a day from it either.
That leaves two options. Multiply the net delta by the plan price you set, which ignores promos, trials, discounted offers and expiries and will be wrong by whatever share of the roster is on a non-standard price. Or count subscribed-type items in the notification feed, which has no offset parameter and returns at most 100 newest-first items, so anything that scrolled past between polls is gone. Both are estimates. Pick one, document which, do not switch silently.
A nightly collector
const BASE = "https://api.ofapis.com/api/public/v1";
const H = { Authorization: `Bearer ${process.env.OFAPIS_TOKEN}` };
const get = async (p) => {
const r = await fetch(`${BASE}${p}`, { headers: H });
if (!r.ok) throw Object.assign(new Error(p), { status: r.status,
reqId: r.headers.get("X-Request-Id"), body: await r.text() });
return (await r.json()).data;
};
async function dailyGross(accountId) {
const me = await get(`/accounts/${accountId}/me`);
let offset = 0, tipCents = 0;
for (;;) {
const page = await get(`/accounts/${accountId}/chats?limit=100&offset=${offset}`);
const hot = page.list.filter((c) => c.hasUnreadTips || c.unreadMessagesCount > 0);
for (const c of hot) {
const msgs = await get(
`/accounts/${accountId}/chats/${c.withUser.id}/messages?limit=50`);
tipCents += msgs.list
.filter((m) => m.isTip && m.price > 0)
.reduce((s, m) => s + m.price, 0);
}
if (!page.hasMore) break;
offset = page.nextOffset;
}
return { subscribersCount: me.subscribersCount, tipCents };
}
Deduplicate on message id before adding anything to a running total — the sweep re-reads the same conversations nightly and will happily double-count. An append-only store is the right shape; the transactions ledger page covers the dedupe keys and the idempotent ingest.
Budget the credits before you schedule it
Work the arithmetic for a real roster before you hand the job to a nightly automation, rather than discovering it on the bill. Five creators on Pro, each with roughly 400 active conversations and 40 flagged for a message read:
/me: 1 credit × 5 = 5/chatssweep: 4 pages × 5 = 20- message reads: 40 × 5 = 200
That is 225 credits a night, about 6,750 a month, against a Pro allowance of 120,000. Comfortable. Now widen the message read to every conversation instead of the flagged subset: 400 × 5 = 2,000 a night, 60,000 a month, half the plan. The flag filter is the difference between a cheap job and an expensive one. See pricing for allowances and API costs for how weights apply.
Pace the loop too. Requests-per-minute is 60 on Free, 100 on Starter and 500 on Pro, and a tight loop over 400 chats hits a Starter ceiling in under five minutes.
The reconciliation warning
This number will never match your OnlyFans payout, and it is not supposed to. Say so on the dashboard, in the header of whatever spreadsheet the figure lands in, and to whoever reads the report. Three reasons, none fixable from the API:
- Everything observed is gross. OnlyFans' platform cut is not present in any field, so your figure runs high by that share before anything else happens.
- Reversals never appear. A refund or chargeback emits no visible counter-event in the feed or in message history. The reconstructed total only ever goes up.
- Coverage is partial and uneven. Tips are found through a read-state flag a chatter's phone can clear before your job runs; subscription revenue is inferred from a headcount; purchases of priced sends are not observable at all. The gap is not a fixed percentage you can calibrate out — it moves with how your team works.
Use this for direction, attribution and period-over-period comparison. Do not file it, do not pay commission on it without stating the basis, and do not call it earnings in a UI without "estimated" nearby. The real figure is in the creator's OnlyFans statements and nowhere else.
FAQ
Can ofapis return my actual OnlyFans earnings?
No. OnlyFans does not expose statement or payout figures to third parties, so there is nothing to relay and no endpoint that pretends to. You compute an estimate from tips, priced sends you logged, and subscriber movement.
Why is my computed figure higher than my payout?
Because every amount visible in the API is gross, before OnlyFans' platform cut, and refunds and chargebacks produce no reversal event you can observe. The total only moves upward, so it drifts high over time rather than hovering near the real number.
How do I know whether a fan actually bought a PPV message?
You cannot determine that from the API. isOpened means the message was opened or read, not paid for, and there is no purchase or unlock event anywhere in the surface. Log priced sends as exposure and measure the response in tips and replies instead.
Can I split earnings per creator in an agency?
Yes, for the observable parts. Every read has an account-scoped variant under /accounts/{accountId}/..., so one token covers a roster — 1 linked account on Free and Starter, 5 on Pro. Run the collector per account and keep totals separate rather than summing first.
What is the cheapest useful version of this?
One GET /me per creator per day for subscribersCount, plus your write-time log of priced sends. That is a handful of credits, needs no sweep, and already gives headcount trend against send volume — the two series most agencies act on.