Onboarding and offboarding an admin

Three role recipes built from real permission keys, the order to create and assign a role in, how long a change takes to bite, and the six-step offboarding that actually closes every door.

11 min readUpdated 6 August 2026permissions, roles, staff, offboarding, security

Roles and permissions explains the model — how a key is built, where it is enforced, what Super Admin is. This page is the task: which keys to actually tick for a support agent, a finance approver and a compliance reviewer, and how to take them all away again when the person leaves.

Two facts shape everything below.

Every account has exactly one role. There is no per-user override, no second role and no inheritance. Two people who need different access need two different roles.

access.admin is not a formality. The URL map covers 245 admin paths explicitly — a count that grows with almost every release; anything under /admin that is not listed falls back to access.admin alone. That bucket is not small — it includes Trading Settings, the page builder, individual user profile pages, ecommerce settings and the affiliate Conditions screen, which is where commission rates are set. The data on those pages is still fetched through the API and still refused without the right view.* key, so an under-privileged operator sees an empty shell rather than real records — but treat access.admin as "may walk the whole admin area", not as "may see the dashboard".

Three recipes

Keys sort by verb, not by area, in the permission picker. So you will tick the access. block, then the view. block, then the edit. block — not one area at a time. Plan for that or you will miss half of a pairing.

Almost every admin screen is a data table, and it asks two questions before it fetches anything: does this role hold the screen's access key, and does it hold its view key. Grant access.user without view.user and the operator gets the Users page, the header, the filters and an empty table — forever, with no error and nothing in the console. This is the commonest support case on the whole permission system.

Support agent

Answers tickets. Can look a customer up to understand the ticket. Cannot touch their money.

Key What it unlocks
access.admin The admin area and its dashboard
access.support.ticket The support console at /admin/crm/support and the archive at /admin/crm/support/tickets
view.support.ticket The tickets themselves, and the per-ticket page
edit.support.ticket Reply, assign, set priority, close and reopen
access.user + view.user Open a customer's record to see who is asking
access.kyc.application + view.kyc.application Read where a verification got to, so "why is my account limited" can be answered

Deliberately absent, and each for a specific reason:

  • edit.user — that one key also covers blocking accounts and resetting two-factor. An agent who can reset a customer's 2FA can take over the account.
  • edit.wallet — adjusting a customer balance up or down.
  • edit.withdraw / edit.deposit — deciding the money queues.
  • edit.kyc.application — approving identity verification.
  • create.api.key — see the warning at the bottom of this page.
  • delete.support.ticket — closing a ticket is the reversible action; deleting it destroys the conversation.

Finance approver

Works the deposit and withdrawal queues. Reads the ledger. Does not configure the platform.

Key What it unlocks
access.admin The admin area
access.withdraw + view.withdraw + edit.withdraw The withdrawal queue and the approve/reject decision — the key that moves money out of the platform
access.deposit + view.deposit + edit.deposit The deposit queue and its decision
access.transaction + view.transaction The full ledger, read-only
access.wallet + view.wallet Customer wallets, read-only
access.user + view.user Identify the customer behind a transaction
access.admin.audit The audit trail, so they can see their own decisions and yours

Add edit.wallet only if this person is allowed to adjust balances by hand and to freeze a wallet. It is a separate decision from deciding the queues; see Customer wallets and balance adjustments.

Deliberately absent:

  • edit.settings — the fee, withdrawal-approval and two-factor policy that govern the queue they work. An approver should not be able to switch off the approval requirement they exist to satisfy.
  • delete.withdraw / delete.deposit / delete.transaction — deleting a queue row destroys the evidence of the decision.
  • create.api.key.

Compliance reviewer

Decides identity verification and country policy. Reads the audit trail. Does not approve payouts.

Key What it unlocks
access.admin The admin area
access.kyc.application + view.kyc.application + edit.kyc.application The application queue and the approve / reject / request-more-info decision
access.kyc.level + view.kyc.level The verification levels and their required fields, read-only
access.user + view.user The account behind an application
access.geo.restriction + view.geo.restriction Geo restrictions and the geo policy screen
access.geo.restriction.log The geo access log and its CSV export — the evidence trail for a refused visitor
access.admin.audit The audit trail

