Permission keys
53 keys, inresource:action form — leave:approve, attendance:write, payroll:read. A route declares the key it needs; the credential either carries it or does not.
For an API key, the permission set comes from its scope, and today that is always employee:read plus attendance:read. For a session or an MCP-bound key, it comes from the employee’s role.
The full catalog with the four presets is in Roles & permissions.
Actor scope
Scope has exactly three levels. There is no fourth.self
self
Acting on your own record. Enabled per surface — reads generally allow it, approvals never do. A request cannot be approved by whoever filed it.
team-direct
team-direct
The subject’s direct manager. Not the transitive subtree — one link only.Always overridable: a holder of
hr:approve clears team-direct for every employee in the organization.org
org
No per-employee narrowing at all. What
hr:approve grants, and what administrative keys imply. It is the absence of scope, never a per-employee decision.Lists agree with detail pages
A list query is filtered with the same scope rule that guards the detail endpoint each row links to. This is a guarantee you can build on: every row a credential can list, it can also fetch. You will not enumerate a queue and then get403 on half of it.
It also means an empty list is not necessarily an empty dataset — it may be a dataset entirely outside your scope. See below.
Telling the three cases apart
This is the practically important part for an integration:Designing an integration around this
1
Prefer org-scoped credentials for reporting
An API key resolves to organization-wide read access on the public endpoints. It is not narrowed by anyone’s org-chart position, which is what you want for an export job.
2
Use MCP when you need a person's scope
An MCP key is bound to an employee and acts with exactly their permissions and their scope. That is the right model for an assistant acting on somebody’s behalf — and the wrong model for a nightly export.
3
Never paper over a 403 with a retry
Scope and permission failures are deterministic. Retrying produces the same answer and burns your rate limit.