Skip to content

API Tokens & Scopes

A Personal Access Token (PAT) lets a script, a CI job, or an AI agent call the SeyalRun API as you — but confined to a set of scopes you choose when you issue it.

Issuing a token

Admin → Security → Personal Access Tokens → + Token

  1. Give it a name that says who will use it (ci-deploy, ops-copilot) — this is what appears in the audit log alongside your username.
  2. Pick the scopes. The picker is populated live from the server's catalog (GET /api/v1/api-tokens/scopes), split into Read and Actions. A token with no scopes can do nothing, so Create stays disabled until you select at least one.
  3. Optionally set an expiry.
  4. Copy the sr_… value. It is shown once — SeyalRun stores only a hash.

Tokens are listed with their prefix, scopes, last-used time, and expiry; you can revoke one at any time, which takes effect on the next request.

Using a token

sh
curl -k https://your-seyalrun-host:8443/api/v1/hosts \
  -H "Authorization: Bearer sr_your_token_here"

The same header authenticates the MCP server.

The scope catalog

Scopes are domain:action. Reads are the baseline; actions are opt-in.

Read scopes

ScopeGrants
inventory:readHosts, host groups, zones, credential templates
automation:readProjects, job templates, schedules, job runs
sessions:readSession history and recordings
audit:readThe audit log
metrics:readThe metrics dashboard
notifications:readNotifications

Action scopes

ScopeGrants
automation:runRun an allowlisted job template (POST /job-templates/{id}/run)
inventory:writeCreate/edit hosts, host groups, zones
sessions:openOpen SSH/SFTP sessions
notifications:ackAcknowledge notifications

automation:run is deliberately separate from automation:write. Running an approved playbook and editing what that playbook does are different levels of trust — an agent that can run templates cannot rewrite them.

Never grantable

credentials:read, credentials:write, and every admin:* scope are rejected at issuance with a 400, and would be unsatisfiable at the gateway even if a token somehow carried them. See the two hard lines.

How enforcement works

On every request, api-gateway checks — in order — the caller's session/token validity, then their role (RBAC), then, only for PAT callers, their scopes:

role says no   → 403 forbidden: your role does not permit this action
scope says no  → 403 forbidden: token scope does not permit this action

A browser session carries no scopes and is governed by role alone; a PAT is governed by role and scopes. That ordering is why a token can only ever do less than its owner, never more.

Two properties worth knowing:

  • Deny by default. The scope required for a route comes from an explicit segment→scope map. A route that isn't in the map is closed to tokens until someone classifies it — new endpoints are not silently reachable.
  • An empty scope list denies everything. There is no "no scopes means unrestricted" fallback.

Legacy read / write tokens

Tokens issued before fine-grained scopes carry the coarse read or write scope. Those still work — read satisfies any read, write any write — so existing integrations did not break. They are, however, far blunter than the per-domain scopes. Reissue them when convenient.

How tokens are stored

The raw token is never persisted. SeyalRun stores an Argon2 hash of token + API_TOKEN_PEPPER, where the pepper lives only in .env — so a full database dump still doesn't let an attacker forge or crack a token. Only the first few characters (the sr_ prefix plus six) are kept in clear, to identify a token in the UI.

Practical advice

  • One token per consumer, not one shared token. Revoking a leaked CI token shouldn't take down your agent.
  • Grant reads first. Let an agent observe before you give it an action scope.
  • sessions:open is the heavy one. It lets the holder open an interactive session — the same thing a human operator does, with the same recording and command filtering, but without a human at the keyboard. Prefer automation:run for agents.

Secure. Controlled. Automated. — Released under the Apache License 2.0.