Skip to content

Permission screens reference ​

The Studio Explorer folder Security > Roles & Permissions contains seventeen screens. Fourteen of them author one kind of rule (a role, a permission set, a grant on one kind of resource, a field rule, a row filter, an approval limit, an API rule, a template or a time-boxed grant), Access Policies authors attribute-based policies, and two screens (Effective Permission Viewer and Permission Audit Log) read the result. This page documents every screen: its fields, the exact resource strings it writes, where the rule is enforced, the REST calls behind it and the messages it can return. How a decision is reached when several rules apply is described in Permissions - how an access decision is made; read that page first.

Behaviour shared by all screens ​

Permission matrix editor. Screens that grant or deny an action on a list of resources (Screen, Widget, Menu, Report, Print, Workflow, and Field permissions) share one editor.

ElementBehaviour
Mode toggle By role / By resourceBy role: pick a role in the left list (a badge shows how many cells are set for it), then see every resource with its action chips. By resource: pick one item with the Item search box, then see every role.
Action chipOne chip per action. Selecting the chip cycles not set (grey outline), allow (green), deny (red), then not set again. Each click is saved immediately through PUT or DELETE /api/v1/role-permissions.
Bullet in a chip labelA chip labelled with a bullet (for example "Visible •") is an action that a server or runtime checkpoint consults today. A chip without a bullet is stored but has no consumer yet.
Filter All / Granted / Denied / Not setNarrows the rows shown. "Granted" means at least one action is allow for that row; "Denied" at least one action is deny.
Search box"Search items" (by role) or "Search roles" (by resource). Matches the label and the resource key.
Show N more (M left)The list is rendered 60 rows at a time.
Bulk bar Action, Allow N, Deny N, Clear NApplies one action to every row currently shown (after search and filter). A confirmation dialog titled "Allow Visible?" (or Deny, Clear) states "Allow "Visible" on 12 items for HR Assistant." and offers Cancel and Allow 12. If every row already has the value the dialog reads "Everything shown already has this setting." and the confirm button is disabled. Field Permissions has no bulk bar.
History icon beside each chipOpens the version history of that cell (see below).

Version history dialog. Every create, change and delete of a role, role grant, permission set, user override, temporary permission, field rule, API rule and approval limit is stored as a numbered version. The dialog titled "History" followed by the item name lists Version (v1, v2, ...), Action (create, update, delete, restore), By and When. Compare from and Compare to select two versions and Compare lists the fields that differ as field: before -> after (or "No differences - the two versions have identical field values."). Restore re-applies the stored state live and records a new restore version; history is never rewritten. Restore requires the PUBLISH capability on artifact type role; without it the button is disabled and shows the denial reason as its tooltip. Endpoints: GET /api/v1/permission-versions?entityType=...&entityKey=..., GET /api/v1/permission-versions/{id}, POST /api/v1/permission-versions/{id}/restore.

entityTypeentityKey format
rolerole id, for example 7
role_permissionroleId|resource|action|orgUnitId with * when tenant-wide, for example 7|page:leave-settings|view|*
permission_setpermission set id
user_permissionactor|resource|action|orgUnitId with * when tenant-wide
temporary_permissiontemporary permission id
field_permissionroleId|entity|fieldName|action
api_permissionroleId|routePattern|httpMethod
approval_permissionroleId|object

Record scope rules and named ACL templates are not versioned.

Authoring gate. Every create, update, delete, set and clear on the screens below is checked with AuthoringPolicy.require(tenant, actor, AUTHOR, "role"). Version restore, and publishing, activating, deactivating, archiving and deleting an access policy, require PUBLISH. Reads are open. The policy bean is replaceable; the bundled default allows every actor. A denial returns HTTP 403 with code capability-denied.

Request headers. X-Tenant-Id selects the workspace. X-Actor names the caller; when absent it is anonymous.

Error format. Authoring endpoints return {"code": ..., "message": ..., "detail": {...}}.

HTTP statuscodeMeaning
400invalid-definitionA validation rule failed. The messages are listed per screen.
403capability-deniedThe caller lacks AUTHOR or PUBLISH. detail names the capability.
404not-foundThe role, set, template or row id does not exist for this tenant.
409lifecycle-violationThe operation is not allowed in the current state (for example editing a read-only role).

CLI. The erp CLI has no dedicated permission commands. Use erp api get|post|put|delete <path> --tenant <id> --body <json-or-@file> against the endpoints listed per screen, after erp login. See Sign in and environments.

Roles ​

A role is a named group of people. Grants are made to roles, and people receive roles on the Users screen. Roles form a tree through a parent. Administrators of the workspace use this screen to maintain the catalog; implementers use it before any permission screen, because every grid below needs at least one role.

Where to find it. Security > Roles & Permissions > Roles. Page key roles.

Key concepts.

TermDefinition
NameDisplay label. Unique per workspace (compared after trimming). It can be renamed.
CodeStable slug of the role. Assigned when the role is created, unique per workspace, never changed by an edit. Role assignments and approval limits are matched by code, so renaming a role does not break them. Not shown on this screen.
Parent roleOptional. A person holding a role is evaluated against that role and all of its ancestors, so a deny on a parent applies to every child. A child can never gain access that the parent is denied.
PriorityInteger, default 0. Orders siblings in the tree (higher first, then by name). It also decides which role-scoped password policy override wins for a person with several roles. It does not change permission precedence.
ActiveStored flag, default on. The permission resolver does not read it in this release; a role marked inactive still contributes its grants.
Plugin-owned roleA role installed by a plugin (for example an HR administrator role). It is read-only; create a role that extends it instead.

Fields and options.

New role panel:

FieldTypeDefaultRequiredDescription
New role nameTextemptyYesHelper text e.g. "HR Manager". New role stays disabled until the trimmed name is not empty.
Parent roleSelect(none - top level)NoLists every existing role.

Role grid (Role, Priority, Status). Rows are shown as a tree: indentation equals depth, ordered by priority descending then name within each parent. The subtitle is the description. Status shows active or inactive and is filterable. Search box: "Search roles". Empty state: "No roles yet" with "Create one above."

Expanded row (select a row):

FieldType or allowed valuesDefaultRequiredDescription
NameTextcurrentYesSave is disabled while empty.
DescriptionTextcurrentNoShown as the grid subtitle.
Parent roleSelectcurrentNoExcludes the role itself. Choosing "(none - top level)" clears the parent.
PriorityNumbercurrentNoA non-numeric entry is saved as 0.
ActiveCheckboxcheckedNoSee Key concepts.

Fields available only through the API: effectiveAt and expiryAt (timestamps stored on the role; not evaluated by the resolver) and extendsRoleId (the id of a role this one extends; existence and tenant are checked, nothing else).

Actions.

