Permissions & RBAC

Who can do what — the seven roles, their permissions, and the rules behind them

How Access Control Works

Every protected endpoint is guarded by a permission string (e.g. hr.employees.create). A user's role grants a fixed default set of these strings. When a request arrives, the backend checks whether the caller's permission set contains the string the route requires — if not, it returns 403 AUTHORIZATION_FAILED.

The backend is the only source of truth

The frontend hides buttons and menu items you can't use, but that's a convenience, not a security boundary. Every rule below is enforced server-side on every request. Tampering with the UI or the request body cannot grant access the role doesn't have.

Anatomy of a permission string

v2 uses dot-style strings shaped as resource.action with an optional scope suffix:

text
v1 strings still work

v1 colon-style strings like product:read are still accepted. New v2 code uses the dot-style strings shown here. There are ~280 permissions in the v2 catalogue; this page focuses on the HRM, ESS, and notification families.

The Seven Roles

RoleScopeIn one line
SUPER_ADMINPlatformEvery permission, across all tenants (checked by a short-circuit before the permission set is even consulted).
ADMINOne businessFull control of their tenant — POS, finance, and all of HRM & Payroll.
MANAGERStore + teamStore operations, plus team-scoped HR: approve their reports' attendance corrections, leave, and shift swaps.
CASHIERPOS terminalSell, handle their own cash shift, and use self-service. No back-office.
HR_MANAGER v2HR domainRuns HR & payroll setup and processing — but does not approve or disburse payroll (see SoD below).
ACCOUNTANT v2FinanceFinance + the payroll approve/disburse half of the split, plus advances approve/disburse.
EMPLOYEE v2Self onlySelf-service only — exactly the ess.* bundle, nothing else.
Everyone is also an employee

Every role except EMPLOYEE also receives the full ess.* self-service bundle — because a manager or accountant still needs to clock in, apply for leave, and read their own payslip. The EMPLOYEE role is the only one limited to just that bundle.

Payroll Separation of Duties (Important)

POSVelo deliberately splits payroll across two roles so the same person can't both prepare and authorize a payment. This is a real, built-in control — not just a recommendation.

Payroll stepPermissionHR_MANAGERACCOUNTANTADMIN
Build structures / assign salarieshr.payroll.salary-structure.manageYesRead onlyYes
Create a runhr.payroll.run.createYesNoYes
Process (calculate payslips)hr.payroll.run.processYesNoYes
Approve (finalize payslips)hr.payroll.run.approveNoYesYes
Disburse (record payment)hr.payroll.run.disburseNoYesYes
Read the split

An HR_MANAGER prepares and processes a payroll run; an ACCOUNTANT approves and disburses it. Neither can do the other half, so two people are always involved. An ADMIN can do everything — but the run-approve step still enforces that the approver isn't the same person who processed it, and may require step-up 2FA.

HRM Capability Matrix

A condensed view of who can do the common HR actions. "Team" means the action is limited to employees who report to that manager.

CapabilityADMINHR_MGRMANAGERACCTCASHIEREMPLOYEE
Manage departments / designations
Create / update employees
Read sensitive PII (NID / bank)
Read salaryOwn
Terminate an employee
Read attendanceAllAllTeamOwn
Manual punch for othersTeam
Approve attendance correctionsTeam
Manage shift templates
Create / edit rosterTeam
Approve a shift swapTeam
Manage leave types / policies
Approve / reject leaveTeam
Process a payroll run
Approve / disburse a payroll run
Read own payslips & apply for leave (ESS)

Scopes: own · team · all

Several read permissions come in three widths. The narrower one a role holds determines how much it sees.

Scope suffixSeesExample permission
.ownOnly the caller's own recordshr.attendance.read.own
.teamRecords of employees who report to the callerhr.attendance.read.team
.allEvery employee in the businesshr.attendance.read.all

The "team" scope is computed once (employees whose reportsToId resolves to the manager's own employee record) and reused consistently across attendance, shifts, and leave.

Permission Catalogue — HRM

GroupPermissions
Orghr.departments.read · .manage · hr.designations.read · .manage
Employeeshr.employees.read · .create · .update · .terminate · .export · .read.sensitive · .update.sensitive · .read.salary · .update.salary
Documents & contractshr.employees.documents.read · .upload · .delete · hr.contracts.read · .create · .update
Attendancehr.attendance.read.own · .read.team · .read.all · .check-in.manual · .check-out.manual · .regularize.request · .regularize.approve · .regularize.reject · .export · .biometric.read · .biometric.configure
Shiftshr.shifts.read · .template.manage · .schedule.read · .create · .update · .delete · .swap.request.own · .swap.respond · .swap.approve
Leavehr.leave.types.read · .manage · .policies.read · .manage · .balances.read.own · .read.team · .read.all · .adjust · .request.create.own · .create.for · .read.* · .approve · .reject · .cancel.own
Holidayshr.holidays.read · .manage
Payrollhr.payroll.read · .read.payslip.all · .salary-structure.read · .manage · .country-preset.read · .apply · .run.create · .process · .approve · .disburse · .cancel · .payslip.void · .advance.read · .create · .approve · .disburse · .export

Permission Catalogue — ESS (the ess.* bundle)

Granted to every role (and the only thing the EMPLOYEE role gets).

text

Permission Catalogue — Notifications

PermissionWho needs it
(none)Reading/managing your own inbox — list, unread-count, mark-read, read-all — needs no permission.
notifications.send.manualSending a broadcast to a role / store / the whole tenant (admin-level).

Inspecting a User's Permissions

You don't have to guess — the API can tell you exactly what a caller (or a role) holds.

MethodPathReturns
GET/api/v1/auth/meThe current user, including a permissions: string[] array.
GET/api/v1/rbac/me/permissionsThe caller's effective permissions.
GET/api/v1/rbac/rolesAll roles.
GET/api/v1/rbac/roles/:roleThe default permission set for one role.
GET/api/v1/rbac/permissionsThe full permission catalogue.
bash
json
Building a feature?

When you add a UI control, gate it on the same permission the backend route checks (the frontend exposes a usePermissions().can() helper). That keeps the UI honest — but remember the server still enforces it regardless. The full endpoint → permission mapping is in the v2 API Reference.