Role & Permission Hierarchy
Written by Rohman Beny Riyanto
Useful context before you're staring at a 403/424 wondering whether it's a missing permission grant or a role that was never allowed near this endpoint in the first place. See FE Auth Error Handling first for what each code means moment-to-moment - this page is about the model behind them.
Two separate questions, checked separately
Every protected endpoint asks two independent questions, and a 403/424 can come from either one:
The first question is about account type - a customer account can never reach an admin endpoint, full stop, no permission grant changes that. The second is about permissions - within the accounts that are even allowed near the endpoint, does this specific one hold the specific capability it needs. See FE Auth Error Handling for exactly which response_code corresponds to which failure.
The account hierarchy
A sub-role (Staff, Device Admin, or any custom one created later) always starts out with whatever baseline permissions its parent type defines, plus anything explicitly granted to it on top.
Why one merchant never sees another merchant's data
Every permission a merchant-side account holds is scoped to that merchant specifically - never global. A system-level account's permissions apply everywhere; a merchant account's permissions apply only within its own merchant, no matter what's sent in the request.
This has been verified directly, not just assumed: sending an explicit "apply everywhere" scope in a grant/revoke or audit-log request from a merchant account still resolves back to that merchant's own scope - there's no way to make it reach outside. system_owner accounts are the only ones whose scope is genuinely global, and only when acting as root.
Where a permission comes from
Three distinct sources determine what an account can do, and they change at different times - worth telling apart when something isn't behaving as expected:
| Source | What it means | Changes when |
|---|---|---|
| Permission catalog | The full list of object:action pairs that exist at all - see Menu Permission Catalog and Get Permission Catalog | Whenever the API itself adds/removes a capability |
| Default baseline | A small set of permissions some sub-roles (like Staff) get automatically, no grant needed | Fixed per sub-role, doesn't change per account |
| Explicit grant | The actual "this account/sub-role can do X" assignment | Any time, via Grant Role Permission / Grant User Permission, by someone with the right access |
If you're building a Roles & Permissions style admin screen: the catalog tells you what's possible to grant, the baseline is what a sub-role already has without you doing anything, and grants are the thing your UI actually creates/removes.
What each account type can actually do
Verified directly against a running server:
| Account | Create a merchant | Manage sub-roles | List roles | Register a sub-account | Grant/revoke permissions | Read audit log |
|---|---|---|---|---|---|---|
| System Super Admin | yes | yes | yes | yes, for anyone | yes, anywhere | everything |
| System Staff | no, unless granted | no, unless granted | yes (default) | depends on grant | depends on grant | depends on grant |
| Merchant Super Admin | no - wrong account type | no - wrong account type | no - wrong account type | yes, own merchant only | yes, own merchant only | own merchant only |
| Merchant Device Admin | no | no | no | no | no, unless granted | no, unless granted |
| Customer | no - different account type entirely | no | no | no | no | no |
A Merchant Super Admin trying to act outside its own merchant - creating a merchant, reaching into another merchant's data, or setting an explicit "global" scope on a grant - is rejected every time, either at the account type check or by having its scope forced back to its own merchant regardless of what the request asked for.