Skip to content

Models: platform accounts and income

A model's page now lists every platform the model is on, income shows up on the roster and on each model, the pay screens say plainly that commission is paid on OnlyFans earnings, and the public API serves models with their platform accounts and income across platforms.

Every platform a model is on, in one place

A model's page has a Platform accounts card beside Local controls. OnlyFans is always the first row; Chaturbate, Stripchat, BongaCams, MyFreeCams and SextPanther can be added by hand.

  • Click the OnlyFans row to open it: session status, join date, first post, last synced, and Replace / Detach. A model without one shows Not linked, with Attach.
  • Each other platform shows Username: …, and the pencil edits it in place. The trash icon removes it after a confirm.

Guide: Platform accounts

Chaturbate username when adding a model

Add model asks for the model's Chaturbate username (optional). It becomes the model's Chaturbate account.

Guide: Add a model

Income on the roster and on each model

The roster has an Income (cutoff) column. On a model's page, Earnings is split into two tabs: OF holds the OnlyFans refetch and lock controls, and Income records income for other platforms without leaving the page.

Guide: Income

Commission is paid on OnlyFans earnings (CT and TMT managers)

Pay Breakdown, the Daily Bonus explainer and Affiliate Mapping now say that they pay on OnlyFans earnings. Income recorded for other platforms doesn't feed CT commission, the bonus pool or affiliate commission. Nothing changed in how anyone is paid.

Guides: CT Pay Breakdown · TMT manager

Also

  • Each model's OnlyFans profile (join date, first post) refreshes on the nightly sync.

Public API (for integrations)

Changes under /api/public/v1/. The full reference is the API's own docs page at /api/public/v1/docs/.

Models. GET /models/ and GET /models/<model_id>/ are now their own Models dataset. The paths haven't changed, and the old /api/public/model-performance/v1/ base still serves them.

  • New fields:
    • aruna_email
    • cb_username: the Chaturbate username, or null
    • has_of_account
    • accounts: one entry per platform, with platform, platform_name, handle and is_active
  • New filters:
    • platform: models with an active account on any of the given platforms
    • has_of_account
  • q also matches platform usernames.

Income. GET /income/ (paged, every model) and GET /income/<model_id>/ (one model) are new.

  • The shape is models → platforms → income per period, and both endpoints use it.
  • Every income entry carries period, period_start, period_end, net_usd, entry_count and apportioned.
  • details holds a platform's own breakdown, tagged by shape. OnlyFans gives gross, subscriptions, tips and messages. Other platforms give null for now.
  • Filters:
    • cutoff, or start + end
    • group_by: cutoff (default), month or day
    • platform, and source (ingest or manual)
    • on the list only: model, status (the model's status, default all), q, page and page_size
  • Income has no payment status yet. It comes once HRIS pays models out itself.

Compatibility.

  • The core fields won't change within v1.
  • A platform that gains its own breakdown adds a new details shape, so ignore shapes and platforms you don't recognise.
  • Breaking changes go to /v2/.

Technical changes

For developers and whoever deploys. None of this needs a guide.

Data model

  • Platforms are a registry in code (agency_models/platforms/), not a table. Each platform's earnings have a different shape, so adding one is a code change. The app reads the list from GET /api/models/platforms/.
  • ModelPlatformAccount (model × platform) is new: a handle, an external id and an active flag. The app manages them through /api/models/<id>/accounts/. OnlyFans accounts are only created by Attach or Add model.
  • The OnlyFans link has moved off Model onto its OnlyFans account. source, of_api_id, of_user_id, email, the join and first-post dates, the upstream status and session flag, the profile snapshot, last_synced_at and the account-change fields now live on ModelPlatformAccount. Code reads them through model.of_account. "Manual" now means the model has no active OnlyFans account.
  • Earnings rows key on the account. ModelDayStats, ModelTransaction and ModelCutoffIngest have an account FK, and their uniqueness is per account. model stays on each row.
  • Deleting a model removes its accounts. The account → model FK is CASCADE. It was PROTECT, which made a model with accounts impossible to delete.

Behaviour

  • Refetch, lock, retry and freeze on a model with no OnlyFans account return 409 with a message, instead of failing silently.
  • The OnlyFans profile (/users/me) is fetched by the nightly ingest only, never by Refetch now.
  • Manual income is validated against the registry, so OnlyFans income can't be hand-entered and Other still needs a platform name.

Deploy

  • Migrations agency_models 0030–0034 run on deploy:
    • 0030 creates one OnlyFans account per OnlyFans model;
    • 0031–0034 move the OnlyFans fields and point the earnings rows at the account.
  • The migrations are reversible. A dry run on a copy of the dev database round-tripped byte for byte.
  • No env or settings changes.

Tests

  • Postgres runs with synchronous_commit=off under pytest only. The full backend suite went from about 79 minutes to about 24.