Authentication and permissions
Use this reference to choose grants for an integration and check which API
operations its credential can perform. Paths below are relative to your
deployment's /api base URL, as described in the API overview.
Credentials and effective grants
| Credential | How to send it | How Watcher determines access |
|---|---|---|
| Organization API key, in SSO mode | x-api-key header | Uses only the key's explicit grants. A key has no user identity and cannot own sessions or graders. |
| Signed-in user, in SSO mode | Authorization: Bearer <access-token> | Combines YAML grants for the verified role and roles; unknown roles add nothing. Only the two WorkOS widget grants pass through from token permissions. |
| User behind a self-hosted auth proxy | Authenticate as your proxy requires | The trusted role header selects a role from the server's YAML mapping. Missing or unrecognized roles grant no permissions. Permission headers do not add grants. |
See API-key setup, Python sign-in, and proxy setup. API keys and WorkOS bearer tokens are not separate authentication paths in proxy mode.
An effective grant is a permission the server accepts from that source after
applying organization policy. Checks use the exact permission slug.
Unknown slugs receive no implied access. The role in a user profile is for
display; use its permissions to determine which actions are available.
Role configuration
The shipped YAML defines these defaults for both sign-in modes:
| Role | Default grants |
|---|---|
admin | All permissions in the public table below. Organization content policy still applies. |
org-viewer | sessions:aggregates:read:any, sessions:findings:read:any, tool-calls:content:read:flagged, and org:managed-settings:read. |
member | org:managed-settings:read. |
Watcher Cloud uses these shipped defaults. Cloud customers can assign available
roles but cannot edit their definitions. Self-hosters can
configure roles in server-config.yaml:
- Each entry under
rolesreplaces that role's whole permission list. Custom role names add new roles. Omitted roles retain their shipped defaults, soroles: {}keeps all defaults. - An empty list, such as
admin: [], removes every Watcher grant from that role. It does not change WorkOS widget access. Identity and temporary approvals can still permit access to individual resources. - Missing or unknown role assignments contribute no Watcher grants in either sign-in
mode. Users receive
membergrants only when explicitly assigned that role. - Role names are trimmed and case-insensitive. Empty names, whitespace within names, and duplicate names after normalization prevent startup.
- Values must be lists; use
[], notnull, for no grants. Entries must exactly match a public permission. Widget grants, unknown names, typos, and reservedinternal:capabilities are rejected. Duplicates are removed.
Invalid configuration prevents startup. Changes take effect after every API replica restarts. WorkOS supplies role assignments; changing assignments requires refreshed user tokens. Changing permissions in WorkOS does not change Watcher API access.
API keys keep their explicit scopes, independent of role YAML. They cannot
grant reserved internal: capabilities either; the prefix check ignores case
and surrounding whitespace.
WorkOS-hosted controls
WorkOS controls access to the Analyzer's member-management and API Keys widgets. Watcher passes through only these grants from verified user tokens:
widgets:users-table:manage: manage members and their roles.widgets:api-keys:manage: create and revoke organization API keys.
These grants are managed in WorkOS, outside role YAML. Standard setup grants
both to admin. Grant or assignment changes require refreshed tokens;
existing tokens retain their claims until expiry.
The Organization page also requires org:settings:read from YAML. Hiding the
page does not revoke WorkOS access. Without member-management access,
users:read:any permits a read-only member list. Proxy mode has no WorkOS widgets.
See WorkOS setup and
WorkOS's widget requirements.
Public permissions
These are the Watcher permission slugs. Each grant applies within the credential's organization; none grants access to another organization. Session findings include grades and problem explanations, which may quote transcript excerpts. Session summaries and raw messages require content access. See sessions and trajectories and the session access rules.
| Permission | What it permits | Limits |
|---|---|---|
sessions:ingest | Upload sessions without assigning a developer owner. | Does not grant reads or arbitrary grade/decision writes. Ingestion can join a known Claude Code or Codex conversation's session even if another user owns it; see write scope. |
sessions:write:any | Write across session owners, attribute uploads to a developer, and attach grades or tool-call decisions. | Does not grant session reads or permission to grade stored transcripts. |
sessions:findings:read:any | Browse all sessions and tool calls, and read their metadata, grades and findings. | Does not grant raw content, session summaries, or other users' email addresses. |
sessions:content:read:any | Read session summaries, transcripts, messages and raw tool content. | Organization content policy can suppress it. It does not grant findings, browsing or aggregates. |
tool-calls:content:read:flagged | Read flagged calls' arguments, recorded results and input previews. | Does not grant findings, browsing, routine-call content, summaries or surrounding transcripts. Organization content policy leaves this narrower grant enabled. |
sessions:aggregates:read:any | Read session counts, trends and risk metrics across the organization. | Required even when an aggregate query is filtered to your own sessions. |
sessions:grade:any | Run standard grading on another user's stored sessions. | Caller-selected graders also need content access. Grading results still respect read permissions. |
sessions:access-requests:review | List, approve, deny and revoke session-content access requests. | Approving, denying or revoking also requires a signed-in user. |
users:read:any | List organization members and read their profiles, including email addresses. | Does not grant access to their sessions. |
org:managed-settings:read | Fetch the resolved managed client settings document for a client to apply. | Does not grant broader organization-settings reads or edits. |
org:settings:read | Read organization settings, saved managed settings, their schema and active monitors, monitor customization, and grading usage. | Does not permit edits. Scrubbing session content also requires settings-write. |
org:settings:write | Update organization settings, managed settings and monitor customization. | Does not imply settings-read. Scrubbing session content requires both. |
Why managed-settings access is separate
The Watcher client pulls
managed client settings
to apply the organization's configuration on a developer's machine. This also
works when the client uses an API key, without an interactive user login.
Giving that key org:managed-settings:read lets it fetch the resolved document
without access to broader settings, usage reports or settings edits.
Every credential needs this grant explicitly, or the broader org:settings:read
grant. Include it in the user role YAML or API-key scopes for clients that
pull settings. The resolved endpoint returns 404 when no
document is saved or distribution uses MDM instead of Watcher.
Session access
The endpoint tables use findings access and content access as defined below. These are separate: a credential may have one without the other.
Session findings access
Findings access means session ownership or sessions:findings:read:any.
It covers metadata, grades and problem explanations. Browsing and filtering
other users' sessions also requires this grant. Without it, naming another
user in a session or tool-call search returns HTTP 403.
GET /v1/sessions/{session_id}/tool-call-decision?tool_use_id=... returns a
call's recorded decision and grades with findings access. It always returns an
empty raw_tool_call and empty grade input_messages lists. has_result indicates
whether a tool response was recorded, without returning its contents.
Session content access
Content access means session ownership, sessions:content:read:any, or an
active approval for that user and session. It permits session summaries,
including the Session detail narrative, transcripts, messages and raw tool content.
The organization content policy
can suppress the broad grant, including for admins and API keys. Ownership
and approvals still work. The setting is restrict_cross_user_session_content;
see settings endpoints for read and write grants.
An approval permits individual-session content reads from reviewed_at until
valid_until, unless revoked earlier. It adds no findings, browsing or aggregate
access. List previews and title searches use ownership or explicit content
grants instead. A content grant alone works for known session URLs; it does
not permit browsing other users' sessions or reading their findings.
Retrieving trajectory content or individual messages returns HTTP 404 when
content access is denied or the messages have been scrubbed. This applies to
truncated and full transcripts alike. Without content access, mixed responses
omit session summaries, including nested grade summaries, and return null for
raw tool-argument previews unless the call qualifies for the
flagged-call content grant.
Title searches match only sessions whose content the caller can read under
the list rules above.
Content-access requests
Use these endpoints to request, review and revoke temporary content access. See endpoint requirements for the grants and user identity each action needs. Submitting a request does not grant access.
GET /v1/users/me/session-content-access-requests lists only your own requests
in the current organization, including for admins. It accepts session_id,
status (pending, approved, denied, or revoked), limit, and offset, and returns
newest requests first. Approved history includes expired requests; check
valid_until. Set active_only=true for pending requests and currently valid
approvals. Credentials without a user identity receive HTTP 401.
POST /v1/users/me/session-content-access-requests with {"session_id": "<uuid>"}
creates a pending request for the signed-in user. Repeated submissions return
the existing pending request. Findings access is not required. Missing,
foreign, or scrubbed sessions return HTTP 404; already-readable content returns
HTTP 409.
GET /v1/session-content-access-requests lists organization requests.
It accepts limit, offset, and repeated status filters (for example,
?status=approved&status=denied). Responses include requester, reviewer, and revoker
identities, newest requests first. Approved results include expired approvals.
Use view=pending, view=active, or view=historical (denied, revoked or expired) to
filter before pagination. Responses also include organization-wide
pending_count and active_count, regardless of filters.
Signed-in reviewers act on a pending request with
PATCH /v1/session-content-access-requests/{request_id} and
{"status": "approved"} or {"status": "denied"}. Approval lasts eight hours
by default. An optional, timezone-aware valid_until must be in the future and
no more than eight hours after review; invalid expiries return HTTP 422.
Self-approval is allowed and records the reviewer. Completed decisions cannot
be changed (HTTP 409), and scrubbed content cannot be approved. Review credentials
without a user identity receive HTTP 401, even with the permission.
POST /v1/session-content-access-requests/{request_id}/revoke takes no body and
revokes an active approval. The response includes revoked_at; organization
listings also include revoked_by_user. The original review and expiry are
preserved. Missing or foreign requests return HTTP 404; inactive or already
revoked requests return HTTP 409.
Session aggregate access
Aggregates have their own permission, even when filtered to your own sessions; see session endpoint requirements.
Omit user_id for organization-wide counts, trends, and risk metrics, or set
it to filter metrics to one owner within the organization.
Requests must include severity_threshold: low, medium, high, or
critical. For example:
curl -H "x-api-key: $WATCHER_API_KEY" \
'https://watcher.example.com/api/v1/sessions/aggregates?severity_threshold=critical'
For proxy mode, use your proxy's required credentials instead of x-api-key.
Endpoint requirements
Except for the public metadata and login endpoints listed below, these routes require valid authentication. A grant does not bypass input validation, organization scope, unavailable resources, or grading limits.
User required means an authenticated user identity. An API key cannot substitute for one, even if it has every public permission.
Sessions and tool calls
| Endpoint | Required grant or resource rule |
|---|---|
GET /v1/sessions, POST /v1/sessions/search, POST /v1/tool-calls/search, GET /v1/sessions/filter-options | sessions:findings:read:any for organization-wide results. Without it, users are limited to their own sessions; API keys receive 403. Searching session titles across owners also requires sessions:content:read:any. |
GET /v1/sessions/aggregates | sessions:aggregates:read:any, even for an own-session filter. |
GET /v1/sessions/{session_id}, GET /v1/sessions/{session_id}/trajectories | Findings access or content access. The response includes only the findings and content the caller may read. |
GET /v1/sessions/{session_id}/tool-call | Findings access, content access, or tool-calls:content:read:flagged for a qualifying call. Findings and call content are returned independently; the narrower grant does not expose the surrounding transcript. Missing, scrubbed or inaccessible resources return the same 404. |
GET /v1/sessions/{session_id}/tool-call-decision | Findings access. Raw tool-call and grade-input content are never returned here. |
GET /v1/sessions/{session_id}/trajectories/{trajectory_id}, GET /v1/sessions/{session_id}/messages/{message_ref} | Content access. Missing, scrubbed or inaccessible content returns 404. |
GET /v1/sessions/{session_id}/deep-review, GET /v1/sessions/{session_id}/deep-review/{operation_id} | Findings access to the session. |
DELETE /v1/sessions/{session_id}/content | Both org:settings:read and org:settings:write. Ownership alone does not permit scrubbing. |
POST /v1/ingest, POST /v1/sessions/{session_id}/trajectories/{trajectory_hash}/append | Signed-in user, sessions:ingest, or sessions:write:any; the write scope constrains which session can be written. |
POST /v1/tool-call-decisions/batch | Own the existing session or hold sessions:write:any. Ingest-only keys cannot attach decisions. |
Session write scope
Signed-in users normally write their own sessions. sessions:write:any permits
writes across owners and attribution to a developer. sessions:ingest alone
permits unattributed uploads and appends.
There is a shared-conversation exception: a Claude Code session ID or Codex thread ID identifies one session within an organization. Ingestion without a user attribution can join that session regardless of its owner; ingest-only credentials can also append to those sessions through the ingestion endpoint. Uploads attributed to a user can claim an unowned conversation, but cannot replace an existing owner. Treat an ingest-only key as able to append to other users' known conversations. See session ownership.
Grading, hooks and graders
Graders and managed monitors have different content-access requirements. A grant to run grading does not grant access to every result. Results respect findings and content access. Reading a full report through a trajectory endpoint needs both. Missing, scrubbed or unauthorized sessions return HTTP 404 when grading stored content. See the Python SDK for requesting and polling a review.
| Endpoint | Required grant or resource rule |
|---|---|
POST /v1/grade | For stored sessions: ownership or sessions:grade:any, plus content access. Supplied messages need no session-read grant. Attaching supplied-message grades to a session requires ownership or sessions:write:any. Private user-created graders require creator identity; unlisted built-ins remain runnable. |
POST /v1/monitors/{role}/grade | For stored sessions: ownership or sessions:grade:any; content access is not required to run the monitor. Attaching supplied-message grades to a session requires ownership or sessions:write:any. |
POST /v1/sessions/{session_id}/deep-review | Ownership or sessions:grade:any. Content access is not required to request the review. |
POST /v1/hooks/pre-tool-use | Authentication permits evaluation of supplied messages. Recording requires session-write authority. Hook append reads stored context: own the parent session, hold sessions:write:any, or use sessions:ingest for an unattributed session. The shared-conversation exception does not apply to hook append. See gateway hook behavior. |
POST /v1/hooks/aperture | sessions:write:any. |
GET /v1/monitors, GET /v1/graders/models | Authentication; no additional permission slug. |
GET /v1/graders, POST /v1/graders/search | Authentication. Private graders appear only for their creator. |
GET /v1/graders/{grader_id}, GET /v1/graders/{grader_id}/{version} | Authentication; private graders require creator identity. Missing or inaccessible graders return 404. |
POST /v1/graders | User required; API keys receive 403. |
PATCH /v1/graders/{grader_id}, DELETE /v1/graders/{grader_id} | Creator identity required. Built-in graders cannot be changed. Admin role and session grants do not bypass ownership. |
Settings, users and access requests
For managed-settings paths, {path} is settings.yaml.
| Endpoint | Required grant or resource rule |
|---|---|
GET /v1/organization/settings, GET /v1/organization/managed-settings/{path}, GET /v1/organization/managed-settings/{path}/schema, GET /v1/organization/managed-settings/{path}/monitors | org:settings:read. |
PATCH /v1/organization/settings, PUT /v1/organization/managed-settings/{path} | org:settings:write. |
GET /v1/organization/managed-settings/{path}/resolved | org:managed-settings:read or org:settings:read. |
GET /v1/monitors/customization, GET /v1/usage/grading | org:settings:read. |
PUT /v1/monitors/customization | org:settings:write. |
GET /v1/usage/limits | Authentication; no additional permission slug. |
GET /v1/users | users:read:any. |
GET /v1/users/{user_id} | Your own user ID or users:read:any. |
GET /v1/users/me, GET /v1/users/me/session-content-access-requests, POST /v1/users/me/session-content-access-requests | User required; no additional permission slug. Personal requests remain scoped to that user, including for admins. |
GET /v1/session-content-access-requests | sessions:access-requests:review. |
PATCH /v1/session-content-access-requests/{request_id}, POST /v1/session-content-access-requests/{request_id}/revoke | sessions:access-requests:review and a user identity. |
Authentication and public metadata
| Endpoint | Required grant or credential |
|---|---|
POST /v1/auth/check-permissions | Any accepted credential; no additional permission slug. |
GET /v1/auth/me | Signed-in user via bearer token or configured proxy authentication. API keys are not accepted. |
GET /v1/health, GET /v1/version, GET /v1/auth/client-config | No Watcher credential required. Your reverse proxy may still require authentication. |
POST /v1/auth/callback, POST /v1/auth/device/authorize, POST /v1/auth/device/token, POST /v1/auth/refresh, POST /v1/auth/logout | Sign-in and session-management endpoints, with no Watcher permission-slug requirement. They validate the login inputs or session credentials required by each operation. See Python authentication. |
Verify a credential
Check the grants your integration needs using that same credential. For an SSO-mode API key that only fetches resolved client settings:
curl -H "x-api-key: $WATCHER_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"permissions":["org:managed-settings:read"]}' \
https://watcher.example.com/api/v1/auth/check-permissions
A granted response is {"granted":true,"missing":[]}. Missing and unknown
slugs appear in missing, including for admins. In proxy mode, send your
proxy's required credentials instead of x-api-key.
This checks grants, not every resource rule. It does not prove ownership,
active approval, or availability of a stored document. Test the intended
endpoint too. For a signed-in user's effective grants and display role,
read GET /v1/auth/me.