# MCP tool reference

Generated from the tool registry (`packages/agent`). 108 tools. Connect with the same API token you use for the REST API; scopes gate each tool exactly as they gate the REST route it maps to. Every mutating call is audited (Settings → Agents → Activity, or `list_agent_activity`).

## Account

### `whoami`

Who am I? Returns how this connection is authenticated (API token or session), the organization(s) it can reach, the granted scopes, and the user behind it. Call this first. Works with any authenticated connection regardless of scopes.

- Scope: `org:read` · read-only
- REST: `GET /v1/whoami`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "whoami",
    "arguments": {}
  }
}
```

## Documentation

### `search_docs`

Full-text search over Bugwatch's documentation (setup, SDKs, API, MCP, alerts, billing, security). Returns slugs to pass to get_doc.

- Scope: `org:read` · read-only
- Arguments:
  - `query` string (required) — Search terms
  - `limit` integer

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_docs",
    "arguments": {
      "query": "source maps"
    }
  }
}
```

### `get_doc`

Read one documentation page as Markdown by slug (from search_docs or list_docs).

- Scope: `org:read` · read-only
- Arguments:
  - `slug` string (required) — Doc slug, e.g. quickstart

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_doc",
    "arguments": {
      "slug": "quickstart"
    }
  }
}
```

### `list_docs`

Table of contents of the documentation: slug, title, one-line description per page.

- Scope: `org:read` · read-only

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_docs",
    "arguments": {}
  }
}
```

## Projects

### `list_projects`

List the projects in an organization with slug, name, platform, and dashboard link.

- Scope: `project:read` · read-only
- REST: `GET /v1/orgs/{org}/projects`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_projects",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `get_project`

Project settings: name, platform, retention tier, store-IP flag, inbound filters, project number.

- Scope: `project:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_project",
    "arguments": {
      "org": "acme",
      "project": "checkout-api"
    }
  }
}
```

### `create_project`

Create a project. Returns its DSN-bearing key; pass the platform id (e.g. javascript-nextjs, python-django) when known.

- Scope: `project:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/projects`
- Arguments:
  - `org` string (required) — Organization slug
  - `slug` string (required) — lowercase letters, digits, dashes
  - `name` string (required)
  - `platform` string — Platform id from the registry (optional)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_project",
    "arguments": {
      "org": "acme",
      "slug": "checkout-api",
      "name": "Checkout API",
      "platform": "node"
    }
  }
}
```

### `update_project`

Update project name, platform, store-IP flag, or inbound filters (crawlers, legacy browsers, localhost, extensions, message deny list, allowed domains).

- Scope: `project:write` · **mutating, audited**
- REST: `PATCH /v1/orgs/{org}/projects/{project}`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `name` string
  - `platform` string
  - `storeIp` boolean — Keep client IP on events (default false)
  - `filters` object — Inbound filters

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_project",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "filters": {
        "dropCrawlers": true
      }
    }
  }
}
```

### `get_repo_connection`

Whether this organization has a repository connected for the fix agent, and which one. Connecting or disconnecting is human-only — it stores a credential that can write to source code.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/repo-connection`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_repo_connection",
    "arguments": {
      "org": "acme"
    }
  }
}
```

## Issues

### `search_issues`

List or search issues in a project (compact: id, level, status, title, impact, deep link). Filter by status, full-text `q`, sort, and page with `cursor`.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/issues`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `q` string — Free-text match on title/culprit
  - `status` string one of `unresolved`, `resolved`, `ignored`, `regressed`
  - `sort` string one of `last_seen`, `first_seen`
  - `limit` integer
  - `cursor` string — nextCursor from a previous call

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_issues",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "status": "unresolved",
      "limit": 10
    }
  }
}
```

### `get_issue`

Compact summary of one issue (title, culprit, status, impact, first/last seen, deep link). For frames and source context call get_fix_context.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/issues/{shortId}`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `shortId` integer (required) — Issue short id (the number in the dashboard URL)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_issue",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "shortId": 42
    }
  }
}
```

### `get_fix_context`

Everything needed to FIX an issue in one payload (JSON, can be large): symbolicated in-app frames with source context, breadcrumbs, release + commit, impact numbers, similar resolved issues. Call before attempting a fix.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/issues/{shortId}/fix-context`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `shortId` integer (required) — Issue short id (the number in the dashboard URL)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_fix_context",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "shortId": 42
    }
  }
}
```

### `get_issue_history`

Day-by-day events and estimated affected users for an issue, from the nightly rollup — so it reaches back FURTHER than the analytics window the issue chart uses (up to 400 days). Use it to answer whether an issue is new, recurring, or seasonal. Sampling-derived estimates, not exact counts.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/issues/{shortId}/history`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `shortId` integer (required) — Issue short id (the number in the dashboard URL)
  - `days` number — How far back to read, 1–400. Defaults to 90.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_issue_history",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "shortId": 42,
      "days": 365
    }
  }
}
```

### `get_issue_activity`

Activity feed for an issue: status changes, regressions, agent actions, with actor and time.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/issues/{shortId}/activity`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `shortId` integer (required) — Issue short id (the number in the dashboard URL)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_issue_activity",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "shortId": 42
    }
  }
}
```

### `resolve_issue`

Mark an issue resolved. Requires event:write; audited.

- Scope: `event:write` · **mutating, audited**
- REST: `PATCH /v1/orgs/{org}/projects/{project}/issues/{shortId}`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `shortId` integer (required) — Issue short id (the number in the dashboard URL)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "resolve_issue",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "shortId": 42
    }
  }
}
```

### `ignore_issue`

Ignore an issue (mute alerts). Requires event:write; audited.

- Scope: `event:write` · **mutating, audited**
- REST: `PATCH /v1/orgs/{org}/projects/{project}/issues/{shortId}`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `shortId` integer (required) — Issue short id (the number in the dashboard URL)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "ignore_issue",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "shortId": 42
    }
  }
}
```

### `unresolve_issue`

Reopen an issue (back to unresolved). Requires event:write; audited.

