Accessibility scanning
Alongside dead-link detection, Overwatch can run an accessibility scan over the pages it crawls, powered by axe-core. It is opt-in per site and adds a scored accessibility report next to the dead-link report.
Pages are fully rendered in a headless browser before axe runs, so the scan sees what a visitor sees — client-side rendering, injected widgets, and computed styles included — and the complete axe ruleset applies (including color-contrast on real computed colors).
Enabling the scan
Section titled “Enabling the scan”Set a11yEnabled when registering a site
(POST /v1/sites) or toggle it later
(PATCH /v1/sites/{id}):
curl -X PATCH https://api.overwatch.weareheavy.dev/v1/sites/<site-id> \ -H "x-api-key: wah_…" -H "content-type: application/json" \ -d '{"a11yEnabled": true}'The scan runs on its own clock, independent of the crawl:
a11yCadenceMinutes defaults to monthly (43200), sized to the rendering
allowance — see Quotas & usage. Each run renders the
crawl’s persisted page set; the first run simply waits for a crawl to populate
it. You can also trigger a scan on demand — no re-crawl — via
POST /v1/sites/{id}/a11y-scan, and a
manual full crawl chains a scan by default (chainA11y: false skips it).
Each accessibility run records a score plus per-impact counts (visible on
RunRow as a11y_score, a11y_critical … a11y_minor,
a11y_pages_scanned, a11y_issue_count, and a11y_resolved — how many
violations from the previous scan are now gone). Runs that didn’t scan have
a11y_score: null.
Reading the report
Section titled “Reading the report”GET /v1/sites/{id}/a11y-report parallels
the dead-link report: the latest run, the list of findings, a summary, and the
site’s suppressions and manual review states.
Each finding is a (page, rule) pair:
rule— the axe-core rule id (e.g.image-alt,color-contrast), withhelp/help_urlexplaining it, and top-level mapsdescriptionsandw3cUrlslinking each rule to its one-line description and W3C Understanding page.impact—critical,serious,moderate, orminor.type—violationis a confirmed failure;incompletemeans axe couldn’t decide and a human should review.countandnodes— how many elements matched, with capped example nodes (selector + snippet) to locate them.standards,sc,principle— the WCAG level (A/AA/AAA), whether the rule falls under EN 301 549 (the EU Accessibility Act’s standard) and US Section 508, its success criterion, and its POUR principle.
The summary rolls the findings up several ways: severity counts, a
byPrinciple POUR rollup, compliance tallies for EN 301 549 and
Section 508, and resolvedSinceLastScan. gainByRule ranks rules by the
score points fixing each would recover, and byPage lists every affected page
worst-first with its own score.
The score and the verdict
Section titled “The score and the verdict”axe-core doesn’t produce a score, so Overwatch synthesizes a 0–100 number per run: confirmed violations are weighted by impact and damped by the number of pages scanned — a large, mostly clean site scores high; a small site with serious issues scores low. A run with any confirmed violation can never score 100, so “100” always means “no confirmed violations”. Treat it as a trend indicator between runs, not a compliance certificate.
Separately, summary.verdict is a pass/fail conformance judgment at the AA
target level: any confirmed violation of a level-A or -AA success criterion
breaks it (AAA and best-practice rules don’t), and a risk band scales with
severity × breadth. Score and verdict move independently — a high score with a
lone level-A violation still fails.
The coverage matrix
Section titled “The coverage matrix”coverage classifies every WCAG success criterion for the run: fail (an
active violation maps to it), pass (auto-testable and clean), or manual
(no automated rule can test it — a human must). It’s grouped
principle → guideline → criteria, ready to render as a matrix, with per-SC
page counts, failing rules, and W3C links.
Manual WCAG review
Section titled “Manual WCAG review”Automation can’t test every criterion (e.g. 2.1.4 Character Key Shortcuts).
PUT /v1/sites/{id}/a11y-manual-checks
records a manual state per criterion — verified, not_applicable, or
failed, each with a note — and
DELETE …/a11y-manual-checks/{sc}
clears it back to “needs review”. Manual states are a documentation/audit
layer only: they surface in the report’s manualChecks and overlay the
coverage matrix, but never affect the score or verdict.
Suppressing findings (“Ignore”)
Section titled “Suppressing findings (“Ignore”)”Not every finding is actionable — a third-party widget, an intentional design
decision. Suppressions let an editor ignore a finding without losing the audit
trail, via
POST /v1/sites/{id}/a11y-suppressions.
Scope is set by which fields you provide:
| Fields | Suppresses |
|---|---|
rule |
The rule, site-wide |
rule + selector |
That element, on every page |
rule + selector + pageUrl |
That one occurrence |
Suppressions apply retroactively and reversibly: they are stored per site,
and the report applies them at read time — suppressed findings drop out of
issues and the score is recomputed from what remains. Deleting a suppression
(DELETE /v1/sites/{id}/a11y-suppressions/{suppressionId})
brings the findings straight back. The site’s excludePaths globs are applied
the same way, so excluding a section removes its findings immediately.
The report also returns each suppression with a matchedCount — how many of
the latest run’s findings it currently hides — so a UI can show an “Ignored”
section and let editors un-ignore with confidence.