# Accessibility Pre-Check for Your Pages (static HTML, WCAG) (`licensedpipe/a11y-static-audit`) Actor

- **URL**: https://apify.com/licensedpipe/a11y-static-audit.md
- **Developed by:** [licensed pipe](https://apify.com/licensedpipe) (community)
- **Categories:** SEO tools, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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

## Accessibility Pre-Check for Your Pages - static HTML, WCAG-referenced (v0.1)

Publisher: **Licensed Pipe** - contact through the Apify actor page / issues tab.

A **quick, load-light pre-check** that reads the HTML of your own pages (no browser) and flags common machine-detectable accessibility problems, each tagged with the WCAG 2.x success criterion it relates to:

- missing `lang`, page title, `main` landmark; viewport that blocks zoom (`user-scalable=no`)
- images without `alt`, image buttons / image-map areas without text alternative
- links, buttons and form fields **without an accessible name or label**; placeholder-only fields
- invalid ARIA roles, `aria-labelledby/describedby/controls` pointing to missing ids, `aria-hidden` on focusable elements, duplicate ids
- heading problems (no h1, skipped levels, empty headings), positive `tabindex`, iframes without title, tables without header cells
- autoplaying media, videos without a captions track (info)
- inline-style colour contrast (only elements that carry both colours in their `style` attribute; external CSS is **not** evaluated)

> **This is not an accessibility audit or a conformance statement.** Automated static checks find only a portion of real barriers; they cannot judge reading order, keyboard operation, focus visibility, colour contrast from CSS files, JavaScript-generated content, screen-reader behaviour or text quality. Passing this check does **not** mean a page is WCAG-, EN 301 549- or EAA-compliant, and the output is not legal advice. Use it to catch cheap defects early and combine it with manual testing.

### Responsible use - your responsibility

- **Only audit sites you own or have written permission to audit.** By ticking the confirmation box in the input you state that this applies. The actor sends real HTTP requests to the web servers you name; you are responsible for any terms of service, contract or law that applies to that.
- The actor fetches only the start URLs you supply and the same-host pages they link to (up to the page limit). It never follows links to other hosts, never logs in, never submits forms, never uses cookies, proxies or stealth techniques.
- Private, loopback, link-local and cloud-metadata addresses (for example `localhost`, `10.x`, `192.168.x`, `169.254.169.254`) are refused.

### Politeness and hard limits (enforced in code, not configurable above these values)

| rule | value |
|---|---|
| robots.txt | fetched once per host and obeyed (group for this actor's name, else `*`); `Crawl-delay` is honoured (delays above 30 s skip the host); robots.txt unreachable (5xx / network) or access denied (401/403) = treated as "disallow all" and reported; URLs disallowed by robots.txt are **not requested** and are reported as such |
| request rate | requests run one at a time, **>= 1 s between request starts per host** (more if robots.txt asks) |
| size | 50 pages by default, hard cap 500 pages per run; 20 start URLs, 3 hosts; response bodies capped at 2 MB (sitemaps 10 MB) |
| retries | max **3** per URL (network errors, 5xx, 408; 429/503 only after waiting), exponential backoff with jitter |
| Retry-After | honoured; if a server asks for more than 60 s the run stops instead of waiting |
| 403 / 429 | **3 consecutive 403/429 responses stop the run**; a 403 is never retried |
| WAF / bot challenge | Cloudflare/Akamai/etc. challenge pages, `cf-mitigated: challenge`, empty HTTP 202 and CAPTCHA pages stop the run immediately - the actor does not try to get around them |
| total requests | hard ceiling per run (about 3x the page limit plus 40); max run time 15 min by default (1 h max) |
| identification | User-Agent contains the actor name, publisher, a contact pointer and the run id: `<actor>/0.1 (Apify actor by Licensed Pipe; ...; contact ...; run <id>)` |
| adaptive back-off / stop | after any 429/503 the gap grows (2 s, 4 s ... up to 10 s) for the rest of the run; 6 consecutive 5xx/network failures stop the run |
Please do not start many runs against the same site at the same time.

### Personal data

- Page bodies are parsed in memory and **discarded**; no page text, e-mail addresses, phone numbers, names, cookies or form values are stored. `mailto:` and `tel:` links are ignored.
- The only free text kept are fields documented below, shortened, with anything that looks like an e-mail address or phone number masked. URLs are stored without their query string (replaced by a salted marker like `?q~1a2b3c4d`; the real URL is still requested). Set `keepQueryStrings` to keep them - only if they contain no personal data.
- If your own pages contain personal data in titles or URLs, the dataset you create contains it too; you are the data controller of that dataset.

### Input (see .actor/input_schema.json)

| field | meaning |
|---|---|
| startUrls | your pages (use the exact host) |
| confirmOwnership | must be ticked |
| crawlInternalLinks / maxDepth | follow same-host links breadth-first (default on, depth 3) |
| maxPages | default 50, max 500 (each returned row is billed) |
| maxRuntimeSeconds | default 900, max 3600 |

### Output

Input option `keepQueryStrings` (default off) keeps real query strings in output URLs; otherwise they are replaced by a salted marker.

One row per URL: `url`, `finalUrl`, `status`, `counts{errors,warnings,checkedElements}`, and `issues[]` where each issue has `code`, `severity`, `wcag` (criterion number, informational), `count` and up to 5 `examples` given only as `tag#id.class` selectors - **no element text or HTML snippets are stored**. `RUN_SUMMARY` lists limits, robots.txt status and any early-stop reason.

### Legal notes, disclaimers and trademarks

- **Ownership is your declaration, not something the actor can check.** The actor cannot verify that you own or may audit a site. The confirmation box is your binding statement; you alone are responsible for having the right to send these requests (site terms, contract, applicable law such as the EU/Finnish rules on unauthorised access to information systems) and for complying with the Apify Terms of Service / Acceptable Use Policy. Do not use this actor against sites you do not control and have no permission for. The publisher may block abuse.
- **Load protection is built in, but it does not replace permission.** Sequential requests, at least 1 s apart (slower after any 429/503), robots.txt and Crawl-delay obeyed (401/403 on robots.txt counts as "disallow"), hard page and request ceilings, run-time limit, retries capped at 3, and the run stops on repeated 403/429, WAF challenges, repeated 5xx/network failures or an excessive Retry-After. Do not start several runs against the same site at the same time.
- **Network safety.** Only http(s) URLs on the hosts you supplied; private, loopback, link-local, metadata and other reserved addresses are refused at connection time (also against DNS rebinding); redirects leaving your hosts are reported, never followed; no cookies, logins or forms.
- **Personal data (GDPR).** By default the actor stores no page text. Query strings are replaced by a salted marker in all stored URLs (`keepQueryStrings` is off by default). E-mail addresses, phone numbers, IBAN-like and Finnish personal-identity-code patterns are masked in any text it keeps. Names cannot be detected automatically, so URL paths may still contain personal data; if they do, you are the controller of the dataset you create and are responsible for its lawful use, retention and deletion. Requests sent to your server appear in its access logs (IP address of the Apify run, the User-Agent below).
- **Identification.** The User-Agent names the actor, the publisher and the run id, and points to the Apify actor page for contact, so site operators can recognise and report the traffic.
- **Not affiliated.** This is an independent, unofficial tool. "Google", "WCAG" and "W3C" and other names are trademarks or marks of their respective owners; they are used only to describe what is checked and do not imply endorsement or affiliation. Findings are informational, not legal, compliance or professional advice and no guarantee of search ranking or conformance.

### Pricing

Pay per event (proposal, see PRICING.md): one `page-audited` event per returned row. Runs that stop before returning any row are not charged.

# Actor input Schema

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

Pages to check and start crawling from. Only these hosts are fetched (max 20 URLs, max 3 hosts). Redirects to other hosts are reported, not followed.

## `confirmOwnership` (type: `boolean`):

Required. The actor sends real HTTP requests (<= 1 per second, robots.txt respected). You are responsible for having the right to audit the site.

## `crawlInternalLinks` (type: `boolean`):

Follow same-host links found on the pages (breadth-first) until the page limit. If off, only the start URLs are checked.

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

Maximum number of URLs fetched and returned (each result is billed). Default 50, hard cap 500.

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

How many link levels from the start URLs to follow.

## `maxRuntimeSeconds` (type: `integer`):

The run stops cleanly after this time and returns what it has.

## `keepQueryStrings` (type: `boolean`):

By default everything after "?" in URLs written to the dataset and run summary is replaced by a short salted marker (?q~xxxxxxxx), because query strings can carry tokens, e-mail addresses or user IDs. Requests are still sent with the full URL. Turn on only if you need the original query strings and know they contain no personal data.

## Actor input object example

```json
{
  "startUrls": [
    "https://example.com/"
  ],
  "confirmOwnership": false,
  "crawlInternalLinks": true,
  "maxPages": 50,
  "maxDepth": 3,
  "maxRuntimeSeconds": 900,
  "keepQueryStrings": false
}
```

# Actor output Schema

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

No description

## `summary` (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": [
        "https://example.com/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("licensedpipe/a11y-static-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": ["https://example.com/"] }

# Run the Actor and wait for it to finish
run = client.actor("licensedpipe/a11y-static-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": [
    "https://example.com/"
  ]
}' |
apify call licensedpipe/a11y-static-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,licensedpipe/a11y-static-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/hQEnQgDsLencuwbpe/builds/NkwX7WhWl1YIRsqFZ/openapi.json
