Case Study 10 min read

The Revenue Breakdown That Changed Our Entire Strategy: 70% Wasn't Coming From Where We Thought

We assumed subscriptions were our primary revenue driver. When we pulled transaction data and categorized it by type, we discovered we had been optimizing for the wrong thing entirely.

OFAPI Team ·

Every decision we made in our first 18 months of operating was built on an assumption we had never verified: that subscriptions were the foundation of our revenue. We optimized for subscriber count. We measured success by net subscriber growth. We directed chatter energy toward retention messaging because keeping subscribers meant keeping revenue.

We were wrong. Not slightly wrong — structurally wrong.

When we finally pulled our onlyfans earnings breakdown through the /payouts/transactions endpoint and categorized every dollar by revenue type, what we found required us to rebuild our entire operational model. The revenue was coming from somewhere we had treated as secondary.

What We Thought We Knew

Going into the analysis, our mental model of the revenue stack looked like this:

  1. Subscriptions — the stable recurring base, the “salary” of the creator economy
  2. PPV — the upside, nice when it works but inconsistent
  3. Tips — gratuity, unpredictable, not worth building strategy around

This model felt intuitive. It mirrored how SaaS companies think about ARR vs. one-time upsells. We built our entire operational layer around protecting the subscription base — chatter focus on re-engagement, retention sequences, subscriber win-back campaigns.

The only problem was the data did not match the model at all.

Pulling the Transaction Breakdown

The /payouts/transactions endpoint returns every earnings event with a type field — subscriptions, PPV purchases, tips, and referrals. We pulled 90 days of data across all creators and built a simple categorization.

import requests
from collections import defaultdict
from datetime import datetime, timedelta

API_KEY = "your_api_key"
BASE_URL = "http://157.180.79.226:4024/api/v1"

headers = {"X-API-Key": API_KEY}

def get_transactions(creator_id, days=90):
    end_date = datetime.utcnow()
    start_date = end_date - timedelta(days=days)

    response = requests.get(
        f"{BASE_URL}/payouts/transactions",
        headers=headers,
        params={
            "creatorId": creator_id,
            "startDate": start_date.date().isoformat(),
            "endDate": end_date.date().isoformat(),
            "limit": 2000
        }
    )
    response.raise_for_status()
    return response.json().get("transactions", [])

def build_revenue_breakdown(creator_id):
    transactions = get_transactions(creator_id)

    revenue_by_type = defaultdict(float)
    count_by_type = defaultdict(int)

    for tx in transactions:
        tx_type = tx.get("type", "unknown")
        amount = float(tx.get("amount", 0))
        revenue_by_type[tx_type] += amount
        count_by_type[tx_type] += 1

    total = sum(revenue_by_type.values())

    print(f"\nRevenue breakdown for creator {creator_id} (90 days)")
    print(f"{'Type':<20} {'Revenue':>12} {'Share':>8} {'Transactions':>14}")
    print("-" * 58)

    for tx_type, amount in sorted(revenue_by_type.items(), key=lambda x: -x[1]):
        share = amount / total * 100 if total else 0
        count = count_by_type[tx_type]
        print(f"{tx_type:<20} ${amount:>11,.2f} {share:>7.1f}% {count:>14,}")

    print("-" * 58)
    print(f"{'TOTAL':<20} ${total:>11,.2f} {'100.0%':>8}")

    return revenue_by_type, total

# Run for all creators
for creator_id in ["creator_101", "creator_102", "creator_103", "creator_104"]:
    breakdown, total = build_revenue_breakdown(creator_id)

The output across our four active creators at the time:

Revenue TypeAmount (90 days)Share
PPV$61,84052%
Tips$24,20020%
Subscriptions$21,39018%
Referrals$11,57010%

Subscriptions were 18% of our onlyfans agency revenue. PPV was nearly three times that. Tips — the category we had essentially written off as noise — were generating more than subscriptions.

We ran the numbers again. Same result.

What the Breakdown Actually Meant

The subscription figure was not telling us subscriptions were unimportant — it was telling us they were table stakes. Subscriptions are what give chatters access to fans, what create the relationship context for PPV and tips. But the economic engine was running entirely on what fans chose to spend beyond their monthly fee.

Every hour we had directed toward subscriber count optimization and retention messaging was an hour we had not directed toward PPV output and tip cultivation. We were tending to the garden while the harvest happened somewhere else.