ActionEffectAPI
New roleCreates a rolePOST /api/v1/roles
Save (expanded row)Replaces name, description, parent, priority and activePUT /api/v1/roles/{id}
History iconOpens the version history, entity type roleGET /api/v1/permission-versions
Delete iconOpens a dialog titled Delete followed by the role name in quotes and a question mark, with the text "This permanently removes the role - there is no undo. A role with children cannot be deleted." Cancel and Delete. A server error is shown inside the dialog.DELETE /api/v1/roles/{id}

Procedure. Create a manager role under an employee role.

  1. In New role name enter Leave Manager.
  2. In Parent role select Employee.
  3. Select New role. The role appears indented below Employee with status active.
  4. Expand the row, set Priority to 10 and select Save.

Statuses and lifecycle. A role has no lifecycle. active is a stored flag only.

Permissions. Mutations require AUTHOR on artifact type role. Reads are open.

API.

Method and pathPurpose
GET /api/v1/rolesAll roles of the tenant (not paginated).
GET /api/v1/roles/{id}One role.
POST /api/v1/rolesCreate. Returns 201.
PUT /api/v1/roles/{id}Update. Every field is replaced, so an omitted parentRoleId clears the parent.
DELETE /api/v1/roles/{id}Delete. Returns 204.
json
{ "name": "Leave Manager", "description": "Approves leave of own team", "parentRoleId": 3, "priority": 10, "active": true }
bash
erp api post /api/v1/roles --tenant 2 --body '{"name":"Leave Manager","parentRoleId":3,"priority":10}'

Every create, update and delete is versioned and recorded in the Permission Audit Log as entity type role.

Errors.

MessageCauseFix
Role name is requiredBlank nameEnter a name.
A role named "Leave Manager" already existsDuplicate nameChoose another name.
A role cannot be its own parentParent equals the roleSelect another parent.
Parent role 99 does not existUnknown parent idSelect an existing role.
Setting this parent would create a role hierarchy cycleThe chosen parent is a descendantSelect a role outside the subtree.
Cannot delete a role that has child roles - reassign or delete them firstRole has childrenRe-parent or delete the children.
Role "HR Administrator" is owned by plugin "hcm-foundation" and is read-only - create a role that extends it instead of editing it directlyEditing or deleting a plugin-owned role (HTTP 409)Create a new role with extendsRoleId.

Related. Permission Sets, Users, Add app-owned roles and permissions.

Permission Sets ​

A permission set is a named list of (resource, action, effect) items that is copied into the grants of a role in one step. Use it when several roles need the same list, for example "HR Operations".

Where to find it. Security > Roles & Permissions > Permission Sets. Page key permission-sets.

Key concepts.

TermDefinition
ItemOne (resource, action, effect). A set holds at most one item per (resource, action); adding the same pair again replaces its effect.
AttachCopies every item of the set into the role's own grants, tagged with the set id, at the chosen scope. It is a copy, not a live link: items added to the set afterwards do not reach roles already attached; attach again to refresh them. Attaching twice is idempotent.
DetachRemoves only the grants that this set contributed to the role at exactly the chosen scope. A grant later edited directly in a matrix screen (which clears its tag) and grants from the same set attached at another scope are kept.
ScopeOrg unit of the attached grants. "(Global - no scope)" attaches tenant-wide. A grant scoped to an org unit applies to people assigned to that unit or any unit below it.

Fields and options.

FieldType or allowed valuesDefaultRequiredDescription
New set nameTextemptyYesHelper text e.g. "HR Operations". Unique per workspace.
Resource (expanded row)Select: employee, org_unit, job_classification, positionemployeeYesThe Studio list is fixed. The API accepts any non-empty resource string, for example page:payroll-run.
ActionSelect: create, read, update, delete, view, approve, reject, cancel, submit, clone, archive, restore, print, export, import, upload, download, share, assign, executecreateYesAny non-empty action is accepted by the API.
EffectSelect: allow, denyallowYes
RoleSelect "(choose a role)"noneFor attach and detach
ScopeOrg unit select(Global - no scope)No

Items are shown as chips resource.action: effect (green allow, red deny); the chip's delete control removes the item. The description of a set can be set only through the API.

Actions.

ActionEffectAPI
New setCreates the setPOST /api/v1/permission-sets body {name, description}
Add itemAdds or replaces an itemPUT /api/v1/permission-sets/{id}/items body {resource, action, effect}
Chip deleteRemoves the itemDELETE /api/v1/permission-sets/{id}/items body {resource, action}
Attach to roleCopies the items into the rolePOST /api/v1/permission-sets/{id}/attach/{roleId}?orgUnitId=...
Detach from roleRemoves this set's grants from the rolePOST /api/v1/permission-sets/{id}/detach/{roleId}?orgUnitId=...
History iconVersion history, entity type permission_set
Delete iconDeletes the set and its items (no confirmation)DELETE /api/v1/permission-sets/{id}
RenameAPI onlyPUT /api/v1/permission-sets/{id} body {name, description}

Procedure. Give the role "Payroll Viewer" read access to payslip data in one step.

  1. Create the set Payroll Read.
  2. Expand it. Add employee / read / allow, then position / read / allow.
  3. Select the role Payroll Viewer in Role, leave Scope on "(Global - no scope)" and select Attach to role.
  4. Open Roles > the matrix screens or the Effective Permission Viewer for a member of the role and confirm employee.read resolves to allowed by layer "Role".

Permissions. AUTHOR on role for every mutation.

Limits and behaviour. Detach the set from every role before deleting it: a role grant that still references the set blocks the delete at the database and the call fails with a server error. Set create, update, delete and item changes are versioned and appear in the Permission Audit Log as entity type permission_set. Attach and detach are neither versioned nor audited as separate events; the resulting grants are ordinary role grants.

Errors.

MessageCauseFix
Permission set name is requiredBlank nameEnter a name.
A permission set named "Payroll Read" already existsDuplicate nameRename.
resource is required, action is requiredBlank item field (API)Supply both.
effect must be one of [allow, deny]Other effect (API)Use allow or deny.
No permission set 12 for tenant 2Unknown id (404)Reload the list.
No role 9 for tenant 2Attach to an unknown role (404)Select an existing role.

Related. Roles, Temporary Permissions.

Field Permissions ​

Field permissions control, per role, whether one field of an entity may be changed or must be filled when a record is saved. Implementers use it for fields such as salary, medical notes or approval comments.

Where to find it. Security > Roles & Permissions > Field Permissions. Page key field-permissions.

Key concepts.

TermDefinition
EntityThe business entity whose fields are listed. The Studio selector offers employee, org_unit, job_classification and position; the API accepts any entity name.
FieldA field name declared by at least one published form of the entity. The matrix columns are exactly these names.
Actionview, edit, require or mask (labelled Visible, Editable, Required, Masked).

Effect of each cell.

ChipEffect setResult on save for a person whose roles (including parents) carry it
Visible •denyThe field is treated as hidden: a save that changes its value is rejected.
Visible •allowNo effect.
Editable •denyThe field is read-only: a save that changes its value is rejected.
Editable •allowNo effect.
Required •allowThe field is required: a save with an empty value is rejected.
Required •denyNo effect.
MaskedeitherStored only. No component reads it in this release.