- Scope: `event:write` · **mutating, audited**
- REST: `PATCH /v1/orgs/{org}/projects/{project}/issues/{shortId}`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `shortId` integer (required) — Issue short id (the number in the dashboard URL)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "unresolve_issue",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "shortId": 42
    }
  }
}
```

### `bulk_update_issues`

Set the status of up to 100 issues at once (resolve, ignore, or reopen). Requires event:write; audited.

- Scope: `event:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/projects/{project}/issues/bulk`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `shortIds` array (required) — Issue short ids
  - `status` string (required) one of `unresolved`, `resolved`, `ignored`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "bulk_update_issues",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "shortIds": [
        42,
        43
      ],
      "status": "resolved"
    }
  }
}
```

### `import_issues`

Import a triaged issue backlog from another error tracker. Carries status, assignee and first/last seen; does NOT copy events — counts arrive as source metadata, never as live counters. DEFAULTS TO A DRY RUN: call once to see the coverage report, then again with dryRun=false. Export the issues from the old tracker and pass them here; Bugwatch never asks for a token to it. An issue that already exists here is left alone, because its triage was made in this product. Requires event:write.

- Scope: `event:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/projects/{project}/import-issues`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `source` string (required) — Which tracker this came from, e.g. sentry or bugsnag
  - `issues` array (required) — The exported issues. Each needs at least an `id`; `title`, `status`, `assignee` (email), `firstSeen`, `lastSeen`, `count`, `userCount`, `fingerprint` and `permalink` are used when present.
  - `dryRun` boolean — Default true. Set false to actually create the issues.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "import_issues",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "source": "sentry",
      "issues": [
        {
          "id": "4512",
          "title": "TypeError: x is not a function",
          "status": "resolved"
        }
      ]
    }
  }
}
```

### `resolve_issues_from_commit`

Resolve the issues a commit message closes. Reads GitHub-style trailers — `Fixes #42`, `Closes COMPAT-42` — and resolves those issues, recording the commit on each one's activity feed. A bare mention without a closing verb resolves nothing. Call it after pushing a fix, or from CI. Requires event:write.

- Scope: `event:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/projects/{project}/resolve-by-commit`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `sha` string (required) — Commit SHA
  - `message` string (required) — The full commit message, including its trailers
  - `url` string — Link to the commit

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "resolve_issues_from_commit",
    "arguments": {
      "org": "acme",
      "project": "web",
      "sha": "9f3a1c2",
      "message": "checkout: guard against a null cart\n\nFixes #412"
    }
  }
}
```

## Events

### `list_events`

List recent events for an issue (event id, timestamp, size), newest first; page with cursor. Use get_event for a body.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/issues/{shortId}/events`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `shortId` integer (required) — Issue short id (the number in the dashboard URL)
  - `cursor` string

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_events",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "shortId": 42
    }
  }
}
```

### `get_event`

Full processed event body (JSON, often 10–40 KB): exception with all frames, breadcrumbs, contexts, tags, request, sdk. Prefer get_fix_context unless you need everything.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/issues/{shortId}/events/{eventId}`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `shortId` integer (required) — Issue short id (the number in the dashboard URL)
  - `eventId` string (required) — 32-hex event id from list_events

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_event",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "shortId": 42,
      "eventId": "0123456789abcdef0123456789abcdef"
    }
  }
}
```

### `list_issue_attachments`

Files the SDK attached to this issue's events (screenshots, logs, view hierarchies): filename, type, size, event id, newest first. Metadata only; the bytes are a binary download from GET …/attachments/{id}, not a tool result.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/issues/{shortId}/attachments`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `shortId` integer (required) — Issue short id (the number in the dashboard URL)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_issue_attachments",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "shortId": 42
    }
  }
}
```

### `replay_raw_events`

Re-process raw events already stored for a project over a time window — for after a pipeline incident. DEFAULTS TO A DRY RUN: call it once to see what it would cover, then again with dryRun=false. Events keep their original ids, so anything the 24h dedup window still remembers is a no-op; a window reaching further back is refused unless acceptDoubleCount is set, because those events would be counted and billed a second time. Transactions are never replayed. Requires event:write.

- Scope: `event:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/projects/{project}/replay`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `from` number (required) — Window start, epoch ms
  - `to` number (required) — Window end, epoch ms
  - `dryRun` boolean — Default true. Set false to actually re-enqueue.
  - `acceptDoubleCount` boolean — Required to replay past the 24h dedup window; acknowledges that events will be re-counted and re-billed.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "replay_raw_events",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "from": 1757930000000,
      "to": 1757940000000
    }
  }
}
```

## Performance

### `query_performance`

Per-transaction performance overview for the last N hours: throughput, p50/p75/p95/p99 ms, failures. Sampling-aware.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/transactions`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `since` integer — Window in hours
  - `limit` integer

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "query_performance",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "since": 24
    }
  }
}
```

### `get_transaction`

One transaction in detail: throughput, p50/p75/p95/p99, failure rate, latency over time, the spans that own the most of its time, and the error issues seen in its recent traces (linked by trace id). Sampling-aware.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/transaction`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `name` string — Exact transaction name, e.g. 'GET /api/orders'
  - `since` integer — Window in hours
  - `interval` integer — Series bucket size in minutes one of `1`, `5`, `15`, `60`, `1440`
  - `spans` integer

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_transaction",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "name": "GET /api/orders",
      "since": 24
    }
  }
}
```

### `get_web_vitals`

p75 Core Web Vitals per transaction for the last N hours (LCP, FCP, CLS, INP, TTFB), sampling-aware. A vital with no measurements is reported as null, never as zero.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/vitals`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `since` integer — Window in hours
  - `limit` integer

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_web_vitals",
    "arguments": {
      "org": "acme",
      "project": "web-app",
      "since": 24
    }
  }
}
```

### `get_stats`

Time series of event volume (errors, transactions, or sessions) for the last N hours at a chosen interval. Sampling-aware estimates.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/stats`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `dataset` string one of `errors`, `transactions`, `sessions`
  - `interval` integer — Bucket size in minutes one of `1`, `5`, `15`, `60`, `1440`
  - `since` integer — Window in hours
  - `issue` string — Restrict to one issue id (internal id, not short id)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_stats",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "dataset": "errors",
      "since": 24,
      "interval": 60
    }
  }
}
```

## Releases

### `list_releases`

Releases in the organization: version, status, commit, deploy count, last deploy, artifact bundles.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/releases`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_releases",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `create_release`

Register a release version (optionally with commit SHA and URL). Requires release:write.

- Scope: `release:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/releases`
- Arguments:
  - `org` string (required) — Organization slug
  - `version` string (required)
  - `commitSha` string
  - `url` string

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_release",
    "arguments": {
      "org": "acme",
      "version": "1.4.2",
      "commitSha": "a3f9c2e"
    }
  }
}
```

