PageSpeed Insights & Core Web Vitals Bulk Checker avatar

PageSpeed Insights & Core Web Vitals Bulk Checker

Pricing

from $3.00 / 1,000 url × strategy auditeds

Go to Apify Store
PageSpeed Insights & Core Web Vitals Bulk Checker

PageSpeed Insights & Core Web Vitals Bulk Checker

Bulk-check PageSpeed Insights scores and Core Web Vitals (LCP, INP, CLS) on mobile and desktop, with CrUX field data and regression alerts.

Pricing

from $3.00 / 1,000 url × strategy auditeds

Rating

0.0

(0)

Developer

Grace M. Hartwell

Grace M. Hartwell

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

20 hours ago

Last modified

Share

Hartwell Toolworks

PageSpeed Insights & Core Web Vitals Bulk Checker runs Google PageSpeed Insights for many URLs at once, on mobile and desktop, and returns the Lighthouse performance score, Core Web Vitals (LCP, INP, CLS) and other speed metrics. It adds real-user field data from the Chrome UX Report (CrUX) and compares every result with the previous run, so it can alert you when page speed gets worse: a flag in the dataset plus an optional webhook. You use your own free Google API key and pay $0.003 per URL per strategy. Failed audits are free.

What does the PageSpeed Insights bulk checker do?

  • Calls the official PageSpeed Insights API v5 for every URL × strategy (mobile and/or desktop).
  • Returns lab data from Lighthouse: performance score, LCP, CLS, TBT, FCP, Speed Index and server response time (TTFB). Accessibility, best-practices and SEO scores are optional.
  • Adds CrUX field data (p75 LCP, INP, CLS, FCP, TTFB) at URL level, falling back to origin level when the URL has too little traffic. Without CrUX, the field data PageSpeed Insights itself returns is used when available.
  • Saves the latest snapshot per URL × strategy and reports deltas compared with the previous run.
  • Raises regression alerts (score drop; LCP, TBT or INP increase in %; CLS increase) and optional absolute budget alerts (minimum score, LCP, CLS and INP budgets).
  • Sends one webhook per run with all alerts, and only when there are alerts.

Key features

  • 📱 Mobile and desktop in one run.
  • 🧪 Lab and field data together: Lighthouse lab metrics plus CrUX real-user p75 values with the collection period.
  • 📉 Regression monitoring: each run is compared with the previous one under the same monitor name (stateKey).
  • 🚨 Alerts: an alert flag and alerts array per item, plus a JSON webhook (for example to Make, Zapier, n8n or your own endpoint).
  • 🔁 Reliable API handling: exponential backoff with jitter for 429 rate limits, 5xx Lighthouse errors and timeouts.
  • 🛑 Fails fast on key problems: if your API key is invalid, the API is not enabled, or the key's daily quota is exhausted, the run stops with a clear message instead of failing on every URL.
  • 🆓 No key? No charge: without an API key, audits that Google refuses are returned as free skipped_no_api_key items and the run still finishes successfully, telling you to add a key.
  • 🔑 Your API key stays secret: it is stored in a secret (encrypted) input field and masked in every log and error message.

Use cases

  1. Core Web Vitals monitoring for SEO: schedule the Actor daily or weekly and track LCP, INP and CLS for your key pages.
  2. Release and deploy checks: run before and after a deployment and get alerted about performance regressions.
  3. Agency and portfolio reporting: check PageSpeed scores for many client sites or page templates in one run.
  4. Mobile vs. desktop comparison: compare both strategies across a site's important templates.
  5. Performance budgets: alert when LCP, CLS, INP or the performance score crosses a fixed limit.

How much does a bulk PageSpeed Insights check cost?

The Actor uses pay-per-event pricing:

EventPriceWhen it is charged
url-audited$0.003 per URL × strategy ($3.00 per 1,000 audits)After a successful audit of one URL for one strategy
apify-actor-start$0.00005 per runAutomatically at run start (Apify default price)

Free: failed audits, invalid URLs and URLs skipped because of the budget. Google API usage is billed by Google, not by this Actor; it stays free within Google's quota for your own key.

Example calculation: 100 URLs, mobile + desktop:

100 URLs × 2 strategies = 200 audits
200 audits × $0.003 = $0.60
+ 1 Actor start × $0.00005 = $0.00005 (default 256 MB memory → 1 start event)
------------------------------------------------
Total per run ≈ $0.60005
Monitoring daily for 30 days = 30 × $0.60005 ≈ $18.00

