Web and domain tooling

PageSpeed Insights Bulk: Core Web Vitals for many URLs

Core Web Vitals and Lighthouse scores for up to 300 URLs per run, from Google's own PageSpeed Insights API. Every row pairs the field data real Chrome users produced over 28 days (LCP, INP, CLS) with the lab scores and the heaviest fixes Lighthouse found. Bring your own free Google key. Pay per URL.

$1.40per 1,000 · URL audited

One audited URL for one strategy, with its field data, lab scores and top fixes. Pages Lighthouse cannot load, invalid URLs and quota errors are never charged.

Call it from your code

curl
ACTOR=steadydata~pagespeed-insights-bulk
URL="https://api.apify.com/v2/acts/$ACTOR/run-sync-get-dataset-items"

curl -X POST "$URL?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://www.wikipedia.org", "https://www.gov.uk"], "apiKey": "<your apiKey>", "strategy": "mobile"}'
python
import os, requests

ACTOR = "steadydata~pagespeed-insights-bulk"
URL = f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items"

rows = requests.post(
    URL,
    params={"token": os.environ["APIFY_TOKEN"]},
    json={'urls': ['https://www.wikipedia.org', 'https://www.gov.uk'], 'apiKey': '<your apiKey>', 'strategy': 'mobile'},
    timeout=900,
).json()

# every row carries a status; failures are records, not exceptions
ok = [r for r in rows if r.get("status") == "ok"]
print(len(ok), "rows delivered")
javascript
const ACTOR = "steadydata~pagespeed-insights-bulk";
const url =
  `https://api.apify.com/v2/acts/${ACTOR}/run-sync-get-dataset-items` +
  `?token=${process.env.APIFY_TOKEN}`;

const rows = await fetch(url, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({"urls": ["https://www.wikipedia.org", "https://www.gov.uk"], "apiKey": "<your apiKey>", "strategy": "mobile"}),
}).then((r) => r.json());

// one object per delivered row, same shape every time
console.log(rows.filter((r) => r.status === "ok").length, "rows");

Use it in an agent

Point an MCP client at Apify's hosted server with this dataset in the tools list, or run our own server locally.

mcp url
https://mcp.apify.com?tools=steadydata/pagespeed-insights-bulk

# the tool arrives in your agent as steadydata--pagespeed-insights-bulk

Input

FieldTypeNameWhat it does
urls
required
array of stringURLsOne page URL per row, up to 300. A missing https:// is added for you.
apiKey
required
stringYour Google API keyRequired, and free. In Google Cloud: enable the PageSpeed Insights API for a project, then create an API key and restrict it to that one API. The key is yours, the quota is yours, and it is only used for these requests.
strategystringDeviceWhich device Lighthouse simulates. 'Both' returns two rows per URL and is charged as two.
allCategoriesbooleanAlso score accessibility and best practicesOff by default: performance and SEO are scored, and only those two columns are returned. On adds two more score columns, but Lighthouse then needs about two and a half times longer per URL, so keep runs under 250 URLs.
localestringReport languageOptional BCP-47 code for the wording of the fixes, for example nl or de. Leave empty for English.

What you get back

