Skip to main content
Authorization in OnTime is two checks, not one: Permission answers can you do this kind of thing. Scope answers to whom. Both must pass.

Permission keys

53 keys, in resource: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.
Acting on your own record. Enabled per surface — reads generally allow it, approvals never do. A request cannot be approved by whoever filed it.
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.
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.
There is no “subtree” scope. Skip-level authority is not automatic: a manager two levels above someone has no authority over them. That case is expressed as org via hr:approve, not as a deeper walk down the org chart.A transitive walk does exist as a list filter — the employee list’s “reports to” parameter — but it only narrows rows you are already entitled to see. It never widens a guard.

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 get 403 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:
An empty list and “no permission” are not distinguishable from the response alone. A manager listing approvals sees only their direct reports’ requests; if they have none, the result is 200 with zero items — identical to an organization with no pending requests.If your integration needs to know which case it is in, check the credential’s expected scope explicitly rather than inferring it from an empty array.

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.