How permissions work
The three tiers Projectri decides "may this person do this" with, how a role default becomes one person's answer, and the two traps that catch every new key.

What it is
Every screen and every API call in Projectri checks a named permission key, like task.create or finance.invoice.view — never a role. A role is only a bundle of keys, and the bundle is data an admin can change. That is why "can Sam do this" has a real answer rather than a guess.
There are three tiers, each narrower than the last, plus one question about the row itself. All four have to say yes.
How to get there
The keys themselves are visible at Admin → Permissions, as a grid of roles against keys with a per-person override table beside it. Reading that screen needs security.view; changing it is an admin act. Admin → Roles shows the four roles.
You do not need a permission to be affected by one. Everywhere else in the product, a key you do not hold shows up as a control that is simply not offered — the row is absent from the navigation, the button is not drawn — and the route behind it answers 403 if you reach it another way.
How to use it
Read what a key means
- Go to Admin → Permissions.
- Keys are named
<resource>.<verb>and grouped by area..viewis always read-only; every other verb changes something and writes an audit entry. - Two wildcards exist in a granted set:
*for everything andproject.*for a whole resource. ADMIN holds*, which is why a key added tomorrow reaches admins with no migration.
Give one person a key without changing their role
- Admin → Permissions → the override table beside the grid.
- Find the person, switch the key on or off.
- It takes effect on their next request. A per-person override beats the company's role setting, which beats the platform-wide role default.
Change what a role can do for this workspace only
- Admin → Permissions → the grid.
- Toggle the key for the role. This is a delta on top of the shipped default; other workspaces are unaffected.
- An explicit "off" removes the key even when the layer below granted it, so later and more specific always wins.
Cap what someone can do inside one project
- Open the project → its member list.
- Give them a project role. It caps what the workspace already allowed and can never grant anything.
| Project role | Keeps |
|---|---|
| ADMIN | everything the workspace tier allowed |
| EDITOR | all but project.delete, project.archive, project.member_manage, project.budget, sprint.delete |
| COMMENTER | .view keys, plus task.comment, document.comment, discussion.post, time.log |
| VIEWER | .view keys only |
Being absent from the member list is not a restriction. Most projects have no explicit member list at all, and treating "not listed" as "locked out" would close a workspace out of its own data. The list is an opt-in narrowing on the people named in it.
What it affects
- **The navigation rail, the command palette and the keyboard shortcuts read the same keys.**
⌘2for My Tasks falls through unhandled if you do not holdtask.view, so the three can never disagree about what is reachable. - API tokens and connected apps are narrowed by it. A token's real access is its stored scopes **intersected with the live permissions of the person who created it**. Demote that person and the token narrows on its next call; suspend them and it dies. Nobody has to remember to revoke it.
- Nia is bound by it. Her tools re-check the key rather than trusting that she is trusted, so she is never a way round a gate.
- Suspension is total. A suspended or removed member resolves to *no keys at all*, not to GUEST — GUEST still carries a few reads.
- **Support tickets are the one place a
.viewkey is not the whole answer.**support.viewgets you a queue; which tickets are in it depends on being the reporter, the assignee, on the assigned team, a named participant, or on the ticket's project.support.managesees the whole queue.
On mobile
The phone reads the same resolved key list and hides the same controls. There is no permission editor in the app — the grid is workspace configuration done once at a desk, and the phone shows the result of it everywhere it applies.
Limits and gotchas
- A grant is inert until the RBAC seed has run. The key set lives in code and is reconciled into the database by
scripts/seed-org-rbac.mjs. Until that runs, a newly added key grants nothing. The seed also revokes: it is a reconciliation, not an append. It never touches an admin's own edits. - Editing a row can need two permissions, not one.
task.updatemeans "edit anybody's task";task.update_ownmeans "edit the ones you created, are assigned, are reviewing or are a co-assignee on". Holding the second without the first gives you a box you can type into only on your own work — and the project tier is re-applied on the way through either way. - "Edit" is split further on a task. Changing the assignee or reviewer needs
task.assign; moving it in or out of a sprint needssprint.scope. Fixing a typo on your own task does not let you push it onto a colleague. - The client-side check is optimistic.
can()answerstruebefore the server has said otherwise, so a control can appear for a moment and then go. The server refuses either way; nothing you see for 200ms was ever permitted. - **
role === ADMINis not cross-tenant.** Being an admin of one workspace confers nothing in another; the role lives on the membership row, so one person can be ADMIN in one workspace and VIEWER in the next. - A member starts on the floor. New members get what they need for their own work. The client list, contracts, reports, proofs, the knowledge base, forms and playbooks are all grants — one toggle each, and the row appears in that person's rail on their next request.
Related
- Roles and permissions — the four roles and what each is for.
- Permissions — the grid and the override table.
- Plans and entitlements — the other reason a screen refuses you, and how to tell them apart.
- Nia, the assistant — why she cannot exceed your own access.