Naming collisions to not conflate
Same-sounding names, different things — a third-party "Chatter" API that isn't your team, two unrelated PPV pricers, and two smart-link systems with near-identical filenames.
chatterapi.py is a third-party platform, not your team model
This is the one most likely to mislead a developer reading the code cold
app/products/club/core/chatterapi.py is a Bearer-key REST client for an external Chatter
Stats&Sales API — a third-party platform that tracks Telegram/Fanvue sales, unrelated to this
product's own team/chatter model (app.core.operator, the Operator/OperatorShift/
Conversation tables covered in the inbox concepts page).
The two share the word "chatter" because the third-party product happens to be named that — not
because one is a client for the other.
If you're reading core/chatterapi.py, scripts/poll_tg_sales.py, or scripts/poll_chatter_fans.py
and looking for where it plugs into the roster, leaderboard, or shifts — it doesn't. It's a
separate Telegram sales feed with its own base URL and its own Bearer key (TG_CHATTER_API_BASE /
TG_CHATTER_API_KEY, both env-only), pulling in money as already-net cents from a platform this
codebase doesn't otherwise touch.
Two unrelated suggest_ppv_price functions
Both are real, both are live, and they take different arguments and answer slightly different questions:
core/pricing.py::suggest_ppv_price | core/ppv/pricing.py::suggest_ppv_price | |
|---|---|---|
| Inputs | lifetime_net_cents, this fan's own avg_ppv_cents | spend_cents, resolved country |
| Signal | Spend tier, nudged toward the fan's own PPV history | Spend tier × country purchasing power (PPP) |
| Reached from | GET /chatters/fan-pricing | GET /inbox/fan/{fan_id}/ppv-suggest |
| Used by | Older fan-context panel | The live PPV composer (ComposerPpvPanel) — the endpoint's own code comment calls the country-PPP signal "the moat" |
Both functions clamp their result to OnlyFans' $3–200 bounds and both are advisory-only — neither ever auto-charges a fan. If a price looks off, check which endpoint (and which function) actually produced it before assuming either one is broken.
Two smart-link systems with near-identical filenames
api/admin/smart_links.py (/api/admin/smart-links, hyphenated) is the read API for the pooled
OfSmartLink registry plus CAPI pixel config. api/admin/smartlinks.py (/api/admin/smartlinks,
no hyphen) is a completely different, creator-facing CRUD for Route Spine's trial/paid policy
smartlinks (models.Smartlink), served publicly at /r/<slug>. The filenames differ by one
underscore and the URLs by one hyphen — read the module docstring, not the filename at a glance,
before assuming which one a given route belongs to.
What's next
- Two campaign systems — the other kind of same-word, different-system trap in this product.
Money masking and chargeback timing
Why a money-hidden request can fail loudly instead of degrading quietly, and why old chargebacks never appear in the recent list.
Async engine write gotchas
Retrying a stalled OnlyFans write the wrong way can create a duplicate on OnlyFans instead of finishing the original.