# Lighthouse Core Web Vitals Portfolio Monitor (`automa-flow/lighthouse-cwv-monitor`) Actor

Monitor a public URL watchlist with lab Lighthouse Core Web Vitals. Run batch mobile or desktop audits, keep last-good baselines, confirm regressions over two runs, and isolate failed checks so they never look like recoveries.

- **URL**: https://apify.com/automa-flow/lighthouse-cwv-monitor.md
- **Developed by:** [Vadim Bezrukov](https://apify.com/automa-flow) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $25.00 / 1,000 successful url audits

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Lighthouse Core Web Vitals Portfolio Monitor

Monitor a public URL watchlist with lab Lighthouse Core Web Vitals. Paste one
page or a batch, run a mobile or desktop audit, and keep a last-good baseline
so a failed check never looks like a recovery.

This is a **lab Lighthouse monitor**, not Google PageSpeed Insights and not an
HTTP technical-SEO crawler. It uses open-source Lighthouse in Chrome on the
URLs you provide. Field CrUX data, Search Console, and multi-category SEO
audits are out of scope.

Save a monitor input as an Apify Task and run it on a schedule. The first
successful run creates a `BASELINE`. A later noisy breach is
`REGRESSION_PENDING` until the next run confirms it or the metrics recover.
Timeouts, HTTP errors, blocked pages and hollow lab rows never replace
last-good.

### Who uses this Lighthouse Core Web Vitals monitor?

- SEO and performance teams watching landing-page LCP, CLS, TBT and FCP;
- agencies that need a batch watchlist instead of a one-URL Lighthouse run;
- engineering teams that want two-stage confirmation before paging on lab noise;
- webhook and MCP workflows that must tell `UNCHANGED` apart from `TIMEOUT`.

### Ready-made example Tasks

- [Snapshot Lighthouse Core Web Vitals for one page](https://apify.com/automa-flow/lighthouse-cwv-monitor/examples/snapshot-lighthouse-core-web-vitals)
- [Monitor Lighthouse Core Web Vitals regressions](https://apify.com/automa-flow/lighthouse-cwv-monitor/examples/monitor-lighthouse-cwv-regressions)
- [Lighthouse Core Web Vitals Website Portfolio Audit](https://apify.com/automa-flow/lighthouse-cwv-monitor/examples/audit-a-website-portfolio-with-lighthouse)

These Task pages stay unpublished until you publish them separately. The
default Actor input is one public `https://example.com` snapshot.

### Quick start

#### 1. Snapshot one page

```json
{
  "urls": [{"url": "https://example.com", "externalId": "homepage"}],
  "mode": "snapshot",
  "formFactor": "mobile"
}
```

The default Store run uses this input. It must finish with `SUCCEEDED`, one
Dataset row and a `RUN_SUMMARY`.

#### 2. Monitor Core Web Vitals

```json
{
  "urls": [{"url": "https://example.com", "externalId": "homepage"}],
  "mode": "monitor",
  "formFactor": "mobile"
}
```

Run the same saved Task again. Keep the URL and form factor stable. Monitor
mode uses the named key-value store `lighthouse-cwv-monitor-state`.

#### 3. Batch a small watchlist

```json
{
  "urls": [
    {"url": "https://example.com", "externalId": "home"},
    {"url": "https://example.org", "externalId": "marketing"},
    {"url": "https://example.net", "externalId": "docs"}
  ],
  "mode": "monitor",
  "formFactor": "mobile"
}
```

One bad URL does not stop the others. Three inputs always produce three
dataset records. If Chrome cannot start, each remaining URL is an explicit
`FAILED` row with `WORKER_START_FAILED`; those rows are not charged.

#### 4. Desktop lab profile

```json
{
  "urls": [{"url": "https://example.com"}],
  "mode": "monitor",
  "formFactor": "desktop"
}
```

Mobile and desktop keep separate last-good baselines. A Lighthouse or Chrome
major change rebases as `PROFILE_CHANGED` instead of a false regression.

#### 5. Daily schedule

1. Run a `monitor` input successfully once.
2. In Apify Console, choose **Save as task**.
3. Open **Schedules**, create a daily schedule, and select that Task.
4. Keep URL and form factor unchanged.

#### 6. Webhook on confirmed regressions

Attach an `ACTOR.RUN.SUCCEEDED` webhook to the scheduled Task. In the
receiver, fetch the run dataset and act only on comparable successes:

```javascript
const confirmed = datasetItems.filter(
  (item) => item.status === 'SUCCESS' && item.monitor_event === 'REGRESSION_CONFIRMED',
);
```

Never treat a missing `monitor_event` or a failed `status` as unchanged.

### Input reference

| Field | Default | Meaning |
| --- | --- | --- |
| `urls` | required | 1–50 public HTTP(S) pages, with optional `externalId` |
| `mode` | `snapshot` | `snapshot` does not touch monitor state; `monitor` compares successful audits |
| `formFactor` | `mobile` | Lighthouse mobile or desktop lab profile |

Concurrency, Chrome flags, Lighthouse categories and proxy settings are not
Store inputs. The Actor runs one Chrome, one audit at a time.

Query strings, including UTM parameters, are part of identity. `www` vs bare
host is distinct. Trailing slashes are preserved. Fragments are dropped.
Credentials and private/loopback hosts are rejected before Chrome starts.

### Output and failure semantics

Every row carries `source`, stable `source_id`, `source_url`, `scraped_at`,
`schema_version` and `fingerprint`, plus lab metrics and an explicit `status`.
Repeated normalized URLs share one audit: alias rows retain each `external_id`,
carry `duplicate_of`, and incur no extra event charge.

Statuses:

- `SUCCESS` — comparable lab audit with performance score, LCP, CLS, TBT, FCP
  and Chrome major;
- `INVALID_URL` — rejected before Chrome access;
- `UNREACHABLE` — DNS/connection failure;
- `HTTP_ERROR` — document HTTP 4xx/5xx other than a block;
- `TIMEOUT` — navigation or hard audit timeout; Chrome is recycled;
- `BLOCKED` — HTTP 401/403/407/429 or a Chrome interstitial;
- `AUDIT_FAILED` — Lighthouse ran but the row is not comparable, including
  off-host redirects and missing lab metrics;
- `FAILED` — spending-limit rejection, Chrome/worker start failure
  (`WORKER_START_FAILED`), corrupt monitor state, or an unexpected per-item
  error.

Monitor events on a classified row:

- `BASELINE` — first comparable success for this URL and form factor;
- `UNCHANGED` — metrics stayed inside the noise thresholds;
- `REGRESSION_PENDING` — one breach; last-good is kept;
- `REGRESSION_CONFIRMED` — two consecutive breaches;
- `RECOVERED` — metrics returned after a confirmed regression;
- `PROFILE_CHANGED` — form factor, Lighthouse version, Chrome major or
  throttling changed, so the baseline is rebased;
- `AUDIT_FAILED` — the check is not comparable.

See [`examples/sample_output.json`](examples/sample_output.json) for a complete
regression row.

### API use

Use the standard Apify Actor API with your own Actor ID and token. Never place
a token in source control.

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("automa-flow/lighthouse-cwv-monitor").call(
    run_input={
        "urls": [{"url": "https://example.com", "externalId": "homepage"}],
        "mode": "monitor",
        "formFactor": "mobile",
    }
)
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
```

### Use with AI agents through Apify MCP

Expose the Actor as a typed MCP tool:

```text
https://mcp.apify.com?tools=automa-flow/lighthouse-cwv-monitor
```

Example prompt:

```text
Run automa-flow/lighthouse-cwv-monitor in monitor mode for these public
landing-page URLs on mobile. Report REGRESSION_CONFIRMED and RECOVERED rows
with LCP/CLS/TBT/FCP, and keep TIMEOUT or AUDIT_FAILED separate from UNCHANGED.
```

The first successful monitor run creates a `BASELINE`. A failed audit never
becomes `UNCHANGED` and never replaces the last good baseline.

Agent budget before a call (plus the platform `apify-actor-start` of about
**$0.0002** at the 4 GiB default): 1 URL **$0.025**, 10 URLs **$0.25**,
maximum 50 URLs **$1.25**. Set `maxTotalChargeUsd` to that `url-audit` bill
**plus** start. A $0.25 cap is too low for 10 successful audits at 4 GiB
(`$0.2502`). If the limit cannot cover every valid URL, Chrome never starts
and each URL gets a free `BILLING_LIMIT_TOO_LOW` row.

Authenticated MCP execution uses the caller's Apify account and pay-per-event.
Direct `?tools=automa-flow/lighthouse-cwv-monitor` requires a token; anonymous
`search-actors` can still discover the public listing after publication.
Discovery via MCP is not the same as an autonomous paid run.

### Pricing

The planned publication price is **$0.025 per successful URL audit** ($25 per
1,000). A URL audit includes the persisted lab metrics, and in `monitor` mode
it also includes comparison with last-good. Platform usage is included in
this pay-per-event price; the Apify Console price shown at run time is
canonical.

The Actor does not define a custom start event. Console PPE still applies
the synthetic `apify-actor-start` at the platform default of $0.00005 per GB
of allocated memory (**$0.0002** at the 4 GiB default) and covers the first
five seconds of compute. That is not `url-audit`.

| Successful URL audits | `url-audit` charge | Plus `apify-actor-start` at 4 GiB |
| ---: | ---: | ---: |
| 1 | $0.025 | ≈ $0.0252 |
| 10 | $0.25 | ≈ $0.2502 |
| 50 | $1.25 | ≈ $1.2502 |
| 1,000 | $25.00 | ≈ $25.0002 |

An unchanged monitor check is billable because the page was audited and
compared. Invalid URLs, blocked pages, HTTP errors, timeouts, unreachable
hosts, failed audits, retries and duplicate aliases are not charged. If a
run's maximum total charge cannot cover every potentially successful URL, the
Actor stops before launching Chrome, writes one free `BILLING_LIMIT_TOO_LOW`
result per otherwise valid URL and fails the run. No last-good is advanced
without a deliverable paid result.

Staging Cloud CU at 4 GiB is **MEASURED** (2026-09-14, build 0.1.1): about
**$0.006** compute for one public URL (~56% contribution), **$0.025** for a
10-URL watchlist with nine billed successes (~69%), and **$0.086** for 50
URLs with 49 billed successes (**73%**). See
[`examples/MONETIZATION.md`](examples/MONETIZATION.md). Do not treat laptop
timings as an invoice.

A completed run is not replayed after resurrection. If output delivery was
interrupted, the Actor retains existing output and stops with
`DELIVERY_UNCERTAIN`; start a new run for a new observation. If every URL
fails, the run fails with `NO_SUCCESS` and retains the free error rows.

### Known limitations

- Lab Lighthouse is not field Core Web Vitals. Scores vary with Chrome,
  Lighthouse version, CPU and simulated throttling.
- Default memory is 4 GiB. A Cloud 2 GiB probe (example.com, python.org,
  web.dev) succeeded without OOM, but a heavy page can take a minute and
  lab scores move with CPU. Keep 4 GiB unless you have measured the page.
- One Chrome, sequential audits, maximum 50 URLs per run. A slow page can
  take tens of seconds.
- No proxy, login, cookie jar or CAPTCHA solving. Pages that require those
  will fail explicitly.
- Off-host redirects are `AUDIT_FAILED`, not success. `www` to bare host on
  the same registrable host is allowed.
- This Actor does not crawl a site, does not emit HTTP technical-SEO issues,
  and does not wrap the PageSpeed Insights API.

### FAQ and troubleshooting

**Why is this not PageSpeed Insights?** PSI is a Google lookup with a
different quota, price and field/lab mix. This Actor runs Lighthouse in
Chrome against the URL you pasted.

**Why was my successful-looking page `AUDIT_FAILED`?** Missing performance
score, LCP, CLS, TBT, FCP or Chrome major, or an off-host `finalUrl`, cannot
be compared. The last-good baseline is kept.

**Why did one slow run not alert?** A single breach is `REGRESSION_PENDING`.
The next comparable run must also breach to become `REGRESSION_CONFIRMED`.

**Can I audit localhost?** No. Private, loopback and metadata hosts are
rejected.

### Security, legal use and retention

You control the URL list. The Actor fetches only those public HTTP(S) pages
in a headless Chrome lab audit. It does not log in, store cookies for later
reuse, or collect personal data beyond what the requested page already
exposes in a Lighthouse report extract (URL, lab metrics, selected audit
titles). Monitor state stores last-good metrics in the named KVS
`lighthouse-cwv-monitor-state` until you delete that store. Dataset rows
follow your Apify storage retention.

Legal class: **MODERATE**. This is a user-controlled public-page auditor, the
same class as a screenshot monitor. Respect each site's terms and
`robots.txt`. Do not use it to access accounts, paywalled content or private
networks.

### Changelog

- `0.1` — first Store candidate: batch Lighthouse CWV watchlist, last-good
  two-stage monitor, pay-per-event `url-audit`. Chrome start failures emit
  free per-item `WORKER_START_FAILED` rows.

# Actor input Schema

## `urls` (type: `array`):

Public HTTP(S) pages, up to 50 per run. externalId is echoed into the result so it can join back to your watchlist. Query strings are part of identity; www and trailing slashes are distinct.

## `mode` (type: `string`):

Snapshot runs a lab audit without reading or changing monitoring state. Monitor compares with the last successful audit; its first successful run is BASELINE. A later noisy breach is REGRESSION\_PENDING until the next run confirms or clears it.

## `formFactor` (type: `string`):

Lighthouse mobile or desktop lab profile. Mobile and desktop keep separate last-good baselines. Chrome major, Lighthouse version and throttling are also part of the comparison profile.

## Actor input object example

```json
{
  "urls": [
    {
      "url": "https://example.com",
      "externalId": "homepage"
    }
  ],
  "mode": "snapshot",
  "formFactor": "mobile"
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        {
            "url": "https://example.com",
            "externalId": "homepage"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automa-flow/lighthouse-cwv-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": [{
            "url": "https://example.com",
            "externalId": "homepage",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("automa-flow/lighthouse-cwv-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    {
      "url": "https://example.com",
      "externalId": "homepage"
    }
  ]
}' |
apify call automa-flow/lighthouse-cwv-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automa-flow/lighthouse-cwv-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/yhDH5R7b660GsY3AG/builds/qRNJY8rzAL4BHmMrL/openapi.json