Add edit.geo.restriction (and create.geo.restriction / delete.geo.restriction) only if this person owns country policy rather than reviewing it. Add edit.user only if they are the person who blocks accounts — remembering that the same key resets two-factor.

Geo-restriction settings are refused by the generic settings endpoint even for a edit.settings holder. They are writable only from Geo Restrictions → Policy, behind edit.geo.restriction. So a compliance reviewer with geo keys does not need edit.settings, and giving them edit.settings would not get them geo policy anyway.

Onboarding, in order

  1. Sign in as a Super Admin. Nobody else can create, rename or delete a role, whatever keys they hold — the route checks the caller's role name, not their create.role grant. A non-Super-Admin gets Forbidden - Only Super Admins can create new roles.

  2. Create the role at /admin/crm/role. Users → Roles & Permissions → User Roles. Name it for the job, not the person.

  3. Tick the keys, verb block by verb block. The Permissions field is a multi-select over every seeded key, sorted alphabetically — which means by verb. Work down the access. block first, then view., then edit., using the recipe above as your checklist rather than trying to find one area's keys together.

  4. Save. The permission set is replaced wholesale by what is in the field: anything unticked is removed. The save writes an ADMIN_CRM row to the audit trail with the title Create role or Update role, so you can prove afterwards who granted what.

  5. Assign the role on /admin/crm/user. Edit the account and pick the role. Only a Super Admin may change an account's role, and the form makes them re-enter their own password first — a hijacked admin session must not be able to promote a second account quietly.

  6. Have them sign out and back in. The role change revokes every session the target account holds, so they will be signed out anyway. A fresh sign-in is the cleanest way for them to pick up the new menu.

How long the change takes to bite

There are three copies of "which role may do what", and they do not converge at the same speed.

Where When it sees your change
The API, on the worker thread that served the save Immediately — the save reloads the in-memory role map on that thread
The API, on every other worker thread Within 60 seconds, on each thread's own refresh timer
The page gate and the admin menu (Next.js middleware) Within 5 minutes — it caches the role list for that long

Sixty seconds is fine for a grant. It is not fine when you are taking access away from somebody you no longer trust, and the five-minute front-end cache means the menu and the page gate lag further still. If the revocation matters right now, restart the API process and remove the doubt:

pm2 restart backend

pm2 restart backend is enough on its own. The cron process serves no traffic and the front end needs no restart. Full detail in Processes and ports.

Verify the grant landed

Do not trust the success toast. Check two things:

  1. /admin/system/audit, filtered to Module ADMIN_CRM. Your save is there as Create role or Update role, with your name on it. If it is not, the save did not happen.
  2. Have the person open one screen from each block you granted and confirm the table has rows in it. An empty table with no error is the access-without-view symptom described above, and it is silent from every other angle.

Two fences you will hit

Super Admin cannot be scoped, and cannot be handed out

Every gate compares the role's name against the literal string Super Admin and returns true before permissions are consulted. There is no way to narrow it.

The role is also fenced off from the screens: it is excluded from the Roles list so it cannot be edited or renamed there, it cannot be deleted, its permission set cannot be synced through the API, and it is excluded from the role picker on the Users screen — so no account can be promoted into it from the panel at all. Super Admin rows are hidden from the user list for everyone below Super Admin.

Give it to people, not to service accounts, and not to an integration.

39 settings keys refuse anyone but a Super Admin

A separate fence sits behind edit.settings. These keys reject a save from any role but Super Admin, even one holding edit.settings, and the refusal is a 403 for the whole save — though unchanged keys are dropped before the check runs, so the rest of a page still saves normally. Key matching is case-insensitive, so renaming a key does not get around it.

