Roles and permissions
The permission model in detail, and how to design roles for your shop.
Every staff account carries one role, and a role is a set of permissions.
Twenty are checked server-side today, and a request without one is refused with a 403 naming the permission:
pos.sell · pos.hold · pos.refund · pos.discount · pos.view_products · pos.open_close_register · inventory.view · inventory.adjust · inventory.stocktake · inventory.transfer · inventory.import · invoicing.view · invoicing.create · invoicing.send · invoicing.payments · crm.view · crm.edit · repairs.view · marketing.send · settings.access_levels
A few are worth being precise about. invoicing.create covers raising an invoice, issuing a credit note against one, and editing or deleting the invoice afterwards — the same authority either way. pos.open_close_register covers closing the till, and putting it on break or resuming it — the actions that stamp counted cash and the variance onto a session. Opening a register is not yet gated, because the POS screen opens one automatically when a shift starts and refusing that would leave the day with no session to reconcile against.
The reads are gated as well as the writes, which is the point of the newer ones: inventory.view, invoicing.view and crm.view decide whether stock levels, invoices and customer records come back at all, and pos.view_products covers the product catalogue wherever it is read — POS grid, barcode scan, and the product pickers in Inventory, Invoicing and Repairs. All four are held by every role on the default matrix, so unticking one is a deliberate act rather than a surprise.
crm.edit is checked on the bulk customer import, which also requires the admin role — so it narrows nothing today and exists so the gate is already right if that route's role floor is ever lowered.
The rest of the matrix currently shapes what the interface offers rather than being enforced at the API — with one qualification: most settings.* keys sit on routes that already require the admin role, which is a stricter rule than the permission would be. Where a key is genuinely unenforced, treat it as an organising tool, not as a security boundary — the real boundaries are company tenancy, store assignment on store-scoped requests, and the PIN gate.
The roles
| Role | What it is |
|---|---|
| staff | Counter staff. Sell and look things up. |
| supervisor | Runs a shift. Refunds, discounts, the register, stock. |
| admin | Runs the business. Everything, always. |
| superadmin | Platform role. Not a tenant role. |
What each role can do by default
A new company starts with this matrix. staff gets:
- Point of Sale — process sales, hold sales, view products
- View-only access to Inventory, Invoicing, CRM, Repairs and the Dashboard
supervisor adds:
- Point of Sale — refunds, discounts, price overrides, voids, opening and closing the register, and POS reports
- Inventory — adjustments, transfers and stocktakes
- Invoicing — create, send and take payments
- CRM — edit customers
- Repairs — create and update tickets
- Marketing — view
- Settings — products, targets and receipts
admin holds every permission. That is not a default you can edit away — see below.
POS-003 is "Process refunds" — which is the quickest way to be precise about one in a support conversation. The admin column is switched on and disabled: admin always holds every permission, because the matrix is edited by admins and a save that could strip their own rights would lock them out of this very screen. Toggles shown are the shipped defaults.Editing the matrix
Only the staff and supervisor columns are editable. The admin column is fixed at full access on purpose: the matrix is edited by admins, so a save that could strip admin rights would lock every admin out of the screen needed to undo it.
A company can deliberately grant staff and supervisors nothing at all. Because admin's full row set is always written alongside, that choice is stored and honoured rather than being mistaken for "this company has never customised its permissions" and silently reset to the defaults.
Permission names and codes
Every permission has a short code as well as a name — POS-003 is "Process refunds", INV-002 is stock adjustments. The code is shown beside the permission on the Access Levels screen and is the least ambiguous way to refer to one.
Permissions are grouped by area — pos.*, inventory.*, invoicing.*, crm.*, repairs.*, marketing.*, dashboard.* and settings.*. The ones that come up most often at the counter:
| Permission | Allows | staff | supervisor |
|---|---|---|---|
pos.sell | Process sales | Yes | Yes |
pos.hold | Hold / park sales | Yes | Yes |
pos.view_products | See the product list | Yes | Yes |
pos.refund | Process refunds | No | Yes |
pos.discount | Apply discounts | No | Yes |
pos.void | Void a sale | No | Yes |
pos.open_close_register | Open / close the till | No | Yes |
pos.view_reports | POS reporting | No | Yes |
pos.price_override is not in that list and no longer appears on the Access Levels screen. There is no price-override path in POS — the server re-derives every line price on a sale — so the switch controlled nothing. The key is still recognised and still stored, so no saved role was rewritten when it was hidden; it comes back if the capability is ever built. Overriding a repair price is a different, real thing, and it is gated on a supervisor or admin PIN rather than on this permission.
Roles are not the only gate
Two further checks sit alongside permissions. Staff are restricted to the store locations they are assigned to, on reads as well as on changes; owners and admins reach every shop. And a number of actions additionally require a staff PIN at the moment they are performed.