output schema, one object per delivered row
{
  "url": string | null,
  "finalUrl": string | null,
  "strategy": string | null,
  "fetchedAt": string | null,
  "lighthouseVersion": string | null,
  "performanceScore": integer | null,
  "seoScore": integer | null,
  "accessibilityScore": integer | null,
  "bestPracticesScore": integer | null,
  "fieldOverall": string | null,
  "fieldLcpMs": integer | null,
  "fieldLcpRating": string | null,
  "fieldInpMs": integer | null,
  "fieldInpRating": string | null,
  "fieldCls": number | null,
  "fieldClsRating": string | null,
  "fieldFcpMs": integer | null,
  "fieldFcpRating": string | null,
  "fieldTtfbMs": integer | null,
  "fieldTtfbRating": string | null,
  "originOverall": string | null,
  "labLcpMs": integer | null,
  "labFcpMs": integer | null,
  "labCls": number | null,
  "labTbtMs": integer | null,
  "labSpeedIndexMs": integer | null,
  "labTimeToInteractiveMs": integer | null,
  "labServerResponseMs": integer | null,
  "totalBytesKb": integer | null,
  "topOpportunities": array | null,
  "opportunitySavingsMs": integer | null,
  "status": string | null,
  "input": string | null,
  "errorCode": string | null,
  "error": string | null
}
FieldTypeWhen it is filled
urlstring, null
finalUrlstring, nullWhere the page ended up after redirects; compare it with url to spot a redirect.
strategystring, nullWhich device Lighthouse simulated. With strategy 'both' a URL returns two rows.
fetchedAtstring, nullWhen Google ran the analysis, so a row can be dated.
lighthouseVersionstring, nullThe Lighthouse version behind the scores, so results stay comparable over time.
performanceScoreinteger, nullAlways scored. Runs from 0 to 100, as on Google's own page.
seoScoreinteger, nullAlways scored. Runs from 0 to 100, as on Google's own page.
accessibilityScoreinteger, nullFilled when allCategories is on. Scoring all four categories makes a run about two and a half times slower and dearer, so it is off by default.
bestPracticesScoreinteger, nullFilled when allCategories is on. Scoring all four categories makes a run about two and a half times slower and dearer, so it is off by default.
fieldOverallstring, nullFilled when Google has enough real-user data for this URL over the last 28 days; empty for pages with little traffic.
fieldLcpMsinteger, nullFilled when Google has enough real-user data for this URL over the last 28 days; empty for pages with little traffic.
fieldLcpRatingstring, nullFilled when Google has enough real-user data for this URL over the last 28 days; empty for pages with little traffic.
fieldInpMsinteger, nullFilled when Google has enough real-user data for this URL over the last 28 days; empty for pages with little traffic.
fieldInpRatingstring, nullFilled when Google has enough real-user data for this URL over the last 28 days; empty for pages with little traffic.
fieldClsnumber, nullFilled when Google has enough real-user data for this URL over the last 28 days; empty for pages with little traffic.
fieldClsRatingstring, nullFilled when Google has enough real-user data for this URL over the last 28 days; empty for pages with little traffic.
fieldFcpMsinteger, nullFilled when Google has enough real-user data for this URL over the last 28 days; empty for pages with little traffic.
fieldFcpRatingstring, nullFilled when Google has enough real-user data for this URL over the last 28 days; empty for pages with little traffic.
fieldTtfbMsinteger, nullFilled when Google has enough real-user data for this URL over the last 28 days; empty for pages with little traffic.
fieldTtfbRatingstring, nullFilled when Google has enough real-user data for this URL over the last 28 days; empty for pages with little traffic.
originOverallstring, nullThe verdict for the whole domain, not this page. Filled when Google has enough real-user data for the domain; the per-metric columns stay about this URL.
labLcpMsinteger, nullMeasured by Lighthouse in the lab; empty when that audit did not run for the page.
labFcpMsinteger, nullMeasured by Lighthouse in the lab; empty when that audit did not run for the page.
labClsnumber, nullMeasured by Lighthouse in the lab; empty when that audit did not run for the page.
labTbtMsinteger, nullMeasured by Lighthouse in the lab; empty when that audit did not run for the page.
labSpeedIndexMsinteger, nullMeasured by Lighthouse in the lab; empty when that audit did not run for the page.
labTimeToInteractiveMsinteger, nullMeasured by Lighthouse in the lab; empty when that audit did not run for the page.
labServerResponseMsinteger, nullMeasured by Lighthouse in the lab; empty when that audit did not run for the page.
totalBytesKbinteger, nullMeasured by Lighthouse in the lab; empty when that audit did not run for the page.
topOpportunitiesarray, nullThe fixes Lighthouse found, largest first, each worth at least 0.1 second; empty when it found nothing that size.
opportunitySavingsMsinteger, nullThe total of those fixes in milliseconds; empty when Lighthouse found nothing worth at least 0.1 second.
statusstring, nullEither 'ok' or 'error'. An error row carries errorCode and error, and leaves the data fields empty; it is never charged.
inputstring, nullThe input this row was built from, so a row can always be traced back.
errorCodestring, nullFilled on an error row only; a delivered row leaves it empty.
errorstring, nullFilled on an error row only; a delivered row leaves it empty.

Related datasets

Already using this one? Ratings are the first thing other buyers look at, and this dataset has none yet. If it does a job for you, a rating on its Apify page is the one thing that helps. It takes a minute and it is the only thing we ask for.

Open the Apify page