If a person has several roles, the most restrictive result applies: any role that hides, locks or requires the field is enough. Field rules read roles only; user overrides, temporary permissions and org scope do not apply to them. A person with no roles is unrestricted. On create, any non-empty value counts as a change; on update the new value is compared with the stored one, so re-submitting the existing value is accepted. The same checks run for the older inline roleRules of a form field.

Fields and options.

FieldTypeDefaultRequiredDescription
EntitySelectemployeeYesChanging it reloads roles, fields and rules.
MatrixSee Behaviour shared by all screensThe history icon uses entity type field_permission. There is no bulk bar.

Empty states: "No roles yet - create one in Roles first." and "No published form declares any field for this entity yet."

Actions.

ActionAPI
Load rulesGET /api/v1/field-permissions?entity=employee
Load fieldsGET /api/v1/field-permissions/fields?entity=employee
Set a cellPUT /api/v1/field-permissions body {roleId, entity, fieldName, action, effect}
Clear a cellDELETE /api/v1/field-permissions body {roleId, entity, fieldName, action}

Procedure. Make salary read-only for HR Assistant.

  1. Select entity employee.
  2. By role: select HR Assistant, search salary.
  3. Select the Editable chip twice: not set, allow, deny. The chip turns red.
  4. An HR Assistant who submits an employee record with a changed salary receives the field error salary is read-only for your role.

Permissions. AUTHOR on role. The rules are applied by the record-save validator on the server. Document management applies the same rules to document metadata with entity name dms.document.<documentTypeCode>.

Limits and behaviour. Rules are not cached. Changes are versioned but not written to the Permission Audit Log. A field rule does not remove the field from read responses; hiding on screen is a function of the form or page.

Errors.

MessageCauseFix
salary is read-only for your role. (field error roleReadOnly)A role hides or locks the field and the value changedRemove the deny or save without changing the value.
salary is required for your role. (field error roleRequired)A role requires the field and it is emptyFill the field.
entity is required, fieldName is requiredBlank value (API)Supply both.
action must be one of [view, edit, require, mask]Other action (API)Use a listed action.
effect must be one of [allow, deny]Other effect (API)Use allow or deny.

Related. Record Permissions, Roles.

Record Permissions ​

Record permissions (the screen is titled "Data Scope Rules") restrict which rows of an entity a role may see. A rule is a boolean expression evaluated against each row and against the signed-in person's organisational context.

Where to find it. Security > Roles & Permissions > Record Permissions. Page key data-scope-rules.

Key concepts.

TermDefinition
RuleOne expression for one (role, entity). Several rules of a role are alternatives: a row is visible if any rule is true.
Row contextThe row's own fields under the capitalised entity name, for example Employee.Department or Document.region.
Person contextCurrentUser.<UnitType> is the id of the nearest org unit of that type at or above the person's own org unit assignment. Unit types: Company, BusinessUnit, Department, Location, CostCenter. A person with no assignment has an empty context, so comparisons do not match.
UnrestrictedA role with no rule for the entity sees every row. If a person holds several roles and any one has no rule, that person sees every row. A person with no roles is unrestricted.

Fields and options.

FieldTypeDefaultRequiredDescription
EntitySelect: employeeemployeeYesThe Studio selector lists one preset. The expression for another entity is authored through the API.
RoleSelectemptyYes
ExpressionTextemptyYesPlaceholder Employee.Department == CurrentUser.Department. Helper text "Compares the row's own fields against the actor's resolved org context".
DescriptionTextemptyNoPlaceholder "Own department only".

Add rule is disabled until a role is chosen and the expression is not empty. The grid shows Role, Expression, Description; the delete icon removes a rule. Empty state: "No data scope rules configured for "employee" yet - every role sees every row."

Actions.

ActionAPI
ListGET /api/v1/record-scope-rules?entity=employee
Add rulePOST /api/v1/record-scope-rules body {roleId, entity, expression, description}, returns 201
Delete ruleDELETE /api/v1/record-scope-rules/{id}

Procedure. Limit Department Manager to employees of their own department.

  1. Entity employee, role Department Manager.
  2. Expression Employee.Department == CurrentUser.Department, description Own department only.
  3. Select Add rule. The rule appears in the grid.
  4. Sign in as a department manager: the employee directory returns only employees whose department id equals the manager's department id.

Where it is enforced. Entity employee in the HCM employee list, query and organisation chart endpoints; entities document and file in document management (the metadata fields of the document form the row context). Other entities accept rules but nothing consults them yet.

Limits and behaviour. The expression is checked for syntax only when saved. A rule that parses but fails when evaluated (for example a misspelt field) excludes the row and logs a warning, so the safe failure is less visibility. Filtering happens in the application after the rows are fetched and pagination follows the filtered list. Rule changes are neither versioned nor audited.

Errors.

MessageCauseFix
Invalid expression: ...The expression does not parseCorrect the syntax.
entity is required, expression is requiredBlank value (API)Supply both.
No role 9 for tenant 2Unknown role (404)Select an existing role.

Related. Field Permissions, Access Policies.

Screen Permissions ​

Screen permissions decide which roles may open a page. Authors of applications use them to close administrative pages to ordinary users.

Where to find it. Security > Roles & Permissions > Screen Permissions. Page key screen-permissions.

ItemValue
Resource stringpage:<page name> where the name is the stable page name, not a version id. A new publish of the page keeps its grants.
Actionview (chip "View •", enforced)
RowsEvery distinct page name in the workspace, sorted alphabetically.

Matrix and bulk behaviour are described under Behaviour shared by all screens. History key roleId|page:<name>|view|*. Empty states: "No roles yet - create one in Roles first." and "No Page artifacts yet - create one in Pages first."

Enforcement. When a page definition is requested, the server checks page:<name> / view through the permission resolver. A denied person receives HTTP 403 with code screen-access-denied and the message Actor "alice" lacks Screen access to page "leave-settings" for tenant 2; the tenant runtime host treats the route as not found, so the page is not revealed to the caller. This protects the page definition itself, not the data APIs the page calls.

Procedure. Close the page leave-settings to everyone except HR Admin.

  1. By resource: select leave-settings.
  2. Set View to deny for each broad role (for example Employee, Manager).
  3. Leave HR Admin not set. Because an unset cell allows, no grant is needed.
  4. Check with the Effective Permission Viewer: actor, resource page:leave-settings, action view.

Permissions and API. AUTHOR on role. GET /api/v1/role-permissions?resources=page:leave-settings lists the cells; PUT and DELETE /api/v1/role-permissions body {roleId, resource, action, effect, orgUnitId} set and clear. The Studio matrices always write tenant-wide cells (orgUnitId null); org-scoped cells are written through the API or by attaching a permission set with a scope.

bash
erp api put /api/v1/role-permissions --tenant 2 --body '{"roleId":7,"resource":"page:leave-settings","action":"view","effect":"deny"}'