How to use the Core Web Vitals checker

  1. Get a Google API key: in Google Cloud Console go to APIs & Services → Credentials → Create credentials → API key.
  2. Enable the PageSpeed Insights API and, for field data, the Chrome UX Report API for that project. The input description lists the free quotas as 25,000 PSI requests per day and 150 CrUX requests per minute; check Google Cloud Console for your current limits.
  3. Open the Actor in Apify Store, click Try for free, and add your URLs.
  4. Paste the key into Google API key and choose the Strategies (mobile, desktop).
  5. Optional: set a Monitor name (state key), Alert thresholds and an Alert webhook URL.
  6. Click Start. To monitor, create an Apify schedule with the same input so every run is compared with the previous one.

Input example

{
"urls": [{ "url": "https://web.dev/" }, { "url": "https://developer.chrome.com/" }],
"apiKey": "YOUR_GOOGLE_API_KEY",
"strategies": ["mobile", "desktop"],
"categories": ["seo"],
"includeCrux": true,
"compareWithPrevious": true,
"stateKey": "my-site",
"thresholds": {
"maxScoreDrop": 5,
"maxLcpIncreasePct": 20,
"maxTbtIncreasePct": 30,
"maxClsIncrease": 0.05,
"maxInpIncreasePct": 20,
"minPerformanceScore": 0,
"lcpBudgetMs": 2500,
"clsBudget": 0.1,
"inpBudgetMs": 200
},
"webhookUrl": "https://example.com/my-webhook",
"alertOnly": false,
"maxConcurrency": 3,
"requestTimeoutSecs": 120,
"maxRetries": 4
}
Input fieldTypeDefaultDescription
urlsarray[{ "url": "https://example.com/" }]Pages to audit, each once per strategy. Duplicates are removed. Replace the default with your own pages.
apiKeystring (secret)–Your Google API key. In practice it is required (see FAQ). Without it, refused audits are skipped for free (skipped_no_api_key).
strategiesarray["mobile","desktop"]mobile and/or desktop.
categoriesarray[]Extra Lighthouse categories: accessibility, best-practices, seo. Performance is always included.
includeCruxbooleantrueAdd CrUX field data (needs the API key).
compareWithPreviousbooleantrueCompare with the last stored snapshot and report deltas and regressions.
stateKeystringdefaultMonitor name. History is stored in the key-value store cwv-monitor-<name>.
thresholdsobjectsee belowRegression limits and absolute budgets (0 = off).
webhookUrlstring–Receives one JSON POST with all alerts of the run (only when there are alerts).
alertOnlybooleanfalseStore only items with alerts in the dataset. All URLs are still audited and charged.
maxConcurrencyinteger3Parallel audits (1–10).
requestTimeoutSecsinteger120Timeout per PSI call (30–300).
maxRetriesinteger4Retries for 429, 5xx and timeouts (0–8).
maxRetryAfterSecsinteger60Longest wait the Actor accepts from a Retry-After header (seconds or HTTP date) before retrying (0–600). Longer requested waits are capped; without the header, exponential backoff with jitter is used.

Threshold defaults used by the code when a value is not set: maxScoreDrop 5, maxLcpIncreasePct 20, maxTbtIncreasePct 30, maxClsIncrease 0.05, maxInpIncreasePct 20; the budgets minPerformanceScore, lcpBudgetMs, clsBudget and inpBudgetMs are off (0). The Console form prefills LCP 2500 ms, CLS 0.1 and INP 200 ms budgets.

Output example

One dataset item per URL × strategy (example from a local test run against a mocked API):

