API keys open only what you grant them, and SextPanther is a platform CT gets paid on¶
An API key used to read everything we publish. Now it's granted specific parts of the API and reads only those, and the reference explaining them lives inside HRIS. SextPanther earnings can be recorded per cutoff and coverage on them is paid, affiliate referrals have a third rate after two years, and the leave calendar lets you open a single day.
Give an outside tool only the data it needs¶
Issuing an API key used to mean sharing everything — the model roster, the employee directory and the payroll totals, in one decision. A partner who needed one of those got all three.
A key is now granted specific API modules and reads only those. Asking for anything else comes back refused.
- Info → API keys is its own tab now, out of the Integrations panel. It pages, and filters by status, by API module and by name — so the live keys stop hiding among the revoked ones.
- New token asks what the key may read before it will create one. Nothing is ticked by default.
- The Opens column on each row says what that key reaches.
Nothing anyone holds today changed. Every key issued before this was granted exactly what it already read, so no integration noticed.
Guide: API keys & the public API
The API reference is inside HRIS¶
Profile menu → HRIS API. Every endpoint, its parameters and its response shape, generated from the API itself so it can't drift from what the API actually does.
- Paste a key into Check scopes and it names the API modules that key opens — rather than six requests and some guessing.
- It explains that keys are requested from an admin, not self-served.
It is no longer a public page: someone with no HRIS account can't read it. Send a partner what their integration needs along with their key.
Guide: The API reference
SextPanther earnings, and coverage paid on them¶
A model with an active SextPanther account gets an SP tab beside OF and Income. Figures are read off SextPanther's own dashboard and typed in, per cutoff — they are already net, so nothing is deducted from them — with an Earning statistics card showing the total, the per-category breakdown, and the CT base coverage is paid on.
Coverage follows: a team now covers an account rather than a model, so "Karina" on OnlyFans and SP Karina on SextPanther are separate teams with separate rosters, each paid at its own platform's rate. A platform dropdown on Model Mapping narrows the board to one.
SextPanther coverage earns its commission and draws nothing from the CT Daily Bonus pool, which is funded and shared exactly as before.
Guide: SextPanther earnings · Model Mapping
A third affiliate rate, after two years¶
Referral commission used to be 5% for three months and 2.5% from then on, forever. There is now a third step: 1% from the 24-month anniversary.
Each referral is paid from one of two rate tables, shown in a Rates column you can filter on:
- 5% → 2.5% (old) — every referral that existed before this. They stay here until someone moves them.
- 5% → 2.5% → 1% (new) — the default for anything added now.
Nobody's rate moved on deploy. Moving a long-standing referral to the new table is a pay cut for their referrer, so that direction warns before it does it, names the tenure and both percentages, and records who made the change.
Guide: Affiliate Mapping
Open a day on the leave calendar¶
The leave Calendar exists to answer "who is out on the 12th", and now you can ask it about one day: click a day cell to open it on its own, instead of expanding entries inline and pushing the week row taller.
The count that sat beside the date badge is gone — on the 4th, 1 out ④ read
as "1 out of 4" when it meant "one person is out".
Guide: Approve leave & record absences
Also¶
- A day-off shift on Model Mapping: a Team Leader covering a model on their rest day earns the commission and takes no Daily Bonus slot. The payslip note and the bonus audit both name it.
- The CT Daily Bonus no longer stalls over a model worked only by coverage that the bonus doesn't pay. Its share goes to whoever did earn one; a model nobody is mapped to still holds the run, which is a mapping mistake worth catching.
- Affiliate Mapping gained search, and filters for rate table and referrer type.
- Recording income by hand no longer offers SextPanther in the single-amount form, and says where to enter it instead.
Public API (for integrations)¶
Every key is now scoped. A key is granted one or more API modules —
models, model_performance, employees, finance, income,
stream_sessions — and reads only those. A request to a module the key doesn't
hold returns 403 (the endpoint exists; the credential wasn't granted it),
as distinct from 401 for a key that is unknown, revoked or expired.
Keys issued before this hold the five modules that existed then, so no integration changes behaviour. Scopes are fixed at issue time: widening one means revoking the key and issuing another.
New: GET /api/public/v1/stream-sessions/ — the SOT stream schedule.
Scheduled and actual windows with the IANA timezone they were entered in,
status, and the operators who worked each session including who covered for
whom and, on a partial cover, the window they covered. Filters: start, end
(on the actual window), model, status, updated_since.
This module is deliberately excluded from what existing keys were granted. A key that should see the schedule has to be reissued.
New: GET /api/public/v1/token/ — tells the calling key which API modules
it holds. Any valid key may call it, including one holding nothing.
The reference moved. /api/public/v1/docs/ is gone and schema.json now
requires an HRIS session. The data endpoints are unchanged and still answer any
valid key — only the description of the API moved, to Profile menu → HRIS
API inside the app.
Timestamps across the API render in Asia/Manila (+08:00), as they already
did.
Technical changes¶
For developers and whoever deploys.
Data model
agency_models.SextPantherDayStats— ten net columns per(account, cutoff, date), stored exactly as typed. No rate is applied to any of them: SextPanther reports what it pays out.agency_models.Model.affiliate_schedule—two_tier|three_tier, defaulting tothree_tier.finance.AffiliateScheduleChange— audit row for a referral moved between rate tables.management.CoverageTeam.account→ModelPlatformAccount(PROTECT). Uniqueness moved from(cutoff, model)to(cutoff, account); both constraints stay partial ondeleted IS NULL.management.CoverageAssignment.commission_only— boolean, the day-off shift.integrations.ApiToken.scopes—ArrayFieldof API module keys.
Behaviour
- CT regular commission reads its base through a per-platform adapter carrying its own rate: OnlyFans 3.00%, SextPanther 1.50%. A platform with no adapter gets no CT team.
- The CT Daily Bonus drops a model's weight when it is worked only by coverage
outside the pool, while keeping its contribution to the pool. A model with
no roster at all still raises
CtBonusValidationErrorand holds the run. - Public API views declare
public_scope;PublicAPIViewMixindefaults it toNoneand the permission denies onNone, so a module added without declaring one serves nothing rather than everything. A test walks the public URLconf and names any view that forgot. GET /api/integrations/tokens/now returns{entries, count, page, page_size, num_pages}instead of a bare array.stateis filtered in SQL againstrevoked_at/expires_atrather than by evaluating the Python property.pnpm lintnow runstsc --noEmitfirst.pnpm buildhad been failing type-check on four pre-existingreact-hook-formfiles with input/output schema divergence; fixed, and the lint change is what stops it going latent again.
Deploy
Six migrations, all forward-only in effect and all reversible:
agency_models/0035 |
SextPantherDayStats |
agency_models/0036 |
Model.affiliate_schedule + pins every existing model to two_tier |
finance/0132 |
AffiliateScheduleChange |
management/0036 |
CoverageTeam.account, the uniqueness swap, + backfill of every team to its model's OnlyFans account |
management/0037 |
CoverageAssignment.commission_only |
integrations/0002 |
ApiToken.scopes + backfill of every token to the five API modules served before this |
The three backfills are the deploy-safety steps. Without agency_models/0036
a long-standing referral's rate silently moves from 2.5% to 1%; without
integrations/0002 every partner integration is revoked at once; without
management/0036 the uniqueness swap leaves teams with no account.
No env vars, no new workers, no one-off commands.
Tests
--create-dbis still needed after this, since the migration set changed.- The
management/sot/suite can block on a live outbound HTTPS call; that is pre-existing and unrelated.