# Bulk PageSpeed Insights & Core Web Vitals Checker (`tidytools/pagespeed-insights-bulk`) Actor

Google PageSpeed Insights (Lighthouse) for many URLs: performance, accessibility, SEO scores, LCP, CLS, TBT, real-user Core Web Vitals (CrUX), top fixes, budgets and regression alerts. No API key needed. $2 per 1,000 measurements, failed free.

- **URL**: https://apify.com/tidytools/pagespeed-insights-bulk.md
- **Developed by:** [Yukai Lin](https://apify.com/tidytools) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 pagespeed measurements

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

### What does Bulk PageSpeed Insights & Core Web Vitals Checker do?

**Paste a list of URLs, get one row per page and device with Lighthouse scores, lab metrics, real-user Core Web Vitals and the biggest fix, at $2 per 1,000 measurements ($0.002 each).** Failed or timed-out measurements, pages that cannot be loaded, invalid URLs and measurements skipped after a quota stop are free. No start fee, no API key needed.

It runs **Google PageSpeed Insights (Lighthouse)** on a whole list of URLs and gives you one clean row per page and device, ready for a spreadsheet, a dashboard or an alert:

- 🚦 **Lighthouse scores (0-100)**: performance, accessibility, best practices and SEO, in one measurement.
- ⏱️ **Lab metrics**: First Contentful Paint, **Largest Contentful Paint**, **Total Blocking Time**, **Cumulative Layout Shift**, Speed Index and Time to Interactive.
- 👥 **Real-user Core Web Vitals** from the Chrome UX Report (CrUX): LCP, **INP**, CLS, FCP and TTFB at the 75th percentile, each rated FAST / AVERAGE / SLOW, and the overall **Core Web Vitals PASSED / FAILED** assessment, for the URL or (when it has too little traffic) for its whole site.
- 🔧 **Top opportunities**: the biggest fixes Lighthouse found (e.g. "Reduce unused JavaScript ~0.8 s") with the estimated time saved.
- 📱 **Mobile, desktop or both** in one run.
- ✅ **Performance budgets**: set a minimum score, a maximum LCP / CLS / TBT / INP or "Core Web Vitals must pass"; every row gets `passed` and `failedChecks`.
- 📉 **Regression alerts** for scheduled runs: each URL is compared with the previous run (`previousScore`, `scoreChange`, `regressed`), and "Only output regressions" returns just the pages that got slower.
- 📄 **CSV file** with the main columns.

**No API key needed**: measurements run on our capacity. You can add your own free Google API key if you want Google to be called directly with it.

#### Why this Actor?

- **Everything in one row**: four Lighthouse scores, six lab metrics, real-user Core Web Vitals for the URL and the site, and the top fixes. Most PageSpeed Actors return the performance score and a few lab metrics only.
- **Built for monitoring**: budgets with pass/fail, run-to-run comparison and a regressions-only output make a daily or weekly schedule an alert, not a pile of data.
- **$2 per 1,000 measurements, failed ones free**: a page Lighthouse cannot load costs nothing (several Store Actors charge for it or fail the run).
- **Keeps working when Google is busy**: if Google's PageSpeed service cannot measure a page, the run falls back to our own Lighthouse servers (same Lighthouse engine, no real-user data).

#### Real results (4 October 2026)

Local runs on the production backend, 4 October 2026 (times include start-up):

- **13 well-known sites + 1 dead domain + 1 typo, mobile and desktop (28 measurements)**: **22 measured and charged ($0.044), 6 failed for free**, 1 invalid input free, **4 min 47 s** with 4 in parallel. Google measured 18 (12-64 s each); for amazon.com and github.com Google could not finish, so our Lighthouse server in Taiwan measured them (26-139 s). nytimes.com and etsy.com block Lighthouse and the dead domain does not resolve: `page_failed`, not charged.
- What it found: news.ycombinator.com 100 / 100, wikipedia.org 98 (mobile), apple.com 53 mobile vs 94 desktop, bbc.com 30 on mobile with a lab LCP of 12.5 s, yet real users **pass** Core Web Vitals there; stripe.com, shopify.com and web.dev **fail** Core Web Vitals on mobile. With the budgets "score ≥ 50, LCP ≤ 4 s, Core Web Vitals must pass", 12 rows passed and 10 failed, each with its `failedChecks`.
- 5 pages, mobile, with comparison: 33 s, $0.01. The same 5 pages a minute later with "Only output regressions" and a 5-point threshold: 3 rows (python.org 82 → 74, apple.com 65 → 56, web.dev 46 → 35), 2 stable pages left out. Lighthouse scores move this much between back-to-back runs, which is why the default threshold is 10 points: a third run with 10 points returned 0 rows (all 5 stable).

### Use cases

- **SEO audits at scale**: score every template or top landing page of a site (or of a client list) on mobile and desktop in one run.
- **Core Web Vitals monitoring**: a weekly schedule with "Compare with the previous run" and "Only output regressions" tells you which pages got slower after a release.
- **Performance budgets in CI or QA**: fail a release check when the home page drops below 80 or LCP goes over 2.5 s (`passed: false`, `failedChecks`).
- **Competitor benchmarks**: compare your speed and real-user experience with competitors' pages.
- **Agency reporting**: CSV with scores, Core Web Vitals and the top fix per page, ready for Google Sheets or Looker Studio.

### How to use it

1. Paste your URLs (or bare domains) under **URLs**, paste text, or upload / link a .txt or .csv file. One per line, or separated by commas, semicolons or tabs; quotes and brackets pasted around a link are removed. **One page is measured and charged once per device**, however it is spelled: `a.com`, `https://www.a.com/`, `https://a.com/?utm_source=x` and `https://a.com/#x` are the same page (the first spelling is measured, without the `#fragment` and tracking parameters such as `utm_*`, `gclid`, `fbclid`; `SUMMARY.duplicatesMerged`). `http://` and `https://`, and different paths or other query parameters, stay separate pages.
2. Choose the **Device**: mobile (what Google uses for ranking), desktop or both.
3. Optional: set **performance budgets** (minimum score, maximum LCP, CLS, TBT, INP, "Core Web Vitals must pass").
4. Optional, for schedules: turn on **Compare with the previous run** (and **Only output regressions**), then create an Apify schedule.
5. Run it. Results appear in the dataset (tabs: PageSpeed results, All Lighthouse scores, Real-user Core Web Vitals, Changes, Errors) and in `PAGESPEED.csv`.

A Google measurement takes 10-50 seconds per page; with 4 in parallel a run does about 10-15 pages per minute.

#### Input example

```json
{
    "urls": ["https://www.python.org/", "https://example.com/"],
    "strategy": "both",
    "minPerformanceScore": 50,
    "maxLcpMs": 4000,
    "requireCoreWebVitals": true
}
```

#### Output example

```json
{
    "type": "measurement",
    "input": "https://www.python.org/",
    "url": "https://www.python.org/",
    "finalUrl": "https://www.python.org/",
    "strategy": "mobile",
    "performanceScore": 72,
    "performanceRating": "needs-improvement",
    "accessibilityScore": 75,
    "bestPracticesScore": 96,
    "seoScore": 92,
    "fcpMs": 2859,
    "lcpMs": 5318,
    "tbtMs": 0,
    "cls": 0,
    "speedIndexMs": 4890,
    "ttiMs": 5318,
    "coreWebVitals": "PASSED",
    "fieldDataScope": "url",
    "fieldLcpMs": 1236,
    "fieldInpMs": 88,
    "fieldCls": 0,
    "fieldFcpMs": 1168,
    "fieldTtfbMs": 531,
    "fieldData": {
        "url": { "lcpMs": 1236, "lcpCategory": "FAST", "inpMs": 88, "inpCategory": "FAST", "cls": 0, "clsCategory": "FAST", "fcpMs": 1168, "fcpCategory": "FAST", "ttfbMs": 531, "ttfbCategory": "FAST", "coreWebVitals": "PASSED", "overallCategory": "FAST" },
        "origin": null
    },
    "topOpportunity": "Reduce unused JavaScript (~750 ms)",
    "opportunities": [
        { "id": "unused-javascript", "title": "Reduce unused JavaScript", "savingsMs": 750, "savingsBytes": 60458 },
        { "id": "unused-css-rules", "title": "Reduce unused CSS", "savingsMs": 560, "savingsBytes": 131404 }
    ],
    "passed": false,
    "failedChecks": ["lcpMs 5318 > 4000"],
    "source": "google",
    "lighthouseVersion": "13.5.0",
    "durationSecs": 15.1,
    "measuredAt": "2026-10-03T17:51:16.672Z",
    "success": true,
    "charged": true
}
```

The run also writes a `SUMMARY` record (status, measurements, average score, sources used, budgets passed/failed, regressions, failures by `errorType`, cost plan) and `PAGESPEED.csv`.

#### What the fields mean

- `performanceScore`, `accessibilityScore`, `bestPracticesScore`, `seoScore`: Lighthouse category scores, 0-100 (90+ is good, 50-89 needs improvement, under 50 is poor; `performanceRating`).
- `fcpMs`, `lcpMs`, `tbtMs`, `cls`, `speedIndexMs`, `ttiMs`: **lab** metrics from this Lighthouse run (an emulated mid-range phone on slow 4G for mobile). Lab data has no INP.
- `coreWebVitals`: the **real-user** assessment from the Chrome UX Report: `PASSED` when LCP, INP and CLS at the 75th percentile are all "good" (2.5 s, 200 ms, 0.1), `FAILED` otherwise, `null` when Google has no real-user data for the page or site.
- `fieldDataScope`: `url` (data for this page) or `origin` (the page has too little traffic, so the whole site's data is shown). `fieldLcpMs`, `fieldInpMs`, `fieldCls`, `fieldFcpMs`, `fieldTtfbMs`: 75th percentiles; `fieldData` has every metric with its FAST / AVERAGE / SLOW category.
- `topOpportunity` and `opportunities`: the biggest suggested fixes with estimated time (`savingsMs`) and bytes saved.
- `passed`, `failedChecks`: only when you set a budget, e.g. `["performanceScore 30 < 50", "lcpMs 9134 > 4000"]`.
- `change` (`new`, `stable`, `improved`, `regressed`), `previousScore`, `scoreChange`, `regressed`, `regressionReason`: only with "Compare with the previous run".
- `source`: `google` (Google PageSpeed Insights), `google (your key)`, or `lighthouse-tw` / `lighthouse-us` (our own Lighthouse servers in Taiwan or the US, used when Google could not measure the page; `fallbackReason` says why).
- `success`, `charged`, `errorType` (`page_failed`, `timeout`, `quota_exceeded`, `invalid_api_key`, `not_measured`, `backend_error`, `backend_unavailable`, `invalid_input`), `error`.

### How much does it cost?

| Event | Price | When |
|---|---|---|
| PageSpeed measurement | **$2.00 / 1,000** ($0.002 each) | one URL measured on one device with a performance score, plus the other scores, lab metrics, real-user Core Web Vitals and top fixes |

**Free:** failed or timed-out measurements, pages Lighthouse cannot load, measurements skipped after a quota stop, invalid URLs. **No start fee.** "Both" devices = 2 measurements per URL. The extra Lighthouse categories, opportunities, budgets, comparison and CSV cost nothing more. Larger Apify plans get the usual volume discount (Silver 10%, Gold and above 20%).

**Monitoring:** with "Only output regressions" every URL is still measured and charged (the measurement is the work); stable pages are just left out of the dataset and listed in `SUMMARY.comparison.notShown`.

**Example:** 200 pages on mobile and desktop every week = 400 measurements = $0.80 a week.

For comparison (Apify Store, 4 October 2026): dev00/Google-PageSpeed-Insights-api $0.002 per result (64 users in the last 30 days), seemuapps/google-pagespeed-insights $0.008, automation-lab/website-lighthouse-seo-audit $0.014, perryay $0.015, neverempty $0.015, nexgendata/page-speed-analyzer $0.02, nexgendata/google-lighthouse-checker $0.10, onescales/website-speed-checker $0.10; constant_quadruped/lighthouse-auditor is a flat monthly rental.

#### Control your cost

- **Maximum URLs** caps the input; the run logs the worst case (URLs × devices × $0.002) at the start.
- When the next measurement could pass your **maximum charge per run**, the run stops (`SUMMARY.status: LIMIT_REACHED`, `SUMMARY.notProcessed`).
- Every row has `charged: true` or `false`; free rows say "(not charged)". If Apify restarts the run, pages already measured are not measured or charged again.

### Use it from code

```bash
curl -X POST "https://api.apify.com/v2/acts/tidytools~pagespeed-insights-bulk/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://www.python.org/","https://example.com/"],"strategy":"both"}'
```

**With AI agents (MCP):** connect Apify's MCP server (`https://mcp.apify.com?tools=tidytools/pagespeed-insights-bulk`) to Claude, Cursor or any MCP client and ask, for example, "Do these 20 landing pages pass Core Web Vitals on mobile, and what is the biggest fix for each?". Minimal input: `{"urls": ["https://example.com/"], "strategy": "mobile"}`. Failed items are not charged and carry an `errorType`.

### Limitations

- **Lighthouse scores vary** by a few points between runs (network, server load, ads). For monitoring, the default regression threshold is 10 points; real-user Core Web Vitals (28-day averages) are steadier.
- **Real-user data needs traffic**: Google only publishes Chrome UX Report data for pages and sites with enough Chrome visitors. Small sites get `coreWebVitals: null`. INP exists only in real-user data.
- **Our Lighthouse fallback** has no real-user data and measures from Taiwan or Phoenix, US, at about 1 minute per page (at most 2 at a time), so its scores can differ from Google's.
- **Some sites block Lighthouse** (bot protection, geo-blocks) or never finish loading: those measurements fail with `page_failed` and are free. A domain that does not exist, a blank error page and a PDF also end as free `page_failed` rows.
- **An error page that renders is measured**: like pagespeed.web.dev, a mistyped URL that shows a styled "404 not found" page (e.g. on github.com) gets scores and is charged. Check `finalUrl` and your list if a score looks odd.
- **URL or site data**: real-user data on the backend route is either the URL's or the site's (not both); with your own Google API key you get both.
- **Daily capacity**: the included capacity is shared; if it runs out, the run falls back to our own Lighthouse servers, and stops with a free `not_measured` row for the rest only if those cannot measure either. With your own free Google key you get 25,000 measurements per day.

### FAQ

**Does the Actor collect usage data?** The Actor sends one anonymous usage counter per run (input type, item count range, outcome) to improve the tool; no URLs, content or account data.

**Do I need a Google API key?** No. Leave "Your Google API key" empty. If you add your own free key (Google Cloud console, enable the PageSpeed Insights API), Google is called directly with it; the price is the same.

**Why is the lab LCP slow but Core Web Vitals PASSED?** Lab metrics come from one Lighthouse run on an emulated slow phone; Core Web Vitals come from real Chrome users over 28 days, usually on faster devices and networks. Google uses the real-user data for ranking; the lab data shows what to fix.

**Is this the same as pagespeed.web.dev?** Yes: with source "auto" or "google" it is the same Google PageSpeed Insights API that powers pagespeed.web.dev, run for every URL in your list.

**What happens if I make a mistake in the input?** Nothing is measured and nothing is charged: the run ends normally (not as "failed") with one free row, `errorType: "invalid_input"`, that says what to fix (no URLs, an unknown device or source, a threshold that is not a number, a Google API key that does not look like one). A single bad line in a list (not a web address, an `ftp://` link, a number, or a private or local address such as `localhost`, `127.0.0.1` or `192.168.x.x`, which Google cannot load) becomes its own free `invalid_input` row, nothing is sent for it, and the other URLs are measured. Through the API, yes/no options also accept `"true"`/`"false"`, `"yes"`/`"no"` and `1`/`0`, and device, source and categories are read in any case (`"Desktop"`, `"SEO, best practices"`); a number option that is not a number uses its default and is listed in `SUMMARY.inputNotes`.

**What if Google's quota runs out or my API key is wrong?** The run stops cleanly: the page that hit the limit gets a free `quota_exceeded` (or `invalid_api_key`) row, every URL after it a free `not_measured` row that says what to do (add your own free Google API key, or fix the key), and measurements already done keep their rows and charges.

**Do you respect robots.txt?** Google's and our Lighthouse servers load each page the way a visitor's browser would; the Actor itself fetches only the API and your URL-list files, and measures only public pages you list.

**Can I check many pages of one site?** Yes, paste or upload the list. To find a site's pages first, run [Sitemap URL Extractor](https://apify.com/tidytools/sitemap-url-extractor) and feed its URLs here.

**How do I get alerts when a page gets slower?** Turn on "Only output regressions", create an Apify schedule (daily or weekly), and add an Apify integration (email, Slack, webhook) that fires when the dataset is not empty.

### Related Actors

- [SEO Audit Crawler](https://apify.com/tidytools/seo-audit-crawler): crawl a whole site for on-page SEO issues (titles, meta, headings, links), with optional PageSpeed per page.
- [Bulk URL Status & Redirect Checker](https://apify.com/tidytools/bulk-url-status-checker): status codes, redirect chains and SSL expiry for thousands of URLs.
- [Broken Link Checker](https://apify.com/tidytools/broken-link-checker): find broken links on a site.

# Actor input Schema

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

Pages to measure, one per line (full URLs or bare domains like example.com). Up to 10,000 per run. You can also paste text or upload a list file below. Each URL x strategy is one measurement.

## `urlsText` (type: `string`):

Optional: paste URLs separated by new lines, commas, tabs or spaces (e.g. a column copied from a spreadsheet).

## `urlSources` (type: `array`):

Optional: upload a .txt or .csv file, or link to one (e.g. a published Google Sheet CSV). Every http(s) URL in the file is measured.

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

mobile = Lighthouse's emulated mid-range phone on a slow 4G connection (what Google uses for ranking); desktop = desktop emulation; both = two measurements per URL (two rows, two charges).

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

Scores to return besides performance (always included). Extra categories cost nothing more. Default: all four.

## `includeOpportunities` (type: `boolean`):

Add the biggest fixes Lighthouse suggests (e.g. "Reduce unused JavaScript", "Render blocking requests") with the estimated time saved. Free.

## `csvExport` (type: `boolean`):

Also save PAGESPEED.csv (one row per measurement, the main columns) in the run's key-value store, ready for Sheets or Excel. Free.

## `minPerformanceScore` (type: `integer`):

Fail rows whose Lighthouse performance score (0-100) is below this, e.g. 90 (Google's "good" band) or 50.

## `maxLcpMs` (type: `integer`):

Fail rows whose Largest Contentful Paint in the Lighthouse test is above this many milliseconds, e.g. 2500.

## `maxCls` (type: `string`):

Fail rows whose Cumulative Layout Shift in the Lighthouse test is above this value, e.g. 0.1.

## `maxTbtMs` (type: `integer`):

Fail rows whose Total Blocking Time in the Lighthouse test is above this many milliseconds, e.g. 200.

## `maxInpMs` (type: `integer`):

Fail rows whose real-user Interaction to Next Paint (Chrome UX Report p75) is above this, e.g. 200. Pages without real-user data are not checked.

## `requireCoreWebVitals` (type: `boolean`):

Fail rows whose real-user Core Web Vitals assessment is FAILED (LCP, INP and CLS p75 not all "good"). Pages without real-user data are not checked.

## `minAccessibilityScore` (type: `integer`):

Fail rows whose Lighthouse accessibility score is below this (needs the accessibility category).

## `minBestPracticesScore` (type: `integer`):

Fail rows whose Lighthouse best-practices score is below this (needs the best-practices category).

## `minSeoScore` (type: `integer`):

Fail rows whose Lighthouse SEO score is below this (needs the seo category).

## `compareWithPrevious` (type: `boolean`):

Remember each URL's score and report change (new, stable, improved, regressed), previousScore and scoreChange against the previous run of the same monitor. Use it with an Apify schedule to catch slow releases. Free.

## `onlyRegressions` (type: `boolean`):

Output only rows that got worse (score down by the regression threshold, LCP 50% and 1 s slower, or Core Web Vitals PASSED -> FAILED). Every URL is still measured and charged; the stable ones are listed in SUMMARY.comparison.notShown. The first run of a monitor outputs everything (the baseline). Turns on comparison.

## `regressionPoints` (type: `integer`):

A drop of at least this many performance points counts as regressed (a rise as improved). Lighthouse scores vary by about 5 points between runs, so 10 avoids noise.

## `monitorName` (type: `string`):

Name of the saved state (letters, digits, dashes). Use one name per schedule; empty = derived from the first URL, the device and the number of URLs.

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

Optional. Not needed: measurements run on our capacity by default. With your own free key (Google Cloud console, enable "PageSpeed Insights API", 25,000 measurements per day) Google is called directly with it; the price per measurement is the same. Stored encrypted, never written to the output.

## `source` (type: `string`):

auto = Google PageSpeed Insights (with real-user Chrome UX Report data), falling back to our own Lighthouse servers if Google's capacity runs out; google = Google only; lighthouse / lighthouse-tw / lighthouse-us = our own Lighthouse servers only (Taiwan residential network or Phoenix, US; slower, about 1 minute per page, no real-user data, at most 2 pages at a time).

## `maxUrls` (type: `integer`):

Stop reading the input after this many distinct URLs (the rest are not measured and not charged).

## `maxConcurrency` (type: `integer`):

How many pages are measured at the same time. A Google measurement takes 10-30 s, so 4 does about 10-20 per minute. Our own Lighthouse servers always take at most 2.

## Actor input object example

```json
{
  "urls": [
    "https://www.python.org/",
    "https://example.com/"
  ],
  "strategy": "both",
  "categories": [
    "performance",
    "accessibility",
    "best-practices",
    "seo"
  ],
  "includeOpportunities": true,
  "csvExport": true,
  "requireCoreWebVitals": false,
  "compareWithPrevious": false,
  "onlyRegressions": false,
  "regressionPoints": 10,
  "monitorName": "",
  "source": "auto",
  "maxUrls": 1000,
  "maxConcurrency": 4
}
```

# Actor output Schema

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

No description

## `scores` (type: `string`):

No description

## `fieldData` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `full` (type: `string`):

No description

## `errors` (type: `string`):

No description

## `csv` (type: `string`):

No description

## `summary` (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": [
        "https://www.python.org/",
        "https://example.com/"
    ],
    "strategy": "both"
};

// Run the Actor and wait for it to finish
const run = await client.actor("tidytools/pagespeed-insights-bulk").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://www.python.org/",
        "https://example.com/",
    ],
    "strategy": "both",
}

# Run the Actor and wait for it to finish
run = client.actor("tidytools/pagespeed-insights-bulk").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://www.python.org/",
    "https://example.com/"
  ],
  "strategy": "both"
}' |
apify call tidytools/pagespeed-insights-bulk --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tidytools/pagespeed-insights-bulk"
        }
    }
}
```

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/IQqhSJHrbNWc7KAzh/builds/BbS5P1x3vGx4dwYz7/openapi.json
