Docs / Security and data

Roles and permissions

How the API decides: permission keys, the staff-editable role matrix, per-person lists, own or organization scope, the staff flag.

Authorization needs an explicit role and an organization membership. Company names, email text and workspace choices never grant either. Every action is a permission key in one registry (packages/contracts/src/permissions.ts, catalog in docs/permissions.md); which roles hold which key is a table staff edit live at /platform/permissions (see Roles and permissions matrix). The code is apps/services/src/permissions.

How a request is decided#

  1. The session guard resolves the caller (see Session API). No session is a 401.
  2. The guard asks PermissionService.resolve(userId, organizationId) for the caller's effective permissions: the role's cells in the live matrix (cached in Redis, a few seconds at most behind an edit), with the person's override list on top. Nothing is read from token claims, so a staff edit or an administrator's change applies to the next request.
  3. A service calls can(user, "applications.decide") (403 when missing) or requireStaff(user, "staff.tenants.decide"). Scoped reads use the own or org scope the permission carries.
  4. Tenant scope: vendor-side callers may only touch applications of their own organization (isApplicationVendorScoped). A resource outside the scope is usually a 404, a missing permission a 403.
  5. Cross-tenant access needs isStaffOperator (a platform role and the session staff flag from the staff sign-in) and, for each staff action, the matching staff.* key.

Roles#

The built-in roles and their default grants are listed in docs/permissions.md. In short: supplier roles Owner, Administrator (ORG_ADMIN), Credit manager, Salesman (SALES_MANAGER), Account admin, Vendor user; customer roles Owner, Administrator, Buyer manager, Buyer (all APPLICANT in the database, told apart by the owner link and a role: entry or the legacy permission set); the reference role; staff roles; and the AGENT_MCP ceiling for AI agents. Staff can add custom roles for an audience, edit any built-in role and reset it to its defaults; built-in roles cannot be deleted.

A per-person list (permissions on the user or membership) replaces the role's cells for the keys it names (old names such as view_pipeline expand to the keys they used to grant); role:<KEY> selects a matrix role instead.

Permissions that matter most#

PermissionGates
applications.deciderecording a credit decision (high risk)
references.remindsending and reminding references for an application
applications.view, references.viewreading applications and references; scope own (a Buyer) or org
users.invite, users.editinviting teammates and changing their access (high risk)
audit.view, staff.audit.viewthe organization's audit trail, the platform audit log
staff.permissions.editediting this matrix

Public registration never creates staff and never joins an existing organization without an invitation. Platform staff use internal email codes; the password option needs an explicit INTERNAL_STAFF_PASSWORD_EMAILS entry.