Errors. screen-access-denied (403) as above; resource, action and effect validation messages are the same as for Roles grants: "resource is required", "action is required", "effect must be one of [allow, deny]", "No role 7 for tenant 2".

Widget Permissions ​

Widget permissions control how a block type is rendered for a role. The resource list is the catalog of core block types (for example core.text-input), sorted alphabetically.

Where to find it. Security > Roles & Permissions > Widget Permissions. Page key widget-permissions.

ItemValue
Resource stringwidget:<block type>, for example widget:core.text-input
Actionsvisible, enabled, masked, all enforced (bullet)
VisibleDeny hides every block of that type. Unset or allow shows it.
EnabledDeny renders the block disabled.
MaskedAn explicit allow renders the block masked; deny or not set leaves it unmasked. Because "not set" resolves to allowed everywhere else, the runtime treats a result that was not decided by an explicit rule as "not masked".

The runtime host asks POST /api/v1/permission-checks once per mount for every block type and action, so a change takes effect at the next mount of the page host, not during a session. If the request fails the page renders without widget restrictions. The checkpoint hides or disables controls; it does not protect the data behind them (see Record Permissions, Field Permissions and API Permissions).

History key roleId|widget:<type>|<action>|*. Authoring gate and endpoints as in Screen Permissions.

Procedure. Mask all number inputs for Intern.

  1. By role: select Intern, search number.
  2. Set Masked on core.number-input to allow.
  3. Reload a page that contains a number input as an intern: the value is replaced by a masked placeholder.

Menu permissions hide navigation entries for a role.

Where to find it. Security > Roles & Permissions > Menu Permissions. Page key menu-permissions.

ItemValue
Menu selectorSelect Menu: every menu that has a published version. Empty state "No published Menu artifacts yet - publish one in Menus first." and, for a menu without nodes, "This menu has no nodes yet."
Resource stringmenu:<menu name>:<node id> for every node of the latest published version; child nodes are indented with dashes in the list
Actionview (enforced)

The navigation block fetches the grants with POST /api/v1/permission-checks and removes a node whose result is denied. It is combined with, not replaced by, the role targeting configured on the node in the Menu Designer: either one can hide the node. Hiding a node does not close the page; use Screen Permissions for that. A node id that has no grant is unset, which allows it, so a node added in a later menu version is visible to every role until you set it.

History key roleId|menu:<name>:<node id>|view|*.

Report Permissions ​

Where to find it. Security > Roles & Permissions > Report Permissions. Page key report-permissions.

ItemValue
Resource stringreport:<report name>
Actionsview, export, email, print
EnforcementNone. The page states "Captured as real data - no report execution endpoint exists yet to enforce any action here." No chip carries a bullet.

The grid lets an administrator record the intended matrix now; the rules take effect only if a future report execution checkpoint reads them. Do not rely on this screen for protection. Printing a print template is protected separately by Print Permissions. Empty state "No Report artifacts yet - create one in Reports first."

Print permissions decide who may produce a document from a print template.

Where to find it. Security > Roles & Permissions > Print Permissions. Page key print-permissions.

ItemValue
Resource stringprint_template:<template name>
export (enforced)PDF, Word, Excel and thermal file downloads.
print (enforced)Sending the document to a networked printer only. It is granted separately, so a clerk can print invoices without downloading raw exports.
RowsEvery print template name. Empty state "No Print Template artifacts yet - create one in Print Templates first."

The five export and print endpoints check the grant before producing output. A denied caller receives HTTP 403, code print-access-denied, message Actor "alice" lacks export access to print template "invoice" for tenant 2. The previous behaviour (requiring drafting rights to print) no longer applies.

History key roleId|print_template:<name>|<action>|*.

Procedure. Allow Cashier to print but not download invoices.

  1. By resource: select invoice.
  2. For Cashier set Print to allow and Export to deny.
  3. As a cashier, a PDF download returns print-access-denied; sending to the receipt printer succeeds.

Workflow Permissions ​

Workflow permissions control who may start, act in and administer a workflow.

Where to find it. Security > Roles & Permissions > Workflow Permissions. Page key workflow-permissions.

ItemValue
Resource stringworkflow:<workflow name>. Rows are the names of published workflows ("No published workflows yet." when none).
Actions in the gridsubmit, approve, reject, return, recall, reopen, cancel, escalate, delegate
Enforced (bullet)submit, approve, reject, return, delegate
Stored onlyrecall, reopen, cancel, escalate (no engine transition checks them; escalate is a timer action with no person to check)

Additional actions checked on the same resource but not listed in the grid: manage (webhook administration of the workflow) and migrate (moving running instances to a new version). Grant them with the API or Permission Sets.

CheckpointAction
Start an instance (POST /api/v1/workflows/instances, POST /api/v1/workflows/{name}/start) and insert an ad hoc stepsubmit
Record a decision on a human taskapprove, reject or return according to the outcome
Delegate or reassign a taskdelegate
Workflow webhooksmanage
Version migration of a runmigrate

Being a candidate approver of a task is necessary but not sufficient: the role grant must also not deny the action. A refusal returns HTTP 403, code workflow-action-not-permitted, message Actor "bob" is not permitted to "approve" workflow "leave-request" (tenant 2). History key roleId|workflow:<name>|<action>|*. See How workflows work.

Procedure. Only Leave Manager may approve leave-request.

  1. By resource: select leave-request.
  2. For Employee set Approve and Reject to deny.
  3. A person who holds both roles is still denied, because any deny wins; remove the Employee role from managers or grant through a user override.

Approval Permissions ​

Approval permissions answer "who may approve how much". They are a separate mechanism from the matrix screens: a tier is a numeric ceiling per role and approval object.

Where to find it. Security > Roles & Permissions > Approval Permissions. Page key approval-permissions.

Fields and options.

FieldTypeDefaultRequiredDescription
RoleSelectemptyYes
Approval ObjectTextemptyYesPlaceholder expense-approval. Free text; it must equal the approvalObject value of the workflow task.
Max AmountNumberblank (placeholder "unlimited")NoBlank means no ceiling. Must not be negative.

Add tier is disabled until a role and an object are entered. Adding for an existing (role, object) replaces its ceiling. Grid: Role, Approval object, Max amount ("Unlimited" or the number; sorted with unlimited last). Icons: history (entity type approval_permission) and remove. Empty state "No approval tiers configured yet."

Resolution. A workflow human task whose payload carries approvalObject (and amount, default 0) takes its candidate approvers from this table instead of a static candidateApprovers list.

  1. Keep every role of that object whose ceiling is at least the amount, or unlimited.
  2. If any finite ceiling remains, keep the roles with the lowest finite ceiling. Roles sharing that lowest ceiling are all kept.
  3. Otherwise keep the unlimited roles.
  4. The candidates are the people directly assigned the kept roles (matched by role code). People who hold only a child role are not candidates.
  5. If nothing qualifies the task has no candidates.
RoleCeiling for expense-approval
Team Lead500
Manager5000
Directorunlimited

