# Website Accessibility Checker: WCAG 2.2 & EAA (`offerastudio/website-accessibility-audit`) Actor

Bulk-check websites for WCAG 2.2 accessibility issues relevant to the European Accessibility Act. Get a 0-100 score, issues with WCAG criterion, severity, example selector and a plain-English fix, plus SEO basics, security headers and detected tech.

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

## Pricing

from $5.00 / 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.

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

### What does Website Accessibility Checker do?

**Website Accessibility Checker** runs an automated **WCAG 2.2 audit** on any list of web pages, up to 1,000 URLs per run. For every page you get:

- ♿ an **accessibility score from 0 to 100**
- 📋 a list of **issues**, each with the WCAG success criterion, level (A/AA), severity, number of affected elements, an **example CSS selector** and a **plain-English fix**
- 🇪🇺 **European Accessibility Act (EAA) notes**: whether the page looks like an online shop, payment or booking service, and how many issues map to WCAG A/AA
- 🔎 **SEO basics**: title, meta description, H1, canonical URL, language, robots meta
- 🔒 **security headers**: HTTPS, HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
- 🧩 **detected tech**: CMS, shop platform, frameworks, analytics, CDN, and accessibility overlay widgets

Paste your URLs, click **Start**, and download the results as JSON, CSV, Excel or HTML, or get them through the Apify API. Because it runs on the Apify platform, you can schedule audits, call them from your own code, and connect the results to Google Sheets, Slack, Zapier, Make and other tools.

### Who is this WCAG checker for?

- **Web agencies and freelancers**: audit a prospect's site in seconds and show concrete issues with the exact element and how to fix it.
- **E-commerce and product teams preparing for the European Accessibility Act**: screen all your storefronts and landing pages and find the most serious problems first.
- **Accessibility consultants**: triage hundreds of pages before the manual review, so your time goes to what automation can't check.
- **SEO and marketing teams**: get accessibility, SEO basics and security headers from one run.
- **Portfolio and compliance monitoring**: schedule a weekly run and track scores over time.

### What does it check?

| Area | Checks | WCAG 2.2 |
| --- | --- | --- |
| Images | missing alt text, alt text that is a file name, image buttons, image map areas, `role="img"` without a name | 1.1.1 |
| Forms | fields without a label, invalid `autocomplete` values | 1.3.1, 4.1.2, 1.3.5 |
| Buttons and links | buttons without a name, empty links, vague link text ("click here", "read more", also in German, French, Spanish, Italian and Dutch) | 4.1.2, 2.4.4 |
| Structure | page language, page title, H1, skipped heading levels, empty headings, main landmark, lists | 3.1.1, 2.4.2, 1.3.1 |
| Frames | `<iframe>` without a title | 4.1.2 |
| Navigation | skip link and its target, way to bypass repeated content, positive `tabindex` | 2.4.1, 2.4.3 |
| Zoom and timing | zoom disabled in the viewport tag, timed page refresh, `<marquee>`/`<blink>` | 1.4.4, 2.2.1, 2.2.2 |
| Media | autoplaying sound, videos with controls but no captions track | 1.4.2, 1.2.2 |
| IDs | duplicate ids used by labels or ARIA (serious), other duplicate ids (best practice; WCAG 2.2 removed 4.1.1) | 1.3.1, 4.1.2 |
| **Colour contrast** | text contrast against solid backgrounds, **only with "Render JavaScript" on** | 1.4.3 |

Hidden elements (`hidden`, `aria-hidden="true"`, `display:none`) are ignored, like a screen reader would.

### How to run an accessibility audit

1. Click **Try for free** and sign in to Apify (the free plan is enough to start).
2. Paste one or more URLs into **Website URLs**, or upload a text file with one URL per line.
3. Optional: set **Pages per website** to follow links and audit up to 20 pages per site.
4. Optional: turn on **Render JavaScript and check colour contrast** for single-page apps and for the contrast check.
5. Click **Start**. Open the **Overview**, **Issues**, **SEO basics** or **Security headers** tab when the run finishes.

### Input example

```json
{
    "startUrls": [{ "url": "https://www.example.com" }, { "url": "shop.example.org/checkout" }],
    "maxPagesPerSite": 5,
    "renderJavaScript": false,
    "includeSeo": true,
    "includeSecurityHeaders": true
}
```

### Output example

One item per page (shortened):

