# Scopes



Every operation in the public API declares the scope it requires. A credential carries a set of scopes, and the intersection of that set with the creator's workspace role is what is actually enforced.

There are **29 grantable scopes** in three verbs.

```
read:   overview workspaces domains uptime ssl crux synthetic rum analytics
        gsc indexing robots sitemap alerts notifications reports credits

write:  crux synthetic analytics gsc indexing robots alerts notifications reports

run:    analytics gsc reports
```

| Verb             | What it means                                                                                                                        |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `read:<module>`  | Returns data. Every module has one.                                                                                                  |
| `write:<module>` | Creates, updates, or deletes stored configuration. Only modules with a real configuration surface in the dashboard have one.         |
| `run:<module>`   | Starts asynchronous or external work that spends something. Only modules with a job trigger the dashboard actually exposes have one. |

Three verbs rather than two, because the HTTP method does not tell you whether an operation mutates anything. Some POSTs change nothing, and some POSTs spend credits. Folding both into one `write:` scope would let a credential granted to rename an alert rule also burn the workspace's credit balance.

## Implication rules [#implication-rules]

* **`write:X` implies `read:X`.** You cannot sensibly edit configuration you cannot read, and requiring both would only produce credentials that fail halfway through a workflow.
* **`run:X` implies nothing.** Triggering a job and editing configuration are orthogonal risks. If you need both, ask for both.

## There is no `read:all` [#there-is-no-readall]

<Callout type="warning">
  `read:all`, `write:all`, and `run:all` do not exist and never will. All three strings are permanently blocked, so every credential has to name what it needs.
</Callout>

That is a few extra seconds when you create a key, and it is the difference between a leaked key being an incident and being a shrug:

* **`write:` scopes fail silently.** A credential that can delete an alert rule stops alerts a team relies on, with no visible symptom until something breaks unnoticed.
* **`run:` scopes spend.** Every `run:` call costs 25 API credits, the highest price in the table.
* **The convenience `read:all` used to serve is now `read:overview`.** One scope, one request, the cross-module summary for a domain and nothing beneath it. That is a materially smaller blast radius than every module's full detail, and it is one request instead of eight.

Ask for the narrowest set that does the job. If you only need a health summary, `read:overview` and `read:domains` is the whole grant.

## Read scopes [#read-scopes]

| Scope                | Grants                                                        |
| -------------------- | ------------------------------------------------------------- |
| `read:overview`      | The cross-module summary for a domain, with period comparison |
| `read:workspaces`    | Your workspaces and their members                             |
| `read:domains`       | The domains in your workspaces                                |
| `read:uptime`        | Uptime Monitoring data                                        |
| `read:ssl`           | SSL certificate data                                          |
| `read:crux`          | CrUX Monitoring data                                          |
| `read:synthetic`     | Synthetic Monitoring data                                     |
| `read:rum`           | RUM Monitoring data                                           |
| `read:analytics`     | Web Analytics data                                            |
| `read:gsc`           | Google Search Console data                                    |
| `read:indexing`      | Indexing Monitoring data                                      |
| `read:robots`        | Robots.txt Monitoring data                                    |
| `read:sitemap`       | Sitemap scanning data                                         |
| `read:alerts`        | Alert rules and their history                                 |
| `read:notifications` | Notification settings                                         |
| `read:reports`       | Report templates and generated reports                        |
| `read:credits`       | The workspace's per-request credit spending log               |

## Configure scopes [#configure-scopes]

| Scope                 | Grants                                                                                                 |
| --------------------- | ------------------------------------------------------------------------------------------------------ |
| `write:crux`          | Change CrUX Monitoring configuration                                                                   |
| `write:synthetic`     | Change Synthetic Monitoring configuration                                                              |
| `write:analytics`     | Change Web Analytics configuration                                                                     |
| `write:gsc`           | Change Google Search Console configuration                                                             |
| `write:indexing`      | Change Indexing Monitoring configuration                                                               |
| `write:robots`        | Change Robots.txt Monitoring configuration, and recompute the sitemap-versus-robots.txt conflict check |
| `write:alerts`        | Create and change alert rules                                                                          |
| `write:notifications` | Change notification settings                                                                           |
| `write:reports`       | Create and change report templates                                                                     |

Every `write:` operation is refused outright when the workspace subscription is past due, canceled, or expired, exactly as it would be in the dashboard.

## Run scopes [#run-scopes]

| Scope           | Grants                            | Costs                  |
| --------------- | --------------------------------- | ---------------------- |
| `run:analytics` | Run Web Analytics reports         | 25 API credits per run |
| `run:gsc`       | Run Google Search Console reports | 25 API credits per run |
| `run:reports`   | Generate PDF reports              | 25 API credits per run |

