Tokens & capabilities

One credential authenticates the API, git over HTTPS, and MCP. What it may do is decided when it is minted, and a request outside that comes back naming what it wanted.

Minting one

From Settings → Tokens, or from the terminal:

$ rezee token create ci --expires 90
$ rezee token list
$ rezee token revoke <id>

A token looks like reze_pat_..., is printed exactly once, and is stored as a hash - there is no way to read it back, here or anywhere else. Pipe it somewhere useful rather than reading it off the screen:

$ rezee token create ci --json | jq -r .token

Lifetimes go up to 365 days. The cap is less a security boundary than a forcing function: a credential nobody has looked at since last summer is one nobody will notice the theft of. A token minted with no lifetime never expires.

Two kinds of token

Unrestricted. A token minted with no capabilities can do whatever its owner can. This is what the tokens page gives you by default, and what the commands that change access need.

Scoped. A token carrying capabilities can do only those, and only ever a subset of what its owner could do anyway - a scope narrows, it never widens. rezee auth login mints one of these: the approval page lists every capability and you tick what this machine gets.

A request outside a token's capabilities comes back 403 naming the capability it wanted, so a client can ask to be widened rather than report a failure it cannot explain.

Capabilities

CapabilityWhat it reaches
account:readwho you are, and your inbox
workspaces:readwhich workspaces you are in
code:readbranches, trees, blobs, commits, blame - and git fetch
code:writegit push, and re-running a pipeline
repos:writecreating a repository
issues:read / issues:writeissues, labels, projects and cycles
pulls:read / pulls:writepull requests, reviews, threads, merging
docs:read / docs:writedocuments, canvases, components and assets
chat:read / chat:writechannels, messages and threads
runs:readpipeline runs, jobs, steps and logs
secrets:writelisting, setting and rotating CI secrets
releases:writecutting a release and its tag
tasks:readreading delegated agent tasks
tasks:runan external agent runtime driving its own task, and nothing else's

There is no secrets:read. The list returns names and update times and never a value, and the token that may rotate a secret is exactly the one that needs to see which ones exist.

releases:write is separate from code:write on purpose, so the token that publishes a build cannot also push to the branch it was built from.

Narrowing to a workspace or a repository

Two resource scopes sit alongside the capabilities and bind whatever else the token holds:

ws:acme                 # only this workspace
repo:acme/api           # only this repository

A repository path names its workspace in the same breath, so ws:acme narrows the code surfaces too - "this token is for acme" means acme's repositories as well as acme's issues.

Search is the one endpoint no single capability describes. It intersects the surfaces it would search with what the token actually holds, so a token carrying only issues:read gets issues back and never learns a document title. The resource scope still binds, and rezee search prints which surfaces it was allowed to read.

What no scoped token can reach

Some surfaces are refused to every scoped token, whatever it carries. They are the ones where a grant could be used to manufacture a wider grant:

  • Members, invitations and workspace creation. A token that could add members could add itself colleagues.
  • SSO and SCIM. A token that could point the workspace at an identity provider could provision itself colleagues.
  • Runners. A runner token is handed the decrypted secrets of every repository at every claim - minting one would be minting a credential strictly wider than the token that asked.
  • Branch protection. A token has no business reading or writing the rules its own pushes are judged against.
  • Collaborators and webhooks. Repository configuration, not a working surface.
  • Agents, their tokens, and schedules. A token that could create a schedule would have given itself work every morning forever, through the one door with no person at it.
  • The audit log, and your own profile and password.

This is why rezee member, rezee invite, rezee runner, rezee protect, rezee webhook, rezee token and rezee ssh-key need an unrestricted token rather than the one a browser login mints.

Using one

# the API
$ curl -H "Authorization: Bearer reze_pat_..." \
    https://api.rezee.app/api/workspaces

# git over HTTPS - username is ignored, password is the token
$ git clone https://git.rezee.app/acme/api.git

# MCP
Authorization: Bearer reze_pat_...

The browser's session cookie authenticates the same endpoints, which is what the web app uses, and reads as unrestricted.

The other credentials in the system

Personal access tokens are the one you mint. The rest exist so you know what you are looking at when you see one:

PrefixWhat it is
reze_pat_a personal, agent, or task token
reze_dev_a device-login code - redeemed for a token rather than presented as one
reze_oat_ / reze_ort_an OAuth connector's access and refresh token
reze_runner_a CI runner's registration token
reze_inv_a workspace invitation

A pipeline's clone credential is separate again: minted per run, valid for that repository only, and read-only. Every build step sees it, and a pipeline that could write with it is a pipeline that could rewrite the branch it was asked to test.

Revoking

rezee token revoke, or the tokens page. Revocation is immediate - the row is deleted and the next request carrying it is a 401.

rezee auth logout does not revoke anything. It forgets the token locally, and the credential stays live until somebody deletes it. If the machine is not yours, revoke it as well.

Connected apps are revoked from their own list in account settings, which disconnects every token that client holds at once.