### `create_deploy`

Record a deploy of a release to an environment. Requires release:write.

- Scope: `release:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/releases/{version}/deploys`
- Arguments:
  - `org` string (required) — Organization slug
  - `version` string (required)
  - `environment` string (required) — e.g. production
  - `name` string
  - `url` string

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_deploy",
    "arguments": {
      "org": "acme",
      "version": "1.4.2",
      "environment": "production"
    }
  }
}
```

### `get_release_health`

Session health of a release in a project: sessions, crashed/errored/abnormal, crash-free rate.

- Scope: `event:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/releases/{version}/health`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `version` string (required)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_release_health",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "version": "1.4.2"
    }
  }
}
```

## Alerts

### `list_channels`

Notification channels in the organization (Slack, webhook, email, PagerDuty) with masked targets.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/channels`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_channels",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `create_channel`

Create a notification channel. config is one of: {kind:'slack_webhook',url}, {kind:'discord',url}, {kind:'teams',url}, {kind:'webhook',url}, {kind:'email',to}, {kind:'pagerduty',routingKey}, {kind:'voice',to}. Each url is host-checked: Slack on hooks.slack.com, Discord on discord.com/api/webhooks, Teams on a .logic.azure.com Workflows URL; anything else uses 'webhook'. voice takes an E.164 number. Stored encrypted. A 'webhook' channel gets its own signing secret (signingSecret, shown once in this response): its receiver verifies x-bugwatch-signature with it. An 'email' channel sends nothing until its address follows the confirmation link mailed to it (the response says verified:false until then). Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/channels`
- Arguments:
  - `org` string (required) — Organization slug
  - `name` string (required)
  - `config` object (required) — Channel config (discriminated by kind)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_channel",
    "arguments": {
      "org": "acme",
      "name": "#alerts",
      "config": {
        "kind": "slack_webhook",
        "url": "https://hooks.slack.com/services/…"
      }
    }
  }
}
```

### `rotate_channel_secret`

Give a webhook channel a new signing secret, returned once; the old one stops verifying at the next delivery. Also how a channel listed with legacySigning:true (signed with the deployment's shared secret) gets a secret of its own. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/channels/{id}/rotate-secret`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Channel id (a webhook channel)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "rotate_channel_secret",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `delete_channel`

Delete a notification channel by id. Anything still pointing at it — services, monitors, alert rules — is detached in the same operation, so nothing is left holding a reference that pages nobody. A service left with no channels stops paging through channels entirely. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/channels/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Channel id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_channel",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `list_alert_rules`

Alert rules for a project: conditions (new_issue, regression, frequency), filters, channel actions, throttle, enabled.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/alert-rules`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_alert_rules",
    "arguments": {
      "org": "acme",
      "project": "checkout-api"
    }
  }
}
```

### `create_alert_rule`

Create an alert rule. conditions: [{type:'new_issue'}|{type:'regression'}|{type:'frequency',count,windowMinutes}]; filters: [{type:'environment'|'min_level'|'release',value}]; actions: [{type:'channel',channelId}]. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/projects/{project}/alert-rules`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `name` string (required)
  - `conditions` array (required)
  - `filters` array
  - `actions` array (required)
  - `environment` string
  - `frequencyS` integer — Min seconds between notifications per issue
  - `enabled` boolean

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_alert_rule",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "name": "New issues → Slack",
      "conditions": [
        {
          "type": "new_issue"
        }
      ],
      "actions": [
        {
          "type": "channel",
          "channelId": "01J…"
        }
      ]
    }
  }
}
```

### `update_alert_rule`

Replace an alert rule's definition (PATCH has replace semantics: send the full rule — name, conditions, actions — plus any of filters, environment, frequencyS, enabled). Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `PATCH /v1/orgs/{org}/projects/{project}/alert-rules/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `id` string (required) — Rule id
  - `name` string (required)
  - `conditions` array (required)
  - `filters` array
  - `actions` array (required)
  - `environment` string
  - `frequencyS` integer
  - `enabled` boolean

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_alert_rule",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "id": "01J…",
      "name": "New issues → Slack",
      "conditions": [
        {
          "type": "new_issue"
        }
      ],
      "actions": [
        {
          "type": "channel",
          "channelId": "01J…"
        }
      ],
      "enabled": false
    }
  }
}
```

### `delete_alert_rule`

Delete an alert rule by id. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/projects/{project}/alert-rules/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `id` string (required)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_alert_rule",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "id": "01J…"
    }
  }
}
```

## Uptime monitors

### `list_monitors`

Uptime monitors in the organization: state (up/degraded/down/paused/pending), type, target, last check and latency.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/monitors`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_monitors",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `get_monitor`

One uptime monitor: full check configuration (secrets redacted), interval, regions, thresholds, quorum, channels, current state and — for heartbeat monitors — the ping URL.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/monitors/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Monitor id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_monitor",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `create_monitor`

Create an uptime monitor. check is one of: {type:'http',url,method?,expectedStatus?,keyword?,keywordAbsent?,timeoutMs?,maxLatencyMs?}; {type:'tcp',host,port}; {type:'tls',host,port?}; {type:'dns',name,recordType?,expected?}; {type:'heartbeat',expectedEverySeconds,graceSeconds?} (returns a ping URL the job must call). intervalSeconds must be at or above the plan minimum; regions are Cloudflare location hints (wnam, enam, weur, eeur, apac, oc, afr, sam) and a DOWN needs `quorum` of them to agree. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/monitors`
- Arguments:
  - `org` string (required) — Organization slug
  - `name` string (required) — Display name
  - `check` object (required) — Check configuration, discriminated by type (http | tcp | tls | dns | heartbeat)
  - `intervalSeconds` integer — Seconds between checks
  - `regions` array — Probe regions
  - `failureThreshold` integer — Consecutive failures in a region before it counts as failing
  - `recoveryThreshold` integer — Consecutive passes before a region counts as passing
  - `quorum` integer — Regions that must agree before DOWN/UP (default ceil(regions/2))
  - `channelIds` array — Notification channel ids to page (see list_channels). With a serviceId set these are only the fallback, used if the escalation cannot start
  - `serviceId` string — Escalate through this service instead of paging channels: a DOWN opens an incident and its policy pages whoever is on call (see list_services)
  - `enabled` boolean

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_monitor",
    "arguments": {
      "org": "acme",
      "name": "Checkout API",
      "check": {
        "type": "http",
        "url": "https://api.example.com/health",
        "keyword": "ok"
      },
      "intervalSeconds": 60,
      "regions": [
        "wnam",
        "weur",
        "apac"
      ]
    }
  }
}
```

### `update_monitor`

Update an uptime monitor: send only the fields to change (name, check, intervalSeconds, regions, failureThreshold, recoveryThreshold, quorum, channelIds, serviceId, enabled). A replaced check replaces the whole object. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `PATCH /v1/orgs/{org}/monitors/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Monitor id
  - `name` string
  - `check` object — Replacement check configuration (same shapes as create_monitor)
  - `intervalSeconds` integer
  - `regions` array
  - `failureThreshold` integer
  - `recoveryThreshold` integer
  - `quorum` integer
  - `channelIds` array
  - `serviceId` string — Escalate through this service; null detaches it
  - `enabled` boolean

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_monitor",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "intervalSeconds": 30,
      "channelIds": [
        "01J…"
      ]
    }
  }
}
```

### `delete_monitor`

Delete an uptime monitor and stop its probes. History stays queryable until it ages out. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/monitors/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Monitor id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_monitor",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `pause_monitor`

Pause an uptime monitor: probes stop and it never pages until resumed (use this during planned maintenance). Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/monitors/{id}/pause`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Monitor id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "pause_monitor",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `resume_monitor`

