# Tokens & scopes

> **For agents:** `whoami()` shows your scopes. `list_tokens(org="acme")` and `revoke_token(org="acme", id="…")` need `org:admin`. There is no tool to create a token, and there is no way to do it with the one you hold either: `POST /v1/orgs/{org}/tokens` refuses bearer-token callers with `403`. A human creates tokens in **Settings → API tokens**.

## Tokens

API tokens are organization-bound bearer credentials with an explicit scope list and an optional expiry (1–365 days). The raw value (`bwt_` + 48 hex) is shown exactly once; only its SHA-256 is stored. Tokens record `last_used_at` on every request and can be revoked instantly (`DELETE /v1/orgs/{org}/tokens/{id}`). A token created by a user carries that user id, so audit rows show both the token name and the person behind it.

Send it as `Authorization: Bearer bwt_…` to the REST API, the MCP endpoint, and `sentry-cli` (`SENTRY_AUTH_TOKEN`).

## Scopes

| Scope | Grants |
|---|---|
| `org:read` | Read org-level data: members, releases, channels, alert rules, usage, plan catalog, agent activity, docs |
| `org:admin` | Everything, plus add/remove members, list/revoke tokens, toggle overage. **Not** minting tokens or Stripe checkout/portal — those need a signed-in user, whatever scope the token carries |
| `project:read` | List projects, read project settings and DSN keys |
| `project:write` | Create/update projects, inbound filters, store-IP flag; create and enable/disable DSN keys |
| `event:read` | Issues, events, fix context, activity, stats, performance, release health |
| `event:write` | Change issue status (resolve, ignore, reopen, bulk) |
| `release:write` | Create releases and deploys; upload artifact bundles (native and `sentry-cli`) |
| `alert:write` | Create/delete channels; create/update/delete alert rules |

Resolution rules, as implemented by `hasScope`:

- A scope you hold matches exactly.
- **`org:admin` implies every scope.**
- **A `:write` scope implies the matching `:read`**: `event:write` satisfies `event:read`, `project:write` satisfies `project:read`. The reverse never holds.
- Cross-organization requests return `404`, never `403` — existence is not leaked.

## Presets

The dashboard offers four presets; they are just scope lists you can also send to `POST /v1/orgs/{org}/tokens`.

| Preset | Scopes | Use for |
|---|---|---|
| Read-only (`reader`) | `org:read`, `project:read`, `event:read` | Exploration, `get_fix_context`, coding agents that only read |
| Triage (`triage`) | reader + `event:write` | Agents allowed to resolve / ignore / reopen (audited) |
| Operator (`operator`) | triage + `project:write`, `release:write`, `alert:write` | CI, release scripts, alert setup, key rotation |
| Admin (`admin`) | `org:admin` | Members, tokens, billing flags — use sparingly |

Mint example:

```sh
curl -sS https://api.bugwatch.io/v1/orgs/acme/tokens -H 'Authorization: Bearer bwt_<admin>' \
  -H 'content-type: application/json' \
  -d '{"name":"claude-code","scopes":["org:read","project:read","event:read"],"expiresInDays":90}'
# → {"token":"bwt_…","note":"save this now — it is not shown again"}
```

## What agents cannot do

These routes intentionally have **no MCP tool**, and the parity test fails if one is added without a reason. The first three go further: they check that the caller is a **signed-in user**, so presenting an `org:admin` token gets `403` rather than access — otherwise a leaked token could simply mint a replacement and outlive its own revocation.

- `POST /v1/orgs/{org}/tokens` — agents must not mint credentials.
- `POST /v1/orgs/{org}/billing/checkout` and `…/billing/portal` — browser redirects to Stripe; purchases and billing-portal sessions stay human-initiated.
- `POST /v1/orgs/{org}/projects/{project}/artifact-bundles` — binary upload; use `sentry-cli` or the endpoint directly.

## Auditing

Every mutating tool call is recorded in `agent_actions` with the token id, user id, tool, arguments (secrets reduced to their kind, capped at 4,000 characters), outcome, HTTP status, and duration; issue-scoped mutations also land in the issue's activity feed. Read-only calls are not recorded. `list_agent_activity(org, limit≤200)` returns the newest first.

## Roles versus tokens

Dashboard users get scopes from their membership role at request time (`owner`/`admin` → everything including `org:admin`; `member` → everything except `org:admin`; `viewer` → `org:read`, `project:read`, `event:read`). Tokens carry their own list and are not affected by the creator's later role changes. See [Members & roles](https://docs.bugwatch.io/account/members-and-roles.md).
