Distinct from API Authentication (proving who you are) — this page covers what an authenticated caller is actually allowed to do.

Model

Permission-based, not role-based, at enforcement time. Every tenant-scoped route declares a specific permission key (e.g. sales.approve) via a permission check; a role is just a named bundle of permission keys, evaluated once at request time into an effective permission set for the caller. See Authentication & Authorization for the full permission catalogue and RBAC matrix, and Roles & Permissions (RBAC) for the module-level operational rules (privilege-ceiling enforcement, self-lockout guards, etc.).

Layers of enforcement, in order, per request

  1. Authentication — a valid bearer token (a bearer token).
  2. Tenant resolution — the X-Tenant header must match one of the caller's own active memberships (tenant:required); otherwise 409.
  3. Subscription status — an active subscription is required for write requests (subscription middleware); a lapsed tenant gets 402 on writes (reads and billing routes stay open).
  4. Permission check — the caller's effective permission set must include the route's required key (permission:<key>); otherwise 403 with the missing key named in the response.
  5. Plan limits — every capability ships on every plan; the only plan gates are record-count limits (user seats at create time, the monthly accounting-transaction cap at posting time), enforced in the service layer with 402 responses.
  6. Country gate — a small number of jurisdiction-specific routes (e.g. Philippines withholding-tax certificates) require the tenant's country to match (country:<ISO2>); otherwise 404 (not 403 — the route is presented as not existing).

Object-level authorization

Beyond the route-level permission check, individual records are scoped to the caller's active tenant automatically — a valid permission key doesn't grant access to another tenant's data. Cross-tenant record lookups return 404, not 403, specifically so a response never confirms whether a given record id exists under a tenant the caller doesn't belong to. See Multi-Tenancy & Organization.

Segregation of duties

Independent of permission keys, every workflow applies the three-person rule: the preparer of a record can never review or approve it, and the reviewer can never also approve it — maker, reviewer and approver are three different users. Both checks are enforced server-side on every workflow transition (journals, commercial documents, expenses, payments, payroll runs, approval requests, employee onboardings and profile changes) regardless of permissions held, owner status, or permission combinations such as sales.approve + payment.approve; a violation is 422 keyed segregation_of_duties. Posting to the ledger additionally refuses any entry that isn't approved by a preparer, reviewer and approver who are three distinct users. A tenant needs at least three active members to complete an approval. See Approval Workflow Engine.

Platform-level authorization

A small set of /admin/* routes operate outside tenant context entirely, gated by a separate platform flag on the user record rather than any tenant permission — see Platform Administration. There is no HTTP path to grant platform-admin status; it's a command-line-only operation.