Resume a paused uptime monitor. It restarts in the pending state and pages again once the consensus is DOWN. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/monitors/{id}/resume`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Monitor id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "resume_monitor",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `snooze_monitor`

Quiet a monitor's paging for 15, 60, 240 or 1440 minutes while somebody works on it (#209). Checks, state and status pages carry on; only the DOWN page is withheld, and a monitor still down when the snooze ends is paged then. Never indefinite. To stop an open incident paging, acknowledge it instead. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/monitors/{id}/snooze`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Monitor id
  - `minutes` integer (required) — How long, in minutes one of `15`, `60`, `240`, `1440`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "snooze_monitor",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "minutes": 60
    }
  }
}
```

### `unsnooze_monitor`

Lift a monitor's snooze now. If it is down, the withheld page goes out at its next check. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/monitors/{id}/snooze`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Monitor id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "unsnooze_monitor",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `get_monitor_history`

Uptime and latency history for one monitor: per-bucket uptime % with p50/p95, plus a per-region summary over the window. Sampling-aware estimates.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/monitors/{id}/history`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Monitor id
  - `since` integer — Window in hours
  - `interval` integer — Bucket size in minutes one of `1`, `5`, `15`, `60`, `1440`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_monitor_history",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "since": 24,
      "interval": 5
    }
  }
}
```

### `list_monitor_events`

State-change history for one monitor (most recent 100): when it went up, degraded, down or paused, how many regions were failing, and why.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/monitors/{id}/events`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Monitor id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_monitor_events",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

## On-call schedules

### `list_schedules`

On-call schedules in the organization: name, timezone and how many rotation layers each has.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/schedules`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_schedules",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `get_schedule`

One on-call schedule in full: timezone, every rotation layer (type, handoff wall-clock time, rotation order, restrictions, active dates) and every override.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/schedules/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Schedule id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_schedule",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `who_is_on_call`

Who is on call for a schedule at an instant (default: now). Returns the user, whether an override or a rotation layer decided it, and null when the window is UNCOVERED — a gap is a real answer, not an error.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/schedules/{id}/on-call`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Schedule id
  - `at` integer — Epoch ms; defaults to now

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "who_is_on_call",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `get_schedule_coverage`

Coverage for a schedule over a window (default: the next 7 days, 90 days maximum) as contiguous segments. Uncovered windows appear as segments with a null user — use this to find gaps before they page nobody.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/schedules/{id}/coverage`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Schedule id
  - `from` integer — Window start, epoch ms; defaults to now
  - `to` integer — Window end, epoch ms; defaults to 7 days after from

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_schedule_coverage",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `create_schedule`

Create an on-call schedule. `timeZone` is an IANA zone name (never a UTC offset — offsets change, zones don't) and handoffs are wall-clock times in it, so a Monday 09:00 handoff stays 09:00 across DST. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/schedules`
- Arguments:
  - `org` string (required) — Organization slug
  - `name` string (required)
  - `timeZone` string (required) — IANA zone, e.g. America/New_York
  - `teamId` string
  - `layers` array (required) — Rotation layers, highest position wins. Each: {name, position, rotationType: daily|weekly|custom, turnLengthSeconds (custom), handoffLocalTime like "09:00" (WALL CLOCK in the schedule timezone), handoffWeekday (weekly), startDate/endDate epoch ms, users (rotation order), restrictions [{kind: daily|weekly, startLocalTime, durationSeconds, weekday}].

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_schedule",
    "arguments": {
      "org": "acme",
      "name": "Platform primary",
      "timeZone": "America/New_York",
      "layers": [
        {
          "name": "Weekly primary",
          "position": 1,
          "rotationType": "weekly",
          "handoffLocalTime": "09:00",
          "handoffWeekday": 1,
          "startDate": 1767225600000,
          "users": [
            "usr_ada",
            "usr_bo",
            "usr_cy"
          ],
          "restrictions": []
        }
      ]
    }
  }
}
```

### `update_schedule`

Update a schedule's name, timezone, team or layers. Sending `layers` REPLACES the whole stack — positions, rotation order and restrictions only make sense as a set. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `PATCH /v1/orgs/{org}/schedules/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Schedule id
  - `name` string
  - `timeZone` string
  - `teamId` string
  - `layers` array — Rotation layers, highest position wins. Each: {name, position, rotationType: daily|weekly|custom, turnLengthSeconds (custom), handoffLocalTime like "09:00" (WALL CLOCK in the schedule timezone), handoffWeekday (weekly), startDate/endDate epoch ms, users (rotation order), restrictions [{kind: daily|weekly, startLocalTime, durationSeconds, weekday}].

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_schedule",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "timeZone": "Europe/London"
    }
  }
}
```

### `delete_schedule`

Delete an on-call schedule. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/schedules/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Schedule id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_schedule",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `create_override`

Put someone on call for a window regardless of the rotation — a cover for illness, holiday or a handover. Overrides beat every layer. Half-open: [startsAt, endsAt). Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/schedules/{id}/overrides`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Schedule id
  - `userId` string (required) — User taking the shift
  - `startsAt` integer (required) — Epoch ms, inclusive
  - `endsAt` integer (required) — Epoch ms, exclusive

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_override",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "userId": "usr_sam",
      "startsAt": 1767225600000,
      "endsAt": 1767268800000
    }
  }
}
```

### `delete_override`

Remove an override, handing the window back to the rotation. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/schedules/{id}/overrides/{overrideId}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Schedule id
  - `overrideId` string (required) — Override id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_override",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "overrideId": "01J…"
    }
  }
}
```