A request of 400 goes to Team Lead, 1200 to Manager, 9000 to Director.

API. GET /api/v1/approval-permissions?objects=expense-approval, PUT /api/v1/approval-permissions body {roleId, object, maxAmount}, DELETE /api/v1/approval-permissions body {roleId, object}. These endpoints are served by the workflow service and are gated by AUTHOR on role.

bash
erp api put /api/v1/approval-permissions --tenant 2 --body '{"roleId":5,"object":"expense-approval","maxAmount":5000}'

Errors. "object must not be blank"; "maxAmount must not be negative".

API Permissions ​

API permissions restrict, rate-limit and IP-filter calls to /api/v1/** per role.

Where to find it. Security > Roles & Permissions > API Permissions. Page key api-permissions.

Fields and options.

FieldType or allowed valuesDefaultRequiredDescription
RoleSelectemptyYes
Route PatternTextemptyYesAnt-style pattern, placeholder /api/v1/hcm/**. * matches within one path segment, ** across segments, ? one character.
MethodSelect: *, GET, POST, PUT, PATCH, DELETEGETYes* matches every method.
EffectSelect: allow, denyallowYes
Rate Limit /minPositive integerblank (unlimited)NoCalls per minute for this actor under this rule.
IP AllowlistTextblank (unrestricted)NoComma-separated addresses.

Grid: Role, Route pattern, Method, Effect, Rate limit, IP allowlist with history and remove icons. A rule is identified by (role, route pattern, method); saving the same triple again replaces it. The API also stores oauthScope; the Studio form does not expose it and the resolver does not evaluate it.

Resolution. For every /api/v1/** request that carries X-Tenant-Id:

  1. The caller's roles, including ancestors, select the candidate rules. A caller with no roles is unrestricted.
  2. Rules whose pattern matches the request path and whose method is * or equals the request method are kept. No match means unrestricted.
  3. Any matching deny rejects the request, regardless of other allows.
  4. Otherwise the lowest rate limit among the matching allow rules applies, and the union of all IP allowlists applies: the caller's remote address must equal one entry exactly (no ranges). The address checked is the connection's remote address, not a forwarded header.
  5. Preflight OPTIONS requests are never checked.

The rate limiter is a fixed one-minute window kept in memory per server instance, keyed by tenant, actor and rule. With several instances behind a load balancer each instance counts separately.

HTTP statuscodeMessage
403api-access-deniedActor "alice" is not permitted to DELETE /api/v1/employees/12
403api-ip-not-allowedRemote address "10.0.0.5" is not on the allowlist for /api/v1/employees
429api-rate-limit-exceededActor "alice" exceeded the 60/minute rate limit for /api/v1/employees

A separate tenant-wide ceiling is also applied to all /api/v1/** calls and answers 429 with code api-rate-limit-exceeded; it is not configured on this screen.

API. GET /api/v1/api-permissions; PUT /api/v1/api-permissions body {roleId, routePattern, httpMethod, effect, rateLimitPerMinute, ipAllowlist, oauthScope}; DELETE /api/v1/api-permissions body {roleId, routePattern, httpMethod}.

bash
erp api put /api/v1/api-permissions --tenant 2 --body '{"roleId":4,"routePattern":"/api/v1/employees/**","httpMethod":"*","effect":"allow","rateLimitPerMinute":120,"ipAllowlist":"203.0.113.10,203.0.113.11"}'

Errors (HTTP 400). "routePattern must not be blank"; "httpMethod must not be blank"; "httpMethod must be one of [...]"; "effect must be one of [allow, deny]"; "rateLimitPerMinute must be positive".

Version history uses entity type api_permission. Changes are not written to the Permission Audit Log.

Named ACL Templates ​

A named ACL template is a reusable list of (who, action, effect) entries that can be applied to any resource string in one step. Its first consumer is document management, where one template protects many cabinets, folders and documents.

Where to find it. Security > Roles & Permissions > Named ACL Templates. Page key named-acls.

Fields and options.

FieldType or allowed valuesDefaultRequiredDescription
New template nameTextemptyYesUnique per workspace.
Description (optional)TextemptyNo
AccessorSelect: Role, UserRoleYes
Role name / ActorTextemptyYesThe label follows the accessor. A role is entered by its name and must exist; a user is a free-text actor name.
ActionSelect: view, read, write, download, edit, delete, relate, version, execute, change_owner, change_permissionviewYesThe API accepts any non-empty action.
EffectSelect: Allow, DenyAllowYes

The left panel lists templates; the delete icon asks Delete template "Standard cabinet"? This does not revoke any permissions it already applied. The entry grid shows Accessor ("Role: Facilities Staff" or "User: alice"), Action and Effect. Placeholders: "No templates yet.", "Select or create a template to author its entries.", "No entries yet - add one above."

Applying a template. Templates are applied from the object, not from this screen. In Files & Documents, the Permissions action of a cabinet or document opens a dialog titled "Permissions" followed by the object name, showing the resource string (dms.cabinet:12, dms.document:47), a list Apply a named ACL template with a Template select and Apply, and the role and user grants of that resource. Applying shows Applied "Standard cabinet" - 3 entries materialized. Each role entry becomes a role grant and each user entry a user override on that exact resource, tenant-wide. Applying twice rewrites the same rows. Cabinet actions offered by the dialog: write (Create inside), edit, delete. Document actions: edit, delete, relate, execute, change_owner. The resolver falls back from dms.document:47 to dms.document (see Per-record access).

API.

Method and pathPurpose
GET /api/v1/named-acls, GET /api/v1/named-acls/{id}Read
POST /api/v1/named-acls body {name, description}Create, returns 201
DELETE /api/v1/named-acls/{id}Delete the template and its entries (grants it already created stay)
GET /api/v1/named-acls/{id}/entriesList entries
POST /api/v1/named-acls/{id}/entries body {accessorType, accessorKey, action, effect}Add an entry, returns 201
DELETE /api/v1/named-acls/{id}/entries/{entryId}Remove an entry
POST /api/v1/named-acls/{id}/apply body {resource}Apply; returns {"entriesApplied": 3}
bash
erp api post /api/v1/named-acls/4/apply --tenant 2 --body '{"resource":"dms.cabinet:12"}'

Errors (HTTP 400 unless noted). "Named ACL name is required"; A named ACL called "Standard cabinet" already exists; "accessorType must be one of [role, user]"; "accessorKey (role name or actor) is required"; "action is required"; "effect must be one of [allow, deny]"; No role named "Facilities Staff" (when adding an entry or applying after the role was renamed or deleted); "resource is required"; "No named ACL 9 for tenant 2" (404). Templates are neither versioned nor audited; deleting one does not revoke grants it created.

Temporary Permissions ​

A temporary permission is an allow or deny for one person, valid only between two instants. Use it for cover during leave or a one-off task.

Where to find it. Security > Roles & Permissions > Temporary Permissions. Page key temporary-permissions.

Fields and options.

FieldType or allowed valuesDefaultRequiredDescription
ActorTextemptyYesThe person's username. Helper text "e.g. alice".
ResourceSelect: employee, org_unit, job_classification, positionemployeeYesStudio list; the API accepts any resource string.
ActionSelect, same 20 values as Permission SetscreateYes
EffectSelect: allow, denyallowYes
ScopeOrg unit(Global - no scope)No
Valid fromDate and timenowYesLocal time; sent as an ISO instant.
Valid untilDate and timenow plus 24 hoursYesMust be after Valid from.

Grant is disabled while Actor is empty. Grid: Actor (with resource.action), Resource, Action, Effect, Scope ("Global" or "Org unit #12"), Window, Status. History and remove icons per row.

Statuses.

StatusCondition (computed in the browser from the current time)
upcomingnow is before Valid from
activeValid from and Valid until inclusive
expirednow is after Valid until

An expired row stays in the list until removed; it simply stops matching. There is no scheduler. A temporary permission is the first layer of the decision: when an active row matches the person, resource, action and org scope, its decision is final, and a deny wins over an allow among temporary rows.

API. GET /api/v1/temporary-permissions; POST /api/v1/temporary-permissions body {actorSubject, resource, action, effect, orgUnitId, validFrom, validUntil} (returns 201); DELETE /api/v1/temporary-permissions/{id}.

bash
erp api post /api/v1/temporary-permissions --tenant 2 --body '{"actorSubject":"alice","resource":"page:payroll-run","action":"view","effect":"allow","validFrom":"2026-10-06T00:00:00Z","validUntil":"2026-10-13T00:00:00Z"}'

Limits and behaviour. Decisions are cached for 60 seconds when the shared cache is enabled; creating or removing a temporary permission does not invalidate that cache, so the start or end can take up to a minute to show. Changes are versioned (entity type temporary_permission) but not written to the Permission Audit Log.

Errors (HTTP 400). "actor is required"; "resource is required"; "action is required"; "effect must be one of [allow, deny]"; "validFrom and validUntil are required"; "validUntil must be after validFrom".

Access Policies ​

Access policies are attribute-based rules: they name subjects (roles, users, departments and others), protected resources, record scope, conditions, actions, field restrictions and a schedule, and they are versioned and conflict-checked. Use them where a role grant is too coarse, for example "HR managers may view compensation of their own department, with the bank account masked, on weekdays".

Where to find it. Security > Roles & Permissions > Access Policies. Page key access-policies.

Key concepts.

TermDefinition
Policy codeIdentifier, letters, digits and _ . -, maximum 100. Stable across versions; fixed once the policy exists.
VersionEach published change is a new row with the same code and the version number plus one. Published rows are immutable.
Resource codeFree-text code such as HCM.EMPLOYEE.COMPENSATION, or ENTITY.<ENTITY_NAME> to protect every screen and API built on an entity (payroll_run becomes ENTITY.PAYROLL_RUN; hyphens become underscores).
Covered resourceA resource that at least one ACTIVE policy names.
Default denyOnce a resource is covered, every person not allowed by an ACTIVE policy is denied for it. A resource no policy names is left to the module's existing checks.

Statuses and lifecycle.

StatusMeaningAllowed next steps
DRAFTEditable, not evaluated.Edit, Save draft, Publish (to ACTIVE), Delete
ACTIVEEvaluated. Immutable.Deactivate (to INACTIVE), Create version (new DRAFT), Archive
INACTIVENot evaluated. Immutable.Activate (to ACTIVE), Create version, Archive
ARCHIVEDRetired.None.

Publishing validates the policy and checks for conflicts, then sets the status to ACTIVE directly. Publishing a new version does not change the status of earlier versions; deactivate or archive the older version yourself. An ACTIVE policy is also a candidate for default deny immediately; recovery from a lock-out is to deactivate the policy, which takes effect within about 10 seconds (the active policy list is cached for 10 seconds).

Fields and options. The editor dialog has eight tabs.

TabFieldAllowed valuesDefaultRequiredDescription
Basic infoPolicy code[A-Za-z0-9_.-], 1 to 100emptyYesLocked after creation.
Policy nameTextemptyYes
DescriptionTextemptyNo
Policy typeAPPLICATION_ACCESS, MODULE_ACCESS, MENU_ACCESS, RECORD_ACCESS, FIELD_ACCESS, ACTION_ACCESS, ORGANIZATION_ACCESS, EMPLOYEE_DATA_ACCESS, TRANSACTION_ACCESS, REPORT_ACCESS, EXPORT_ACCESSRECORD_ACCESSYesA label and filter; it does not change evaluation.
EffectALLOW, DENYALLOWYes
PriorityInteger50YesHigher wins between policies of the same effect. A DENY beats an ALLOW whatever the priority.
SubjectsTypeROLE, USER, GROUP, POSITION, DESIGNATION, JOB_GRADE, DEPARTMENT, BUSINESS_UNIT, LOCATION, EMPLOYEE_CATEGORY, EMPLOYMENT_TYPE, MANAGER, DYNAMIC_SUBJECTROLENo subjects means everyone ("Everyone" chip). Every type except MANAGER needs a code.
Dynamic subjectCURRENT_USER, CURRENT_USER_DEPARTMENT, CURRENT_USER_BUSINESS_UNIT, CURRENT_USER_LOCATION, CURRENT_USER_DIRECT_REPORTS, CURRENT_USER_ORGANIZATION, CURRENT_USER_COST_CENTERCompares employee.* with currentUser.*.
ResourcesResource codeFree text; suggestions HCM.EMPLOYEE.PROFILE, HCM.EMPLOYEE.COMPENSATION, HCM.PAYROLL.PAYSLIP, HCM.PAYROLL.SUMMARY, HCM.LEAVE.REQUESTS, HCM.ATTENDANCE.REGISTER, HCM.RECRUITMENT, HCM.REPORTS, ENTITY.PAYROLL_RUN, ENTITY.EMPLOYEENo spaces. None means "All resources".
Access scopeWhich recordsGLOBAL, ORGANIZATION, LEGAL_ENTITY, BUSINESS_UNIT, DIVISION, DEPARTMENT, TEAM, LOCATION, BRANCH, COST_CENTER, REPORTING_TREE, SELF, DIRECT_REPORTS, CUSTOM_FILTERGLOBALScopes other than GLOBAL compare the record with the requester, so they apply where one record is read; lists and entity-level checks only match GLOBAL. A warning shows for a GLOBAL ALLOW.
ConditionsMatchAND, OR, NOTANDNo rules means the policy always applies.
Field, Operator, ValueOperators =, !=, >, <, >=, <=, IN, NOT IN, CONTAINS, STARTS WITH, IS NULL, IS NOT NULL, BETWEEN=Placeholder field employee.departmentId. IS NULL and IS NOT NULL take no value; every other operator needs one.
ActionsActionsVIEW, CREATE, EDIT, DELETE, APPROVE, REJECT, SUBMIT, CANCEL, EXPORT, IMPORT, DOWNLOAD, PRINT, SHARE, ASSIGN, REASSIGN, DELEGATEVIEWEmpty means every action.
RestrictionsRestrictionMASK, HIDE, READ_ONLY, NO_EXPORT, NO_DOWNLOAD, NO_PRINTMASKMASK, HIDE and READ_ONLY need a field. NO_EXPORT, NO_DOWNLOAD and NO_PRINT turn an otherwise allowed EXPORT, DOWNLOAD or PRINT into a deny.
Masking modeFULL, PARTIAL, LAST_4, FIRST_4, EMAIL_MASK, PHONE_MASK, CUSTOMFULLMASK only.
ScheduleEffective from, Effective toYYYY-MM-DDemptyTo must not be before From.
Days of weekMON to SUNempty (every day)
Start time, End timeHH:MMemptyBoth or neither. Overnight windows are supported.
TimezoneIANA zone, for example Asia/KolkataUTC
Allowed network zonesCORPORATE, VPN, TRUSTED_IPempty (any)The platform has no trusted network-zone service, so a policy restricted to a zone denies everyone at run time.

Actions. Row buttons depend on status: Edit (DRAFT) or View, Publish, Activate, Deactivate, Create version, Test, Clone, History, Export, Archive, Delete. Header buttons: Import, New policy, reload.

ActionEffectAPI (base /api/v1/authoring/access-policies)
New policy, Save draftCreates a DRAFT (AUTHOR)POST
Save draft (existing)Optimistic-locked update, body includes expectedRevisionPUT /{id}/draft
Save & publishSave then publish (PUBLISH)POST /{id}/publish
Activate, Deactivate, ArchiveStatus changes (PUBLISH)POST /{id}/activate, /deactivate, /archive
Create versionNew DRAFT with version plus one (AUTHOR)POST /{id}/new-version
CloneDialog titled Clone followed by the policy code, field New policy code, helper "Creates a new draft policy with the same content." The clone keeps the source name with " (Clone)" appended and starts at version 1.POST /{id}/clone body {newCode}
DeleteDialog titled "Delete draft" followed by the code; text "This permanently deletes the draft. Published policies cannot be deleted - archive them instead." (PUBLISH)DELETE /{id}
HistoryDialog titled "Version history" followed by the code: Version, Status, Priority, Updated, ByGET /code/{code}/history
ExportDownloads access-policy-<code>.jsonGET /{id}/export
ImportDialog "Import a policy": paste JSON, Validate (shows a summary, errors and warnings), Import as draftPOST /validate-text, POST /import-text body {json}
TestDialog "Test - evaluates the tenant's ACTIVE policies (nothing is changed)": Resource (default HCM.EMPLOYEE.PROFILE), Action (default VIEW), Context (JSON) (default {"roleCode": "HR_MANAGER"}). Result shows the decision, matched policy, reason, field restrictions and the evaluation trace.POST /{id}/evaluate body {resourceCode, action, context}

Context keys understood by the test and by callers: roleCode, userId, groupCode, positionCode, designationCode, jobGradeCode, departmentId, businessUnitId, locationId, employeeCategory, employmentType, isManager, networkZone, evaluationTime, employee.* (the record) and currentUser.* (the requester). In production calls the platform derives roles, user id and org units itself; a request body cannot claim a role, the time or a network zone.

Evaluation. For a resource and action, the engine reads the ACTIVE policies and skips a policy unless all of the following match: resource, action, effective dates, days, time window and network zone, record scope, subject, then conditions. Among matches, an explicit DENY wins; otherwise the highest-priority ALLOW applies, and if that policy carries NO_EXPORT, NO_DOWNLOAD or NO_PRINT for the requested action the result is DENY; if nothing matches the result is DENY. An ALLOW returns its MASK, HIDE and READ_ONLY restrictions, which the calling module applies to the record.

Validation and warnings. Publishing and import run the validator. Errors block; warnings do not.

MessageKind
Policy code is required (letters, digits, _ . - only, max 100).Error
Policy name is required.Error
Unknown policy type 'X'. / Effect must be ALLOW or DENY.Error
Invalid subject type 'X'. / Subject of type ROLE needs a code.Error
Invalid resource code 'X' (expected e.g. HCM.EMPLOYEE.COMPENSATION).Error
Unknown access scope 'X'. / Unknown action 'X'.Error
Unknown condition group operator 'X'. / A condition is missing its field. / Unknown condition operator 'X'. / Condition on 'f' needs a value.Error
Unknown restriction type 'X'. / Restriction MASK needs a field name. / Unknown masking mode 'X'.Error
Unknown day of week 'X'. / Schedule startTime 'X' is not HH:MM. / Schedule needs both a start and an end time, or neither. / Unknown timezone 'X'. / Schedule effectiveFrom 'X' is not a valid date (use YYYY-MM-DD). / Effective To is before Effective From.Error
Global ALLOW: grants access to every record in scope.Warning
Global DELETE access. / Global EXPORT access.Warning
Global access to sensitive resource X. (resource code contains COMPENSATION, BANK, SALARY, DOCUMENTS, GOVERNMENT_ID or MEDICAL)Warning

The service layer adds: "Cannot publish an invalid policy: ..." (400); "Conflicting policies detected: 1 existing active policy with opposing effect at equal or higher priority" (409, code policy-conflict, detail.conflicts lists code, effect and priority); a published policy cannot be edited ("... published policies are immutable; create a new version instead", 409); "Cannot activate access policy definition 5 in status ACTIVE (requires INACTIVE)" and similar for other transitions (409); stale draft (409 stale-draft, reload and retry); "A new code is required to clone ..."; "An access policy definition with code 'X' already exists for tenant 2"; "only DRAFT policies can be deleted; archive it instead". A conflict exists when another ACTIVE policy has the opposite effect, equal or higher priority, an overlapping resource (a policy with no resources overlaps all) and an overlapping subject (a policy with no subjects overlaps all). The Studio shows the conflict as "Conflicting policies detected: ...: HR_ALLOW (ALLOW, priority 60). Change a priority, subject or resource, or deactivate the other policy."

Where policies are enforced. Through POST /api/v1/access-policies/authorize (body {resourceCode, action, context, record}; the tenant comes from the gateway header) and the generic data-access seam.

ResourceWhereNotes
HCM.EMPLOYEE.PROFILEHCM employee directory query and single record, action VIEWHIDE and MASK rewrite the response.
HCM.EMPLOYEE.COMPENSATIONHCM compensation reads, payroll compensation comparison
HCM.PAYROLL.PAYSLIPLatest payslip, its explanation, payslip download (DOWNLOAD)A PDF cannot be masked: allow or deny only.
HCM.PAYROLL.SUMMARYPayroll organisation summary, country roll-upAggregates have no owner, so only GLOBAL scope matches.
ENTITY.<NAME>Every generic entity endpoint (record CRUD, grid query, export, lookup)Actions READ to VIEW, CREATE, UPDATE to EDIT, DELETE. Only GLOBAL scope without record conditions can match; no field masking; export is treated as a read. The internal system actor is exempt.

Existing role grants and row scope checks run first; a policy can only deny or restrict further. A failure of the policy service itself lets the request proceed under the existing checks and logs a warning; a policy DENY never fails open. Other modules do not consult policies.

Procedure. Mask the bank account of employees for HR assistants and keep compensation to HR managers.

  1. New policy: code HR_COMP_VIEW, name HR managers view compensation, effect ALLOW, priority 60.
  2. Subjects: ROLE / HR_MANAGER. Resources: HCM.EMPLOYEE.COMPENSATION. Access scope GLOBAL. Actions VIEW.
  3. Restrictions: MASK, field bankAccount, mode LAST_4.
  4. Test with resource HCM.EMPLOYEE.COMPENSATION, action VIEW, context {"roleCode":"HR_MANAGER"}: the result is ALLOW with "MASK bankAccount (LAST_4)". With {"roleCode":"INTERN"} the result is DENY (the resource is covered, the intern is not allowed).
  5. Save & publish. The status becomes ACTIVE and the notice reads Published HR_COMP_VIEW (v1).
json
{
  "code": "HR_COMP_VIEW",
  "name": "HR managers view compensation",
  "description": "Compensation visible to HR managers with the bank account masked",
  "policyType": "RECORD_ACCESS",
  "effect": "ALLOW",
  "priority": 60,
  "subjects": [{ "type": "ROLE", "code": "HR_MANAGER" }],
  "resources": [{ "code": "HCM.EMPLOYEE.COMPENSATION" }],
  "conditions": { "operator": "AND", "rules": [], "scope": { "type": "GLOBAL" } },
  "actions": ["VIEW"],
  "restrictions": [{ "type": "MASK", "field": "bankAccount", "mode": "LAST_4" }],
  "schedule": { "effectiveFrom": "2026-01-01", "effectiveTo": "", "daysOfWeek": ["MON", "TUE", "WED", "THU", "FRI"], "startTime": "08:00", "endTime": "19:00", "timezone": "Asia/Kolkata", "networkZones": [] }
}
bash
erp api post /api/v1/authoring/access-policies --tenant 2 --body @hr_comp_view.json
erp api post /api/v1/authoring/access-policies/5/publish --tenant 2

Limits and behaviour. Policies are stored per workspace; there is no decision cache beyond the 10-second policy list. Tests, imports and exports are not audited. The validator checks format, not whether a role or department code exists. The policy type does not alter behaviour.

Related. Permissions - how an access decision is made, Record Permissions.

Effective Permission Viewer ​

The viewer shows why a person is allowed or denied an action on a resource, and replays the question for a past instant. Administrators use it first when an access result is not what they expected.

Where to find it. Security > Roles & Permissions > Effective Permission Viewer. Page key effective-permission-viewer.

Fields and options.

FieldTypeDefaultRequiredDescription
ActorTextemptyYesHelper text e.g. "alice".
ResourceTextemptyYesHelper text e.g. "payroll". The exact resource string, for example page:leave-settings or workflow:leave-request.
ActionTextemptyYesHelper text e.g. "delete".
As of (optional, for Simulate)Date and timeempty (now)NoUsed by Simulate & save only.

Actions.

ActionEffectAPI
Explain nowRuns the resolver at the current instant and shows the result. Nothing is stored.GET /api/v1/permission-explain?actor=&resource=&action=
Simulate & saveRuns the resolver at As of (or now), shows the result and stores it.POST /api/v1/permission-simulations body {actor, resource, action, asOf}, returns 201
Load (history)Lists stored simulations, optionally for one actor ("Filter by actor").GET /api/v1/permission-simulations?actor=

Result. A chip shows Allowed or Denied and "Decided by:" followed by one of: "Temporary permission", "User override", "Role", "Default (no matching rule)". The trace grid lists every candidate row of the layer that decided: Layer, Source (the role name, or the actor for temporary and user rows), Effect, Org unit ("tenant-wide" or the id), Applied? ("yes" or "no - scope mismatch"; the latter row exists but its org scope does not reach the person and played no part in the decision). When the trace is empty the viewer states "No temporary permission, user override, or role grant matched at all - the platform-wide fail-open default applied."

History columns: Actor, Resource, Action, As of, Result, Run by, Run at (newest first). Placeholders: "No history loaded yet - click Load, or run a simulation above." and "No simulations recorded yet."

Limits and behaviour. The simulator varies one input: the instant. It replays the person's real current roles and org units, so it can answer "was this temporary grant active last Tuesday" but not "what if I gave this person role X". Role grants and user overrides carry no validity window and are always evaluated as they are now. The explanation is never cached, so it reflects the database, while production checks can lag by up to 60 seconds when the shared cache is on. The endpoints are open to any caller; Simulate records the caller in Run by.

Procedure. Diagnose "Alice cannot open the payroll page".

  1. Actor alice, resource page:payroll-run, action view, then Explain now.
  2. Read "Decided by". If it is "Role", the Source column names the role carrying the deny. If a row shows "no - scope mismatch", a role grant exists but at another org unit.

Permission Audit Log ​

The log lists changes to roles, role grants, permission sets and user overrides: who changed what, with before and after values. It is read-only; to undo a change, restore a version from the History dialog of the item.

Where to find it. Security > Roles & Permissions > Permission Audit Log. Page key permission-audit-timeline.

Fields and options.

FieldType or valuesDefaultDescription
Entity type(any), role, role_permission, permission_set, user_permission(any)
Entity key (optional)TextemptyHelper "Look up one entity's own timeline". When filled, the call uses entity type (default role) and key and ignores Actor. Key formats are in the table under Behaviour shared by all screens.
Actor (optional)TextemptyExact match on the person who made the change.

Nothing is loaded until Load is selected ("No timeline loaded yet - click Load."; "No audit records match."). Columns: When (newest first), Entity (type · key), Operation (create, update, delete), Actor, What changed (one line field: before -> after per changed field). A search box searches the loaded rows.

What is recorded.

RecordedNot recorded
Role create, update, deleteField permissions, API permissions, approval limits, record scope rules, temporary permissions, named ACL templates
Role grant set and clear (matrix cells on every grid)Permission set attach and detach (the resulting role grants are not logged as separate events either)
Permission set create, update, delete, item add and removeAccess policy changes (see the policy History dialog)
User override set and clearAccount and sign-in events (see Audit Logs and Audit Log)

API. GET /api/v1/permission-audit?entityType=role_permission&actor=alice and GET /api/v1/permission-audit/entity?entityType=role&entityKey=7. Each row carries id, entityType, entityKey, operation, oldValuesJson, newValuesJson, actor, correlationId, occurredAt.

bash
erp api get "/api/v1/permission-audit?entityType=role_permission&actor=alice" --tenant 2

Limits and behaviour. The same events invalidate the 60-second decision cache. Reads need no capability.

Related. Effective Permission Viewer, Audit Log.