The tips number was the biggest surprise. When we dug into which conversations generated tips, the pattern was relationship depth — fans who had been engaged through personalized, consistent conversation for more than 60 days were tipping at 4x the rate of newer subscribers. Tips are not random gratuity. They are relationship dividends.

How We Rebuilt the Strategy

Chatter reorientation. We shifted the primary KPI for our chatter team from “subscriber retention rate” to “revenue per conversation.” A chatter who maintains 200 subscribers but generates $2 per conversation is less valuable than a chatter who converts 150 subscribers at $18 per conversation. The metric drives behavior, and the old metric was driving the wrong behavior.

PPV cadence. We increased PPV output frequency from roughly two per week to five per week per creator, with dedicated campaign planning for each drop. Subject lines, preview framing, urgency windows — all standardized. The first 30 days at the new cadence produced a 67% increase in PPV revenue.

Tip cultivation protocol. We built a specific conversation track for high-tenure fans (60+ days) focused on deepening relationship quality rather than selling. Fans in this track received more personalized messages, more genuine conversation, fewer promotional pushes. The hypothesis was that tip behavior follows emotional investment, not sales pressure. Tip revenue from this cohort increased 140% over 45 days.

Subscription as acquisition, not revenue. We reframed subscriptions internally: the purpose of a subscriber is not to generate $X/month in subscription fees. The purpose is to create a fan relationship from which PPV and tips can be earned. This changed how we thought about sub-price optimization — we became more willing to run lower sub prices to acquire fans, knowing the real revenue opportunity was downstream.

Rebuilding the Tracking Layer

Once we had the correct mental model, we built monitoring to track revenue mix on a weekly basis. We wanted to know immediately if PPV was dropping as a share — that would signal chatter focus drift or content output problems. We also wanted alerts when tip revenue dipped, since that would indicate relationship quality was degrading.

def weekly_revenue_mix_report(creator_ids, alert_threshold=0.40):
    """
    Pull weekly revenue breakdown and alert if PPV share drops
    below threshold — signals chatter or content issues.
    """
    end_date = datetime.utcnow()
    start_date = end_date - timedelta(days=7)

    for creator_id in creator_ids:
        response = requests.get(
            f"{BASE_URL}/payouts/transactions",
            headers=headers,
            params={
                "creatorId": creator_id,
                "startDate": start_date.date().isoformat(),
                "endDate": end_date.date().isoformat(),
                "limit": 500
            }
        )
        transactions = response.json().get("transactions", [])

        revenue_by_type = defaultdict(float)
        for tx in transactions:
            revenue_by_type[tx["type"]] += float(tx.get("amount", 0))

        total = sum(revenue_by_type.values()) or 1
        ppv_share = revenue_by_type.get("ppv", 0) / total

        status = "OK"
        if ppv_share < alert_threshold:
            status = "ALERT — PPV share below threshold"

        print(f"Creator {creator_id}: PPV {ppv_share:.1%} of revenue | {status}")

weekly_revenue_mix_report(["creator_101", "creator_102", "creator_103", "creator_104"])

We pipe these alerts into our team Slack channel via the Google Sheets integration for weekly review. The revenue tracking use case covers the full monitoring setup in more detail.

The Result

Six months after rebuilding the strategy around PPV and tips as primary revenue drivers:

  • Total agency revenue: +83% vs. the prior 6-month period
  • PPV revenue per creator per month: +112%
  • Tip revenue per creator per month: +97%
  • Subscription revenue: +14% (modest increase from lower sub-price experiments)

The subscription number growing slowly while everything else grew fast validated the reframe: subscriptions were now functioning as an acquisition mechanism, and the real economics were playing out in PPV and tips as designed.

The analysis took one afternoon. The mistake it corrected had been running for 18 months.


If you have not pulled a revenue breakdown by transaction type for your creators, you almost certainly have the same misalignment we did. The assumptions you are operating on feel right because the aggregate revenue number grows — but it may be growing despite your strategy, not because of it.

Pull your transaction data, categorize it, and let the actual numbers tell you where to focus. The OFAPI pricing page covers what you get at each tier, and the API documentation will get you pulling transaction data within the hour.

Ready to automate your OnlyFans operations?

Get full API access and start building in minutes.