A fan's card shows another page's data
Why the same fan_id can render one page's money and DMs under another page's name — and the fail-closed header that stops it.
Symptom
In an "all accounts" summary view, opening the same fan_id that exists on two different
creator pages of the same workspace shows one page's money and conversation under the other
page's name.
Why this can happen at all
fan_id is only unique per creator page, not per workspace. A workspace with two pages
(say, a main page and a free/tip page) can have the same numeric fan id subscribed to both — two
different people, one number. Every fan-scoped read (the card itself, its transactions, notes,
actions, purchased content) has to carry which page it means, or a cache keyed on the bare
fan_id will happily serve one page's answer for the other's request.
How the panel actually avoids it today
Every fan-scoped request the panel makes carries the fan's page as an explicit
X-Account-Owner header, and that same page address is folded into the client-side query
cache key (pageOwnerId / pageAddressKey) for every fan read — the card, its transactions,
notes, actions, and purchased content all key on (fan, page), never on fan alone. Server-side,
X-Account-Owner re-pins the request's owner only after an RBAC check
(_effective_owner_id → rbac.authorize_pages): an unrestricted owner may re-pin to any page it
owns; a restricted team principal only to a page inside its own authorized scope. Anything else —
a forged, foreign, or unauthorized owner id — is silently ignored and the request stays on the
authenticated owner. It fails closed, not open.
This is a guarded contract, not an open bug
This behavior is covered by a dedicated regression test asserting every fan-card request (detail, transactions, notes, actions, purchased content) carries the header. If you're building your own client against these same fan-scoped reads — rather than using the panel — the guard is only as good as your own request threading it: carry the fan's page explicitly on every call, the same way the panel does, or a multi-page account can produce exactly this symptom in your own integration.
What to check if you still see it
- Confirm the request in question actually sets
X-Account-Owner(or, for a resource that can't set custom headers, the equivalent authorized?account=query parameter) to the fan's own page — not the workspace's default/main page. - Confirm the caller is actually authorized for that page. An unauthorized header doesn't error; it's dropped, and the request quietly runs under the authenticated owner instead — which can look like the bug above from the outside, but is the fail-closed guard working as intended.
What's next
Async engine write gotchas
Retrying a stalled OnlyFans write the wrong way can create a duplicate on OnlyFans instead of finishing the original.
Attribution doc gaps
Stale or misleading claims we found in the previous attribution guide and in the code's own comments — so future edits don't reintroduce them.