### `list_devices`

The phones registered to you in this organization, with when each last confirmed a delivery. Push tokens are never returned — this is for recognising a device, not for reading its credentials. A device that stopped confirming deliveries is going stale and stops being counted on for pages.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/devices`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_devices",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `revoke_device`

Stop paging one of your devices and clear its push credentials — a lost or replaced phone. Yours only; revoking somebody else's device would take them off call without telling them. Requires a session.

- Scope: `org:read` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/devices/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Device id from list_devices

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "revoke_device",
    "arguments": {
      "org": "acme",
      "id": "01JDEVICE"
    }
  }
}
```

### `get_notification_rules`

Your notification rules for this organization: which categories are on, quiet hours, active mutes and which devices take pages. Pages are not among the things that can be switched off — an escalation targeting you always rings.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/notification-prefs`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_notification_rules",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `set_notification_rules`

Replace your notification rules for this organization. Send the whole object. `categories` may not switch `page` off, and every mute needs an expiry (at most 30 days) — an indefinite silent mute is how a page gets missed. Requires a session.

- Scope: `org:read` · **mutating, audited**
- REST: `PUT /v1/orgs/{org}/notification-prefs`
- Arguments:
  - `org` string (required) — Organization slug
  - `categories` object — Category → on/off. `page` cannot be false.
  - `quietHours` object — {startLocalTime:"22:00", endLocalTime:"08:00", timeZone:"Europe/London"} or null. Never applies to pages.
  - `mutes` array — [{scope:{kind:"all_non_page"|"category"|"thread", …}, until: epochMs}]
  - `pageDeviceIds` array — Devices that take pages; empty means all of them

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "set_notification_rules",
    "arguments": {
      "org": "acme",
      "quietHours": {
        "startLocalTime": "22:00",
        "endLocalTime": "08:00",
        "timeZone": "Europe/London"
      }
    }
  }
}
```

### `diff_imported_schedule`

Compare an Opsgenie or PagerDuty schedule against one of ours over a 30-day window, without importing anything. Pass the provider's schedule payload as `source`, name the `provider`, and give a `userMap` from their usernames to our user ids. Returns what the mapping could not carry across, then the coverage differences. Read-only: it stores nothing and cannot activate a rotation — a migration needs human sign-off before it pages anybody.

- Scope: `org:read` · read-only
- REST: `POST /v1/orgs/{org}/schedules/{id}/import-diff`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Schedule id
  - `provider` string — Which system the payload came from. Default opsgenie. one of `opsgenie`, `pagerduty`
  - `source` object (required) — The provider's schedule payload — Opsgenie (id, timezone, rotations[]) or PagerDuty (id, time_zone, schedule_layers[], overrides[])
  - `userMap` object — Their username or id → our user id. Unmapped people are reported, never assumed.
  - `from` integer — Window start, epoch ms. Defaults to now.
  - `days` integer — Window length in days (1–30, default 30)
  - `boundaryToleranceMs` integer — Differences shorter than this are listed separately as boundary noise. Never reduces uncovered time.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "diff_imported_schedule",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "source": {
        "id": "sch-og",
        "timezone": "Europe/London",
        "rotations": []
      },
      "userMap": {
        "ada@acme.test": "usr_ada"
      }
    }
  }
}
```

## Incidents & escalation

### `list_incidents`

Incidents in the organization, newest first. Filter by status (triggered, acknowledged, resolved) — `triggered` is the list that matters during an outage: nobody has picked those up yet.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/incidents`
- Arguments:
  - `org` string (required) — Organization slug
  - `status` string one of `triggered`, `acknowledged`, `resolved`
  - `limit` integer

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_incidents",
    "arguments": {
      "org": "acme",
      "status": "triggered"
    }
  }
}
```

### `get_incident`

One incident with its full timeline: when it triggered, every escalation step, who was paged on which channel, who acknowledged and when it resolved. Read this before asking anyone what happened.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/incidents/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Incident id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_incident",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `trigger_incident`

Open an incident and start its escalation chain. A repeated call with the same dedupKey joins the open incident instead of paging a second time. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/incidents`
- Arguments:
  - `org` string (required) — Organization slug
  - `serviceId` string (required) — Service id
  - `title` string (required)
  - `summary` string
  - `severity` string one of `sev1`, `sev2`, `sev3`, `sev4`
  - `urgency` string one of `low`, `high`
  - `dedupKey` string — Repeated triggers with this key join the open incident

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "trigger_incident",
    "arguments": {
      "org": "acme",
      "serviceId": "01J…",
      "title": "Checkout API returning 500s",
      "severity": "sev2"
    }
  }
}
```

### `acknowledge_incident`

Acknowledge an incident: escalation stops immediately. If the service sets an ack timeout and nobody resolves it, it re-escalates — acking is not a way to make it go away. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/incidents/{id}/acknowledge`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Incident id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "acknowledge_incident",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `resolve_incident`

Resolve an incident: escalation stops and the timer clears. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/incidents/{id}/resolve`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Incident id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "resolve_incident",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `list_escalation_policies`

Escalation policies: how many rules each chain has, whether it repeats, and its fallback target.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/escalation-policies`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_escalation_policies",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `get_escalation_policy`

One escalation policy: every rule in order, its targets (schedule or user), delay and urgency.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/escalation-policies/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Escalation policy id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_escalation_policy",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `create_escalation_policy`

