Permissions
Permission keys are defined in Go, not in a database table — so a newly added permission is automatically available to the Super Admin without a migration.
The live list is available at GET /api/roles/permissions, which is what the admin UI’s
role editor is built from.
The keys
Section titled “The keys”| Key | Gates |
|---|---|
jobs:read |
List and get jobs, recent calls, heartbeats, per-job activity, a job’s invocation alarms, the disabled-group list. |
jobs:write |
Create, update, delete a job; enable/disable (single, all, by group); run-now. Also the /riverui/ mount. |
jobs:script |
Creating or editing a script job — in addition to jobs:write. Also gates GET /api/scripts/liveness. Also required, on top of workflows:write, to save a workflow whose graph contains a script block. |
workflows:read |
List and get workflows, their runs, and their run steps. |
workflows:write |
Create, update, delete, enable/disable, and manually run a workflow. Distinct from jobs:write — a workflow is its own schedulable entity, not a scheduled_jobs row. |
globalvars:read |
List global variables. |
globalvars:write |
Create, update, delete global variables. Also gates a variable’s allow_in_scripts flag and, combined with jobs:script, a job’s script_secret_vars list. |
metrics:read |
/api/metrics/live, /api/metrics/history, and the Prometheus /prometheus/metrics endpoint. |
users:read |
List users. |
users:write |
Create a user, change role or status, admin password reset, delete a user. |
roles:read |
List roles and list permission keys. |
roles:write |
Create, update, delete roles. Only permissions the caller holds can be added to a role. |
invites:create |
Create an invite, list pending invites, resend an invite. |
audit:read |
Read the audit log and the audit retention setting. |
audit:write |
Change the audit retention setting. |
settings:smtp |
Read and change the outbound email (SMTP) settings, and send a test message. |
settings:river_jobs |
Read and change the River job (river_job) retention setting. |
settings:mcp |
Manage MCP tokens (create, list, revoke) and toggle the MCP endpoint on/off. Gates the token-issuing surface only — what a token can then do is controlled by its own linked role’s permissions, not by this key. |
scriptvolumes:read |
List script volumes (with how many jobs and workflows use each) and read script worker status. |
scriptvolumes:write |
Create, update and delete script volumes. Declaring an existing volume on a script job needs only jobs:script, not this key. |
notify:read |
View a job’s per-job notification config (notify_on, notify_all_targets, notify_targets). |
notify:write |
Change a job’s per-job notification config, and create, update or delete the job’s invocation alarms. Gates only an actual change to those fields — a caller without it can still edit every other field of a job that already has notifications configured. |
notify_settings:read |
List notification targets, the Apprise server connection settings, and the metrics alarms (Settings → Notifications). Also populates the job wizard’s target picker. |
notify_settings:write |
Create, update, delete, and test notification targets; change the Apprise server connection and metrics alarms. |
Custom roles receive neither scriptvolumes key; add them to any custom role that should
see or manage Settings → Script volumes.
Built-in roles
Section titled “Built-in roles”A user can be assigned more than one role — the admin UI’s create/invite form is a checkbox multi-select, not a single dropdown — and always holds at least one; the API rejects any create or update that would leave a user with zero roles. When a user holds several roles, their effective permissions are the union of what each assigned role grants.
Three system roles are seeded and cannot be renamed or deleted. Super Admin’s permissions
can’t be touched either — its access is computed from the user’s super-admin flag, not
role rows. Admin and Read-Only permissions can be edited by a caller holding
roles:write; the table below shows each one’s seeded default.
| Super Admin | Admin (default) | Read-Only (default) | |
|---|---|---|---|
jobs:read |
✓ | ✓ | ✓ |
jobs:write |
✓ | ✓ | |
jobs:script |
✓ | ||
workflows:read |
✓ | ✓ | ✓ |
workflows:write |
✓ | ✓ | |
globalvars:read |
✓ | ✓ | ✓ |
globalvars:write |
✓ | ✓ | |
metrics:read |
✓ | ✓ | ✓ |
users:read |
✓ | ✓ | |
users:write |
✓ | ||
roles:read |
✓ | ✓ | |
roles:write |
✓ | ||
invites:create |
✓ | ||
audit:read |
✓ | ✓ | |
audit:write |
✓ | ||
settings:smtp |
✓ | ✓ | |
settings:river_jobs |
✓ | ✓ | |
settings:mcp |
✓ | ✓ | |
scriptvolumes:read |
✓ | ✓ | ✓ |
scriptvolumes:write |
✓ | ✓ | |
notify:read |
✓ | ✓ | |
notify:write |
✓ | ✓ | |
notify_settings:read |
✓ | ✓ | |
notify_settings:write |
✓ | ✓ |
Enforcement
Section titled “Enforcement”Checks happen per route:
403 {"error":"you do not have permission to perform this action"}jobs:script is the exception. It can’t be a route-level check, because the job type is
only known after the request body is decoded, so it’s enforced inside the create and
update handlers:
403 {"error":"script jobs require the jobs:script permission"}Routes that need no permission
Section titled “Routes that need no permission”| Route | Why |
|---|---|
GET /api/meta |
Server timezone. Needed by the UI shell before any permission check. |
GET /api/account/me |
The caller’s own identity and permission list. |
POST /api/account/change-password |
Self-service; must work for a user who is required to change their password. |
/login, /logout, /admin/assets/, /admin/accept-invite |
Registered outside the auth middleware so login stays reachable. /logout accepts POST only. |
Everything else requires a valid session plus the permission in the table above.
Super Admin protections
Section titled “Super Admin protections”These return 409 regardless of the caller’s permissions:
- Changing a Super Admin’s role or status.
- Deleting a Super Admin.
- An admin password reset targeting a Super Admin.
- Minting an invite for an already-active user (which closes an account-takeover path
available to a caller holding only
invites:create).
The first-boot seeding path sets the super-admin flag once, for the bootstrap account.
Beyond that, an account gains it whenever it’s assigned the Super Admin role — by
accepting an invite into that role, by being created directly into it
(POST /api/users), or by having its roles changed to include it
(PATCH /api/users/{id}). All three routes — plus POST /api/invites — only let an
existing Super Admin assign that role, or assign any role whose permissions exceed the
caller’s own, so a plain invites:create or users:write holder can’t use them to mint a
Super Admin or escalate past their own access.
POST /api/mcp-tokens applies the same role-grant check as those routes — a caller
can’t mint a token bound to a role whose permissions exceed their own. It goes one step
further than the human-user routes: a token can never be bound to the Super Admin
role at all, even by an actual Super Admin, since the token’s hidden linked account is
created without the super-admin flag that role normally carries — binding it anyway
would silently issue a token with zero effective permissions.