# Site Speed Audit for Agencies: Mobile, Desktop & Regressions (`hani-tools/site-speed-audit`) Actor

Audit whole sites or URL lists on mobile and desktop with the open-source Lighthouse engine: median-of-N stable scores, regression alerts vs last run, performance budgets for CI and a client-ready HTML report per site. No Google API key. Not affiliated with Google.

- **URL**: https://apify.com/hani-tools/site-speed-audit.md
- **Developed by:** [HaniDev](https://apify.com/hani-tools) (community)
- **Categories:** SEO tools, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 page 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

## Site Speed Audit for Agencies — Mobile + Desktop, Regressions & Client Reports

**Bulk website speed test for whole sites, sitemaps or URL lists.** Every page is measured on **mobile and desktop** with the open-source **Lighthouse** engine running in Chromium, and you get **stable median scores**, **regression alerts versus your last run**, **performance budgets for CI** and a **client-ready, white-label HTML report per site** — in one run.

> **Not affiliated with or endorsed by Google.** Lighthouse is an open-source project licensed under Apache-2.0. This Actor runs it locally; it does **not** call the PageSpeed Insights API or any other Google API, and needs **no API key**.

### What does Site Speed Audit do?

- 🗺️ **Audit whole sites, not just single URLs** — give a sitemap or just a domain. Pages are sampled per template (e.g. 3 from `/blog/*`, 3 from `/products/*`) up to your page limit, so you see every template without paying for 5,000 product pages.
- 📱💻 **Mobile + desktop in one run** — one row per page and device.
- 📊 **Stable scores** — choose 1, 3 or 5 runs per page; the median run is reported together with the min–max range. (Lighthouse's own docs: the median of 5 runs is about twice as stable as a single run.)
- 🛠️ **Site-wide fixes in plain English** — "Images are larger than needed… affects 37 of 40 pages, ~1.2 s per page load", ranked by how many pages are affected and the estimated impact, with who usually fixes it (developer, hosting, content, marketing).
- 📉 **Regression alerts** — results are kept in a named key-value store per site. Each new run is compared with the median of your last 3 runs and flags drops beyond configurable noise thresholds. Optional **webhook** (Slack/Zapier/n8n/Make) when something regresses.
- ✅ **Performance budgets for CI** — set limits such as `performance ≥ 80`, `LCP ≤ 2500 ms`, `CLS ≤ 0.1`, `TBT ≤ 300 ms`; each audit gets `pass`/`fail`, the `OUTPUT` record gives an overall verdict, and you can make the run fail on a breach.
- 🧾 **White-label client reports** — one self-contained HTML report per site with your agency name, logo and colour, plus a **portfolio overview** of all sites and an **LLM-friendly Markdown summary** (`summary.md`) for AI agents, emails and tickets.
- 🔍 **Transparent measurements** — every row records the Lighthouse and Chromium versions, CPU/memory, benchmarkIndex and throttling settings, so you can explain why a number differs from another tool.
- 🤝 **Honest billing** — unreachable pages, 404s, non-HTML URLs, login redirects, pages blocked by robots.txt and audits that fail are **never charged**.

### Who is it for?

- **Web agencies & freelancers**: monthly performance reports for all clients, quick audits of prospects' sites for sales calls.
- **SEO teams**: lab Core Web Vitals per template, site-wide issues, accessibility and SEO checks.
- **Developers**: post-deploy regression checks and performance budgets in CI (call the Actor via API and read `OUTPUT`).
- **AI agents & automations**: compact JSON rows with a one-line `summary` and a ready-made Markdown report.

### How to use it

1. Add **Page URLs**, **Sitemap URLs** and/or **Domains**.
2. Keep **Devices** on mobile + desktop, choose **Runs per page** (1 for quick checks, 3 for client reports).
3. Optional: set **Budgets**, a **Baseline name** (runs with the same name are compared), a **Webhook URL**, and your **agency name / logo URL / colour**.
4. Run it, or **schedule** it weekly/monthly. Open the reports from the run's **Storage → Key-value store** (`REPORT-<site>.html`, `PORTFOLIO.html`, `summary.md`) or via the links in `OUTPUT`.

#### Example input

```json
{
  "urls": ["https://client-a.com/", "https://client-a.com/pricing"],
  "sitemapUrls": ["https://client-b.com/sitemap.xml"],
  "startDomains": ["client-c.com"],
  "maxUrlsPerSite": 30,
  "samplePerPathPattern": 3,
  "devices": ["mobile", "desktop"],
  "runsPerUrl": 3,
  "baselineName": "my-agency-portfolio",
  "budgets": { "performance": 80, "lcpMs": 2500, "cls": 0.1, "tbtMs": 300 },
  "failRunOnBudgetBreach": false,
  "brandName": "Acme Digital",
  "logoUrl": "https://acme.example/logo.png",
  "accentColor": "#2255aa",
  "webhookUrl": ""
}
```

### Output

**Dataset — one row per page × device** (views: Overview, Lab metrics, Top fixes, Regressions & budgets, Measurement environment):

```json
{
  "url": "https://example.com/", "finalUrl": "https://example.com/", "site": "example.com", "device": "mobile", "status": "ok",
  "scores": { "performance": 99, "accessibility": 96, "bestPractices": 100, "seo": 80 },
  "scoreRange": null,
  "metrics": { "lcpMs": 852, "fcpMs": 794, "tbtMs": 58, "cls": 0, "speedIndexMs": 794, "ttfbMs": 21, "totalBytes": 2023, "requestCount": 2 },
  "cwvLabStatus": "good",
  "topFixes": [{ "id": "meta-description", "title": "Document does not have a meta description", "plainEnglish": "The page has no meta description, so search engines pick a random snippet. Add a unique 120-160 character description.", "estSavingsMs": null, "estSavingsKb": null }],
  "previous": { "scores": { "performance": 100 }, "measuredAt": "…" }, "delta": { "performance": -1 }, "regression": false, "regressionReasons": [],
  "budgetStatus": "pass", "budgetFailures": [],
  "runsMeasured": 1, "lighthouseVersion": "13.5.0", "chromiumVersion": "154.0.8037.92", "benchmarkIndex": 1350, "throttling": { "method": "simulate", "rttMs": 150, "throughputKbps": 1638.4, "cpuSlowdownMultiplier": 4 },
  "summary": "mobile https://example.com/: performance 99, accessibility 96, best practices 100, SEO 80; LCP 852 ms, TBT 58 ms, CLS 0."
}
```

`status` is one of `ok`, `skipped_robots`, `http_404`, `not_html`, `login_redirect`, `lighthouse_error`, `timeout_limit`, `invalid_url`, `network_error`, `dns_error`, `private_address`, `not_audited_limit`. Only `ok` rows are charged.

**Named dataset `site-speed-audit-site-summaries`** — one row per site and run: pages audited, median scores and metrics per device, lab Core Web Vitals distribution, site-wide `commonIssues`, worst pages, regressions, budget pass rate and the report URL.

**Key-value store**: `REPORT-<site>.html`, `PORTFOLIO.html`, `summary.md`, `OUTPUT` (CI summary: overall status, regression count, budget verdict, links), `SITE-SUMMARIES`; optional `LH-…` original Lighthouse HTML reports. Baselines live in the named store `site-speed-baselines-<baselineName>` in your account (one `BASELINE-<site>` record per site).

### Sample report

[Open the full sample HTML report](https://api.apify.com/v2/key-value-stores/afojEahUKRNXEDyiP/records/site-speed-audit-sample-report.html) – this is the white-label report the Actor saves for each site.

![Sample client report for choosealicense.com (mobile + desktop)](https://api.apify.com/v2/key-value-stores/afojEahUKRNXEDyiP/records/site-speed-audit-sample-report.png)

### Pricing (pay per event)

| Event | What it is | Price |
|---|---|---|
| `page-audit` | One page on one device, first run | **$0.015** |
| `extra-run` | Each additional run for the median (runs 2–5) | **$0.01** |
| `site-report` | Branded HTML report + summary for one site | **$0.03** |

Free: failed or skipped pages, the portfolio overview, `summary.md`, regression checks, budget checks, webhooks. Apify also charges its standard small Actor start fee.

Examples: 1 page × mobile + desktop × 1 run = **$0.03** (+ $0.03 for the site report). 10 client sites × 30 pages × 2 devices × 1 run + 10 reports = **$9.30**. The same with 3 runs per page = **$21.30**. You can cap spending with the run's *maximum cost* setting; the Actor stops cleanly before exceeding it.

### How long does it take?

Audits run **one at a time** (running Lighthouse in parallel on one machine distorts the results), on 4 GB memory = 1 CPU core. In our 1-CPU tests a light page took about **13–24 s per device**. Each measurement is capped by **Max time per measurement** (`maxAuditSeconds`, default **45 s**, including browser start-up): a page that needs longer is stopped and reported as `timeout_limit` — **not charged**. Very heavy pages (large news/media home pages can need 45 s to 2.5 min on one CPU) therefore need a higher limit, e.g. `"maxAuditSeconds": 150`. With 3 or 5 runs per page, extra median runs are skipped for a page when one measurement took longer than 36 s (noted in `extraRunsSkipped`), so slow pages are never billed for extra runs. Plan roughly 1 hour for 100 typical pages × 2 devices.

### FAQ

**Why are my scores different from PageSpeed Insights or Chrome DevTools?**
Lab scores depend on the machine's CPU, the network, the location and the Lighthouse/Chromium version. This Actor runs on one CPU core in the cloud with Lighthouse's default simulated throttling, so heavy pages often score lower than on a fast laptop. Every row records the versions, CPU, `benchmarkIndex` and throttling settings. For comparisons over time, always compare runs of this Actor with each other (that is what the baseline does), and use 3 or 5 runs for important pages.

**Is this real-user (field) Core Web Vitals data?**
No. These are **lab** measurements. `cwvLabStatus` combines LCP, CLS and Total Blocking Time (the lab stand-in for INP). It is not the real-user Core Web Vitals assessment.

**Does it use a Google API or my API key?**
No. Lighthouse runs inside the Actor in open-source Chromium. No Google API is called and no key is needed.

**How are regressions detected?**
For each page and device, the Actor stores the last 5 results in the named key-value store for your baseline name. A new result is compared with the median of the last 3; a drop of at least the threshold (default: performance 8 points, accessibility 3, SEO 3, LCP +500 ms) is flagged. Use 3 runs per page to reduce false alarms.

**Does it respect robots.txt and the sites it tests?**
Pages discovered from sitemaps or domains are skipped when robots.txt disallows them (on by default). Pages you list yourself are audited with a warning (or skipped if you enable that option). Requests to the same host are spaced out (default 2 s), audits run sequentially, and no proxies, user-agent spoofing, login or CAPTCHA bypassing are used. Only audit your own or your clients' sites, or public pages you are allowed to test; you are responsible for complying with the tested sites' terms.

**What about pages behind a login?**
They are not supported in this version; redirects to a login page are reported as `login_redirect` (not charged).

**Which pages fail?**
Unreachable hosts, 404/410 pages, non-HTML URLs, login redirects and pages that block automated browsers (e.g. HTTP 403/429 to headless browsers) are reported with a clear status and are not charged. Very heavy pages that exceed **Max time per measurement** (default 45 s) are reported as `timeout_limit` (not charged, not retried); raise `maxAuditSeconds` if you need them measured.

### Open-source credits & licenses

Built with [Lighthouse](https://github.com/GoogleChrome/lighthouse) (Apache-2.0), Chromium (BSD-3-Clause), the Apify SDK (Apache-2.0), chrome-launcher (Apache-2.0) and robots-parser (MIT). See `THIRD_PARTY_NOTICES.md` and `licenses/` in the source. Lighthouse is used unmodified. Not affiliated with or endorsed by Google.

# Changelog

This Actor's version history is a separate document: https://apify.com/hani-tools/site-speed-audit/changelog.md

# Actor input Schema

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

Individual pages to audit (e.g. home page, pricing page). Use full URLs; "https://" is added if missing.

## `sitemapUrls` (type: `array`):

Sitemaps (sitemap.xml, sitemap index or .xml.gz). Pages are sampled per path pattern up to "Max pages per site". robots.txt is respected for these pages by default.

## `startDomains` (type: `array`):

Domains such as "client.com". The sitemap is found via robots.txt or /sitemap.xml; without a sitemap, links on the home page are used (no deep crawl).

## `maxUrlsPerSite` (type: `integer`):

Upper limit of discovered pages audited per site. Directly listed URLs are not limited.

## `samplePerPathPattern` (type: `integer`):

Template sampling for sitemaps/domains: take at most this many pages from each first-level path (e.g. 3 from /blog/*, 3 from /products/*). 0 = no per-pattern limit.

## `includeUrlPatterns` (type: `array`):

Optional filters for discovered URLs. Plain text is matched as a substring; \* works as a wildcard.

## `excludeUrlPatterns` (type: `array`):

Skip discovered URLs that contain any of these (e.g. /tag/, /page/).

## `groups` (type: `object`):

Optional labels shown next to each site in reports, e.g. {"client-a.com": "Client A"}.

## `devices` (type: `array`):

Mobile (Lighthouse mobile emulation, slow 4G + 4x CPU slowdown) and/or desktop. Each page x device is one audit.

## `runsPerUrl` (type: `integer`):

1 = fastest and cheapest. 3 or 5 = the median run is reported, which is much more stable (2 is rounded up to 3, 4 to 5). Each additional run is billed as an extra-run event. Extra runs are skipped for a page when one measurement took longer than 36 s (shown in lighthouseWarnings / extraRunsSkipped).

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

Score categories to measure. Performance is always included.

## `throttling` (type: `string`):

"simulated" (Lighthouse default, fastest and most repeatable) or "devtools" (applies real network/CPU throttling; slower).

## `maxAuditSeconds` (type: `integer`):

Hard limit for one measurement of one page on one device, including browser start-up. Pages that need longer are stopped, reported as "timeout_limit" and NOT charged. 45 s covers typical pages on the 1-CPU machine; very heavy pages (big news/media home pages) can need 60–180 s – raise this limit if you need them measured.

## `pageLoadTimeoutSecs` (type: `integer`):

How long Lighthouse waits for the page to finish loading. Automatically kept at least 15 s below "Max time per measurement" (minimum 10 s) so there is time left for the analysis.

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

Store results in a named key-value store and flag regressions against the previous runs with the same baseline name.

## `baselineName` (type: `string`):

Runs with the same baseline name are compared with each other (e.g. "my-agency-portfolio"). Stored in the named key-value store "site-speed-baselines-<name>" in your account.

## `regressionThresholds` (type: `object`):

Score-point drops (or LCP milliseconds increase) that count as a regression, compared with the median of up to the last 3 runs. Keys: performance, accessibility, bestPractices, seo, lcpMs.

## `budgets` (type: `object`):

Optional pass/fail limits per audit. Minimum scores: performance, accessibility, bestPractices, seo. Maximum values: lcpMs, fcpMs, tbtMs, cls, speedIndexMs, ttfbMs, totalBytes.

## `failRunOnBudgetBreach` (type: `boolean`):

Useful in CI pipelines. All results and reports are still saved.

## `webhookUrl` (type: `string`):

Optional. Receives a JSON POST when a regression or budget failure is found (e.g. Slack/Zapier/n8n incoming webhook).

## `reportEnabled` (type: `boolean`):

One branded HTML report per site plus a portfolio overview (billed once per site as a site-report event).

## `brandName` (type: `string`):

Shown in the report header and footer.

## `logoUrl` (type: `string`):

Public https URL of your logo (PNG/SVG).

## `accentColor` (type: `string`):

Hex colour used in the report, e.g. #2255aa.

## `saveRawReports` (type: `boolean`):

Also store the full Lighthouse HTML report for every page/device (link in rawReportUrl).

## `respectRobotsTxt` (type: `boolean`):

Pages found via sitemaps/domains that robots.txt disallows are skipped (never charged).

## `enforceRobotsForListedUrls` (type: `boolean`):

By default, URLs you list yourself are audited with a warning only.

## `perHostDelaySecs` (type: `integer`):

Politeness delay between page loads on the same host.

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

Transient failures (timeouts, crashes, 429/5xx) are retried this many times. Retries are never charged.

## Actor input object example

```json
{
  "urls": [
    "https://choosealicense.com/"
  ],
  "maxUrlsPerSite": 50,
  "samplePerPathPattern": 3,
  "excludeUrlPatterns": [
    "/tag/",
    "/page/"
  ],
  "devices": [
    "mobile",
    "desktop"
  ],
  "runsPerUrl": 1,
  "categories": [
    "performance",
    "accessibility",
    "best-practices",
    "seo"
  ],
  "throttling": "simulated",
  "maxAuditSeconds": 45,
  "pageLoadTimeoutSecs": 30,
  "compareWithPrevious": true,
  "baselineName": "default",
  "regressionThresholds": {
    "performance": 8,
    "accessibility": 3,
    "seo": 3,
    "lcpMs": 500
  },
  "budgets": {
    "performance": 80,
    "lcpMs": 2500,
    "cls": 0.1,
    "tbtMs": 300
  },
  "failRunOnBudgetBreach": false,
  "reportEnabled": true,
  "accentColor": "#2255aa",
  "saveRawReports": false,
  "respectRobotsTxt": true,
  "enforceRobotsForListedUrls": false,
  "perHostDelaySecs": 2,
  "maxRetries": 1
}
```

# Actor output Schema

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

One row per page and device: scores, lab metrics, regressions, budgets and top fixes.

## `portfolio` (type: `string`):

Overview of all sites with links to each site's client report.

## `summary` (type: `string`):

Short Markdown summary of the run.

## `siteSummaries` (type: `string`):

One record per site: scores, issues, changes and report URL.

## `files` (type: `string`):

All key-value store records, including the per-site HTML reports.

# 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://choosealicense.com/"
    ],
    "excludeUrlPatterns": [
        "/tag/",
        "/page/"
    ],
    "budgets": {
        "performance": 80,
        "lcpMs": 2500,
        "cls": 0.1,
        "tbtMs": 300
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("hani-tools/site-speed-audit").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://choosealicense.com/"],
    "excludeUrlPatterns": [
        "/tag/",
        "/page/",
    ],
    "budgets": {
        "performance": 80,
        "lcpMs": 2500,
        "cls": 0.1,
        "tbtMs": 300,
    },
}

# Run the Actor and wait for it to finish
run = client.actor("hani-tools/site-speed-audit").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://choosealicense.com/"
  ],
  "excludeUrlPatterns": [
    "/tag/",
    "/page/"
  ],
  "budgets": {
    "performance": 80,
    "lcpMs": 2500,
    "cls": 0.1,
    "tbtMs": 300
  }
}' |
apify call hani-tools/site-speed-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hani-tools/site-speed-audit"
        }
    }
}
```

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/75XOhu0t1gzB2GSRa/builds/TTtFCtZgja7MejQ9H/openapi.json
