Slack: where each creator's automations post, and who they mention¶
The spreadsheet that told our tools which Slack channel belongs to which creator now lives in HRIS — along with a screen that shows you who is still missing a piece.
See who is still unmapped, instead of hunting record by record¶
Info → Integrations → Routing lookup lists every creator with her personal email, Slack channel, Slack user and talent manager. Each missing piece is flagged in the row rather than left as an empty cell.
- The filter beside the search box is the point of the page: Anything missing, No Slack channel, No Slack user, No personal email, or No manager, or unmapped.
- Edit opens the row in place — the table doesn't move, and a creator's name opens her full record in a new tab.
- 100 rows a page by default, because this is a screen you work down.
Guide: The lookup page
A creator's Slack routing sits on her own record¶
Her page has a Local controls tab, now laid out in groups — Status, Email, Slack routing, Launch arm, Internal notes, Commission — across the full width of the page rather than squeezed into a column.
- Slack routing holds her channel and her own Slack account.
- Personal email sits under Email, directly below the Aruna one. It has to be unique across the roster, and saving one that another creator already has tells you who has it.
- Channels are remembered by their Slack id, so renaming one in Slack changes nothing here.
Guide: Slack routing
Time Doctor and Slack in one table (P&C)¶
People & Culture → Account mapping replaces Time Doctor mapping. Same table, a second column.
- Pick someone's Time Doctor user and their Slack account from the same row. Both save the moment you pick.
- The last filter answers who still needs one: Anything missing, No Time Doctor, No Slack.
- A manager's Slack account is what lets a tool mention the person responsible for a creator, so the two screens point at each other.
Guide: Account mapping
The Slack workspace, visible from inside HRIS¶
Info → Integrations → Slack shows everyone and every channel the HRIS bot can see, with People and Channels tabs, filters for status and account type, and a Refresh button.
It reads the workspace and nothing more — nothing posts to Slack, and nothing notifies anyone. The card also shows which permissions the bot was granted, so a missing one is visible before it causes a confusing empty list.
Guide: The directory
Also¶
- The old Time Doctor mapping link still works and lands on Account mapping, filters and all.
- A model's page had two identical copy buttons on some values. Now one.
- The Slack pickers stay filled in when HRIS can't reach Slack — they go read-only and keep showing what's set, instead of looking empty.
- Services on System Info now checks Slack alongside the other upstreams, and says when the bot is missing a permission — which otherwise shows up as a channel list that looks oddly short.
Public API (for integrations)¶
A new API module, Routing lookup, and one endpoint:
GET /api/public/v1/lookup/model/?of_email=<address>
GET /api/public/v1/lookup/model/?personal_email=<address>
Give one of the two addresses, get back her Slack channel id, her Slack member id, and her current talent manager with theirs.
- No email address is in any response — not hers, not her manager's.
- Matching is exact and case-insensitive; a blank value is a
400, not everything. This endpoint cannot list the roster. of_emailmatches the creator's current OnlyFans account address, which HRIS refreshes nightly. An address changed upstream keeps resolving once that sync runs, instead of silently stopping — which is the failure this whole release exists to remove.- Paused and offboarded creators still resolve. Two creators can share an
OnlyFans address, so
countcan be2and both rows come back. - A missing manager is reported, not fatal:
manager_missingisno_open_assignmentorno_slack_mapping, with a normal200.
Routing lookup is its own module, so a key can hold it alone — resolving one creator without being able to read the roster. Modules are fixed when a key is issued; widening one means revoking and reissuing.
The reference is in the app at Profile menu → HRIS API, which needs an HRIS account — a partner who only has a key can't read it.
Guide: What a key opens · What an integration calls
Technical changes¶
For developers and whoever deploys. None of this needs a guide.
Data model
agency_models.Modelgainspersonal_email,slack_channel_id,slack_channel_name,slack_member_id,slack_member_name. The ids are canonical; the_namecolumns are a display cache re-stamped from the live directory on write, the same shapepnc.Employeeuses for Time Doctor.uniq_model_personal_email_ci— a partial unique constraint onLower(personal_email), conditioned ondeleted__isnull=Trueand~Q(personal_email=""). These columns default to"", not NULL, so without that second clause the second model with no personal email fails to save.pnc.Employeegainsslack_member_idandslack_member_name.- The creator's OnlyFans address is deliberately not stored a second time —
the lookup joins through
ModelPlatformAccount.email, whichof_link.sync_profilerefreshes.
Behaviour
- New
backend/slack/app: a bot-token client with cursor pagination (bothusers.listandconversations.listpage, and ignoringnext_cursorreturns the first 200 of everything with no error at all) and granted-scope reporting fromx-oauth-scopes. GET /api/models/slack-directory/, gatedHasModule(MODELS), projects only picker fields from the cached Slack reads — rather than widening/api/slack/'s System Info gate, which would publish every member's email, phone and title to anyone who can edit a model.PATCH /api/pnc/employees/<id>/slack/, gatedHasModule(PNC);{}clears.finance.retainer.scoping.current_manager_for()— the model → manager read that didn't exist, built on the existingassignment_overlap_qrather than a third hand-written copy of the predicate.- Breaking: the employees list filter
mapping=mapped|unmappedis replaced byunmapped=time_doctor|slack|any.anymeans missing either. The only consumer was the page that was replaced. /pnc/time-trackernow 308s to/pnc/account-mapping, carrying query params and translatingmapping=unmapped→unmapped=time_doctor.- Unconfigured or unreachable Slack returns
200withconfigured: false, never a 500; model and employee records still save.
Deploy
agency_models/0042_model_slack_routing— fiveAddFields plus the partial constraint. Reversible, no data migration.pnc/0059_employee_slack_member— twoAddFields. Reversible.SLACK_BOT_TOKENmust be set for the directory and the pickers. Without it every surface renders its "not configured" state and nothing calls Slack. Scopes needed:users:read,users:read.email,channels:read,groups:read.- Run
pnpm generateconsumers: the public schema gained thelookupmodule.
Tests
- The headline test drives
of_link.sync_profilewith a stubbed upstream profile and asserts the old address resolves to nothing while the new one resolves to the same creator — pinning the path that actually broke the spreadsheet, rather than assigning to the column.