# Endpoint Reference



217 operations across 19 groups. Every path below is relative to `https://app.vitalsentinel.com/api/public/v1`.

This page is a map. The [OpenAPI document](https://app.vitalsentinel.com/api/public/v1/openapi.json) is the contract, and it carries the request and response schemas, the query parameters, and the per-operation notes that are too long to fit in a table.

**Read the conventions first.** [Pagination, time ranges, and idempotency](/api/conventions) apply across the whole surface and are not repeated per endpoint.

## Meta [#meta]

No scope required. Both cost 0 credits.

| Method | Path      | Description                                                           |
| ------ | --------- | --------------------------------------------------------------------- |
| `GET`  | `/health` | Service health. Unauthenticated.                                      |
| `GET`  | `/me`     | Describe the calling credential, its scopes, and its effective scopes |

## Workspaces and domains [#workspaces-and-domains]

Read-only. Creating or editing a workspace or a domain is a dashboard-only operation.

| Method | Path                          | Scope             | Description                |
| ------ | ----------------------------- | ----------------- | -------------------------- |
| `GET`  | `/workspaces`                 | `read:workspaces` | List accessible workspaces |
| `GET`  | `/workspaces/{workspace_id}`  | `read:workspaces` | Get a workspace            |
| `GET`  | `/domains`                    | `read:domains`    | List domains               |
| `GET`  | `/domains/{domain_id}`        | `read:domains`    | Get a domain               |
| `GET`  | `/domains/{domain_id}/limits` | `read:domains`    | Get plan limits and usage  |

`/workspaces` and `/domains` return the page envelope but always as a single page today, and both cost 0 credits.

## Domain overview and health [#domain-overview-and-health]

The two cross-module summaries. Start here rather than fanning out across eight modules.

| Method | Path                            | Scope           | Description                                 |
| ------ | ------------------------------- | --------------- | ------------------------------------------- |
| `GET`  | `/domains/{domain_id}/overview` | `read:overview` | Cross-module summary with period comparison |
| `GET`  | `/domains/{domain_id}/health`   | `read:domains`  | Cross-module health snapshot                |

Both cover the same eight modules over a fixed 28-day window that is not configurable. The differences that decide which you want:

|                           | `/overview`                                        | `/health`                                          |
| ------------------------- | -------------------------------------------------- | -------------------------------------------------- |
| Scope required            | `read:overview`                                    | `read:domains`                                     |
| Window                    | Last 28 days                                       | Last 28 **complete UTC days** versus the 28 before |
| Comparison                | Three modules: CrUX, Search Console, Web Analytics | **Every** configured module                        |
| Freshness control         | None. Always cached, up to an hour old             | `freshness=cached` (default) or `freshness=live`   |
| Reports its own staleness | No                                                 | Yes, `meta.cache_age_seconds`                      |

**`/health` is the one to reach for during an incident.** Pass `freshness=live` to bypass the cache and recompute every module now. It is slower, and `cache_age_seconds` comes back as 0 so you know you got a fresh answer.

On the default `freshness=cached`, check `meta.cache_age_seconds` before trusting `uptime.is_up`. A snapshot up to an hour old is fine for a dashboard and wrong for "is the site up right now".

`/overview` has no freshness lever at all, so do not use it for a time-sensitive question. Use `/health?freshness=live`, or `/uptime/status`, which is never cached.

## Credits [#credits]

| Method | Path                  | Scope                                                | Description                                                   |
| ------ | --------------------- | ---------------------------------------------------- | ------------------------------------------------------------- |
| `GET`  | `/credits`            | none                                                 | Workspace credit balance and your own cap                     |
| `GET`  | `/credits/log`        | `read:credits` for OAuth; admin or owner role always | This cycle's spending, request by request                     |
| `GET`  | `/credits/workspaces` | none                                                 | One balance per accessible workspace, each with its own cycle |

All three cost 0 credits. `/credits` and `/credits/log` take an optional `workspace_id`; `/credits/workspaces` takes no parameters, because it already covers every workspace the credential can reach. A request is charged to the workspace whose data it read, so a connection covering several workspaces has a separate balance and a separate log for each. Omit the parameter for the connection's default workspace, which is the only one for an API key.

Workspaces do not share a billing cycle, so the balances `/credits/workspaces` returns each carry their own `cycle_start` and `cycle_end`. Adding their `used` figures together blends different date ranges into a number that describes no real cycle.

See [API credits](/api/credits).

## Uptime [#uptime]

Scope: `read:uptime`. There is no `write:uptime`; uptime configuration is dashboard-only.

| Method | Path                                         | Description                                                                                       |
| ------ | -------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `GET`  | `/domains/{domain_id}/uptime/status`         | Current uptime status. **Never cached**, so this is the right call for "is the site up right now" |
| `GET`  | `/domains/{domain_id}/uptime/availability`   | Uptime availability percentage                                                                    |
| `GET`  | `/domains/{domain_id}/uptime/timeline`       | Uptime check timeline, bucketed                                                                   |
| `GET`  | `/domains/{domain_id}/uptime/incidents`      | Downtime incidents                                                                                |
| `GET`  | `/domains/{domain_id}/uptime/response-times` | DNS, connect, TLS, and total response-time breakdown                                              |
| `GET`  | `/domains/{domain_id}/uptime/slow-checks`    | Slow but successful checks                                                                        |
| `GET`  | `/domains/{domain_id}/uptime/stats-extra`    | Response-time percentiles, MTTR, and slow-check count                                             |
| `GET`  | `/domains/{domain_id}/uptime/config`         | Get uptime monitoring configuration                                                               |

## SSL [#ssl]

Scope: `read:ssl`.

| Method | Path                                         | Description                    |
| ------ | -------------------------------------------- | ------------------------------ |
| `GET`  | `/domains/{domain_id}/ssl/summary`           | SSL status summary             |
| `GET`  | `/domains/{domain_id}/ssl/latest`            | Latest SSL certificate         |
| `GET`  | `/domains/{domain_id}/ssl`                   | SSL certificate chain detail   |
| `GET`  | `/domains/{domain_id}/ssl/history`           | SSL certificate history        |
| `GET`  | `/domains/{domain_id}/ssl/events`            | SSL certificate change events  |
| `GET`  | `/domains/{domain_id}/ssl/domain-expiration` | Domain registration expiration |

## CrUX [#crux]

Scopes: `read:crux`, `write:crux`.

| Method   | Path                                                | Scope        | Description                           |
| -------- | --------------------------------------------------- | ------------ | ------------------------------------- |
| `GET`    | `/domains/{domain_id}/crux/overview`                | `read:crux`  | Latest CrUX overview                  |
| `GET`    | `/domains/{domain_id}/crux/timeline`                | `read:crux`  | CrUX metrics timeline                 |
| `GET`    | `/domains/{domain_id}/crux/compare`                 | `read:crux`  | Compare metrics between two periods   |
| `GET`    | `/domains/{domain_id}/crux/trends/{metric_name}`    | `read:crux`  | Trend for one metric                  |
| `GET`    | `/domains/{domain_id}/crux/histogram/{metric_name}` | `read:crux`  | Distribution histogram for one metric |
| `GET`    | `/domains/{domain_id}/crux/urls`                    | `read:crux`  | List monitored URLs                   |
| `GET`    | `/domains/{domain_id}/crux/urls/{url_id}`           | `read:crux`  | Get a monitored URL                   |
| `POST`   | `/domains/{domain_id}/crux/urls/bulk`               | `write:crux` | Add multiple URLs. **25 credits**     |
| `POST`   | `/domains/{domain_id}/crux/urls/bulk-delete`        | `write:crux` | Remove multiple URLs                  |
| `DELETE` | `/domains/{domain_id}/crux/urls/{url_id}`           | `write:crux` | Remove a URL                          |

There is no way to trigger a CrUX refresh through the API. Collection draws from a shared platform-wide key pool, so a scripted refresh loop would degrade collection for every other customer.

## Synthetic Monitoring [#synthetic-monitoring]

Scopes: `read:synthetic`, `write:synthetic`. There is no `run:synthetic`: a scripted "run it now" against the schedule is an unbounded test bill for data that was coming anyway.

| Method   | Path                                                     | Scope             | Description                                                       |
| -------- | -------------------------------------------------------- | ----------------- | ----------------------------------------------------------------- |
| `GET`    | `/domains/{domain_id}/synthetic/overview`                | `read:synthetic`  | Overview                                                          |
| `GET`    | `/domains/{domain_id}/synthetic/timeline`                | `read:synthetic`  | Test timeline                                                     |
| `GET`    | `/domains/{domain_id}/synthetic/trends`                  | `read:synthetic`  | Metric trends over time                                           |
| `GET`    | `/domains/{domain_id}/synthetic/compare`                 | `read:synthetic`  | Compare performance between two periods                           |
| `GET`    | `/domains/{domain_id}/synthetic/tests/{test_id}`         | `read:synthetic`  | Full results for one test                                         |
| `GET`    | `/domains/{domain_id}/synthetic/opportunities`           | `read:synthetic`  | Recurring Lighthouse opportunities, aggregated across recent runs |
| `GET`    | `/domains/{domain_id}/synthetic/urls`                    | `read:synthetic`  | List monitored URLs                                               |
| `GET`    | `/domains/{domain_id}/synthetic/urls/{url_id}`           | `read:synthetic`  | Get a monitored URL                                               |
| `GET`    | `/domains/{domain_id}/synthetic/urls/{url_id}/locations` | `read:synthetic`  | Configured locations for a URL                                    |
| `GET`    | `/domains/{domain_id}/synthetic/locations`               | `read:synthetic`  | Available testing locations                                       |
| `GET`    | `/domains/{domain_id}/synthetic/locations-with-data`     | `read:synthetic`  | Locations with collected data                                     |
| `GET`    | `/domains/{domain_id}/synthetic/location-limits`         | `read:synthetic`  | Location limits for this workspace                                |
| `POST`   | `/domains/{domain_id}/synthetic/urls/bulk`               | `write:synthetic` | Add multiple URLs. **25 credits**                                 |
| `POST`   | `/domains/{domain_id}/synthetic/urls/bulk-delete`        | `write:synthetic` | Remove multiple URLs                                              |
| `POST`   | `/domains/{domain_id}/synthetic/urls/bulk-frequency`     | `write:synthetic` | Update frequency for multiple URLs                                |
| `PUT`    | `/domains/{domain_id}/synthetic/urls/{url_id}/frequency` | `write:synthetic` | Update the frequency for one URL                                  |
| `PUT`    | `/domains/{domain_id}/synthetic/urls/{url_id}/locations` | `write:synthetic` | Set the locations for one URL                                     |
| `DELETE` | `/domains/{domain_id}/synthetic/urls/{url_id}`           | `write:synthetic` | Remove a URL                                                      |

## RUM [#rum]

Scope: `read:rum`. Read-only, and [redacted more strictly than the dashboard](/api/conventions#rum-is-stricter-here-than-the-dashboard).

| Method | Path                                                         | Description                                         |
| ------ | ------------------------------------------------------------ | --------------------------------------------------- |
| `GET`  | `/domains/{domain_id}/rum/overview`                          | RUM overview                                        |
| `GET`  | `/domains/{domain_id}/rum/has-data`                          | Whether this domain has any RUM data at all         |
| `GET`  | `/workspaces/{workspace_id}/rum/has-data`                    | Whether each domain in a workspace has any RUM data |
| `GET`  | `/domains/{domain_id}/rum/vitals`                            | Core Web Vitals, by device type                     |
| `GET`  | `/domains/{domain_id}/rum/vitals/timeline`                   | Core Web Vitals over time                           |
| `GET`  | `/domains/{domain_id}/rum/vitals/distribution-timeline`      | Good, needs-improvement, and poor split over time   |
| `GET`  | `/domains/{domain_id}/rum/vitals/lcp/subparts`               | LCP timing breakdown                                |
| `GET`  | `/domains/{domain_id}/rum/vitals/lcp/subparts/timeline`      | LCP timing breakdown, daily                         |
| `GET`  | `/domains/{domain_id}/rum/vitals/lcp/element-types`          | LCP breakdown by element type                       |
| `GET`  | `/domains/{domain_id}/rum/vitals/lcp/element-types/timeline` | LCP element types, daily                            |
| `GET`  | `/domains/{domain_id}/rum/vitals/lcp/resources`              | Top LCP resources                                   |
| `GET`  | `/domains/{domain_id}/rum/vitals/inp/subparts/timeline`      | INP timing breakdown, daily                         |
| `GET`  | `/domains/{domain_id}/rum/vitals/inp/script-domains`         | Top INP script sources                              |
| `GET`  | `/domains/{domain_id}/rum/vitals/ttfb/subparts/timeline`     | TTFB timing breakdown, daily                        |
| `GET`  | `/domains/{domain_id}/rum/pages`                             | Page performance breakdown                          |
| `GET`  | `/domains/{domain_id}/rum/pageviews`                         | Aggregated page views                               |
| `GET`  | `/domains/{domain_id}/rum/events`                            | Individual RUM events                               |
| `GET`  | `/domains/{domain_id}/rum/errors`                            | Error counts by type                                |
| `GET`  | `/domains/{domain_id}/rum/errors/groups`                     | Distinct error kinds                                |
| `GET`  | `/domains/{domain_id}/rum/errors/details`                    | Detailed error records with stack traces            |
| `GET`  | `/domains/{domain_id}/rum/engagement`                        | User engagement metrics                             |
| `GET`  | `/domains/{domain_id}/rum/engagement/correlation`            | Performance versus engagement correlation histogram |
| `GET`  | `/domains/{domain_id}/rum/geographic`                        | Geographic distribution of RUM metrics              |

`/workspaces/{workspace_id}/rum/has-data` is the one workspace-scoped route in this group, so it takes a `workspace_id` rather than a `domain_id`. It reports every domain in the workspace in one call, which is how to find out which of your sites are reporting RUM at all without asking domain by domain.

## Web Analytics [#web-analytics]

Scopes: `read:analytics`, `write:analytics`, `run:analytics`.

| Method   | Path                                                                            | Scope             | Description                                                      |
| -------- | ------------------------------------------------------------------------------- | ----------------- | ---------------------------------------------------------------- |
| `GET`    | `/domains/{domain_id}/analytics/overview`                                       | `read:analytics`  | Traffic overview                                                 |
| `GET`    | `/domains/{domain_id}/analytics/timeline`                                       | `read:analytics`  | Visitors and pageviews timeline                                  |
| `GET`    | `/domains/{domain_id}/analytics/realtime`                                       | `read:analytics`  | Live activity                                                    |
| `GET`    | `/domains/{domain_id}/analytics/pages`                                          | `read:analytics`  | Top, entry, or exit pages                                        |
| `GET`    | `/domains/{domain_id}/analytics/sources`                                        | `read:analytics`  | Traffic source breakdown                                         |
| `GET`    | `/domains/{domain_id}/analytics/devices`                                        | `read:analytics`  | Device, browser, and OS breakdown                                |
| `GET`    | `/domains/{domain_id}/analytics/geographic`                                     | `read:analytics`  | Geographic breakdown                                             |
| `GET`    | `/domains/{domain_id}/analytics/events`                                         | `read:analytics`  | Custom events                                                    |
| `GET`    | `/domains/{domain_id}/analytics/search`                                         | `read:analytics`  | Site search queries                                              |
| `GET`    | `/domains/{domain_id}/analytics/outbound`                                       | `read:analytics`  | Outbound link clicks                                             |
| `GET`    | `/domains/{domain_id}/analytics/downloads`                                      | `read:analytics`  | File download clicks                                             |
| `GET`    | `/domains/{domain_id}/analytics/ecommerce/overview`                             | `read:analytics`  | Ecommerce overview                                               |
| `GET`    | `/domains/{domain_id}/analytics/ecommerce/products`                             | `read:analytics`  | Ecommerce products. **25 credits**, never discounted             |
| `GET`    | `/domains/{domain_id}/analytics/config`                                         | `read:analytics`  | Get analytics configuration                                      |
| `PUT`    | `/domains/{domain_id}/analytics/config`                                         | `write:analytics` | Update analytics configuration                                   |
| `GET`    | `/domains/{domain_id}/analytics/funnels`                                        | `read:analytics`  | List funnels                                                     |
| `GET`    | `/domains/{domain_id}/analytics/funnels/{funnel_id}`                            | `read:analytics`  | Get a funnel                                                     |
| `POST`   | `/domains/{domain_id}/analytics/funnels`                                        | `write:analytics` | Create a funnel                                                  |
| `PUT`    | `/domains/{domain_id}/analytics/funnels/{funnel_id}`                            | `write:analytics` | Update a funnel                                                  |
| `DELETE` | `/domains/{domain_id}/analytics/funnels/{funnel_id}`                            | `write:analytics` | Delete a funnel                                                  |
| `GET`    | `/domains/{domain_id}/analytics/experiments`                                    | `read:analytics`  | List experiments                                                 |
| `GET`    | `/domains/{domain_id}/analytics/experiments/{experiment_id}`                    | `read:analytics`  | Get an experiment                                                |
| `GET`    | `/domains/{domain_id}/analytics/experiments/{experiment_id}/evaluations`        | `read:analytics`  | List evaluations                                                 |
| `GET`    | `/domains/{domain_id}/analytics/experiments/{experiment_id}/evaluations/latest` | `read:analytics`  | Latest evaluation                                                |
| `POST`   | `/domains/{domain_id}/analytics/experiments`                                    | `write:analytics` | Create an experiment                                             |
| `PUT`    | `/domains/{domain_id}/analytics/experiments/{experiment_id}`                    | `write:analytics` | Update an experiment                                             |
| `DELETE` | `/domains/{domain_id}/analytics/experiments/{experiment_id}`                    | `write:analytics` | Delete an experiment                                             |
| `POST`   | `/domains/{domain_id}/analytics/experiments/{experiment_id}/evaluate`           | `run:analytics`   | Evaluate an experiment. **25 credits**, needs `Idempotency-Key`  |
| `GET`    | `/domains/{domain_id}/analytics/reports/_registered`                            | `read:analytics`  | List registered report keys                                      |
| `GET`    | `/domains/{domain_id}/analytics/reports/_summary`                               | `read:analytics`  | Latest run per registered report                                 |
| `GET`    | `/domains/{domain_id}/analytics/reports/{key}/latest`                           | `read:analytics`  | Latest completed run for a report                                |
| `GET`    | `/domains/{domain_id}/analytics/reports/runs/{run_id}`                          | `read:analytics`  | Get a report run                                                 |
| `GET`    | `/domains/{domain_id}/analytics/reports/runs/{run_id}/rows`                     | `read:analytics`  | Paginated slice of a run's rows                                  |
| `POST`   | `/domains/{domain_id}/analytics/reports/{report_key}/run`                       | `run:analytics`   | Run an on-demand report. **25 credits**, needs `Idempotency-Key` |

## Google Search Console [#google-search-console]

Scopes: `read:gsc`, `write:gsc`, `run:gsc`.

| Method   | Path                                                                      | Scope       | Description                                                               |
| -------- | ------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------- |
| `GET`    | `/domains/{domain_id}/gsc/overview`                                       | `read:gsc`  | Overview statistics                                                       |
| `GET`    | `/domains/{domain_id}/gsc/performance`                                    | `read:gsc`  | Performance time series. **25 credits**, never discounted                 |
| `GET`    | `/domains/{domain_id}/gsc/queries`                                        | `read:gsc`  | Query-level performance                                                   |
| `GET`    | `/domains/{domain_id}/gsc/pages`                                          | `read:gsc`  | Page-level performance                                                    |
| `GET`    | `/domains/{domain_id}/gsc/countries`                                      | `read:gsc`  | Performance by country                                                    |
| `GET`    | `/domains/{domain_id}/gsc/devices`                                        | `read:gsc`  | Performance by device                                                     |
| `GET`    | `/domains/{domain_id}/gsc/search-appearance`                              | `read:gsc`  | Performance by search appearance type                                     |
| `GET`    | `/domains/{domain_id}/gsc/ctr-benchmark`                                  | `read:gsc`  | Actual versus expected CTR across this domain's own position distribution |
| `GET`    | `/domains/{domain_id}/gsc/new-rankings`                                   | `read:gsc`  | Queries or pages new to this domain's rankings                            |
| `GET`    | `/domains/{domain_id}/gsc/known-events`                                   | `read:gsc`  | Known events annotated on this domain's traffic                           |
| `GET`    | `/domains/{domain_id}/gsc/brand-patterns`                                 | `read:gsc`  | Brand-query patterns                                                      |
| `GET`    | `/domains/{domain_id}/gsc/url-patterns`                                   | `read:gsc`  | URL-template pattern overrides                                            |
| `GET`    | `/domains/{domain_id}/gsc/locale-config`                                  | `read:gsc`  | Locale-detection configuration                                            |
| `GET`    | `/domains/{domain_id}/gsc/experiments`                                    | `read:gsc`  | List experiments                                                          |
| `GET`    | `/domains/{domain_id}/gsc/experiments/{experiment_id}/evaluations/latest` | `read:gsc`  | Latest evaluation                                                         |
| `POST`   | `/domains/{domain_id}/gsc/experiments`                                    | `write:gsc` | Create an A/B experiment                                                  |
| `PUT`    | `/domains/{domain_id}/gsc/experiments/{experiment_id}`                    | `write:gsc` | Update an experiment                                                      |
| `DELETE` | `/domains/{domain_id}/gsc/experiments/{experiment_id}`                    | `write:gsc` | Delete an experiment                                                      |
| `POST`   | `/domains/{domain_id}/gsc/experiments/{experiment_id}/evaluate`           | `run:gsc`   | Evaluate an experiment. **25 credits**, needs `Idempotency-Key`           |
| `GET`    | `/domains/{domain_id}/gsc/reports/_registered`                            | `read:gsc`  | Reports available to run                                                  |
| `GET`    | `/domains/{domain_id}/gsc/reports/_summary`                               | `read:gsc`  | Reports hub summary                                                       |
| `GET`    | `/domains/{domain_id}/gsc/reports/{report_key}/latest`                    | `read:gsc`  | Latest completed run for a report                                         |
| `GET`    | `/domains/{domain_id}/gsc/reports/runs/{run_id}`                          | `read:gsc`  | Get a report run                                                          |
| `GET`    | `/domains/{domain_id}/gsc/reports/runs/{run_id}/rows`                     | `read:gsc`  | Paginated slice of a run's rows                                           |
| `POST`   | `/domains/{domain_id}/gsc/reports/{report_key}/run`                       | `run:gsc`   | Run a report. **25 credits**, needs `Idempotency-Key`                     |

Search Console data hangs off the domain its property is linked to. Connecting or disconnecting a Google account is a dashboard-only operation.

## Indexing Monitoring [#indexing-monitoring]

Scopes: `read:indexing`, `write:indexing`.

| Method   | Path                                                            | Scope            | Description                                |
| -------- | --------------------------------------------------------------- | ---------------- | ------------------------------------------ |
| `GET`    | `/domains/{domain_id}/indexing/timeline`                        | `read:indexing`  | Coverage-state timeline                    |
| `GET`    | `/domains/{domain_id}/indexing/issue-summary`                   | `read:indexing`  | Aggregate issue counts                     |
| `GET`    | `/domains/{domain_id}/indexing/inspections`                     | `read:indexing`  | URL Inspection results                     |
| `GET`    | `/domains/{domain_id}/indexing/url-history`                     | `read:indexing`  | Every past inspection for one URL          |
| `GET`    | `/domains/{domain_id}/indexing/state-changes`                   | `read:indexing`  | Recent per-URL coverage-state transitions  |
| `GET`    | `/domains/{domain_id}/indexing/sitemaps`                        | `read:indexing`  | Search Console sitemap coverage stats      |
| `GET`    | `/domains/{domain_id}/indexing/diagnostics`                     | `read:indexing`  | Collection diagnostics                     |
| `GET`    | `/domains/{domain_id}/indexing/groups`                          | `read:indexing`  | List URL groups                            |
| `GET`    | `/domains/{domain_id}/indexing/priority-urls`                   | `read:indexing`  | List priority-queue URLs                   |
| `GET`    | `/domains/{domain_id}/indexing/properties`                      | `read:indexing`  | List enrolled Search Console properties    |
| `POST`   | `/domains/{domain_id}/indexing/groups`                          | `write:indexing` | Create a URL group                         |
| `PATCH`  | `/domains/{domain_id}/indexing/groups/{group_id}`               | `write:indexing` | Update a URL group                         |
| `DELETE` | `/domains/{domain_id}/indexing/groups/{group_id}`               | `write:indexing` | Delete a URL group                         |
| `POST`   | `/domains/{domain_id}/indexing/priority-urls`                   | `write:indexing` | Add a URL to the priority inspection queue |
| `DELETE` | `/domains/{domain_id}/indexing/priority-urls/{priority_url_id}` | `write:indexing` | Remove a URL from the priority queue       |
| `POST`   | `/domains/{domain_id}/indexing/properties/{property_id}`        | `write:indexing` | Enroll a property. **25 credits**          |
| `DELETE` | `/domains/{domain_id}/indexing/properties/{property_id}`        | `write:indexing` | Remove a property                          |

## Robots.txt Monitoring [#robotstxt-monitoring]

Scopes: `read:robots`, `write:robots`. There is no `run:robots`; robots.txt is fetched on a schedule.

| Method   | Path                                                      | Scope          | Description                                                                                |
| -------- | --------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------ |
| `GET`    | `/domains/{domain_id}/robots/latest`                      | `read:robots`  | Latest fetched robots.txt                                                                  |
| `GET`    | `/domains/{domain_id}/robots/health`                      | `read:robots`  | Fetch health                                                                               |
| `GET`    | `/domains/{domain_id}/robots/timeline`                    | `read:robots`  | Fetched versions                                                                           |
| `GET`    | `/domains/{domain_id}/robots/changes`                     | `read:robots`  | List changes                                                                               |
| `GET`    | `/domains/{domain_id}/robots/diff-impacts`                | `read:robots`  | Per-change before and after impact                                                         |
| `GET`    | `/domains/{domain_id}/robots/crawler-access`              | `read:robots`  | Per-crawler allow and disallow matrix                                                      |
| `GET`    | `/domains/{domain_id}/robots/ai-bots`                     | `read:robots`  | AI-bot governance view                                                                     |
| `GET`    | `/domains/{domain_id}/robots/test-url`                    | `read:robots`  | Test whether a URL is allowed for one crawler                                              |
| `POST`   | `/domains/{domain_id}/robots/test-urls/query`             | `read:robots`  | Test a batch of URLs. Read-only POST                                                       |
| `POST`   | `/domains/{domain_id}/robots/simulate/query`              | `read:robots`  | Simulate a candidate robots.txt. Read-only POST                                            |
| `GET`    | `/domains/{domain_id}/robots/overlapping-rules`           | `read:robots`  | Overlapping rules in the stored file                                                       |
| `POST`   | `/domains/{domain_id}/robots/overlapping-rules/query`     | `read:robots`  | Overlapping rules in a candidate file. Read-only POST                                      |
| `GET`    | `/domains/{domain_id}/robots/sitemap-conflicts`           | `read:robots`  | Sitemap URLs blocked by robots.txt                                                         |
| `GET`    | `/domains/{domain_id}/robots/sitemap-conflicts/summary`   | `read:robots`  | Conflict summary                                                                           |
| `GET`    | `/domains/{domain_id}/robots/top-blocked`                 | `read:robots`  | High-traffic URLs blocked by robots.txt                                                    |
| `GET`    | `/domains/{domain_id}/robots/validation/history`          | `read:robots`  | Validation results                                                                         |
| `GET`    | `/domains/{domain_id}/robots/annotations`                 | `read:robots`  | List change annotations                                                                    |
| `POST`   | `/domains/{domain_id}/robots/annotations`                 | `write:robots` | Add a note to a change                                                                     |
| `PATCH`  | `/domains/{domain_id}/robots/annotations/{annotation_id}` | `write:robots` | Update an annotation                                                                       |
| `DELETE` | `/domains/{domain_id}/robots/annotations/{annotation_id}` | `write:robots` | Delete an annotation                                                                       |
| `POST`   | `/domains/{domain_id}/robots/sitemap-conflicts/recompute` | `write:robots` | Recompute the sitemap conflict check. Needs `Idempotency-Key`. Priced as an ordinary write |

`write:robots` edits VitalSentinel's own annotations on a change, and triggers the sitemap-conflict recompute. It does not, and cannot, edit the robots.txt file on your site.

The recompute re-diffs the sitemap URLs VitalSentinel has already stored against the current robots.txt. It returns straight away and does the work in the background, so poll `/robots/sitemap-conflicts/summary` for the result. It does not fetch a fresh sitemap, so it clears the staleness caused by the check lagging behind a stored sitemap, but not the staleness caused by the sitemap scan itself being old. Only a new scan clears that, and starting one is a dashboard-only action.

## Sitemap [#sitemap]

Scope: `read:sitemap`. Read-only.

| Method | Path                                      | Description                         |
| ------ | ----------------------------------------- | ----------------------------------- |
| `GET`  | `/domains/{domain_id}/sitemap/stats`      | Scan statistics                     |
| `GET`  | `/domains/{domain_id}/sitemap/urls`       | Export every discovered sitemap URL |
| `GET`  | `/domains/{domain_id}/sitemap/config`     | Scan configuration                  |
| `GET`  | `/domains/{domain_id}/sitemap/rate-limit` | Whether a scan can be started       |

To find sitemap URLs blocked by robots.txt, either read the stored result at `/robots/sitemap-conflicts`, refreshing it first with `POST /robots/sitemap-conflicts/recompute` if it is stale, or export the full URL list here plus the latest robots.txt and compute the intersection yourself.

## Alerts [#alerts]

Scopes: `read:alerts`, `write:alerts`. Members can create and edit alert rules, because a rule belongs to whoever created it.

| Method   | Path                                          | Scope          | Description                                                                                                                           |
| -------- | --------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `GET`    | `/alerts/metrics`                             | `read:alerts`  | Available alert metrics. Costs 0 credits                                                                                              |
| `GET`    | `/alerts/rules`                               | `read:alerts`  | Account-wide: rules on every accessible domain. Optional `workspace_id`, `module`, and `enabled` filters. Costs 0 credits             |
| `GET`    | `/alerts/history`                             | `read:alerts`  | Account-wide: fired alerts on every accessible domain, newest first. Optional `workspace_id` and `domain_id` filters. Costs 0 credits |
| `GET`    | `/domains/{domain_id}/alerts/rules`           | `read:alerts`  | Per domain: rules on this one domain                                                                                                  |
| `GET`    | `/domains/{domain_id}/alerts/rules/{rule_id}` | `read:alerts`  | Get a rule                                                                                                                            |
| `GET`    | `/domains/{domain_id}/alerts/history`         | `read:alerts`  | Per domain: fired alerts on this one domain                                                                                           |
| `POST`   | `/domains/{domain_id}/alerts/rules`           | `write:alerts` | Create a rule                                                                                                                         |
| `PUT`    | `/domains/{domain_id}/alerts/rules/{rule_id}` | `write:alerts` | Update a rule                                                                                                                         |
| `DELETE` | `/domains/{domain_id}/alerts/rules/{rule_id}` | `write:alerts` | Delete a rule                                                                                                                         |

The account-wide `/alerts/rules` and `/alerts/history` answer "what do I have set up" and "what has fired lately" across every workspace the credential can reach, without looping over `/domains` first. Every row names its own domain and workspace, so nothing has to be correlated against a second call. Both cost 0 credits, because a call that reads every accessible workspace has no single workspace to charge. Narrowing one with `workspace_id` or `domain_id` does not change that, since those are query filters rather than part of the path.

Read `/alerts/metrics` before building a rule. It is free and it is what tells you which metric and comparison combinations are valid.

There is no endpoint to test-fire a rule. The dashboard does not expose one either.

## Notifications [#notifications]

Scopes: `read:notifications`, `write:notifications`. Scoped to the calling user, not to the workspace. Members keep write access here, because these are personal preferences.

Because they are user-scoped rather than workspace-scoped, every route in this group costs 0 credits. The one exception is `GET /notifications/{notification_id}/trend`, which recomputes a series from live data and costs 1.

| Method   | Path                                      | Scope                 | Description                      |
| -------- | ----------------------------------------- | --------------------- | -------------------------------- |
| `GET`    | `/notifications`                          | `read:notifications`  | List notifications               |
| `GET`    | `/notifications/unread-count`             | `read:notifications`  | Unread count                     |
| `GET`    | `/notifications/stats`                    | `read:notifications`  | Notification statistics          |
| `GET`    | `/notifications/{notification_id}/trend`  | `read:notifications`  | Live trend behind a fired alert  |
| `GET`    | `/notifications/preferences`              | `read:notifications`  | Get preferences                  |
| `GET`    | `/notifications/domain-subscriptions`     | `read:notifications`  | Per-domain email subscriptions   |
| `POST`   | `/notifications/read`                     | `write:notifications` | Mark notifications as read       |
| `POST`   | `/notifications/{notification_id}/read`   | `write:notifications` | Mark one as read                 |
| `POST`   | `/notifications/{notification_id}/unread` | `write:notifications` | Mark one as unread               |
| `DELETE` | `/notifications/{notification_id}`        | `write:notifications` | Delete a notification            |
| `PUT`    | `/notifications/preferences`              | `write:notifications` | Update preferences               |
| `PUT`    | `/notifications/domain-subscriptions`     | `write:notifications` | Update a per-domain subscription |

Fired alerts arrive as notifications carrying the alert rule and alert history identifiers, so this is where you watch for an alert firing.

## Reports [#reports]

Scopes: `read:reports`, `write:reports`, `run:reports`.

| Method   | Path                                                             | Scope           | Description                                                   |
| -------- | ---------------------------------------------------------------- | --------------- | ------------------------------------------------------------- |
| `GET`    | `/domains/reports/sections-catalog`                              | `read:reports`  | Available sections and widgets. Costs 0 credits               |
| `GET`    | `/domains/reports/default-config`                                | `read:reports`  | Default `sections_config` for a new template. Costs 0 credits |
| `GET`    | `/domains/{domain_id}/reports/templates`                         | `read:reports`  | List templates                                                |
| `GET`    | `/domains/{domain_id}/reports/templates/{template_id}`           | `read:reports`  | Get a template                                                |
| `POST`   | `/domains/{domain_id}/reports/data/{template_id}`                | `read:reports`  | Collect report data as JSON. Read-only POST                   |
| `POST`   | `/domains/{domain_id}/reports/templates`                         | `write:reports` | Create a template                                             |
| `PUT`    | `/domains/{domain_id}/reports/templates/{template_id}`           | `write:reports` | Update a template                                             |
| `POST`   | `/domains/{domain_id}/reports/templates/{template_id}/duplicate` | `write:reports` | Duplicate a template                                          |
| `DELETE` | `/domains/{domain_id}/reports/templates/{template_id}`           | `write:reports` | Delete a template                                             |
| `POST`   | `/domains/{domain_id}/reports/{template_id}/generate`            | `run:reports`   | Generate a PDF. **25 credits**, needs `Idempotency-Key`       |
| `GET`    | `/domains/{domain_id}/reports/jobs/{job_id}`                     | `run:reports`   | Check a PDF job. Priced as a read                             |
| `GET`    | `/domains/{domain_id}/reports/jobs/{job_id}/download`            | `run:reports`   | Download a completed PDF. Priced as a read                    |

PDF generation is asynchronous. `POST .../generate` returns a job identifier; poll `/reports/jobs/{job_id}` until it completes, then download. Polling is priced as an ordinary read, so watching a report never costs more than starting one.

If you want the numbers rather than a PDF, use `POST /reports/data/{template_id}`. It returns the same content as JSON, needs only `read:reports`, and costs a read rather than 25 credits.

## OAuth [#oauth]

The authorization server. See [Authentication](/api/authentication#oauth-21) for the full flow.

| Method | Path                       | Description                                     |
| ------ | -------------------------- | ----------------------------------------------- |
| `POST` | `/oauth/register`          | Register a client (RFC 7591)                    |
| `GET`  | `/oauth/authorize`         | Start an authorization request                  |
| `GET`  | `/oauth/authorize/request` | Resolve an authorization request handle         |
| `POST` | `/oauth/authorize/consent` | Record a consent decision                       |
| `POST` | `/oauth/token`             | Exchange an authorization code or refresh token |
| `POST` | `/oauth/revoke`            | Revoke a token (RFC 7009)                       |

Two discovery documents sit at the site root rather than under the API prefix:

```
GET https://app.vitalsentinel.com/.well-known/oauth-authorization-server
GET https://app.vitalsentinel.com/.well-known/oauth-protected-resource
```