Create an escalation policy. Rule 0 fires the moment an incident opens; each rule's delaySeconds is how long to wait before the NEXT one. Set fallbackUserId so an uncovered rotation still reaches somebody. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/escalation-policies`
- Arguments:
  - `org` string (required) — Organization slug
  - `name` string (required)
  - `rules` array (required) — Ordered escalation rules. Each: {position (0 fires immediately), delaySeconds (wait before the NEXT rule), urgency: low|high, targets: [{kind: schedule|user, targetId}]}. Low urgency notifies without paging.
  - `repeatCount` integer — Times to run the whole chain again
  - `repeatDelaySeconds` integer
  - `fallbackUserId` string — Paged when a rule resolves to nobody
  - `teamId` string

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_escalation_policy",
    "arguments": {
      "org": "acme",
      "name": "Platform chain",
      "fallbackUserId": "usr_lead",
      "rules": [
        {
          "position": 0,
          "delaySeconds": 300,
          "urgency": "high",
          "targets": [
            {
              "kind": "schedule",
              "targetId": "01J…"
            }
          ]
        },
        {
          "position": 1,
          "delaySeconds": 300,
          "urgency": "high",
          "targets": [
            {
              "kind": "user",
              "targetId": "usr_lead"
            }
          ]
        }
      ]
    }
  }
}
```

### `update_escalation_policy`

Update a policy's name, repeat behaviour, fallback or rules. Sending `rules` REPLACES the whole chain — positions and targets only mean anything as an ordered set. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `PATCH /v1/orgs/{org}/escalation-policies/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Escalation policy id
  - `name` string
  - `rules` array — Ordered escalation rules. Each: {position (0 fires immediately), delaySeconds (wait before the NEXT rule), urgency: low|high, targets: [{kind: schedule|user, targetId}]}. Low urgency notifies without paging.
  - `repeatCount` integer
  - `repeatDelaySeconds` integer
  - `fallbackUserId` string

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_escalation_policy",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "repeatCount": 2
    }
  }
}
```

### `delete_escalation_policy`

Delete an escalation policy. Services pointing at it stop paging, so repoint them first. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/escalation-policies/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Escalation policy id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_escalation_policy",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `list_services`

Services: what an incident is about, which escalation policy it pages, which channels it notifies, and its auto-resolve and ack-timeout settings.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/services`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_services",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `create_service`

Create a service: a name, the escalation policy its incidents page, the channels they notify, and optionally the product-line-1 project it belongs to. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/services`
- Arguments:
  - `org` string (required) — Organization slug
  - `name` string (required)
  - `escalationPolicyId` string — Escalation policy id
  - `projectId` string — Links the service to a Bugwatch project
  - `channelIds` array — Notification channels to page
  - `autoResolveSeconds` integer
  - `ackTimeoutSeconds` integer — Re-escalate an ack nobody followed up

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_service",
    "arguments": {
      "org": "acme",
      "name": "Checkout",
      "escalationPolicyId": "01J…",
      "channelIds": [
        "01J…"
      ],
      "ackTimeoutSeconds": 1800
    }
  }
}
```

### `update_service`

Update a service's name, policy, channels, project link, auto-resolve or ack timeout. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `PATCH /v1/orgs/{org}/services/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Service id
  - `name` string
  - `escalationPolicyId` string — Escalation policy id
  - `projectId` string
  - `channelIds` array
  - `serviceId` string — Escalate through this service; null detaches it
  - `autoResolveSeconds` integer
  - `ackTimeoutSeconds` integer

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_service",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "ackTimeoutSeconds": 900
    }
  }
}
```

### `delete_service`

Delete a service. Its open incidents keep their escalation; new alerts for it stop. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/services/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Service id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_service",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `get_incident_context`

What else changed around an incident: error rate and probe latency before vs after it opened, and deploys in the two hours before. Ranked evidence for a responder who has already been paged — it never delays or replaces a page, and cannot acknowledge, resolve or suppress anything. Says plainly when nothing stood out, and which checks it was able to run.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/incidents/{id}/context`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Incident id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_incident_context",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `draft_incident_retro`

A retro draft assembled from the incident's own timeline: how long detection, acknowledgement and the fix each took, what the response itself got wrong (pages that were never delivered, a chain that reached its fallback, nobody answering), and the questions left to fill in. It measures; it does not write the analysis. Read-only.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/incidents/{id}/retro`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Incident id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "draft_incident_retro",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `get_incident_brief`

Catch up on an incident that is still happening: whether anybody has it, who has been paged and who answered, what has already been tried, and what is wrong with the RESPONSE itself — pages that were never delivered, a service with no escalation policy, a source that fired again. Written for somebody who was just paged and has ten seconds. Distinguishes 'nobody answered' from 'nothing was ever delivered', which have different next actions. Read-only.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/incidents/{id}/brief`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Incident id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_incident_brief",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `draft_status_update`

A status-page update drafted for CUSTOMERS from what the incident records: the stage (investigating, identified, monitoring, resolved), which components are affected in the words the public page uses, how long it has been going on, and when the next update is promised. It never states a cause, never names anything internal, and refuses to write an all-clear while a component is still reporting a fault. Optional `stage` asks for `monitoring`; optional `nextUpdateMinutes` sets the cadence. Read-only — publishing is a human action.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/incidents/{id}/status-draft`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Incident id
  - `stage` string — Ask for a stage. Only `monitoring` is honoured, and only once the incident is acknowledged — every other stage is derived from the incident itself, because an operator who can choose the stage can choose `resolved` during an outage. one of `investigating`, `identified`, `monitoring`, `resolved`
  - `nextUpdateMinutes` number — Minutes until the next promised update. Defaults per stage.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "draft_status_update",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

## Status pages

### `list_status_pages`

