Website Accessibility WCAG Audit API
Pricing
from $35.00 / 1,000 page auditeds
Website Accessibility WCAG Audit API
Batch WCAG accessibility audits with a real headless browser and axe-core: real CSS selectors, screenshot evidence, mobile-viewport auditing, a flat per-violation dataset mode, a batch rollup, and an optional court-ready PDF compliance report. Never charges for a failed/blocked audit.
Pricing
from $35.00 / 1,000 page auditeds
Rating
0.0
(0)
Developer
Barak Eliov
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
6 hours ago
Last modified
Categories
Share
Batch WCAG accessibility audits using a real headless browser (Playwright/Chromium) and the real axe-core engine (MIT, Deque Systems) - built for the legal/ compliance-audit buyer this category actually serves, not just developers wanting raw JSON.
This is v1 (the scope approved in SPEC.md). See "What's not in v1 yet" below.
What makes this different from other WCAG-audit Actors on Apify
Every incumbent Actor in this category was fetched and live-tested during this build's spec research
(see SPEC.md sections 0-3 for run IDs and evidence). Confirmed, working differentiators:
- Real, pasteable CSS selectors - the
selectorfield on every violation is axe-core's owntargetoutput, not a raw HTML snippet. Verified during this build by resolving the returned selector back to the exact element with Playwright (tests/axe.test.ts). - Screenshot evidence - an element-cropped screenshot per violation, plus one full-page
screenshot per URL, bundled into the
page-auditedprice. No incumbent offers this. - Mobile-viewport auditing - set
viewport: "mobile"to catch mobile-relevant WCAG criteria (e.g. WCAG 2.2's Target Size rule) that a desktop-only audit cannot see. Verified with a real fixture during this build: the same page produces a violation atmobileviewport and no violation atdesktopviewport (tests/axe.test.ts, "EDGE CASE 2"). No incumbent offers any viewport control. - Flat per-violation dataset mode (
outputMode: "flat"or"both") - one row per individual finding, spreadsheet/ticketing-friendly, instead of only a nested array per page. - Batch rollup across exactly the URLs you supply (
rowType: "summary"row): total violations by impact, and the rules most commonly failing across your batch. - Optional PDF compliance report (
generatePdfReport: true) - violations grouped by impact, screenshots inlined, WCAG citations, a batch rollup, and failed pages explicitly noted rather than hidden. No incumbent Actor offers any document/report output at all. - Mandatory SSRF protection - every URL's DNS resolution is checked against private/link-local/ loopback/cloud-metadata IP ranges before any browser navigation. Always on, not a toggle.
- Never charges for a failed/blocked audit. This directly fixes a bug this build's own spec
research caught live in the category leader: auditing a stable, public W3C demo page returned a
403-blocked, zero-data result that was still written to its priced dataset. Verified here with a
real local-server 403 response end-to-end (
tests/edge-case-blocked.test.ts).
Input example
{"urls": ["https://example.com", "https://info.cern.ch/hypertext/WWW/TheProject.html"],"wcagLevel": "wcag21aa","viewport": "desktop","outputMode": "both","generatePdfReport": false}
Full input fields (all have defaults; see .actor/input_schema.json for the authoritative list):
urls, wcagLevel, viewport, viewportWidth, viewportHeight, includeScreenshots,
includeIncomplete, includePasses, outputMode, generatePdfReport, waitForSelector,
timeoutSecs, proxyConfiguration, maxConcurrency.
Output example
This dataset has three row shapes, distinguished by rowType.
Page row (one per audited URL):
{"rowType": "page","url": "https://info.cern.ch/hypertext/WWW/TheProject.html","finalUrl": "https://info.cern.ch/hypertext/WWW/TheProject.html","status": "success","wcagLevel": "wcag21aa","viewport": "desktop","score": 88,"violationCount": 2,"incompleteCount": 0,"passCount": 0,"violationsByImpact": { "critical": 0, "serious": 0, "moderate": 2, "minor": 0 },"fullPageScreenshotUrl": "https://api.apify.com/v2/key-value-stores/.../records/fullpage-....png","waitForSelectorTimedOut": false,"errorCategory": null,"errorMessage": null,"auditedAt": "2026-10-01T18:00:00.000Z","engineVersion": "4.13.0"}
Violation row (one per individual finding, when outputMode is flat/both):
{"rowType": "violation","pageUrl": "https://info.cern.ch/hypertext/WWW/TheProject.html","ruleId": "region","impact": "moderate","status": "violation","description": "Ensure all page content is contained by landmarks","helpText": "All page content must be contained by landmarks","helpUrl": "https://dequeuniversity.com/rules/axe/4.13/region","wcagCriteria": null,"selector": "body > h1","htmlSnippet": "<h1>World Wide Web</h1>","screenshotUrl": "https://api.apify.com/v2/key-value-stores/.../records/violation-....png","nodeCount": 1}
Note wcagCriteria: null above: axe-core's region rule is a "best-practice" rule with no numbered
WCAG success-criterion mapping. This is never guessed or hidden - see "WCAG citation accuracy" below.
Summary row (exactly one per run, also saved to the key-value store as batchSummary):
{"rowType": "summary","totalPagesAudited": 2,"totalPagesFailed": 0,"totalViolations": 2,"violationsByImpact": { "critical": 0, "serious": 0, "moderate": 2, "minor": 0 },"topFailingRules": [{ "ruleId": "region", "pageCount": 1 }],"pdfReportUrl": null}
A failed URL's page row has status: "error", errorCategory set to one of blocked / not_found /
timeout / render_error / invalid_url / upstream_changed, and every audit-specific field left
null. Failed page rows are never charged.
Errors (never silent, never charged)
| Category | When |
|---|---|
invalid_url | Malformed URL, non-http(s) URL, or the URL resolves to a private/link-local/loopback/cloud-metadata IP address (SSRF protection, always on) |
not_found | DNS failure, connection refused, 404/410 |
blocked | 401/403/429 or a bot-challenge page - the exact failure mode this spec exists to fix vs. the category leader |
timeout | Navigation, the axe-core audit, or screenshot capture exceeded timeoutSecs combined |
render_error | Browser crash or an unclassified render failure |
upstream_changed | 5xx upstream server error |
A blocked/not-found/upstream-error page never reaches axe-core at all - it is categorized from the HTTP response status alone, before any audit work happens, and is never charged.
A waitForSelector that never appears is not an error: the page is still audited and
waitForSelectorTimedOut: true is reported (the successful audit is charged normally - a real audit
was in fact delivered).
WCAG citation accuracy (read this before citing a report)
Not every axe-core rule maps to a single numbered WCAG success criterion. axe-core also ships
"best-practice" rules (e.g. region, landmark-one-main) that catch real accessibility problems but
are not themselves a WCAG requirement. For those, wcagCriteria is explicitly null - never guessed,
never silently omitted. If you need every row to cite a numbered criterion, filter on
wcagCriteria !== null.
score is this Actor's own derived heuristic (100 minus a weighted penalty per violation impact -
critical hurts most, minor least). It is not an official W3C/WCAG conformance score; no incumbent
Actor provides one either. Use violationsByImpact/violationCount for anything that must be
defensible on its own.
status: "incomplete" violation rows are axe-core checks that could not be resolved automatically and
need manual human review (e.g. some color-contrast cases). They are never counted in score or
violationCount, and are kept distinguishable from confirmed violations via the status field.
WCAG level and the mobile-viewport differentiator
wcagLevel values are cumulative: wcag21aa includes WCAG 2.0 A, 2.0 AA, 2.1 A, and 2.1 AA.
wcag22aa additionally enables axe-core's target-size rule (WCAG 2.2 AA, success criterion 2.5.8,
Target Size Minimum) - the rule actually exercised by this build's mobile-viewport verification test.
Select wcagLevel: "wcag22aa" if you want the mobile-viewport differentiator to have a rule that can
fire on it. At wcag21aa and below, target-size is correctly never run (verified in
tests/axe.test.ts).
SSRF protection (always on)
Identical approach and identical module (src/ssrf.ts) as actors/universal-screenshot-api: before
any browser navigation, the URL's hostname is DNS-resolved and every resolved IP is checked against
RFC1918/link-local/loopback/CGNAT/cloud-metadata ranges (IPv4 and IPv6). A hit is rejected as
invalid_url with zero navigation attempted.
Known limitation: this is a pre-navigation check, not a pinned connection - a target that changes DNS or redirects to a private address after the check passed would not be caught by this v1 (shared with most browser-automation tools; closing it fully needs IP-pinned egress, out of scope for v1).
Screenshots and the PDF compliance report
includeScreenshots (default on) captures an element-cropped PNG per violation plus one full-page PNG
per URL, bundled into the page-audited price.
generatePdfReport: true produces one PDF per run: violations grouped by impact (critical first),
WCAG citations, inlined screenshots, and the batch rollup. Failed pages are listed with their error
category - never silently dropped from the report. Charged as a separate report-generated event,
and only when it actually succeeds - if every page in the batch failed, PDF generation is skipped
and nothing is charged.
Output modes
perPage- one row per URL, violations summarized inviolationsByImpact/violationCount.flat- one row per individual finding (rowType: "violation"), no page rows.both(default) - both row types in the same dataset, filter byrowType.
Regardless of outputMode, the chargeable unit is always one audited page - outputMode only changes
dataset shape, never price.
Limits
- Max 100 URLs per run (internal safety cap; no
maxItemsinput field in v1). - Max 120s
timeoutSecs, minimum 5s. Bounds navigation, the axe-core audit, and screenshot capture combined - exceeding it reportserrorCategory: "timeout"and is not charged. - Full-page screenshots are capped at 20,000px height (cost/reliability safety limit, not a Chromium
limit - same rationale as
actors/universal-screenshot-api). - This Actor audits exactly the URLs you list. It does not crawl or discover additional pages.
buildSelectorreturns axe-core's own selector for plain-document elements (the common case, verified to resolve back to the correct element). For shadow-DOM or cross-origin-iframe targets, axe-core'stargetis a multi-entry traversal chain that is not valid plain CSS; this Actor falls back to a best-effort, human-readable join of those entries, which will not always paste directly into DevTools in that specific (rare) case.- axe-core rules tagged
experimentalordeprecatedare not run, even if disabled-by-default and otherwise WCAG-tagged (css-orientation-lock,label-content-name-mismatch,p-as-heading,table-fake-caption,td-has-header,aria-roledescription,audio-caption) - a deliberate choice favoring defensibility over maximum coverage for a legal/compliance audience.target-size(WCAG 2.2 AA) is the one disabled-by-default rule force-enabled, and only whenwcagLeveliswcag22aa.
Use with AI agents / MCP
Call the Actor with urls as an array (never a comma-separated string). Check rowType first, then
status/errorCategory on page rows. Only status: "success" page rows have populated
violationsByImpact/fullPageScreenshotUrl. Filter rowType: "violation" rows by
status === "violation"rowType: "summary"FAQ
Am I charged for a blocked or failed audit? No. This is the single most important guarantee in
this Actor, verified end-to-end in tests/edge-case-blocked.test.ts against a real HTTP 403 response.
Is the score an official WCAG conformance score? No - see "WCAG citation accuracy" above.
Does wcagCriteria: null mean the finding is wrong or unimportant? No - it means axe-core
classifies that specific rule as a "best-practice" check rather than a numbered WCAG requirement. The
finding itself is still real and still worth fixing; just don't cite a WCAG clause number for it.
Can this bypass CAPTCHAs or logins? No. It audits publicly-rendered pages only, same operating
pattern as actors/universal-screenshot-api.
What's not in v1 yet (explicitly deferred, not silently dropped)
Site-crawl/auto-discovery mode, CI/CD webhook callbacks, scheduled recurring audits, cookie-consent- gated page auditing, and custom rule-set authoring. Each exists in at least one incumbent but adds real scope beyond this spec's 8 approved differentiators.
Local development
npm installnpx playwright install chromium # one-time, downloads a browser to the shared Playwright cachenpm run typechecknpm testnpm run buildapify run --input-file smoke-input.json --purge