Skip to content

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.

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.

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 ✓ ✓

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"}
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.

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.