{
"url": "https://regress.test/",
"strategy": "mobile",
"checkedAt": "2026-10-25T22:53:55.272Z",
"status": "ok",
"finalUrl": "https://regress.test/",
"performanceScore": 61,
"categoryScores": { "performance": 61 },
"lab": { "performanceScore": 61, "lcpMs": 4200, "cls": 0.21, "tbtMs": 650, "fcpMs": 900, "speedIndexMs": 1500, "ttfbMs": 120 },
"field": { "source": "crux-url", "lcpP75Ms": 3900, "inpP75Ms": 320, "clsP75": 0.18, "fcpP75Ms": 1100, "ttfbP75Ms": 350, "overallCategory": "AVERAGE", "collectionPeriod": "2026-09-26..2026-10-23" },
"cwvAssessment": "AVERAGE",
"previous": { "checkedAt": "2026-10-25T22:53:46.228Z", "performanceScore": 88, "lab": { "performanceScore": 88, "lcpMs": 2300, "cls": 0.02, "tbtMs": 180, "fcpMs": 900, "speedIndexMs": 1500, "ttfbMs": 120 }, "field": { "source": "crux-url", "lcpP75Ms": 2150, "inpP75Ms": 150, "clsP75": 0.03, "fcpP75Ms": 1100, "ttfbP75Ms": 350, "overallCategory": "FAST", "collectionPeriod": "2026-09-26..2026-10-23" } },
"deltas": { "performanceScore": -27, "lcpMs": 1900, "tbtMs": 470, "cls": 0.19, "fieldLcpP75Ms": 1750, "fieldInpP75Ms": 170, "fieldClsP75": 0.15 },
"alert": true,
"alerts": [
{ "type": "regression", "metric": "performanceScore", "previous": 88, "current": 61, "threshold": 5, "message": "Performance score dropped 88 → 61 (more than 5 points)." },
{ "type": "regression", "metric": "lcpMs", "previous": 2300, "current": 4200, "threshold": 20, "message": "lcpMs worsened 2300 ms → 4200 ms (+83%, limit 20%)." }
],
"lighthouseVersion": "12.8.2",
"runWarnings": [],
"cruxError": null,
"error": null,
"errorCode": null
}

Error item (free):

{ "url": "https://example.com/", "strategy": "mobile", "checkedAt": "2026-10-25T22:54:06.564Z", "status": "error", "errorCode": "QUOTA_DAILY_EXHAUSTED", "error": "PageSpeed Insights API: daily quota exhausted (quota is 0 – an API key is required). Provide your own Google API key or wait for the quota reset." }

Item when no API key was provided and Google refused the keyless request (free, the run still succeeds):

{ "url": "https://example.com/", "strategy": "mobile", "checkedAt": "2026-10-26T15:00:00.000Z", "status": "skipped_no_api_key", "errorCode": "API_KEY_REQUIRED", "error": "Skipped (not charged): no Google API key provided and Google allows (almost) no PageSpeed Insights requests without one. Add your own free key in the \"Google API key\" field (Google Cloud Console → APIs & Services → Credentials, enable \"PageSpeed Insights API\"). Google said: PageSpeed Insights API: daily quota exhausted (quota is 0 – an API key is required). Provide your own Google API key or wait for the quota reset." }

Output fields

FieldDescription
url, finalUrl, strategy, checkedAtAudited URL, final URL reported by Lighthouse, mobile/desktop, timestamp.
statusok, error, or skipped (budget reached).
performanceScoreLighthouse performance score, 0–100.
categoryScoresScores (0–100) of performance and any extra categories.
lablcpMs, cls, tbtMs, fcpMs, speedIndexMs, ttfbMs, performanceScore.
fieldsource (crux-url, crux-origin, psi-url, psi-origin), lcpP75Ms, inpP75Ms, clsP75, fcpP75Ms, ttfbP75Ms, overallCategory, collectionPeriod; null if no field data.
cwvAssessmentFAST / AVERAGE / SLOW from the field data, or null.
previous, deltasPrevious snapshot and the change per metric (null on the first run).
alert, alertstrue if any alert fired; list of { type, metric, previous, current, threshold, message }.
lighthouseVersion, runWarningsLighthouse version and warnings returned by PSI.
cruxErrorCrUX problem for this item (PSI data is still returned).
error, errorCodeError details.

Error codes: PAGE_LOAD_FAILED, TIMEOUT, RATE_LIMITED, SERVER_ERROR, API_KEY_INVALID, API_ACCESS_DENIED, QUOTA_DAILY_EXHAUSTED, BAD_REQUEST, HTTP_ERROR, REQUEST_FAILED, INVALID_URL, BUDGET_REACHED.

Run summary (OUTPUT record): urls, strategies, audited, alerts, itemsWithAlerts, webhook (sent, status or error), skippedNoApiKey, fatalError.

Integrations, webhook and API

Alert webhook: when a run has alerts, the Actor sends one POST request with this JSON body to webhookUrl:

{
"text": ":rotating_light: PageSpeed/Core Web Vitals monitor: 12 alert(s) on 6 URL × strategy check(s)\n• https://regress.test/ (mobile) – Performance score dropped 88 → 61 (more than 5 points).\n…",
"actor": "pagespeed-cwv-monitor",
"runId": "<run ID>",
"checkedAt": "2026-10-25T22:54:00.344Z",
"alertCount": 12,
"items": [{ "url": "https://regress.test/", "strategy": "mobile", "alerts": [{ "type": "regression", "metric": "performanceScore", "previous": 88, "current": 61, "threshold": 5, "message": "Performance score dropped 88 → 61 (more than 5 points)." }] }]
}

The top-level text field is a human-readable summary (one line per alert, at most 30 lines), so a Slack incoming webhook URL works directly, as do chat tools that read text. The structured fields stay in the same body, so you can also point it at a Make, Zapier or n8n webhook or your own endpoint.

Apify API: results are in a standard Apify dataset, so you can use the Apify API, API clients, schedules and integrations.

curl -X POST "https://api.apify.com/v2/acts/grace_hartwell~pagespeed-cwv-monitor/run-sync-get-dataset-items?token=[YOUR_APIFY_TOKEN]" \
-H "Content-Type: application/json" \
-d '{"urls":[{"url":"https://web.dev/"}],"apiKey":"YOUR_GOOGLE_API_KEY","strategies":["mobile"]}'

FAQ

Why do I need my own Google API key?

Without a key, the PageSpeed Insights API currently allows (almost) no requests; in testing, a keyless request came back with a daily quota of 0. The CrUX API always requires a key. Your own key gives you your own Google quota. If you run the Actor without a key, it tries the keyless API once per URL × strategy (at most one retry); refused audits are saved as free skipped_no_api_key items, nothing is charged, and the run ends successfully with a hint to add a key.

What is the difference between lab data and field data?

Lab data comes from a single Lighthouse run in Google's infrastructure, and it can vary from run to run. Field data is the 28-day, 75th-percentile experience of real Chrome users (CrUX). Use the regression thresholds to absorb normal lab variation.

Why is there no field data for my URL?

CrUX only has data for pages and origins with enough real-user traffic. The Actor falls back from URL level to origin level; if neither has data, field is null.

How is cwvAssessment calculated?

With CrUX data, the Actor calculates it: FAST if every available metric is good at p75 (LCP ≤ 2500 ms, INP ≤ 200 ms, CLS ≤ 0.1), SLOW if any is poor (LCP > 4000 ms, INP > 500 ms, CLS > 0.25), otherwise AVERAGE. Without CrUX, it uses the overall category returned by PageSpeed Insights.

Where is the monitoring history stored?

In a key-value store named cwv-monitor-<stateKey> in your Apify account. It holds the latest snapshot per URL × strategy, and each run's dataset keeps that run's full results.

What happens if my API key is invalid or my quota runs out?

The run stops early with a clear message (API_KEY_INVALID, API_ACCESS_DENIED or QUOTA_DAILY_EXHAUSTED) instead of trying every URL. Failed audits are not charged. (Without any key, see the previous question: audits are skipped for free and the run succeeds.)

Is alertOnly cheaper?

No. All URLs are still audited and charged, and the history is still updated. alertOnly only keeps items without alerts out of the dataset.

Limitations: what this Actor does not do

  • It does not run Lighthouse itself: all lab data comes from Google's PageSpeed Insights API, so results depend on Google's availability, quotas and run-to-run variance. Audits can take 10–60 seconds each.
  • No crawling or URL discovery: it audits only the URLs you list.
  • Scores and metrics only: it does not return Lighthouse opportunities, diagnostics or the full Lighthouse report.
  • Only the previous snapshot is used for comparison (one stored snapshot per URL × strategy), not a long-term trend.
  • Long Retry-After waits are capped: the Actor waits as long as Google asks (seconds or HTTP date), but at most maxRetryAfterSecs (default 60 s) per retry; daily-quota errors are not retried at all.
  • The webhook is a single JSON POST with a plain text summary: no Slack Block Kit layout, Teams cards or e-mail. It is sent once per run, only when there are alerts, and a failed webhook does not fail the run (see webhook in OUTPUT).
  • Budget alerts are off by default via API: the LCP, CLS, INP and score budgets are 0 (off) unless you set them; only the Console form prefills them.
  • Each run is capped by your Max total charge; remaining audits are reported as skipped / BUDGET_REACHED.

Author: Grace M. Hartwell