Public status pages in the organization: slug, public URL, and whether each is published.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/status-pages`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_status_pages",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `get_status_page`

One status page with its components, in display order, and what each component reads its health from.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/status-pages/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Status page id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_status_page",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `create_status_page`

Create a public status page. `components` are what readers see, in order — each one a public NAME for a monitor or service, so nobody outside learns your monitor ids. Pages are unpublished until `published` is true. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/status-pages`
- Arguments:
  - `org` string (required) — Organization slug
  - `slug` string (required) — Public URL segment: lowercase letters, digits and dashes
  - `name` string (required) — Page title, e.g. "Acme Status"
  - `headline` string — One line shown above the components
  - `published` boolean — False (the default) authors the page without serving it
  - `components` array — What the page shows, in display order. Each: {name (the public label, e.g. "Checkout"), description?, sourceKind: monitor|service, sourceId}. The name is what readers see — a monitor id never reaches the page.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_status_page",
    "arguments": {
      "org": "acme",
      "slug": "acme",
      "name": "Acme Status",
      "published": true,
      "components": [
        {
          "name": "Checkout",
          "sourceKind": "monitor",
          "sourceId": "01J…"
        }
      ]
    }
  }
}
```

### `update_status_page`

Update a status page. Sending `components` REPLACES the whole list — order is the display order, so it only means anything as a set. Setting `published` to false takes the page down immediately. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `PATCH /v1/orgs/{org}/status-pages/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Status page id
  - `slug` string — Changing this changes the public URL and retires the old one
  - `name` string
  - `headline` string
  - `published` boolean
  - `components` array — What the page shows, in display order. Each: {name (the public label, e.g. "Checkout"), description?, sourceKind: monitor|service, sourceId}. The name is what readers see — a monitor id never reaches the page.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_status_page",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "published": true
    }
  }
}
```

### `delete_status_page`

Delete a status page. Its public URL stops answering immediately. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/status-pages/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Status page id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_status_page",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `list_status_page_domains`

Custom domains on a status page: the hostname, whether it is live, and the DNS records still waiting to be added. `state` is pending, active or failed — a domain serves traffic only when it is active.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/status-pages/{id}/domains`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Status page id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_status_page_domains",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `add_status_page_domain`

Point a hostname you own at a status page. Returns the DNS records to add; the domain goes live on its own once they resolve and the certificate issues — usually minutes. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/status-pages/{id}/domains`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Status page id
  - `hostname` string (required) — The hostname to serve the page on, e.g. status.acme.com. No wildcards.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "add_status_page_domain",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "hostname": "status.acme.com"
    }
  }
}
```

### `verify_status_page_domain`

Re-check a custom domain with the certificate authority now, instead of waiting for the next automatic check. Use after adding the DNS records. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/status-pages/{id}/domains/{domainId}/verify`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Status page id
  - `domainId` string (required) — Custom domain id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "verify_status_page_domain",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "domainId": "01J…"
    }
  }
}
```

### `remove_status_page_domain`

Stop serving a status page on a custom domain and release its certificate. The hostname stops answering immediately. Requires alert:write.

- Scope: `alert:write` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/status-pages/{id}/domains/{domainId}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Status page id
  - `domainId` string (required) — Custom domain id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "remove_status_page_domain",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "domainId": "01J…"
    }
  }
}
```

### `list_maintenance`

Scheduled maintenance windows on a status page, newest first, including past ones. A window with no components covers the whole page.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/status-pages/{id}/maintenance`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Status page id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_maintenance",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `schedule_maintenance`

Schedule a maintenance window on a status page. Components inside an open window read 'maintenance' — unless they are genuinely down, which always wins. Omit componentIds to cover the whole page. Times are epoch ms. Requires alert:write; audited.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/status-pages/{id}/maintenance`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Status page id
  - `title` string (required) — What the notice says, e.g. "Database upgrade".
  - `body` string — Optional detail shown under the title.
  - `startsAt` number (required) — Epoch ms when the window opens.
  - `endsAt` number (required) — Epoch ms when it closes. Must be after startsAt.
  - `componentIds` array — Component ids this covers. Omit or leave empty for the whole page.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "schedule_maintenance",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "title": "Database upgrade",
      "startsAt": 1789000000000,
      "endsAt": 1789007200000
    }
  }
}
```

### `cancel_maintenance`

Cancel a scheduled maintenance window. The status page stops showing it immediately. Requires alert:write; audited.

- Scope: `alert:write` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/status-pages/{id}/maintenance/{windowId}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Status page id
  - `windowId` string (required) — The window's id.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "cancel_maintenance",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "windowId": "01K…"
    }
  }
}
```

### `list_status_incidents`

Incidents this status page is showing right now — open ones and any resolved in the last 7 days — each with the stage readers see and every update published about it. This is the page's own view, so it is what readers see and not a separate report of it.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/status-pages/{id}/incidents`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Status page id

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_status_incidents",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

### `post_status_update`

Publish an update about an incident to a status page: the stage it is at (investigating, identified, monitoring, resolved) and what you want readers to know, in your words. This is PUBLIC the moment it is written — the page rebuilds immediately. The stage you set is what the page shows, overriding the one derived from whether anybody has acknowledged the page; a closed incident always reads resolved whatever was last published. Requires alert:write; audited.

- Scope: `alert:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/status-pages/{id}/incidents/{incidentId}/updates`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required) — Status page id
  - `incidentId` string (required) — The incident to publish about. It must be one this page shows.
  - `state` string (required) — investigating: we know something is wrong. identified: we know what. monitoring: a fix is out and we are watching. resolved: it is over. one of `investigating`, `identified`, `monitoring`, `resolved`
  - `body` string (required) — What readers are told. Plain prose; no internal names or responder identities.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "post_status_update",
    "arguments": {
      "org": "acme",
      "id": "01J…",
      "incidentId": "01K…",
      "state": "identified",
      "body": "A bad deploy is returning errors on checkout. We are rolling it back."
    }
  }
}
```

## DSN keys

### `list_keys`

DSN keys for a project (public key, DSN, label, status). Use the DSN to configure an SDK.

- Scope: `project:read` · read-only
- REST: `GET /v1/orgs/{org}/projects/{project}/keys`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_keys",
    "arguments": {
      "org": "acme",
      "project": "checkout-api"
    }
  }
}
```

### `create_key`

Create an additional DSN key for a project (for rotation or a second environment). Requires project:write.

- Scope: `project:write` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/projects/{project}/keys`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `label` string

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_key",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "label": "staging"
    }
  }
}
```

### `set_key_status`

Enable or disable a DSN key. Disabled keys are rejected at ingest within seconds. Requires project:write.

