# Website Audit: Accessibility, SEO & Speed + Evidence (`fetchgrove/page-audit-evidence`) Actor

Audit public web pages for accessibility (WCAG 2.2), SEO basics and speed in a real browser, and see each problem boxed on a full-page screenshot, with close-up crops and a SHA-256 evidence record. Repeat runs show only new and fixed issues. Failed and blocked pages are free.

- **URL**: https://apify.com/fetchgrove/page-audit-evidence.md
- **Developed by:** [fetchgrove](https://apify.com/fetchgrove) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $17.50 / 1,000 page auditeds

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

## Website Audit: Accessibility, SEO & Speed + Evidence

Audit public web pages for accessibility, SEO basics and speed in a real browser, and see every problem on the page itself: a full-page screenshot with a numbered box on each failing element, coloured by how serious it is, plus a close-up crop of each issue. Every report carries a SHA-256 hash and the capture time, so you can show what failed and when.

### What you get for each page

| Part | What is in it |
|---|---|
| Scores | accessibility, SEO and speed, each 0 to 100 (our own simple formulas, explained below) |
| Issue counts | critical, serious, moderate and minor |
| Issues | worst first: what fails, how to fix it, the WCAG success criteria (and EN 301 549 clauses), the element's CSS selector and its box on the page |
| Annotated screenshot | the page as a PNG, from the top down to the screenshot height limit, with numbered boxes that match the issue numbers |
| Close-ups | one cropped image per issue (the top 20 per page), linked from the issue |
| Speed metrics | lab estimates: largest contentful paint, layout shift, total blocking time (estimate), first contentful paint, time to first byte, requests, bytes by type, the largest images, and the largest content element with its position (boxed on the screenshot when it is slow to appear); plus what they were measured with (memory, pages at the same time, device) |
| SEO facts | title and description length, canonical, robots settings, headings, language alternates, structured data types, social sharing tags, viewport, links, redirects |
| Evidence | SHA-256 of the screenshot, capture time (UTC), final URL, every redirect with its status, browser identity and window size |

### What it checks

- **Accessibility**: automated rules for WCAG 2.2 level AA (or A, or AAA), run in the loaded page: alternative text, colour contrast, form labels, link and button names, language, ARIA use, target size, ids that labels point to, and more. Contrast issues come with the measured and the required ratio.
- **SEO basics**: title, meta description, canonical link, robots meta tag and header, one h1, heading order, image alt text, hreflang codes, structured data present and valid (JSON-LD), Open Graph and twitter:card tags, viewport tag, https, redirect chains and mixed content. Links are counted, never followed.
- **Speed**: lab estimates from the browser's performance metrics on a cold load (empty cache, no cookies), counted from the start of the load to 5 seconds after the page's load event (at most 15 seconds after the first content appears). Only the page's own scripts count towards blocking time, not those of embedded frames from other sites. A slow largest content and the elements that shifted are boxed on the screenshot; slow scripts, page weight and oversized images are flagged. With speed among the checks, pages are audited one at a time by default so they do not slow each other down.

Automated rules find many accessibility problems, not all of them. A page with no issues here still needs a manual review before you can say it conforms.

### Use it for

- **Accessibility reviews**: hand developers a picture of each failing element instead of a list of selectors.
- **Agencies and freelancers**: audit a client's key pages and attach the annotated screenshots to the report.
- **Compliance records**: a dated, hashed capture of what failed, for European Accessibility Act or ADA work.
- **Monitoring**: schedule it and get only the issues that are new since the last run, and the ones that were fixed.

### How to use it

1. Add the pages to audit, one per line.
2. Optional: pick the checks, a device (`desktop`, `iphone-15`, `pixel-8`, `ipad`), the WCAG level, how many issues to list, and how far down the screenshot goes on long pages.
3. Run it. Each page becomes one row you can download as JSON, CSV or Excel, or read through the API; the screenshots and crops are in the run's storage, linked from the row.

Leave the input empty and it audits one example page made with known accessibility problems, free, so you can see the output first.

### Input example

```json
{
  "urls": ["https://www.example.com/", "https://www.example.com/pricing"],
  "checks": ["accessibility", "seo", "performance"],
  "device": "desktop",
  "wcagLevel": "AA",
  "maxIssuesPerPage": 50,
  "issueCrops": true,
  "maxCaptureHeight": 10000
}
```

### Output example

Real output for the example page (shortened):

```json
{
  "url": "https://www.w3.org/WAI/demos/bad/before/home.html",
  "httpStatus": 200,
  "scores": { "accessibility": 61, "seo": 81, "performance": 100 },
  "issueCounts": { "critical": 34, "serious": 13, "moderate": 34, "minor": 4, "total": 85 },
  "issues": [
    {
      "number": 2,
      "id": "select-name",
      "category": "accessibility",
      "impact": "critical",
      "title": "Select element must have an accessible name",
      "wcag": ["4.1.2"],
      "selector": "td:nth-of-type(5) > select",
      "box": { "x": 879, "y": 243.17, "width": 139, "height": 19 },
      "cropUrl": "https://api.apify.com/v2/key-value-stores/<your-store>/records/crop-<id>-1-2.png"
    }
  ],
  "metrics": { "lcpMs": 352, "cls": 0, "tbtMs": 0, "fcpMs": 304, "requests": 42, "transferBytes": 113329 },
  "measuredWith": { "memoryMbytes": 2048, "pagesInParallel": 1, "device": "desktop" },
  "annotatedScreenshotUrl": "https://api.apify.com/v2/key-value-stores/<your-store>/records/audit-<id>-1.png",
  "evidence": { "sha256": "48d8b7885d03cf62…", "capturedAtUtc": "2026-10-20T01:52:10.473Z" }
}
```

### How the scores work

Our own scores, so you can compare pages and runs at a glance. They are not an official score of any standard body or search engine.

- **Accessibility and SEO**: each failing check costs points by impact (critical 10, serious 7, moderate 4, minor 1), plus a tenth of that for each further element it fails on, up to ten more. Score = 100 × 100 ÷ (100 + points). No issues: 100. 100 points: 50.
- **Speed** (a lab estimate): each metric scores 100 at its "good" threshold (largest content 2.5 s, layout shift 0.1, blocking time 200 ms, first content 1.8 s), 50 at its "poor" threshold (4 s, 0.25, 600 ms, 3 s), and 0 further on; the score is their weighted average (30, 25, 30, 15).
- **Include recommended practices** adds rules that are not WCAG requirements, and they count in the accessibility score: compare scores made with the same setting.

Speed is measured on a fast connection in a data centre, without slowing the network or the processor: compare pages and runs made the same way with each other (each row says how it was measured), not with what visitors on phones see.

### Only new and fixed issues (scheduled runs)

Turn on **Only new and fixed issues** and schedule the run. The first run is the baseline. After that, each row lists only the issues that are new since the last run with the same settings, and names the ones that were fixed. Scores and counts still cover the whole page. Issues are matched by rule and element position, ignoring list positions (so a carousel or a list whose items move does not show up as new and fixed issues; several issues of one rule on similar elements are matched by count), and ids that look generated (they change on every load) are not used. Speed issues are not part of the change lists: their numbers vary from run to run. Baselines are kept per page, per settings and per saved task, in a storage in your own Apify account.

### Pricing

You pay only for pages that were audited. Pages that fail to load, return an error status, show a "verify you are human" check or another bot challenge, are closed to automated visitors by the site's robots.txt, or stay blank cost nothing and are marked in the results. The same address listed twice is audited and charged once. The example page (empty input) is free. A page your spending limit cannot pay for is not loaded.

| What | Price |
|---|---|
| Page audited (all three checks and the annotated screenshot) | $0.025 per page ($25 per 1,000) |
| Close-up crops of the issues | $0.002 per page, when at least one crop is saved |
| Run start | $0.00005 per GB of run memory, at least one (at the default 2 GB: $0.0001 per run) |

The evidence record costs nothing extra. Free Apify plan: up to 10 pages per run.

### Good to know

- **Real browser, fresh session per page**: each page loads in a fresh browser session with no cookies or saved data. Page loads on the same site start at least one second apart (longer if its robots.txt asks). With speed among the checks, one page is audited at a time by default; set **Pages at the same time** to change it (at most one per 1,024 MB of memory).
- **Polite by design**: robots.txt is respected, also for every redirect. Pages cannot open other windows or load other pages ahead of time (prefetching), so nothing past robots.txt is fetched. Sites that block automated visitors are reported as failed, never worked around. Nothing is clicked, no form is filled, no one is logged in: addresses with a user name or password are refused.
- **Cookie banners** from the common consent tools are hidden before checking and capturing, and a page they locked from scrolling is unlocked; nothing is ever accepted for you. Turn off **Hide cookie banners** to check the banner too.
- **No personal data**: issue texts are the rules' own wording, never text from the page, and elements are named by selector, not by their content.
- **Long pages**: every check covers the whole page, but the screenshot and the close-ups stop at the **Screenshot height limit** (10,000 CSS pixels from the top by default, 2,000 to 60,000) or at 16,383 image pixels tall, whichever comes first: 10,000 CSS pixels on desktop by default, 5,461 on iPhone 15 (3x), 6,241 on Pixel 8, 8,191 on iPad. Issues further down keep their selector and position, with `onImage: false` and `offImageReason: "below-capture"`; the row says `truncated: true` and how far the screenshot goes (`capturedHeight`). Set it lower for smaller screenshot files of very long pages; the price stays the same. Elements that cannot be seen (hidden, transparent, scrolled or clipped away) are listed without a box (`offImageReason: "no-box"`).
- **Phones and tablets**: the page keeps the device's screen size, pixel density and touch input for the whole audit, and the screenshot is taken at that density, so boxes sit on their elements.
- **Sections that load later** are scrolled into view one screen at a time and given time to load before checking. Some sites refuse their own data requests to automated browsers; those sections stay as loading placeholders, and the row says so.
- **Embedded frames** (maps, videos, ads in iframes) are not checked inside.
- **Languages**: Chinese, Japanese, Korean and emoji render correctly in the screenshots.

Not affiliated with, endorsed by or connected to the W3C or its Web Accessibility Initiative. WCAG is a W3C standard; this tool checks part of it automatically and does not certify conformance.

### Support

Open an issue on this page. We reply within two business days.

# Actor input Schema

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

Public web pages, one per line. A missing https:// is added for you, and the same address twice is audited (and charged) once. Addresses with a user name or password ("name:password@") are refused: logging in is not supported. Only the pages listed are opened; links are never followed. Each site's robots.txt is respected: a page it closes to automated visitors (or a redirect to one) gets an error row and is not charged. If the list is empty, the run audits one example page made with known accessibility problems.

## `checks` (type: `array`):

What to check on each page. Accessibility: rules for the WCAG 2.2 level chosen below. SEO: title, description, canonical, robots, headings, image alt text, language alternates, structured data, social sharing tags, viewport, https, redirects and mixed content. Speed: lab estimates from the browser's performance metrics on a cold load (largest content, layout shift, blocking time, first content, page weight). Empty means all three. The price is the same for any choice.

## `device` (type: `string`):

Audit the page as a desktop browser or as a phone or tablet shows it: that device's screen size, pixel density, mobile layout, touch input and browser identity. Desktop: 1280 x 800. iPhone 15: 393 x 659 at 3x. Pixel 8: 412 x 839 at 2.625x. iPad: 834 x 1194 at 2x (iPad Pro 11 inch, portrait).

## `wcagLevel` (type: `string`):

Accessibility rules up to this conformance level of WCAG 2.0, 2.1 and 2.2. AA is what most laws and policies ask for (including the European Accessibility Act through EN 301 549). Automated rules find many problems, not all: a page with no issues here still needs a manual review to conform.

## `includeBestPractices` (type: `boolean`):

Also report accessibility practices that are recommended but not WCAG requirements, such as landmarks and one main heading. They are marked as best practice in each issue, and they count in the accessibility score, so scores with and without this option are not comparable.

## `maxIssuesPerPage` (type: `integer`):

How many issues each row lists, worst first (1 to 200). Counts and scores always cover every issue found. Within one impact level, different rules take turns, so one rule that fails on hundreds of elements does not hide the others.

## `issueCrops` (type: `boolean`):

Save a cropped close-up image of each listed issue that has a box on the screenshot (the top 20 per page), with a link from the issue. Charged once per page when at least one crop is saved.

## `maxCaptureHeight` (type: `integer`):

How far down each page the annotated screenshot and the close-ups go, in CSS pixels from the top (2,000 to 60,000). The checks still cover the whole page: issues further down are listed with their selector and position but without a box on the screenshot (offImageReason "below-capture"), and the row says truncated. Screenshots also stop at 16,383 image pixels tall: 16,383 CSS pixels on desktop, 5,461 on iPhone 15 (3x), 6,241 on Pixel 8, 8,191 on iPad. Set it lower for smaller screenshot files of very long pages; the price stays the same.

## `onlyChanges` (type: `boolean`):

For scheduled runs: each row lists only issues that are new since the last run with the same settings, and names the issues that were fixed. The first run is the baseline and lists everything. Scores and counts still cover the whole page. Baselines are kept per page, per settings and per saved task, in a storage in your own account.

## `hideCookieBanners` (type: `boolean`):

Hide common cookie consent banners before checking and capturing, so they do not cover the page. Banners are only hidden, never accepted. Turn this off to check the banner itself.

## `parallelPages` (type: `integer`):

How many pages are audited at the same time (1 to 4). Leave empty for the default: 1 when Speed is among the checks, so pages do not slow each other down and speed numbers stay comparable between runs, and 2 without it. Never more than one page per 1,024 MB of run memory.

## Actor input object example

```json
{
  "urls": [
    "https://www.w3.org/WAI/demos/bad/before/home.html"
  ],
  "checks": [
    "accessibility",
    "seo",
    "performance"
  ],
  "device": "desktop",
  "wcagLevel": "AA",
  "includeBestPractices": false,
  "maxIssuesPerPage": 50,
  "issueCrops": true,
  "maxCaptureHeight": 10000,
  "onlyChanges": false,
  "hideCookieBanners": true
}
```

# Actor output Schema

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

One row per URL: scores, issue counts, the ranked issues with their boxes, speed metrics, SEO facts and links to the images.

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

The annotated full-page screenshots and the close-up crop of each issue, saved by the run.

# 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/before/home.html"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fetchgrove/page-audit-evidence").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/before/home.html"] }

# Run the Actor and wait for it to finish
run = client.actor("fetchgrove/page-audit-evidence").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/before/home.html"
  ]
}' |
apify call fetchgrove/page-audit-evidence --silent --output-dataset

```

## MCP server setup

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

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/nBZombgTwQKDtgLNO/builds/eWR9mXCfIzXb1T0qk/openapi.json