All three spend server compute and credits, and all three sit in a tighter [rate-limit bucket](/api/rate-limits) than reads and require an `Idempotency-Key` header on every call. See [Conventions](/api/conventions#idempotency-keys-are-mandatory-on-run-endpoints).

## A credential is a ceiling, not a grant [#a-credential-is-a-ceiling-not-a-grant]

This is the single most common source of confusion, so it is worth stating precisely.

**Effective permission is the credential's scopes intersected with whatever the creator's *current* workspace role allows**, recomputed on every request.

An API key does not carry a frozen snapshot of its creator's permissions. It tracks them. If the person who created a key is demoted from admin to member, every key they created loses its `write:` and `run:` power within about a minute, without the key being touched. Promote them again and the power comes back the same way.

Scopes only ever narrow what a role permits. They never widen it. A member-created key holding `write:crux` still cannot add a CrUX URL, because that operation is admin-gated in the product itself.

### What a member can do [#what-a-member-can-do]

A **member is read-only across every module**, with two exceptions, because those objects belong to the individual rather than to the shared workspace configuration:

| Scope                 | Why a member keeps it                                  |
| --------------------- | ------------------------------------------------------ |
| `write:alerts`        | An alert rule is owned by whoever created it           |
| `write:notifications` | A member's own read state and notification preferences |

Everything else that is shared, team-visible configuration requires **admin** or **owner**: funnels, experiments, uptime configuration, sitemap configuration, indexing groups, Search Console brand patterns and locale configuration, report templates, and robots.txt annotations.

This is the same rule the dashboard enforces, not an API-only restriction. The two layers cannot disagree.

### Reads are never role-restricted [#reads-are-never-role-restricted]

A credential holding every `read:` scope resolves identically for an owner, an admin, and a member. The only role-gated read in the whole product is workspace billing, and billing is excluded from the public API entirely.

One exception sits just outside that rule: `GET /credits/log` lists every member's requests, which is workspace-wide visibility rather than a view of your own spend, so it requires **admin or owner** and, for an OAuth credential, the `read:credits` scope. `GET /credits` stays readable by anyone, because a capped member has to be able to see whether the workspace is out of credits or they personally are.

### Ask, do not guess [#ask-do-not-guess]

`GET /me` returns both sets:

```json
{
  "scopes": ["read:crux", "write:crux", "read:domains"],
  "effective_scopes": ["read:crux", "read:domains"],
  "roles": { "e0d4a2c8-1b77-4f39-9c52-6a8d3e0f4b21": "member" }
}
```

<Callout type="warning">
  Build your client's capability list from &#x2A;*`effective_scopes`**. A client that branches on `scopes` will advertise operations that always fail with `403`, and will keep retrying them.
</Callout>

For a read-only credential the two sets are always identical, so this only matters once you ask for `write:` or `run:`.

## Scopes that do not exist [#scopes-that-do-not-exist]

Some scope strings are permanently blocked. They are not missing features waiting to be added; each one is a decision, and a test asserts the registry never contains them:

| Blocked scope                                              | Why                                                                                                                                                                                                               |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `read:all`, `write:all`, `run:all`                         | Aggregates would defeat the point of naming what you need                                                                                                                                                         |
| `write:domains`, `write:workspaces`                        | Domain verification is coming, and an API-created domain would bypass it                                                                                                                                          |
| `read:rum.sessions`                                        | Individual visitor session records are not reachable through the API at any scope                                                                                                                                 |
| `write:uptime`, `write:sitemap`                            | The backend route exists, but the dashboard does not expose it, and the API surface must be a subset of the dashboard                                                                                             |
| `run:crux`                                                 | Draws from a shared platform-wide key pool, so one customer's loop would degrade collection for everyone                                                                                                          |
| `run:sitemap`, `run:synthetic`                             | A scripted trigger turns into permanent recurring collection or an unbounded test bill for data that was coming anyway                                                                                            |
| `run:robots`                                               | The one robots.txt job this API starts, recomputing the sitemap conflict check, fully overwrites its own stored result, so a repeat cannot corrupt or duplicate anything and it sits under `write:robots` instead |
| `run:uptime`, `run:ssl`, `run:alerts`, `run:notifications` | No dashboard-reachable manual trigger exists                                                                                                                                                                      |
| `read:billing`, `write:billing`, `run:billing`             | Billing is out of scope for the API entirely                                                                                                                                                                      |

## Next [#next]

<Cards>
  <Card title="Endpoint reference" href="/api/endpoints">
    Which scope each operation requires.
  </Card>

  <Card title="Errors" href="/api/errors">
    Telling `insufficient-scope` and `insufficient-role` apart, and why it matters.
  </Card>
</Cards>
