# Website Accessibility Checker (`arched_friend/accessibility-checker`) Actor

Crawl any website for WCAG accessibility problems and get a page-by-page fix list: images with no alt text, form fields with no label, links and buttons with no accessible name, skipped heading levels, untitled iframes and missing page language. Each issue names its WCAG criterion.

- **URL**: https://apify.com/arched\_friend/accessibility-checker.md
- **Developed by:** [Peach O](https://apify.com/arched_friend) (community)
- **Categories:** Developer tools, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.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

## Website Accessibility Checker: The WCAG Fix List for Any Site

Crawl a site and get a page-by-page list of accessibility barriers: images with no alt attribute, form fields with no label, links and buttons a screen reader cannot announce, skipped heading levels, untitled iframes and missing page language. Every issue names the WCAG criterion it fails.

Built for teams facing the European Accessibility Act, ADA complaints, or a procurement questionnaire asking for a VPAT.

### How it works

```mermaid
flowchart LR
    A["Your website"] --> B["Crawl pages"]
    B --> C["Extract the facts:<br/>images, fields, links,<br/>headings, landmarks"]
    C --> D["Apply WCAG rules"]
    D --> E[("Fix list,<br/>worst pages first")]
```

### It tells you what it cannot check

This is the part most tools skip. A static checker reads the HTML your server sends, which decides many WCAG criteria and **cannot** decide others. Every run states this plainly in its summary:

- **Colour contrast (1.4.3)** needs computed styles from a rendered page
- **Keyboard operability and focus order (2.1.1, 2.4.3)** need real interaction
- **Whether alt text is meaningful (1.1.1)** needs a human; only its presence is checked
- **Captions and audio description (1.2.x)** need the media itself

So "0 issues" here means "no issues this method can detect", not "this site is accessible". Anyone selling you the second thing from a crawler is overselling.

### What it checks

| Severity | Issue | WCAG |
| --- | --- | --- |
| **Critical** | Image with no alt attribute | 1.1.1 |
| **Critical** | Form field with no label | 3.3.2 |
| **Critical** | Link with no announceable text | 2.4.4 |
| **Critical** | Button with no accessible name | 4.1.2 |
| **Critical** | Page with no title · no `lang` | 2.4.2, 3.1.1 |
| **Serious** | Iframe with no title · data table with no headers | 4.1.2, 1.3.1 |
| **Serious** | Positive `tabindex` · autoplaying media | 2.4.3, 1.4.2 |
| **Moderate** | Skipped heading level · vague link text · no `main` · no skip link | 1.3.1, 2.4.4, 2.4.1 |

**`alt=""` is not an error.** An empty alt is the correct way to mark a decorative image, so only a genuinely missing attribute counts. On one real site that distinction was the difference between 104 reported problems and zero actual ones.

### Built for

- **Anyone in scope of the European Accessibility Act**, in force since June 2025
- **US organisations** managing ADA exposure across a large site
- **Agencies** auditing a client site before a build or a handover
- **Developers** who want the specific elements to fix, not a score

### Input

```json
{
  "startUrls": ["example.com"],
  "maxPages": 100,
  "minSeverity": "serious"
}
```

| Setting | What it does |
| --- | --- |
| `startUrls` | The site to check |
| `maxPages` | How many pages to crawl. This is what the run is charged on |
| `minSeverity` | Report only serious and critical issues, or only critical |
| `onlyPagesWithIssues` | Leave clean pages out of the output |
| `excludePatterns` | Skip areas like `/admin` |

### Output

```json
{
  "url": "https://example.com/contact",
  "title": "Contact us",
  "lang": "en",
  "imageCount": 8,
  "formFieldCount": 5,
  "issueCount": 3,
  "criticalCount": 2,
  "affectedElements": 11,
  "wcagCriteria": ["3.3.2", "2.4.4", "1.3.1"],
  "issueCodes": ["input-label-missing", "link-name-missing", "heading-level-skipped"],
  "issues": [
    {
      "code": "input-label-missing",
      "severity": "critical",
      "wcag": "3.3.2",
      "count": 4,
      "message": "4 form fields have no label, aria-label or aria-labelledby."
    }
  ],
  "auditedAt": "2026-09-17T18:02:41.550Z"
}
```

`affectedElements` is the number worth prioritising on: one issue on one page can cover eleven broken elements. The run summary ranks pages by that, not by issue count.

### Run it as an API

```bash
curl -X POST "https://api.apify.com/v2/acts/arched_friend~accessibility-checker/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": ["example.com"],
    "maxPages": 100,
    "minSeverity": "critical"
  }'
```

### Pricing

You pay $0.003 for each page checked. Every rule on that page is included.

| Auditing a 500-page site monthly | Monthly cost |
| --- | --- |
| A manual accessibility audit | £3,000 to £15,000 one-off |
| Accessibility monitoring suites | $99 to $500 per month |
| This Actor | $1.50 per run |

### Common questions

**Does this make my site legally compliant?** No, and be wary of anything that claims to. It finds the machine-detectable barriers quickly and cheaply, which is the right first pass before a manual audit — not a replacement for one.

**Why is a link flagged when it clearly has text?** Check whether the text is "click here" or "read more". Screen-reader users often navigate by pulling up a list of links with no surrounding context, so a page of "read more" links is a page of unlabelled links.

**Does it render JavaScript?** No. It checks the HTML your server sends. If your content only exists after hydration, this checks what arrives first — which is also what a search engine and a slow connection see.

**Why no colour contrast?** Because it cannot be done honestly from static HTML, and reporting it from inline styles alone would miss almost everything real. It is listed as not-checked rather than silently omitted.

### Related products

- **SEO Meta & Content Auditor** for titles, descriptions and duplicate content on the same crawl
- **Broken Link Checker** for the dead links on the same pages
- **Website Change Monitor** to catch it when a fixed page regresses
- **Tech Stack Checker** to profile the sites you audit

# Actor input Schema

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

The site to check, as a domain or full URL.

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

Upper limit on how many pages to crawl and audit. This is what the run is charged on.

## `maxDepth` (type: `integer`):

How many clicks from the starting page to follow. 0 checks only the pages you list.

## `includeSubdomains` (type: `boolean`):

Also crawl blog.example.com when checking example.com.

## `excludePatterns` (type: `array`):

Do not crawl any URL containing one of these strings, for example /admin.

## `minSeverity` (type: `string`):

Report only issues at this level or above. Critical covers the barriers that stop someone using the page at all, such as an unlabelled form field.

## `onlyPagesWithIssues` (type: `boolean`):

Leave clean pages out, so the output is a fix list rather than an inventory.

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

How many pages to fetch at once. Lower this if a site rate limits you.

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

Optional, and off by default. Useful only if a site rate limits a large crawl.

## Actor input object example

```json
{
  "startUrls": [
    "apify.com"
  ],
  "maxPages": 100,
  "maxDepth": 3,
  "includeSubdomains": true,
  "minSeverity": "moderate",
  "onlyPagesWithIssues": true,
  "maxConcurrency": 10
}
```

# Actor output Schema

## `pages` (type: `string`):

One row per page with every issue found, each carrying its WCAG criterion, severity and how many elements it affects.

## `runSummary` (type: `string`):

Issue counts by type and severity, the worst pages by affected elements, and an explicit list of what a static check cannot judge.

# 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": [
        "apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arched_friend/accessibility-checker").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": ["apify.com"] }

# Run the Actor and wait for it to finish
run = client.actor("arched_friend/accessibility-checker").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": [
    "apify.com"
  ]
}' |
apify call arched_friend/accessibility-checker --silent --output-dataset

```

## MCP server setup

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

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/fBTFaH2FBOacbsBvG/builds/rELH4kCbzhiaebZbn/openapi.json
