Roles and permissions
Three ranks, enforced server-side on every route. What each can read and write, which operations are privileged, and how a token narrows further.
On this page
Access on the admin plane is three ranks, checked on the server for every route. This page is the complete description: what each rank can do, which operations sit behind an extra gate, and how a bearer token can be narrowed below its own rank.
Sign in to the dashboard covers creating the credentials these roles attach to. This page covers what they then permit.
The three ranks
Ranks are ordered, and a higher rank includes everything below it.
| Role | Can | Cannot |
|---|---|---|
viewer |
every read, exports included | change anything |
operator |
viewer, plus writes that emit configuration | manage users, tokens, or delete data |
admin |
operator, plus user management, token management, retention deletion | — |
Viewer means read parity, and the emphasis is on parity. An export is a read, so a viewer can export. A role that could see a figure but not export it would push people to screenshot tables, which is worse for everyone.
Operator is the rank that changes the running system. Applying config, moving a ladder rung, activating a calibration, running a replay, submitting feedback, silencing an alert, writing a saved view or a schedule or a reconciliation, and writing a pricing override.
Admin is the rank that changes who can do the above, plus the one destructive capability in the product: retention deletion.
What each role can do, by area
| Area | viewer | operator | admin |
|---|---|---|---|
| Read any surface, query, export | y | y | y |
| Apply config through validate → diff → apply | — | y | y |
| Set the ladder mode | — | y | y |
| Activate a calibration | — | y | y |
| Run a replay or a policy preview | — | y | y |
| Submit feedback on a request | — | y | y |
| Silence an alert rule | — | y | y |
| Create, edit or schedule a saved view | — | y | y |
| Import an invoice for reconciliation | — | y | y |
| Write a pricing override | — | y | y |
| Add, remove or re-role a user | — | — | y |
| Mint or revoke a bearer token | — | — | y |
| Mint or revoke a share token | — | — | y |
| Delete data under a retention policy | — | — | y |
Assign the rank someone needs for the job in front of them. Most people who look at a dashboard are viewers; the operator rank is for whoever is changing routing.
Privileged operations
Seven operations sit behind an extra gate, because each one either changes who can act or exposes something a read normally cannot reach.
| Scope | Operation |
|---|---|
config:apply |
apply a configuration change |
content:read |
read a captured request or response body |
keys:mint |
mint a virtual key |
share:admin |
mint or revoke a share token |
tokens:mint |
mint a bearer token |
tokens:write |
modify or revoke a bearer token |
users:admin |
manage users |
content:read is on that list for a reason worth stating: it is the only scope that returns
request or response text, and it applies only where content
capture has been turned on. Everything else on the admin plane
deals in metadata and money.
Narrowing a token below its role
A bearer token carries a role, and it can also carry an explicit list of scopes narrower than that role would grant. This is how you give a Grafana panel or a CI job exactly the access it needs and nothing more.
The instance publishes its whole scope vocabulary, so you never have to guess a name:
$ curl -s http://127.0.0.1:9090/tt/api/v1/capabilities | \
python -c "import json,sys; print(json.load(sys.stdin)['management_api']['scopes'])"
['alerts:read', 'alerts:write', 'analytics:read', 'audit:read', 'auth:read', 'budgets:read',
'cache:read', 'cache:write', 'config:apply', 'config:read', 'content:read', 'evals:read',
'evals:write', 'events:read', 'evidence:read', 'evidence:write', 'exports:read',
'feedback:write', 'fleet:read', 'fleet:write', 'governance:read', 'governance:write',
'guardrails:read', 'instances:read', 'keys:mint', 'pricing:read', 'pricing:write',
'providers:read', 'providers:write', 'quality:read', 'quality:write', 'reconcile:read',
'reconcile:write', 'schedules:read', 'schedules:write', 'share:admin', 'tokens:mint',
'tokens:read', 'tokens:write', 'users:admin', 'views:read', 'views:write', 'webhooks:read',
'webhooks:write']
That output was line-wrapped to fit; the endpoint returns it as one JSON array.
A denial for a missing scope names the scope in a TT-Required-Scope response header as well
as in the problem body, so a caller can tell “I need a bigger role” apart from “I need this
one extra scope”.
Two confinement rules that apply to tokens and not to sessions
A token can be bound to a subtree. A management token may name a governance node, after
which it reaches only that node and everything under it. The root of the tree answers; any
node outside the binding gets a 403.
A token reaches only registered operations. A session can reach anything its role permits; a management token is confined to operations the server has registered, so a route added without its registry entry is unreachable by token rather than reachable by accident. That asymmetry is intentional and it fails closed.
Sessions carry one more requirement in the other direction: a session-authenticated write must present the CSRF header. Token principals never do, because a token is not sent automatically by a browser.
How this relates to virtual keys
Two access systems exist in this product and they govern different planes.
Roles govern the admin plane — who may read and change the dashboard and the management API. That is this page.
Virtual keys govern the data plane — which callers may send traffic through the proxy, under which limits, attributed to which tenant. That is Governance.
They meet in one place: minting a virtual key is an admin-plane operation, so it needs the
keys:mint scope and the admin role. Nothing else about them is shared, and a virtual key is
not a dashboard credential.
What can go wrong
Everyone is an admin. With auth.mode: off every request resolves to a local admin,
which is correct on loopback and wrong anywhere else. The mode is the control, not the roles.
An operator cannot mint a token for a colleague. That is admin work. It is on the admin side of the line because minting a credential grants access, and granting access is the thing the top rank exists to hold.
A viewer reports that a page is empty rather than refused. A surface whose read is declined says so on the surface. A navigation entry promises the screen exists, not that every read inside it is permitted; why your sidebar is shorter covers the other reason a screen may be missing.