# Bulk PageSpeed Insights & Core Web Vitals Checker (`halos_ai_main/bulk-pagespeed-core-web-vitals-checker`) Actor

Bulk-test URLs with Google's official PageSpeed Insights API: Lighthouse scores (performance, accessibility, best practices, SEO), lab metrics, real-user CrUX Core Web Vitals (LCP, INP, CLS) and top fixes, mobile and desktop.

- **URL**: https://apify.com/halos_ai_main/bulk-pagespeed-core-web-vitals-checker.md
- **Developed by:** [Halos AI](https://apify.com/halos_ai_main) (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 $3.00 / 1,000 page checkeds

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

## Bulk PageSpeed Insights & Core Web Vitals Checker

Test **hundreds of URLs at once** with **Google's official PageSpeed Insights API** and get, for every page and device:

- 📊 **Lighthouse scores** (0–100): Performance, Accessibility, Best Practices, SEO
- 🧪 **Lab metrics**: LCP, CLS, TBT, FCP, Speed Index and TTI
- 👥 **Real-user Core Web Vitals** from the Chrome UX Report (CrUX): LCP, INP, CLS, FCP and TTFB at p75, each labeled *good / needs-improvement / poor*, plus the overall **Core Web Vitals passed/failed** assessment for the **URL** and the whole **origin**
- 🛠️ **Top fixes** ranked by estimated savings (e.g. "Improve image delivery: LCP −1.2 s")
- 🔗 Final URL after redirects, Lighthouse version, fetch time and clear errors

### 🔑 Bring your own Google API key (free, required in practice)

This Actor runs every check on **your own Google API key**. Google has closed the shared keyless PageSpeed quota (it's set to 0), so without a key every check comes back with a quota error. Those rows are free, but you get no data.

**Get a key in about 2 minutes:**

1. Open the [Google Cloud Console](https://console.cloud.google.com/) and create a project (or pick an existing one). No billing account is needed for these APIs.
2. Go to **APIs & Services → Library** and enable **PageSpeed Insights API**. Also enable **Chrome UX Report API**, which this Actor uses to fill real-user data gaps.
3. Go to **APIs & Services → Credentials → Create credentials → API key**.
4. Optional but recommended: click the key, choose **Restrict key → API restrictions**, and select only those two APIs.
5. Paste the key into the **Google API key** field. The field is secret, so Apify stores it encrypted.

**Quota (Google's defaults per Cloud project, free):**

- **PageSpeed Insights API:** 25,000 checks per day and about 400 per 100 seconds. That's roughly 12,500 URLs a day on "Both" devices. You can see your exact limits under *APIs & Services → PageSpeed Insights API → Quotas*. To go higher, request an increase from Google; don't spread one job across several keys.
- **Chrome UX Report API:** about 150 queries per minute. It's used only when PageSpeed returns no real-user data.
- The default concurrency of 4 stays well under the per-100-second limit. If Google returns HTTP 429, the Actor backs off and retries. If your **daily** quota runs out, it stops and marks the remaining rows `billable: false`.

**What happens to your key:** it's sent only to Google's APIs, in the `X-goog-api-key` request header (never in a URL). It's never logged, never written to the dataset or key-value store, and never shared with Halos AI. Usage counts against your Google project, and you can revoke or rotate the key any time in Google Cloud.

Why this one:

- ✅ **Official data only.** Every number comes straight from Google's PageSpeed Insights API v5 and the Chrome UX Report API. That's the same engine as pagespeed.web.dev, with no scraping.
- 💵 **Fair billing.** You pay only for successful page checks (one per URL per device). Invalid URLs, pages Lighthouse can't load and quota errors are still returned with an `error` and `billable: false`, and they're **never charged**.
- 🔑 **Your key, your quota.** It works with your own free Google API key (see above), so you get Google's full 25,000 checks a day instead of a shared limit.
- 🧭 **CrUX that keeps working.** Google has announced it will phase real-user data out of the PageSpeed API. When PageSpeed has no field data, this Actor asks the CrUX API directly.
- ♻️ **Resilient.** Backoff on rate limits (HTTP 429) and Google 5xx errors. It also stops cleanly when a daily quota runs out instead of burning retries.

### Use cases

- **SEO and web agencies**: audit a client's top 200 pages on mobile before a pitch or each month, then hand over the Overview tab as CSV
- **Core Web Vitals monitoring**: schedule weekly runs and alert when `cwvOrigin` flips from `passed` to `failed`
- **Lead generation for speed and dev services**: run prospects' homepages and sort by `performanceScore`
- **Pre- and post-release checks**: compare scores for key templates (home, category, product, blog) before and after a deploy

### How to use it

1. Get a free Google API key (2 minutes, see **Bring your own Google API key** above) and paste it into **Google API key**.
2. Paste your URLs, one per line, and pick **Mobile**, **Desktop** or **Both**.
3. Click **Start**. Each check takes about 10–40 seconds on Google's side, and 4 run in parallel by default.
4. Open **Output** → *Overview*, *Lab metrics*, *Real-user Core Web Vitals* or *Top fixes*, and export as CSV, Excel or JSON.

### Agency recipe: a client speed audit in one run

Input the client's sitemap's top pages with `"strategy": "both"`. In the Overview table, sort by `performanceScore`, filter `cwvOrigin = failed` and read `topOpportunity`. That gives you a prioritized fix list per template. Re-run monthly on a schedule and keep each dataset as the before/after record in your report.

### Cost

Pay per event:

- **$0.005 per run** (start fee)
- **$0.003 per successful page check** ($3.00 per 1,000), one check = one URL on one device

Examples:

- 200 URLs on mobile = 200 checks = $0.005 + $0.60 = **$0.61**
- 200 URLs on both devices = 400 checks = **$1.21**
- 1,000 URLs, mobile, weekly = **about $3.01 a week**

Failed and invalid rows cost nothing. You can also cap spend with Apify's **Maximum cost per run**. The Actor stops cleanly when the cap is reached.

### Input example

```json
{
  "urls": ["https://www.example.com/", "https://www.example.com/pricing", "shop.example.com/category/shoes"],
  "strategy": "both",
  "categories": ["performance", "accessibility", "best-practices", "seo"],
  "googleApiKey": "YOUR_GOOGLE_API_KEY",
  "includeCruxApi": true,
  "maxOpportunities": 5,
  "concurrency": 4,
  "maxRetries": 3
}
```

### Output example (one item)

```json
{
  "url": "https://www.example.com/",
  "strategy": "mobile",
  "performanceScore": 62,
  "accessibilityScore": 91,
  "bestPracticesScore": 100,
  "seoScore": 92,
  "lcpMs": 4870,
  "cls": 0.04,
  "tbtMs": 410,
  "fcpMs": 2120,
  "speedIndexMs": 3980,
  "ttiMs": 6900,
  "fieldDataUrl": {
    "lcp": {"p75": 2900, "category": "needs-improvement"},
    "inp": {"p75": 180, "category": "good"},
    "cls": {"p75": 0.03, "category": "good"},
    "fcp": {"p75": 1700, "category": "good"},
    "ttfb": {"p75": 900, "category": "needs-improvement"},
    "coreWebVitals": "failed",
    "source": "pagespeed-api"
  },
  "fieldDataOrigin": {"coreWebVitals": "passed", "source": "crux-api", "collectionPeriod": "2026-09-19/2026-10-16", "...": "..."},
  "cwvUrl": "failed",
  "cwvOrigin": "passed",
  "fieldLevel": "url",
  "opportunities": [
    {"id": "image-delivery-insight", "title": "Improve image delivery", "displayValue": "Est savings of 955 KiB", "metricSavings": {"LCP": 1200}}
  ],
  "topOpportunity": "Improve image delivery",
  "finalUrl": "https://www.example.com/",
  "fetchTime": "2026-10-18T18:07:10.635Z",
  "lighthouseVersion": "13.5.0",
  "durationSecs": 18.4,
  "error": null,
  "billable": true
}
```

The values above are illustrative. Failed rows look like `{"url": "...", "strategy": "mobile", "error": "Lighthouse could not test the page (FAILED_DOCUMENT_REQUEST) ...", "errorType": "check-failed", "billable": false}`.

### API example

Run it and get results in one call (replace `YOUR_APIFY_TOKEN`):

```bash
curl -X POST "https://api.apify.com/v2/acts/halos_ai_main~bulk-pagespeed-core-web-vitals-checker/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN&view=overview&format=csv" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://www.example.com/", "https://www.example.com/blog/"], "strategy": "mobile", "googleApiKey": "YOUR_GOOGLE_API_KEY"}'
```

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("halos_ai_main/bulk-pagespeed-core-web-vitals-checker").call(
    run_input={"urls": ["https://www.example.com/"], "strategy": "both", "googleApiKey": "YOUR_GOOGLE_API_KEY"})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["url"], item["strategy"], item.get("performanceScore"), item.get("cwvOrigin"))
```

It also works from Make, Zapier, n8n, Google Sheets (via Apify integrations) and AI agents through the Apify MCP server.

### FAQ

**Do I need a Google API key?**
Yes, in practice. Google's shared keyless quota is set to 0, so runs without a key return only free quota-error rows. See **Bring your own Google API key** above for the 2-minute setup. The key is free, and Google doesn't bill for these APIs within the default quotas.

**Why is real-user data missing even though I have a key?**
Either the page or site doesn't have enough Chrome traffic (`fieldDataNote` explains which), or the **Chrome UX Report API** isn't enabled for your key's project. The run log and status message warn you about the second case.

**Why do my scores differ from run to run?**
Lighthouse lab tests vary by a few points between runs (network, server load, ads). Use real-user CrUX data (`fieldData*`, `cwv*`) for decisions, and lab scores for debugging and before/after comparisons.

**Why is real-user data empty for some pages?**
CrUX only covers pages and origins with enough Chrome traffic. Small sites often have origin-level data but no URL-level data. `fieldLevel` tells you which one was used.

**What counts as a billable check?**
One URL tested on one device with a successful Lighthouse result. "Both" devices = 2 checks. Errors are free.

**Is TTI always present?**
Newer Lighthouse versions may stop reporting Time to Interactive. When Google doesn't return a value, the field is `null`.

**Is this affiliated with Google?**
No. It's an independent tool by Halos AI that calls Google's public APIs on your behalf. Google, PageSpeed Insights, Lighthouse and Chrome are trademarks of Google LLC. CrUX data is © Google, licensed CC BY 4.0.

### Support

Found a bug or need a field? Open an issue on the Actor's **Issues** tab.

# Actor input Schema

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

Pages to test, one per line. A missing scheme gets https:// added (example.com becomes https://example.com/). Fragments are dropped and duplicates removed. Test full page URLs (e.g. a product page), not just homepages, for page-level results.

## `strategy` (type: `string`):

Lighthouse emulation profile. Mobile uses a throttled mid-range phone, which is what Google's mobile-first indexing and most Core Web Vitals reports focus on. 'Both' tests every URL twice and bills 2 page checks.

## `categories` (type: `array`):

Which Lighthouse category scores (0-100) to compute. Lab metrics and fixes come from Performance, so keep it on for speed audits. Fewer categories make each check slightly faster; the price is the same.

## `googleApiKey` (type: `string`):

Your own free Google Cloud API key. Google's shared keyless quota is 0, so without a key every check returns a free quota-error row. Setup (2 min): Google Cloud Console → create a project → APIs & Services → Library → enable 'PageSpeed Insights API' and 'Chrome UX Report API' → Credentials → Create credentials → API key (restrict it to those two APIs). Google's default quota is 25,000 checks/day and about 400 per 100 seconds per project, free. The key is stored encrypted, sent only to Google in a request header, never logged or written to the output, and usage counts against your own Google project.

## `includeCruxApi` (type: `boolean`):

When PageSpeed returns no real-user data for a URL or origin, query Google's Chrome UX Report API directly. This needs 'Chrome UX Report API' enabled on the same Google project as your key; it's free and doesn't change the price. If it isn't enabled, the run still works and the log tells you. Google has announced it will phase field data out of the PageSpeed API, so this keeps CrUX results working.

## `maxOpportunities` (type: `integer`):

How many of the top Lighthouse opportunities and insights to return per page, ranked by estimated time saved (then bytes). Set 0 to skip them.

## `concurrency` (type: `integer`):

How many pages are tested in parallel. Each PageSpeed test takes about 10-40 seconds on Google's side. Google's per-key limit is about 400 requests per 100 seconds, so 4-8 is safe; lower it if you share the key with other tools.

## `maxRetries` (type: `integer`):

Retries for rate limits (HTTP 429), Google server errors (5xx) and network errors, with exponential backoff (honors Retry-After). A daily-quota error stops the run instead of retrying, because retries can't fix it.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for Google to finish one Lighthouse test before retrying. Very heavy pages can take over a minute.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com/",
    "https://www.wikipedia.org/"
  ],
  "strategy": "mobile",
  "categories": [
    "performance",
    "accessibility",
    "best-practices",
    "seo"
  ],
  "includeCruxApi": true,
  "maxOpportunities": 5,
  "concurrency": 4,
  "maxRetries": 3,
  "requestTimeoutSecs": 120
}
```

# Actor output Schema

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

One item per URL per device with Lighthouse scores, lab metrics, CrUX Core Web Vitals and top fixes. Invalid URLs and failed checks have an error and billable: false.

# 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": [
        "https://apify.com/",
        "https://www.wikipedia.org/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("halos_ai_main/bulk-pagespeed-core-web-vitals-checker").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": [
        "https://apify.com/",
        "https://www.wikipedia.org/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("halos_ai_main/bulk-pagespeed-core-web-vitals-checker").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": [
    "https://apify.com/",
    "https://www.wikipedia.org/"
  ]
}' |
apify call halos_ai_main/bulk-pagespeed-core-web-vitals-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,halos_ai_main/bulk-pagespeed-core-web-vitals-checker"
        }
    }
}
```

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/VgOuhZ4YrNXhJ2rc0/builds/Jc7SoeRLE7NhCeqoG/openapi.json
