# Website Audit Monitor: Lighthouse + Accessibility (`singgendarhan/website-audit-monitor`) Actor

Lighthouse performance, SEO, best-practices and accessibility scores, lab Core Web Vitals, top fixes with estimated savings, and an axe-core WCAG scan for your URLs. Monitor mode stores each run and reports score drops, new failing audits and new accessibility violations.

- **URL**: https://apify.com/singgendarhan/website-audit-monitor.md
- **Developed by:** [Nick Na](https://apify.com/singgendarhan) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 url audited (per device)s

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

## Website Audit Monitor: Lighthouse + Accessibility

**Lighthouse scores, lab Core Web Vitals, the fixes that matter and a full axe-core accessibility scan for every URL you give it. Name a monitor and schedule it to get a change log of what got worse (or better) since the last run.**

For each URL and device (mobile and/or desktop) you get:

- **Lighthouse scores**: Performance, SEO, Best practices, Accessibility (0-100, same engine as PageSpeed Insights and Chrome DevTools).
- **Lab Core Web Vitals**: LCP, CLS, TBT, FCP and Speed Index.
- **Top fixes**: failing audits and performance opportunities ranked by estimated time saved, with KiB savings and a "learn more" link.
- **Accessibility violations from axe-core** (WCAG 2.2 A/AA plus best practices): rule, impact, number of affected elements, WCAG success criteria, help link and example CSS selectors.
- **Monitor mode**: score drops and rises beyond your threshold, slower or faster lab metrics, newly failing audits, new or resolved accessibility violations, pages that stopped loading, and redirects that changed.
- An **HTML report** you can open or send to a client, plus a JSON summary.

> **Only audit sites you own or have permission to test.** The Actor loads each URL once per selected device, like a single visitor would, and never follows links or crawls.

### Who it is for

- **Agencies and freelancers**: put each client's key pages in a named monitor, schedule it weekly, and send the report link. You see the regression before the client does.
- **SEO and performance teams**: track Lighthouse scores and lab Core Web Vitals for templates (home, category, product, article) after every release.
- **Developers**: a cheap post-deploy check on staging or production pages, wired to Slack, email or a webhook with Apify integrations.
- **Accessibility leads**: a recurring axe-core scan with element-level evidence, so new WCAG violations are caught when they ship.

### What you get

| Output | Where | What is in it |
|---|---|---|
| Audit results | Default dataset (one row per URL × device) | `performanceScore`, `accessibilityScore`, `bestPracticesScore`, `seoScore`, `lcpMs`, `cls`, `tbtMs`, `fcpMs`, `speedIndexMs`, `topIssues`, `opportunities`, `failedAudits`, `axeViolations`, `axeByImpact`, `changes`, `changeSummary`, `finalUrl`, `warnings`, `benchmarkIndex`, versions and timing |
| Dataset views | Output tab | **Scores and Core Web Vitals**, **Monitor changes**, **Accessibility (axe-core)** tables |
| HTML report | Key-value store `REPORT.html` | Summary table with colored scores, change log, per-URL opportunities, failing audits and axe violations. Light and dark mode, readable on phones |
| Run summary | Key-value store `SUMMARY.json` | Counts, settings, change log and compact results, handy for integrations |
| Full Lighthouse reports (optional) | Key-value store `LIGHTHOUSE-<device>-<hash>.html` | Lighthouse's own HTML report per audit, linked from the row (`saveLighthouseReports`) |

Failed and skipped URLs are rows too (`status: "failed"` or `"skipped"`, with an `errorCode`), so you always see what happened to every input. **They are never charged.**

### How to use it

1. Paste your page URLs into **URLs to audit**, one per line.
2. Pick **Devices** (mobile is the default, like PageSpeed Insights) and, if you want, fewer **Lighthouse categories**.
3. Click **Start**. One URL on one device takes roughly 10-30 seconds depending on the page.
4. Open the **Output** tab for the tables and the HTML report, or export the dataset as JSON, CSV or Excel.

#### Monitor mode (scheduled change tracking)

1. Enter a **Monitor name**, for example `client-acme`.
2. Run once: every URL is saved as the baseline in a named key-value store `website-audit-monitor-client-acme` in your account.
3. Create a **schedule** (daily or weekly) with the same input. Every run compares each URL and device with its previous result and fills `changes`, `changeSummary`, `regressionCount` and `improvementCount`.
4. Connect the dataset to Slack, email, Google Sheets, Make or Zapier, or filter on `regressionCount > 0`.

What counts as a change (defaults, all adjustable):

| Change | Rule |
|---|---|
| Performance score | moves by **10+ points** (`performanceChangeThreshold`); lab performance scores vary a few points between identical runs |
| Accessibility, SEO, Best practices score | moves by **5+ points** (`scoreChangeThreshold`); these scores are stable, so a change usually means the page changed |
| Lab metric (LCP, FCP, Speed Index, TBT, CLS) | changes by **25%+** (`metricChangeThresholdPct`) **and** at least LCP/SI 500 ms, FCP 300 ms, TBT 200 ms, CLS 0.05 |
| Newly failing / fixed Lighthouse audit | any SEO, accessibility or best-practices audit; performance audits only when worth 200+ ms of estimated savings (smaller performance insights flip between runs) |
| New / resolved accessibility violation | an axe-core rule that starts or stops failing |
| Audit failed / recovered | the page could not be audited (HTTP error, DNS, timeout...) after it worked before, or works again |
| Final URL changed | the page now redirects somewhere else |

If an audit fails, the last good result is kept, so the next successful run is compared with it.

#### Input example

```json
{
  "urls": [
    "https://www.example.com/",
    "https://www.example.com/pricing",
    "https://www.example.com/blog/launch"
  ],
  "formFactors": ["mobile", "desktop"],
  "categories": ["performance", "seo", "best-practices", "accessibility"],
  "monitorName": "example-weekly",
  "axeStandard": "wcag22aa",
  "axeBestPractices": true
}
```

| Field | Default | Notes |
|---|---|---|
| `urls` | required | http(s) page URLs; `https://` is added when missing; duplicates and `#fragments` removed; up to 500 per run |
| `formFactors` | `["mobile"]` | `mobile` (Lighthouse mobile emulation: moto g power, simulated slow 4G, 4x CPU slowdown) and/or `desktop` (Lighthouse desktop preset). Each device is a separate audit |
| `categories` | all four | `performance`, `seo`, `best-practices`, `accessibility` |
| `monitorName` | none | Turns on change tracking. Letters, digits and hyphens |
| `performanceChangeThreshold` | 10 | Points |
| `scoreChangeThreshold` | 5 | Points, for accessibility, SEO and best practices |
| `metricChangeThresholdPct` | 25 | Percent, plus the absolute floors above |
| `axeScan` | `true` | Runs axe-core on the page Lighthouse already loaded (no second page load) |
| `axeStandard` | `wcag22aa` | `wcag22aa`, `wcag21aa`, `wcag2aa` or `wcag2a` |
| `axeBestPractices` | `true` | Adds axe best-practice rules, flagged `bestPractice: true` |
| `maxIssuesPerUrl` | 10 | Failing audits and opportunities listed per row |
| `maxAxeExamples` | 3 | Example selectors per axe rule |
| `saveHtmlReport` | `true` | `REPORT.html` |
| `saveLighthouseReports` | `false` | Full Lighthouse HTML report per audit |
| `maxConcurrency` | 1 | Up to 4 parallel audits. Parallel audits compete for CPU and lower performance scores; each needs about 2 GB of memory |
| `perUrlTimeoutSecs` | 90 | Per attempt |
| `maxRetries` | 1 | For temporary failures only (timeouts, connection errors, HTTP 5xx/429, browser crashes) |

#### Output example (one row, shortened)

Real result from a test run on 2026-10-23 against W3C's deliberately inaccessible demo page:

```json
{
  "url": "https://www.w3.org/WAI/demos/bad/before/home.html",
  "formFactor": "mobile",
  "status": "ok",
  "performanceScore": 98,
  "accessibilityScore": 50,
  "bestPracticesScore": 88,
  "seoScore": 75,
  "lcpMs": 1384,
  "cls": 0,
  "tbtMs": 152,
  "fcpMs": 787,
  "speedIndexMs": 823,
  "axeViolationCount": 8,
  "axeViolationNodes": 69,
  "axeByImpact": { "critical": 2, "serious": 4, "moderate": 2, "minor": 0 },
  "topIssues": [
    "Improve image delivery (est. 300 ms saved)",
    "Image elements do not have [alt] attributes",
    "Select elements do not have associated label elements."
  ],
  "opportunities": [
    { "id": "image-delivery-insight", "title": "Improve image delivery", "estSavingsMs": 300, "estSavingsKiB": 54.6, "metricSavings": { "LCP": 300 } }
  ],
  "axeViolations": [
    {
      "rule": "image-alt", "impact": "critical", "count": 33,
      "help": "Images must have alternative text", "wcag": ["1.1.1"], "level": "WCAG 2.0 A",
      "helpUrl": "https://dequeuniversity.com/rules/axe/4.14/image-alt?application=axeAPI",
      "selectors": ["img[src$=\"border_left_top.gif\"]", "img[src$=\"border_top.gif\"]", "img[src$=\"border_right_top.gif\"]"]
    }
  ]
}
```

In monitor mode the row also has (illustrative values):

```json
{
  "monitorStatus": "compared",
  "previousAuditAt": "2026-10-22T06:00:12.000Z",
  "regressionCount": 2,
  "improvementCount": 0,
  "changeSummary": "2 regressions: Accessibility score 91 → 78 (-13); New accessibility violation: image-alt (critical, 3 elements): Images must have alternative text",
  "changes": [
    { "type": "score-drop", "severity": "regression", "category": "accessibility", "previous": 91, "current": 78, "delta": -13 },
    { "type": "new-axe-violation", "severity": "regression", "rule": "image-alt", "impact": "critical", "count": 3 }
  ]
}
```

### Lab data, not field data: what the numbers mean

Lighthouse measures **one page load in a controlled lab setup**. Mobile audits use Lighthouse's default simulated throttling (slow 4G network and a 4x slower CPU), the same method PageSpeed Insights uses for its lab section.

- **It is not field data.** The "real user" Core Web Vitals in PageSpeed Insights and Google Search Console come from the **Chrome UX Report (CrUX)**: 28 days of visits from real Chrome users. This Actor does not read CrUX. Lab and field numbers often differ, and Google ranking signals use field data.
- **INP cannot be measured in a lab page load.** Total Blocking Time (TBT) is the lab metric that best tracks responsiveness.
- **Scores vary between runs.** Network conditions, server response times, ads and third-party scripts change from load to load. Performance usually moves by a few points between identical runs; accessibility, SEO and best-practices scores are stable. The monitor thresholds are set with this in mind.
- **Scores can differ from PageSpeed Insights** because the audit runs on different hardware in a different location. Each row includes `benchmarkIndex` (Lighthouse's CPU benchmark of the machine) so you can compare like with like. For the most stable performance scores keep **Parallel audits** at 1.

### Accessibility: what axe-core does and does not tell you

axe-core is the open-source rules engine behind many accessibility tools (and behind Lighthouse's own accessibility score). It reports issues it can detect with certainty, such as missing alt text, missing form labels, low contrast, missing document language and invalid ARIA.

- Automated rules cover only **part of WCAG**. A page with zero violations is not automatically accessible or "compliant": keyboard use, meaningful alt text, focus order and screen reader behavior still need a human review.
- The scan runs on the page as loaded, at the selected device's screen size. Content behind clicks, logins, cookie banners or menus that are closed is not tested.
- Cross-origin iframes (maps, video players, chat widgets) are tested only when the browser allows it.

### Pricing

Pay per event: **you pay only for audits that produce a result.**

| Event | Price |
|---|---|
| URL audited (one URL on one device: Lighthouse + axe-core + monitor comparison + report) | **$0.015** |
| Actor start | Apify's standard synthetic start event |

Free: failed or unreachable URLs, invalid inputs, URLs skipped because of your spending limit, the HTML report, the summary and the change log.

Examples: 10 pages on mobile = **$0.15**. 10 pages on mobile and desktop = **$0.30**. A weekly monitor of 25 pages on mobile = about **$1.60 a month**.

The Actor respects the **maximum cost per run** you set: it never starts an audit your limit cannot pay for, stops cleanly, and lists the remaining URLs as `skipped` (`CHARGE_LIMIT_REACHED`).

### Error codes

| `errorCode` | Meaning | Retried? |
|---|---|---|
| `INVALID_URL` | Not a valid public http(s) URL (also local/private addresses) | no |
| `DNS_FAILURE` | Domain could not be resolved | no |
| `CONNECTION_FAILED` | Connection refused, reset or timed out | once |
| `HTTP_ERROR` | Server answered 4xx/5xx (`httpStatus` has the code) | 5xx and 429 once |
| `TLS_ERROR` | Invalid HTTPS certificate or browser interstitial | no |
| `NOT_HTML` | The URL is a PDF, image, JSON or other non-HTML file | no |
| `NO_CONTENT` | The page never painted anything | once |
| `PAGE_TIMEOUT` | No result within the per-URL timeout | once |
| `BROWSER_ERROR` | The browser crashed or ran out of memory | once |
| `AUDIT_ERROR` | Lighthouse failed for another reason (see `errorMessage`) | once |
| `CHARGE_LIMIT_REACHED`, `RUN_TIME_LIMIT`, `ABORTED` | Not audited (status `skipped`) | - |

A run where every URL fails ends with a failed status (nothing is charged), so schedules and alerts notice when a site is down.

### Limits

- **Public pages only.** No logins, cookies, form filling or clicking. Local and private network addresses are refused.
- **One page load per URL and device.** Lighthouse's back/forward-cache test is skipped because it needs a second navigation. Lighthouse also fetches `/robots.txt` for the SEO category.
- **Memory**: 4 GB is the default and recommended for one audit at a time; 2 GB is the minimum. Each extra parallel audit needs about 2 GB more (the Actor lowers parallelism automatically when memory is too small).
- Up to 500 URLs per run. Split bigger lists into several runs or schedules.
- The axe-core scan reports violations only (not "needs review" items); `axeIncompleteCount` tells you how many rules need a manual check.

### FAQ

**Is this the same as PageSpeed Insights?** It runs the same open-source Lighthouse engine with the same default mobile settings, but on Apify's machines and without Google's API, quotas or API key. PageSpeed Insights also shows CrUX field data, which this Actor does not.

**Why did my performance score change when I didn't deploy anything?** Lab measurements vary (server response time, third-party scripts, ads). That is why the monitor uses a 10-point performance threshold and minimum metric changes. Raise the thresholds for very busy pages.

**Why does `robots.txt is not valid` sometimes appear and disappear?** Lighthouse fetches `/robots.txt` on every audit. If the server rate-limits or returns an error page for that request, the SEO audit fails for that run. The monitor reports it, and reports the recovery next time.

**Can I audit pages behind a login or on localhost?** No. The Actor audits public URLs from Apify's cloud.

**Will this get me "ADA compliant"?** No tool can certify that. Use the axe-core results to find and fix detectable issues, and pair them with a manual review.

**Is it polite to the sites I audit?** Each URL is loaded once per device, audits run one at a time by default, and nothing is crawled. Only audit sites you own or have permission to test.

**How do I get alerts?** Schedule the Actor with a monitor name and add an integration (Slack, email, webhook) on the dataset or run. Filter on `regressionCount > 0` or `monitorStatus = "failed"`.

**Where is the monitor history stored?** In a named key-value store in your own Apify account (`website-audit-monitor-<name>`), one small record per URL and device. Delete the store to reset the baseline.

### Changelog

See [CHANGELOG.md](CHANGELOG.md).

# Changelog

This Actor's version history is a separate document: https://apify.com/singgendarhan/website-audit-monitor/changelog.md

# Actor input Schema

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

One page URL per line (http or https). Each URL is loaded once per selected device; links on the page are not followed. 'https://' is added when missing. Up to 500 URLs per run. Only audit sites you own or have permission to test.

## `formFactors` (type: `array`):

Mobile uses Lighthouse's default mobile emulation (moto g power, simulated slow 4G, 4x CPU slowdown), like PageSpeed Insights. Desktop uses Lighthouse's desktop preset. Each device is a separate audit (and a separate charge).

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

Categories to score. Fewer categories make each audit a little faster; the price is the same.

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

Give your monitor a name (for example 'client-acme') and schedule this Actor. Each run is compared with the previous run of the same monitor: score drops and rises, slower lab metrics, newly failing audits, new or resolved axe violations, and pages that stopped loading. Results are kept in a named key-value store 'website-audit-monitor-<name>' in your account. Letters, digits and hyphens.

## `performanceChangeThreshold` (type: `integer`):

Report a performance score change only when it moves by at least this many points (0-100). Lab performance scores vary between identical runs, typically a few points; 10 keeps scheduled change logs clean.

## `scoreChangeThreshold` (type: `integer`):

Report an accessibility, SEO or best-practices score change only when it moves by at least this many points. These scores are stable between runs, so a change usually means the page changed.

## `metricChangeThresholdPct` (type: `integer`):

Report LCP, FCP, Speed Index, TBT or CLS changes only when they change by at least this percentage and by a minimum absolute amount (LCP/SI 500 ms, FCP 300 ms, TBT 200 ms, CLS 0.05).

## `axeScan` (type: `boolean`):

Run the full axe-core rule set on the page Lighthouse loaded (no extra page load) and list violations by rule with element counts, WCAG criteria, help links and example selectors.

## `axeStandard` (type: `string`):

Which WCAG level-A and AA rules axe-core runs.

## `axeBestPractices` (type: `boolean`):

Also run axe-core's best-practice rules (landmarks, heading order and similar). They are marked bestPractice: true and are not WCAG failures.

## `maxIssuesPerUrl` (type: `integer`):

How many failing Lighthouse audits and performance opportunities to list per URL, ranked by estimated time savings and Lighthouse weight.

## `maxAxeExamples` (type: `integer`):

CSS selectors of affected elements to include for each axe-core rule.

## `saveHtmlReport` (type: `boolean`):

Save REPORT.html (scores, Core Web Vitals, top fixes, axe violations and the change log) to the run's key-value store.

## `saveLighthouseReports` (type: `boolean`):

Also save Lighthouse's own HTML report for every audit (about 0.5-1 MB each) and link it from the dataset row.

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

Audits running at the same time. Lighthouse is CPU-heavy and parallel audits compete for CPU, which lowers performance scores; keep 1 for the most stable scores. Each parallel audit needs about 2 GB of run memory (it is lowered automatically if memory is too small).

## `perUrlTimeoutSecs` (type: `integer`):

Maximum time for one audit attempt (browser start, Lighthouse, axe-core). Pages that do not finish are reported with errorCode PAGE_TIMEOUT and are not charged.

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

Extra attempts for temporary failures (timeouts, connection errors, HTTP 5xx, browser crashes). DNS failures, HTTP 4xx, invalid certificates and non-HTML URLs are not retried.

## Actor input object example

```json
{
  "urls": [
    "https://www.w3.org/WAI/demos/bad/after/home.html"
  ],
  "formFactors": [
    "mobile"
  ],
  "categories": [
    "performance",
    "seo",
    "best-practices",
    "accessibility"
  ],
  "performanceChangeThreshold": 10,
  "scoreChangeThreshold": 5,
  "metricChangeThresholdPct": 25,
  "axeScan": true,
  "axeStandard": "wcag22aa",
  "axeBestPractices": true,
  "maxIssuesPerUrl": 10,
  "maxAxeExamples": 3,
  "saveHtmlReport": true,
  "saveLighthouseReports": false,
  "maxConcurrency": 1,
  "perUrlTimeoutSecs": 90,
  "maxRetries": 1
}
```

# Actor output Schema

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

One row per URL and device: scores, lab Core Web Vitals, top fixes, axe-core violations and (in monitor mode) changes since the last run.

## `report` (type: `string`):

Readable summary of all audited URLs with the change log (when 'Save HTML report' is on).

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

Counts, settings, the change log and compact per-URL results.

# 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.w3.org/WAI/demos/bad/after/home.html"
    ],
    "formFactors": [
        "mobile"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("singgendarhan/website-audit-monitor").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.w3.org/WAI/demos/bad/after/home.html"],
    "formFactors": ["mobile"],
}

# Run the Actor and wait for it to finish
run = client.actor("singgendarhan/website-audit-monitor").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.w3.org/WAI/demos/bad/after/home.html"
  ],
  "formFactors": [
    "mobile"
  ]
}' |
apify call singgendarhan/website-audit-monitor --silent --output-dataset

```

## MCP server setup

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

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/jg5ryk11wNZNJ4emU/builds/xLrHMPbh2W4PwDdlh/openapi.json