```json
{
    "url": "https://www.example.com/",
    "accessibilityScore": 74,
    "issuesCount": 3,
    "criticalCount": 1,
    "seriousCount": 1,
    "moderateCount": 1,
    "minorCount": 0,
    "issues": [
        {
            "rule": "image-alt",
            "title": "Images without alternative text",
            "wcag": "1.1.1",
            "level": "A",
            "severity": "critical",
            "count": 4,
            "exampleSelector": "main > section.team > img:nth-of-type(2)",
            "exampleHtml": "<img src=\"/team/photo-2.jpg\" class=\"avatar\">",
            "fix": "Add an alt attribute to every <img>. Describe what the image shows or does; use alt=\"\" for purely decorative images."
        }
    ],
    "seo": { "title": "Example Shop", "metaDescription": "…", "h1": "Welcome", "canonical": "https://www.example.com/", "lang": "en" },
    "securityHeaders": { "https": true, "contentSecurityPolicy": false, "missing": ["Content-Security-Policy", "Permissions-Policy"] },
    "tech": ["Shopify", "Google Tag Manager", "Cloudflare"],
    "eaaRelevant": {
        "serviceSignals": ["e-commerce platform detected (Shopify)", "\"add to cart\" control"],
        "wcagAAIssueCount": 2,
        "notes": ["The European Accessibility Act (EAA) has applied since 28 June 2025 …", "…"]
    },
    "notChecked": ["Colour contrast (WCAG 1.4.3): run with renderJavaScript enabled to check it", "…"],
    "error": null
}
```

Pages that can't be audited get a row with an `error` code (`invalid-url`, `blocked-by-robots-txt`, `http-404`, `http-403`, `request-failed`, …) and cost nothing.

### How much does a website accessibility audit cost?

This Actor uses **pay per event**. You pay only for pages that were actually audited:

| Event | Price |
| --- | --- |
| Page audited | **$0.005** per page |
| Page that failed, was blocked by robots.txt or returned an error | **free** |

- 100 pages cost **$0.50**; 1,000 pages cost **$5**.
- The price is the same with or without JavaScript rendering.
- Apify also charges a tiny standard start fee per run ($0.00005 per GB of memory, so $0.00005 for a normal run and $0.0002 with browser rendering).
- Apify's free plan includes $5 of monthly usage, enough for about 1,000 audited pages a month.
- Set **Maximum cost per run** in the run options and the Actor stops when it is reached.

### How is the score calculated?

Each check that applies to the page (for example, the image checks only apply when the page has images) has a weight by severity: critical 10, serious 7, moderate 3, minor 1. The score is the weighted share of applicable checks that passed, similar in spirit to how Lighthouse scores accessibility. A page with no detected issues scores 100.

The score is a way to compare and prioritise pages. **It is not a compliance grade.**

### Limitations

- **Automated checks find only part of all accessibility problems.** Keyboard operation, visible focus, focus order, target size, reading order and whether alt texts and labels are meaningful need manual testing. Each row lists what wasn't checked in `notChecked`.
- **Colour contrast is only checked with "Render JavaScript" on.** It needs a real browser to compute styles. Text over images, gradients, semi-transparent layers or with text shadows is skipped rather than guessed, and the row says how many items were skipped.
- **Without JavaScript rendering, only the HTML sent by the server is checked.** Content added later by JavaScript (common in React/Vue apps) is only seen with rendering on.
- The Actor **respects robots.txt** and waits at least one second between requests to the same site. Sites that block automated visitors return an error row (free).
- Login-protected pages are not supported.
- **This is not legal advice and not an EAA or WCAG conformance certificate.**

### FAQ

#### Does this make my website EAA compliant?

No tool can do that on its own. The Actor shows automatically detectable WCAG issues and signals that a page belongs to a service type covered by the European Accessibility Act. Use it to find and fix problems fast, then have a person test what automation can't. For legal questions, ask a qualified advisor.

#### Which standard is used?

The checks map to WCAG 2.2 Level A and AA. EN 301 549, the European standard used for the EAA, is based on WCAG 2.1 AA in its current legal version (v3.2.1); its new version v4.1.1, published in September 2026, uses WCAG 2.2 AA.

#### How is this different from Lighthouse or axe?

Those tools check one page at a time in your browser. This Actor audits up to 1,000 pages per run in the cloud, adds SEO, security header and tech data, and gives you a spreadsheet-friendly dataset and an API.

#### An accessibility overlay is installed on my site. Why are there still issues?