- Scope: `project:write` · **mutating, audited**
- REST: `PATCH /v1/orgs/{org}/projects/{project}/keys/{keyId}`
- Arguments:
  - `org` string (required) — Organization slug
  - `project` string (required) — Project slug
  - `keyId` string (required)
  - `status` string (required) one of `active`, `disabled`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "set_key_status",
    "arguments": {
      "org": "acme",
      "project": "checkout-api",
      "keyId": "01J…",
      "status": "disabled"
    }
  }
}
```

## Members

### `list_members`

Organization members with role (admin, member, viewer) and email.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/members`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_members",
    "arguments": {
      "org": "acme"
    }
  }
}
```

## API tokens

### `list_tokens`

API tokens in the organization (name, scopes, last used, expiry). Secrets are never returned. Requires org:admin.

- Scope: `org:admin` · read-only
- REST: `GET /v1/orgs/{org}/tokens`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_tokens",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `revoke_token`

Revoke an API token by id. Anything using it stops working immediately. Requires org:admin.

- Scope: `org:admin` · **mutating, audited**
- REST: `DELETE /v1/orgs/{org}/tokens/{id}`
- Arguments:
  - `org` string (required) — Organization slug
  - `id` string (required)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "revoke_token",
    "arguments": {
      "org": "acme",
      "id": "01J…"
    }
  }
}
```

## Billing & usage

### `get_agent_usage`

Fix-agent credits and usage: this month's allowance and what is left of it, purchased credit remaining, which models the plan may use and what they cost per million tokens, and the most recent runs.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/agent-usage`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_agent_usage",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `get_usage`

Exact usage and plan for the organization: plan limits, overage flag, daily accepted events by category, credit balance and expiry.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/usage`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_usage",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `get_billing_plans`

Plan catalog with prices, included events, retention, overage rate, and whether checkout is available.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/billing/plans`
- Arguments:
  - `org` string (required) — Organization slug

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_billing_plans",
    "arguments": {
      "org": "acme"
    }
  }
}
```

### `set_overage`

Turn pay-as-you-go overage on or off (off = hard cap at the plan's included events). Requires org:admin.

- Scope: `org:admin` · **mutating, audited**
- REST: `POST /v1/orgs/{org}/billing/overage`
- Arguments:
  - `org` string (required) — Organization slug
  - `enabled` boolean (required)

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "set_overage",
    "arguments": {
      "org": "acme",
      "enabled": true
    }
  }
}
```

## Agents

### `list_agent_activity`

Recent agent (MCP) actions across the organization: tool, arguments, token, outcome, time.

- Scope: `org:read` · read-only
- REST: `GET /v1/orgs/{org}/agent-activity`
- Arguments:
  - `org` string (required) — Organization slug
  - `limit` integer

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_agent_activity",
    "arguments": {
      "org": "acme"
    }
  }
}
```

## Routes without a tool

- `POST /v1/orgs/{org}/tokens` — Agents must not mint credentials; tokens are created by a human in Settings → Agents / API tokens.
- `POST /v1/orgs/{org}/members` — Adding a member (or changing a role) grants access that outlives the credential that granted it, so a leaked token or a misled agent could keep its foothold after revocation (#276). Human-only, in Settings → Members.
- `GET /v1/orgs/{org}/projects/{project}/attachments/{id}` — The bytes of a file an SDK attached to an event: binary and always served as a download, not something a tool result can carry. Agents get the metadata from list_issue_attachments; the file is fetched from this route with an event:read token, or from the issue page (#240).
- `GET /v1/orgs/{org}/export` — A bulk download of every row the org owns, streamed as NDJSON for a backup or a move; far beyond what a tool result can carry. Use curl with an org:admin token (P2.2).
- `GET /v1/orgs/{org}/projects/{project}/export/events` — A bulk stream of a project's stored event bodies from R2; a download, not a tool result (P2.2).
- `DELETE /v1/orgs/{org}` — Deleting an organization erases all of its data and cannot be undone. Owner-only, signed-in-only, with the slug typed back, in Settings → General (P2.2).
- `DELETE /v1/orgs/{org}/members/{userId}` — A leaked token must not be able to remove the admins who would revoke it (#276). Human-only, in Settings → Members.
- `POST /v1/orgs/{org}/billing/checkout` — Browser redirect to Stripe Checkout; purchases stay human-initiated.
- `POST /v1/orgs/{org}/billing/portal` — Browser redirect to the Stripe Billing Portal.
- `POST /v1/orgs/{org}/billing/agent-credits/checkout` — Browser redirect to Stripe Checkout for a fix-agent credit pack; purchases stay human-initiated.
- `POST /v1/orgs/{org}/projects/{project}/artifact-bundles` — Binary source-map upload; use sentry-cli or the REST endpoint directly.
- `POST /v1/orgs/{org}/repo-connection` — Stores a credential that can write to the customer's source code; connecting a repository stays human-initiated, like minting a token.
- `DELETE /v1/orgs/{org}/repo-connection` — The same human-only gate as connecting: an agent must not be able to sever, or silently re-point, the org's source-code access.
- `POST /v1/orgs/{org}/projects/{project}/issues/{shortId}/fix-agent` — Server-sent event stream that runs a model; an agent wanting the same material should call get_fix_context and do its own reasoning rather than nest an agent inside a tool call.
- `POST /v1/orgs/{org}/devices` — A device registers itself with its own push credentials from the app; there is nothing for an agent to do here, and a tool that could write push tokens could redirect somebody's pages to another handset.
- `POST /v1/orgs/{org}/chat` — The mobile companion's own model loop, which runs THESE tools. An agent calling it would be nesting an agent inside a tool call to reach the same registry it already has — the same reason the fix-agent stream is exempt. Its mutations are the mobile allowlist and each needs a person to tap a confirmation card, which a tool call cannot do.
- `POST /v1/orgs/{org}/test-page` — Rings the caller's own physical phone at critical-alert insistence to prove their paging setup works. Session-only by construction, and making somebody's handset ring is a thing a person asks for about their own hardware, not a thing an agent does on their behalf.
- `GET /v1/orgs/{org}/devices/vapid-key` — The application server key a BROWSER needs to call PushManager.subscribe(). It is public key material for a client-side API an agent has no way to call; list_devices already reports what is registered.
- `POST /v1/orgs/{org}/services/{id}/inbound-key` — Mints a credential that can open an incident on this service, shown exactly once. Minting stays human-initiated for the same reason API tokens do — and rotating one silently breaks every integration still pointed at the old key, which is a decision a person makes.
- `POST /v1/orgs/{org}/devices/{id}/receipt` — A handset confirms a push it actually received. An agent confirming on its behalf would mark a silent device fresh, which is exactly the state the receipt exists to detect.
