How We Automated 80 Hours/Month of Financial Ops and Never Had a Payout Dispute Again
Managing 25 creators meant 20+ hours per payout cycle of manual reconciliation, 30% error rate on commission calculations, and monthly disputes. We automated the entire workflow using 4 API endpoints. Zero disputes since.
At 25 creators, our financial ops were a disaster.
Not visibly — from the outside, we looked like a functioning agency. Payouts went out. Invoices got issued. Creators got paid. But behind the scenes, every payout cycle was a week of manual hell: four team members copying numbers between browser tabs and spreadsheets, commission calculations done by hand and rechecked by someone else, and still — a 30% error rate on individual creator statements.
Every month, at least two or three creators would push back on their numbers. “This doesn’t match what I saw on my dashboard.” “Where’s my referral bonus?” “I thought the split was different for the first $10K.” We’d spend another half-day digging through the original numbers, finding where the copy-paste had gone wrong, issuing corrections.
The total cost: roughly 80 hours per month across the team, concentrated in the worst possible place — right before payouts, when everyone was already stressed.
We fixed it by connecting all four of our financial endpoints to a single automated reconciliation workflow. Here’s the full system.
The Four Endpoints That Power the Automation
The reconciliation system relies on four API endpoints, each serving a distinct function:
/payouts/transactions— Every line-item revenue event: PPV purchases, tips, subscription renewals, unlocks. The raw ledger./payouts/statistics— Aggregated totals by date range. Used for period summaries and sanity-checking line items./payouts/balances— Current pending balance per creator. Used to show creators their accrued-but-not-yet-paid earnings in real time./payouts/requests/referral— Referral income from creators who’ve brought in other accounts. This was the most commonly disputed line item when we did it manually.
Before automation, pulling these four data sources for 25 creators meant 100 separate manual lookups per cycle. Now it runs in under 3 minutes, fully automated.
The Reconciliation System
The system has three components:
- Data collection — Pull all four endpoints for all creators in a single pass
- Commission calculation — Apply each creator’s split agreement to their gross numbers
- Report generation — Produce a creator-facing statement and an internal ledger entry
Here’s the core reconciliation script:
import requests
from datetime import datetime, timedelta
from dataclasses import dataclass, field
from typing import Optional
import json
API_BASE = "http://157.180.79.226:4024/api/v1"
HEADERS = {"X-API-Key": "YOUR_API_KEY"}
@dataclass
class CreatorContract:
"""Commission structure for a single creator."""
creator_id: str
name: str
agency_split: float # e.g. 0.20 = agency takes 20%
referral_eligible: bool = True
advance_outstanding: float = 0.0
custom_tiers: Optional[list] = None # Optional tiered splits
def pull_creator_financials(creator_id: str, start_date: str, end_date: str) -> dict:
"""Pull all four financial endpoints for a single creator."""
# Line-item transactions
txn_resp = requests.get(
f"{API_BASE}/payouts/transactions",
headers=HEADERS,
params={
"creator_id": creator_id,
"start_date": start_date,
"end_date": end_date
}
)
# Period statistics (aggregated totals)
stats_resp = requests.get(
f"{API_BASE}/payouts/statistics",
headers=HEADERS,
params={
"creator_id": creator_id,
"start_date": start_date,
"end_date": end_date
}
)
# Current pending balance
balance_resp = requests.get(
f"{API_BASE}/payouts/balances",
headers=HEADERS,
params={"creator_id": creator_id}
)
# Referral income
referral_resp = requests.get(
f"{API_BASE}/payouts/requests/referral",
headers=HEADERS,
params={
"creator_id": creator_id,
"start_date": start_date,
"end_date": end_date
}
)
return {
"transactions": txn_resp.json().get("transactions", []),
"statistics": stats_resp.json(),
"pending_balance": balance_resp.json().get("pending_balance", 0.0),
"referral_income": referral_resp.json().get("total_referral_income", 0.0)
}
def calculate_creator_payout(
contract: CreatorContract,
financials: dict,
period_label: str
) -> dict:
"""
Apply commission structure to raw financials.
Returns full payout breakdown for creator statement and internal ledger.
"""
stats = financials["statistics"]
# Revenue categories
gross_subscriptions = stats.get("gross_subscriptions", 0.0)
gross_ppv = stats.get("gross_ppv", 0.0)
gross_tips = stats.get("gross_tips", 0.0)
platform_fees = stats.get("platform_fees", 0.0) # OF's cut
referral_income = financials["referral_income"]
gross_total = gross_subscriptions + gross_ppv + gross_tips
net_after_platform = gross_total - platform_fees
# Apply tiered splits if configured, otherwise flat split
if contract.custom_tiers:
agency_commission = _apply_tiered_split(net_after_platform, contract.custom_tiers)
else:
agency_commission = net_after_platform * contract.agency_split
creator_earnings = net_after_platform - agency_commission + referral_income
# Deduct any outstanding advance
advance_deduction = 0.0
if contract.advance_outstanding > 0:
advance_deduction = min(contract.advance_outstanding, creator_earnings)
creator_earnings -= advance_deduction
# Sanity check against statistics totals
stats_reported_net = stats.get("net_earnings", None)
discrepancy = None
if stats_reported_net is not None:
discrepancy = abs(net_after_platform - stats_reported_net)
if discrepancy > 1.0: # Flag if off by more than $1
discrepancy = f"WARNING: ${discrepancy:.2f} discrepancy vs. statistics endpoint"
else:
discrepancy = None
return {
"creator_id": contract.creator_id,
"creator_name": contract.name,
"period": period_label,
"gross_revenue": round(gross_total, 2),
"platform_fees": round(platform_fees, 2),
"net_after_platform": round(net_after_platform, 2),
"agency_commission": round(agency_commission, 2),
"agency_split_rate": f"{contract.agency_split * 100:.0f}%",
"referral_income": round(referral_income, 2),
"advance_deduction": round(advance_deduction, 2),
"advance_remaining": round(
contract.advance_outstanding - advance_deduction, 2
),
"creator_payout": round(creator_earnings, 2),
"pending_balance": round(financials["pending_balance"], 2),
"data_discrepancy": discrepancy,
"transaction_count": len(financials["transactions"])
}
def _apply_tiered_split(net_revenue: float, tiers: list) -> float:
"""Apply tiered commission structure (e.g. 20% up to $10K, 15% above)."""
commission = 0.0
remaining = net_revenue
for tier in sorted(tiers, key=lambda t: t["up_to"]):
tier_max = tier["up_to"]
tier_rate = tier["rate"]
tier_amount = min(remaining, tier_max)
commission += tier_amount * tier_rate
remaining -= tier_amount
if remaining <= 0:
break
if remaining > 0:
commission += remaining * tiers[-1]["rate"]
return commission
def run_full_reconciliation(
contracts: list,
start_date: str,
end_date: str
) -> dict:
"""Run reconciliation for all creators and produce summary."""
period_label = f"{start_date} to {end_date}"
results = []
for contract in contracts:
financials = pull_creator_financials(
contract.creator_id, start_date, end_date
)
payout = calculate_creator_payout(contract, financials, period_label)
results.append(payout)
if payout["data_discrepancy"]:
print(f"[ALERT] {contract.name}: {payout['data_discrepancy']}")
total_agency_revenue = sum(r["agency_commission"] for r in results)
total_creator_payouts = sum(r["creator_payout"] for r in results)
total_gross = sum(r["gross_revenue"] for r in results)
return {
"period": period_label,
"creator_statements": results,
"agency_summary": {
"total_gross_managed": round(total_gross, 2),
"total_agency_revenue": round(total_agency_revenue, 2),
"total_creator_payouts": round(total_creator_payouts, 2),
"creator_count": len(results)
}
}
# Example: run the full reconciliation
contracts = [
CreatorContract("creator_001", "Creator A", agency_split=0.20),
CreatorContract("creator_002", "Creator B", agency_split=0.20, advance_outstanding=2500.0),
CreatorContract("creator_003", "Creator C", agency_split=0.15,
custom_tiers=[
{"up_to": 10000, "rate": 0.20},
{"up_to": 999999, "rate": 0.15}
]),
# ... remaining 22 creators
]
report = run_full_reconciliation(
contracts,
start_date="2026-01-01",
end_date="2026-01-31"
)
print(json.dumps(report["agency_summary"], indent=2))
The Creator-Facing Statement
The script above generates the numbers. The second piece is turning those numbers into a creator-facing statement that’s clear enough to be self-explanatory — no more “where does this number come from?” messages.
We generate a simple Markdown statement per creator that includes:
- Gross revenue by category (subscriptions, PPV, tips)
- Platform fee deduction (clearly labeled as OnlyFans’ cut, not ours)
- Net after platform fees
- Agency commission with rate explicitly shown
- Referral income as a separate line
- Advance deduction if applicable
- Final payout amount
- Current pending balance (what’s accrued for next cycle)
Showing the math explicitly — “Platform took $4,820 (20% OF fee), we took $8,236 (20% of net)” — eliminated virtually all disputes. Creators could verify our calculation themselves. There was nothing to dispute.
We auto-generate and send these via a simple email template on the 1st and 15th of each month, synchronized with our payout schedule.
The Discrepancy Check
One feature that saved us early on: the sanity check between /payouts/transactions (line items) and /payouts/statistics (aggregated totals).
When we first ran the system, we had a $340 discrepancy on one creator — line items summed to one number, the statistics endpoint showed another. That would have been invisible in the manual process. The automated check caught it, flagged it, and we found a transaction that had been reversed after our statistics pull window. We updated the query window and the discrepancy resolved.
Having two independent data sources and checking them against each other is a basic accounting control. It’s also essentially free to implement when you have API access to both.
Before and After
| Metric | Before | After |
|---|---|---|
| Hours per payout cycle | ~20 hrs | ~1 hr (review only) |
| Commission errors | 30% of statements | 0% in 6 months |
| Creator disputes | 2-4 per cycle | 0 since launch |
| Time to generate all statements | 2-3 days | 8 minutes |
| Advance tracking | Manual spreadsheet | Automated deduction |
The one hour that remains is a human review pass — someone looks at the flagged discrepancies, confirms the numbers look right, and approves the run. That’s appropriate. You want a human sign-off before money moves. Everything else is automated.
What We Do With the Time Back
80 hours per month is two full-time work-weeks. Here’s where that time actually went after we recovered it:
- Creator growth work — Account managers now spend time on strategy rather than copy-pasting numbers
- New creator onboarding — Faster cycles because the finance ops team isn’t bottlenecked
- Expanded roster — We signed 8 more creators in the 3 months after launch, largely because we had operational capacity to take them on
The financial ops automation paid for itself in the first month. The time savings alone — at blended team cost — covered the API subscription cost many times over. The dispute elimination was a secondary benefit that turned out to matter more than we expected for creator relationships.
If you’re running more than 5 creators and still doing reconciliation by hand, this is the highest-leverage thing you can automate. The endpoints exist. The math isn’t complicated. The hard part is just building it once.
This pairs directly with the 30-day revenue forecast model — once you have clean reconciliation data, forecasting becomes trivial because you finally trust the underlying numbers.
The Google Sheets integration is a fast path to get the output in front of your team without building a custom frontend. The revenue tracking use case covers the monitoring layer that sits on top of this reconciliation foundation.
View pricing to see which plan covers your creator count, or start with the getting started guide to connect your first account.
Every hour you spend on manual reconciliation is an hour not spent on growth. Build it once. Let it run.