Overlay widgets don't change the HTML that the checks analyse, so the issues are still in the code. The Actor lists detected overlays under `tech`.

#### Can I run it on a schedule or from my code?

Yes. Create a task with your URLs and add a schedule in Apify Console, or call the Actor through the Apify API, the JavaScript or Python client, or integrations such as Make, Zapier and n8n.

#### Why did a page return `blocked-by-robots-txt` or `http-403`?

The site's robots.txt disallows crawlers for that page, or the site blocks automated visitors. These rows are free. You can try the Apify datacenter proxy in **Advanced options** for 403 errors.

### More tools from the same developer

All pay-per-result, no proxy or login needed, built and maintained by the same developer:

**Website audits**

- [Cookie & Tracker Audit: GDPR Consent Checker](https://apify.com/offerastudio/cookie-tracker-audit): cookies and tracking tags that load before consent.
- [AI Crawler Access Checker: robots.txt & llms.txt](https://apify.com/offerastudio/ai-crawler-access-audit): which AI crawlers a site allows, plus llms.txt.
- [Website Change Monitor: Diffs, Prices & Alerts](https://apify.com/offerastudio/website-change-monitor): get a row only when a page changes, with a clean diff.

**Company data and compliance**

- [Company Contact Finder: Emails, Phones & Socials](https://apify.com/offerastudio/company-contact-finder): contact details published on company websites.
- [UK New Companies Feed: Companies House Daily](https://apify.com/offerastudio/uk-new-companies-feed): newly incorporated UK companies with sector filters.
- [EU VAT Number Validator: Bulk VIES Checker](https://apify.com/offerastudio/eu-vat-number-validator): bulk VAT checks with name, address and consultation number.
- [LEI Corporate Tree: GLEIF Parents & Subsidiaries](https://apify.com/offerastudio/gleif-lei-corporate-tree): LEI lookup with parents, subsidiaries and a KYC summary.

**Market signals**

- [US WARN Layoff Notices: 12 States Daily Feed](https://apify.com/offerastudio/us-warn-layoff-notices): layoff and plant closure notices from official state sources.
- [US Product Recalls Monitor: FDA & CPSC Feed](https://apify.com/offerastudio/us-product-recalls-monitor): FDA and CPSC recalls in one feed, with severity.

### Feedback

Found a false positive or missing check? Open an issue on the **Issues** tab. Bug reports with the page URL are fixed first.

# Changelog

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

# Actor input Schema

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

Pages to audit, up to 1,000 per run. Bare domains such as example.com work too. Each page becomes one row in the results. You can also upload a text file with one URL per line.

## `maxPagesPerSite` (type: `integer`):

1 = audit only the URLs you entered. Set up to 20 to also follow links on the same website (same hostname) and audit those pages too. Every audited page is billed as one page.

## `renderJavaScript` (type: `boolean`):

Load each page in a real headless browser (Playwright + Chromium). Use it for single-page apps whose content is built by JavaScript. It also adds the WCAG 1.4.3 colour contrast check. Slower and uses more memory (4 GB by default), same price per page.

## `includeSeo` (type: `boolean`):

Add title, meta description, H1, canonical URL, language, robots meta and Open Graph image to each row.

## `includeSecurityHeaders` (type: `boolean`):

Add HTTPS, HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy and Permissions-Policy checks to each row.

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

Usually not needed. Use Apify datacenter proxies if a site blocks the default IP addresses. Residential proxies are not supported and fall back to datacenter proxies.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://apify.com"
    },
    {
      "url": "https://crawlee.dev"
    }
  ],
  "maxPagesPerSite": 1,
  "renderJavaScript": false,
  "includeSeo": true,
  "includeSecurityHeaders": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `issues` (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://apify.com"
        },
        {
            "url": "https://crawlee.dev"
        }
    ],
    "maxPagesPerSite": 1,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("offerastudio/website-accessibility-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 = {
    "startUrls": [
        { "url": "https://apify.com" },
        { "url": "https://crawlee.dev" },
    ],
    "maxPagesPerSite": 1,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("offerastudio/website-accessibility-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 '{
  "startUrls": [
    {
      "url": "https://apify.com"
    },
    {
      "url": "https://crawlee.dev"
    }
  ],
  "maxPagesPerSite": 1,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call offerastudio/website-accessibility-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,offerastudio/website-accessibility-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/JnISzhCbhOuTaKukv/builds/ThBuWBwsE6GGLUISe/openapi.json
