# EU Accessibility Scanner (WCAG 2.1 AA / EAA) (`mrdexter4/eu-accessibility-scanner`) Actor

Bulk WCAG 2.1 AA checker for the European Accessibility Act. Not an overlay: scans your pages in a real browser and reports each issue in your actual code, mapped to WCAG and EN 301 549, with fix guidance (EN/NL) and an HTML + PDF audit report. No subscription: pay per page.

- **URL**: https://apify.com/mrdexter4/eu-accessibility-scanner.md
- **Developed by:** [Mateus Correia](https://apify.com/mrdexter4) (community)
- **Categories:** Developer tools, SEO tools, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.50 / 1,000 page scanneds

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

## EU Accessibility Scanner: WCAG 2.1 AA accessibility checker with EN 301 549 / EAA report

**Not an overlay or widget. It finds issues in your actual code.** This accessibility checker loads each page in a real browser, runs axe-core and reports each WCAG 2.1 A/AA failure that axe-core detects, with its CSS selector, EN 301 549 clause (the standard behind the **European Accessibility Act, EAA**) and fix guidance, plus an HTML and PDF WCAG audit report in English or Dutch.

**No subscription, no auto-renew, no page-view tiers.** $0.015 per page scanned, $0.50 per site report.

**Use cases**

- **Agencies and freelancers:** attach the audit PDF to a proposal or monthly client report.
- **E-commerce and SaaS teams facing the EAA:** scan every domain, then re-scan weekly for new and fixed issues.
- **Consultants:** screen dozens of sites in one run before manual testing.
- **Developers:** scan after each release via the API, n8n, Make or Zapier.

**Sample output** (one issue row, real run on the W3C "BAD" demo site):

```json
{
  "type": "issue",
  "url": "https://www.w3.org/WAI/demos/bad/before/home.html",
  "ruleId": "image-alt",
  "impact": "critical",
  "wcagCriteria": ["1.1.1 Non-text Content"],
  "en301549Clauses": ["9.1.1.1"],
  "selector": "img[src$=\"border_left_top.gif\"]",
  "fixGuidance": "Images need a text alternative. Add an alt attribute..."
}
```

> The EAA has applied to most consumer-facing e-commerce, banking, transport and e-book services in the EU since **28 June 2025**. Microenterprises providing services (fewer than 10 staff and an annual turnover or balance sheet of at most €2 million) are exempt from its service requirements. Clause numbers refer to EN 301 549 V3.2.1, which maps to WCAG 2.1 AA. An automated scan alone does not make a site compliant: it finds a large share of issues, and manual testing covers the rest (see the FAQ).

### What does the EU Accessibility Scanner do?

- **Crawls a site automatically.** It reads `robots.txt` and `sitemap.xml` (including sitemap indexes and `.gz` sitemaps) and falls back to following links. Sitemap pages are interleaved across sections, so the first 50 pages are not all blog posts.
- **Skips duplicate templates.** It scans a few pages per URL template (`/product/*`, `/p/<slug>/<id>`) and spends the page budget on distinct templates. You can switch this off.
- **Tests rendered pages.** axe-core runs after JavaScript has rendered, so React, Angular and Vue sites and shops work.
- **Maps every issue** to WCAG 2.1 success criteria (for example 1.4.3 Contrast (Minimum)) and EN 301 549 clauses (for example 9.1.4.3).
- **Explains the fix** in plain English or Dutch for about 50 common rules: contrast, alt text, form labels, link names, ARIA, landmarks and more.
- **Generates an HTML and PDF report** with a score, a criteria table (WCAG SC × EN 301 549), issues grouped by severity and rule, page results, the method and an honest disclaimer.
- **Monitor mode** remembers the previous run and outputs only **new** and **fixed** issues.
- **Several sites per run**, with a combined report plus one per site. Export results to CSV, Excel or JSON.

### Input

| Field | Description | Default |
|---|---|---|
| `startUrls` | One or more websites or pages. Several start URLs on one domain are treated as one site. | – |
| `maxPages` | Max pages scanned per site | 50 |
| `language` | Report and fix-guidance language: `en` or `nl` | `en` |
| `useSitemap` | Discover pages from sitemap.xml before link crawling | `true` |
| `crawlScope` | `site` = whole domain, `path` = only below the start URL's folder | `site` |
| `dedupeTemplates` / `maxPagesPerTemplate` | Scan at most N pages per URL template | `true` / 2 |
| `monitorMode` / `monitorStoreName` | Output only new and fixed issues compared with the previous run that used the same store name | `false` |
| `includeBestPractices` | Also report axe best-practice rules, flagged `isBestPractice` (not WCAG failures) | `false` |
| `viewport` | `desktop` (1366×900) or `mobile` (390×844) | `desktop` |
| `generateReport` / `generatePdf` | HTML / PDF report in the key-value store | `true` |
| `maxConcurrency`, `pageTimeoutSecs`, `maxIssuesPerRulePerPage`, `proxyConfiguration` | Advanced settings | |

### Output

**Dataset:** one row per failing element (`type: "issue"`, full fields include `wcagLevel`, `html`, `helpUrl` and `changeType`) plus one row per site (`type: "site-summary"`) with `score`, `pagesScanned`, `issuesByImpact`, `criteriaFailed` and `needsManualReview`.

**Key-value store:** `report.html` and `report.pdf` (plus `report-<host>.html/.pdf` per site when you scan several), `SUMMARY` (per-site JSON) and `OUTPUT` (run overview with report links and events charged).

The score (0–100) is the share of applicable axe rules a page passes, weighted by severity (critical 10, serious 7, moderate 3, minor 1), averaged per site. It is a progress indicator, **not** a compliance percentage.

### Schedule recurring accessibility scans

A theme update or a new product template can add issues at any time. A schedule turns a one-off WCAG audit into monitoring.

1. Save your input as an Apify **Task** (for example one task per client, each with its own `monitorStoreName`).
2. Turn on `monitorMode`.
3. In Apify Console, open **Schedules**, create a schedule with a cron expression (for example `0 6 * * 1` for Mondays at 06:00, in your time zone) and add the task.

Each run then returns only issues marked `new` or `fixed`, and the report gets a "Changes since previous scan" section. See [Apify schedules](https://docs.apify.com/actors/running/schedules).

### Use it from n8n, Make and Zapier

Apify has official integrations for all three tools. Connect your Apify account with an API token, then use the Apify app or node to **run an Actor or Task** and **fetch its dataset items**, or use a trigger that fires **when an Actor or Task run finishes**.

Typical workflows:

- **Weekly client report:** a schedule runs the task; when the run succeeds, n8n, Make or Zapier fetches the `site-summary` row and posts the score and new issue count to Slack, or emails the `report.pdf` link to the client.
- **Ticket per new issue:** with `monitorMode` on, send each `changeType: "new"` issue to Jira, Linear, Trello or a Google Sheet. Skip the first run: with no baseline yet, every issue comes through as `new` (the site summary's `monitor.previousRunAt` is `null`). An issue is identified by page, rule and selector, so a changed selector shows up as a `fixed` + `new` pair.
- **Post-deploy check:** your pipeline calls the Apify API to scan the changed pages.

Guides: [n8n](https://docs.apify.com/integrations/n8n), [Make](https://docs.apify.com/integrations/make), [Zapier](https://docs.apify.com/integrations/zapier).

### Pricing (pay per event)

No subscription, no auto-renew, no page-view or traffic tiers. You only pay for what is scanned:

| Event | Price |
|---|---|
| Page scanned (`page-scanned`) | **$0.015 per page** |
| Report generated (`report-generated`) | **$0.50 per site report** (HTML + PDF) |

Examples: a 50-page scan with a report costs **$1.25**. 20 sites × 25 pages with reports cost **$17.50**. A weekly 50-page monitor scan with a report costs about **$5 a month**.

Apify's free plan includes $5 of monthly platform credit, enough for about four 50-page audits a month. Set **Maximum cost per run** in the run options to cap spend; the scanner stops cleanly at the limit. Failed pages are not charged.

### FAQ

**Does a clean report mean my site is EAA or WCAG compliant?**
No. Automated tools reliably check only part of WCAG, an estimated 30–50% of real-world issues. Meaningful alt text, keyboard use of complex widgets and clear error messages need manual testing. Use it as a prioritised fix list and monitoring tool, not as a certificate or legal advice.

**Is this an accessibility overlay or widget?**
No. Nothing is added to your site. It is an external scan that reports problems in your HTML, CSS and ARIA so they can be fixed in your code. It tests the page as a visitor's browser loads it, including any overlay script on the page. It does not detect or switch off overlays.

**Which standard is used?**
WCAG 2.1 level A and AA rules from axe-core (tags `wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa`), mapped to EN 301 549 V3.2.1 clause 9, where clause 9.x.y.z corresponds to WCAG success criterion x.y.z. ETSI published EN 301 549 V4.1.1, based on WCAG 2.2 AA, in September 2026. Until the European Commission cites it in the Official Journal, V3.2.1 remains the reference version.

**Can it scan pages behind a login?**
Not in this version. It scans public pages.

**Can I get the report in Dutch?**
Yes. Set `language` to `nl` and the report ("Toegankelijkheidsrapport"), criteria names, rule titles and fix guidance are in Dutch.

**How do I get only changes each week?**
Turn on `monitorMode`, keep `monitorStoreName` the same between runs (one name per client) and schedule the Actor or Task.

**Why was a page reported as failed?**
The site returned an HTTP error (404, or 403 from bot protection) or timed out. Failed pages are listed in the report and not charged. For sites with aggressive bot protection, try Apify Proxy (residential) in `proxyConfiguration`.

### Limitations

- Automated testing only. No manual checks, screen-reader testing, or PDF/document accessibility checks.
- Template de-duplication is heuristic. If it skips pages you need, raise `maxPagesPerTemplate` or turn it off.
- Results reflect the page after load (cookie banners included). Open menus, modals and checkout steps are not exercised.
- Single-page apps that never expose links or a sitemap may need several start URLs.
- WCAG 2.2-only criteria (for example 2.5.8 Target Size) are not checked yet. They are in EN 301 549 V4.1.1 but not in V3.2.1.

### Keywords

accessibility checker, WCAG checker, WCAG audit, WCAG 2.1 AA, EAA accessibility check, European Accessibility Act, EN 301 549, accessibility audit report, bulk accessibility scanner, axe-core, toegankelijkheid scan, toegankelijkheidsrapport.

# Actor input Schema

## `startUrls` (type: `array`):

Homepage (or any page) of each website to scan. Each distinct domain is treated as a separate site with its own summary and report. Several URLs on the same domain are all included in that site's scan.

## `maxPages` (type: `integer`):

Upper limit of pages scanned (and charged) per website.

## `language` (type: `string`):

Language of the HTML/PDF report and the fix guidance in the dataset.

## `monitorMode` (type: `boolean`):

Compare with the previous run for the same site (stored in a named key-value store) and output only issues that are new or were fixed. Ideal for weekly/monthly schedules. The first run outputs all issues as "new".

## `monitorStoreName` (type: `string`):

Named key-value store used to keep scan history between runs. Use different names to keep separate baselines (e.g. per client).

## `useSitemap` (type: `boolean`):

Read robots.txt / sitemap.xml (including sitemap indexes and .gz) to find pages. If the sitemap is missing or tiny, the scanner falls back to following links from the start URL.

## `crawlScope` (type: `string`):

"Whole site" scans any page on the same domain. "Start URL folder only" stays within the folder of the start URL (e.g. /shop/).

## `dedupeTemplates` (type: `boolean`):

Group URLs by pattern (e.g. /product/\* or /blog/2024/\*) and scan only a few per group. Saves cost on large shops where 1,000 product pages share one template. Disable to scan every URL up to the page limit.

## `maxPagesPerTemplate` (type: `integer`):

How many pages to scan per URL pattern group when template dedupe is on.

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

Also report axe-core best-practice rules (landmarks, heading order, …). These are not WCAG requirements and are clearly labelled as such.

## `viewport` (type: `string`):

Screen size used to render pages.

## `maxIssuesPerRulePerPage` (type: `integer`):

Caps dataset rows when one rule fails on hundreds of elements of the same page (e.g. contrast on every product tile).

## `generateReport` (type: `boolean`):

Save a client-ready HTML report to the key-value store (report.html, plus report-<domain>.html per site when scanning several sites).

## `generatePdf` (type: `boolean`):

Render the report to PDF (report.pdf).

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

Pages scanned in parallel. Higher is faster but needs more memory (≈ 1 GB per 2 pages).

## `pageTimeoutSecs` (type: `integer`):

Navigation timeout per page.

## `proxyConfiguration` (type: `object`):

Optional. Use Apify Proxy (residential) for sites behind aggressive bot protection.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.w3.org/WAI/demos/bad/before/home.html"
    }
  ],
  "maxPages": 10,
  "language": "en",
  "monitorMode": false,
  "monitorStoreName": "eaa-scanner-monitor",
  "useSitemap": true,
  "crawlScope": "site",
  "dedupeTemplates": true,
  "maxPagesPerTemplate": 2,
  "includeBestPractices": false,
  "viewport": "desktop",
  "maxIssuesPerRulePerPage": 25,
  "generateReport": true,
  "generatePdf": true,
  "maxConcurrency": 4,
  "pageTimeoutSecs": 60,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Client-ready accessibility report (WCAG 2.1 AA / EN 301 549) with score, issues by criterion and severity, and fix guidance.

## `reportPdf` (type: `string`):

No description

## `issues` (type: `string`):

One item per issue with WCAG criterion, EN 301 549 clause, selector, HTML snippet and fix guidance.

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

No description

## `siteSummaries` (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 = {
    "startUrls": [
        {
            "url": "https://www.w3.org/WAI/demos/bad/before/home.html"
        }
    ],
    "maxPages": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("mrdexter4/eu-accessibility-scanner").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 = {
    "startUrls": [{ "url": "https://www.w3.org/WAI/demos/bad/before/home.html" }],
    "maxPages": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("mrdexter4/eu-accessibility-scanner").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 '{
  "startUrls": [
    {
      "url": "https://www.w3.org/WAI/demos/bad/before/home.html"
    }
  ],
  "maxPages": 10
}' |
apify call mrdexter4/eu-accessibility-scanner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mrdexter4/eu-accessibility-scanner"
        }
    }
}
```

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/pXmatgenOYp1Iu8Oj/builds/iAOd57GlyjRHlCc2G/openapi.json