Group Keys
Withdrawal policy withdrawAutoApprove (and withdrawApproval, its legacy twin — same guard, written automatically when the first is saved)
Withdrawal two-factor withdrawTwoFactorRequired, withdrawTwoFactorChallenge, withdrawTwoFactorAppAllowed, withdrawTwoFactorEmailAllowed, withdrawTwoFactorSmsAllowed
P2P two-factor p2pTwoFactorRequired, p2pTwoFactorChallenge
Transfer money walletTransferFee, walletTransferFeePercentage, walletTransferSpread
TransFi ramps and custody transfiOnrampEnabled, transfiOfframpEnabled, transfiOnrampCustody, transfiOfframpCustody, transfiIbanEnabled
Trading bots tradingBotEnabled, tradingBotLiveTradingEnabled, tradingBotPlatformFeePercent, tradingBotTradingFeePercent
AI Support aiSupportEnabled, aiSupportAutonomy, aiSupportProvider, aiSupportDailyBudgetUsd, aiSupportMonthlyBudgetUsd, aiSupportAccountToolsEnabled, aiSupportOperationsEnabled, aiSupportWorkflowsEnabled, aiSupportWorkflowKeys
DEX / Swap dexEnabled, dexAllowlistMode, dexKycRequired, dexDirectPoolsEnabled, dexPoolRiskAckRequired
FX routing fxTradingExternalRoutingEnabled
KYC kycFeatureEnforcement
Demo mode DEMO_STATUS, NEXT_PUBLIC_DEMO_STATUS

So "the save says Forbidden" is expected behaviour for these, not a bug. When a delegated admin reports it, the answer is that a Super Admin has to make that particular change — not that their role is broken.

The two demo-mode keys are on the list but govern nothing: demo mode is read from the environment (NEXT_PUBLIC_DEMO_STATUS in .env) and no reader consults a settings row. Do not go looking for the screen that edits them.

Offboarding

This is the half nobody writes down, and the order matters — each step is harder once the one before it has been done.

Once you have blocked the account or emptied the role, their assigned tickets are still assigned to them, and the queue now shows an owner who can never answer. The archive screen does not offer a per-agent filter, so tracking them down afterwards is manual work you can avoid by doing it while you still know what to look for.

  1. Reassign their open support tickets. /admin/crm/support/tickets. The Assigned Agent column is on every row. Select the departing agent's tickets and use Unassign selected from the bulk menu — that returns them to PENDING and back into the unassigned pool, where the rest of the desk picks them up. (The other bulk option is Assign to me, so the colleague taking the work over can claim them directly.) CLOSED tickets are left alone by design; re-assigning a resolved ticket would drag it back into the open queue.

  2. Take the role away. /admin/crm/user, edit the account, and move them to a role with no permissions — the seeded User role is exactly that, and it ships empty. Do not leave them on an admin role "until the handover is finished".

    Changing the role revokes every session that account holds, immediately. They are signed out of every browser and every device on the spot.

  3. Revoke their API keys. /admin/api/key. A key acts as its owner and does not care what role the owner is on now, so a key minted while they were an admin keeps working after the role is gone. Delete every key issued to their account — and if they held create.api.key, check for keys they issued against other accounts too.

  4. Set the account status. /admin/crm/user, the row's status control, or the Block User action if you want a recorded reason and an optional duration. BANNED and SUSPENDED both revoke sessions; a block requires a reason, which is what lands on the audit trail and in their block history. You cannot change your own status, and a bulk status change that includes your own account is refused outright.

  5. Restart the backend if this is a for-cause departure. Otherwise the worker threads that did not serve your save keep enforcing the old permissions for up to a minute, and the page gate for up to five. See the warning above.

  6. Read the trail. /admin/system/audit, filtered to Module ADMIN_CRM, confirms the role change and the block are both recorded with your name on them. Then filter the Admin column to the departing account and read the last few weeks of what they did — this is the moment to do it, while you still have the context.

/admin/api/key creates a key against a chosen userId, with whatever scopes the form was given. A key acts as its owner, so an operator holding create.api.key can mint working credentials for any account on the platform, including scopes that move money — and those credentials survive the operator's own offboarding until somebody deletes them. Grant it only to roles you would trust with the accounts themselves, and audit it at step 3 above.