GitHub Action

GitHub Action

Run an accessibility scan as a step in your own workflow, and fail the build when the score drops.

Overview

adaguard-io/accessibility-action@v1 scans a URL against WCAG 2.2, ADA, Section 508 and EAA, then exposes the score and issue counts as step outputs. It is a step in a workflow you control, so it runs wherever you put it — after a deploy, on a schedule, on a tag, or on a pull request.

Fails the build

Below min-score exits non-zero. Set min-score: 0 to report without gating.

Step outputs

11 outputs, so later steps can branch on the result.

Any trigger

Your workflow, your triggers — not limited to pull requests.

Requires a Professional plan or above — the Action authenticates with an API key, and API access starts at Professional.

Quick Start

Add this as .github/workflows/accessibility.yml. The only required inputs are api-key and url.

[ YAML ]
name: Accessibility on: [pull_request] jobs: a11y: runs-on: ubuntu-latest permissions: pull-requests: write # only needed for comment-on-pr steps: - uses: adaguard-io/accessibility-action@v1 with: api-key: ${{ secrets.ADAGUARD_API_KEY }} url: https://staging.example.com min-score: 80

The URL must be publicly reachable — the scanner runs on our infrastructure, not on your runner, so localhost and private addresses are rejected. Scan a deployed preview or staging URL.

Getting an API Key

  1. Create a key at Settings → API Keys. It is shown once.
  2. In your repository, go to Settings → Secrets and variables → Actions and add it as a secret named ADAGUARD_API_KEY.
  3. Reference it as ${{ secrets.ADAGUARD_API_KEY }} — never inline the key.

Each run spends one scan from your monthly quota, unless idempotency-key matches a scan from the last 24 hours.

Scanning Behind a Login

Capture a session once at Settings → Authenticated Targets, copy its target key, and pass it as auth-target. Use latest for your most recent session.

[ YAML ]
- uses: adaguard-io/accessibility-action@v1 with: api-key: ${{ secrets.ADAGUARD_API_KEY }} url: https://app.example.com/dashboard auth-target: ${{ vars.ADAGUARD_AUTH_TARGET }} # or "latest" scan-mode: crawl max-pages: 25
Sessions expire. When one does, the scan falls back to public pages and reports auth-status: expired_fallback with passed: false — so a silently-public scan cannot pass as an authenticated one. Recapture the session to fix it.

Crawling Many Pages

scan-mode is single by default, which ignores max-pages. To scan more than one page you must change both.

[ YAML ]
- uses: adaguard-io/accessibility-action@v1 with: api-key: ${{ secrets.ADAGUARD_API_KEY }} url: https://example.com scan-mode: sitemap # read sitemap.xml, then crawl max-pages: 100 max-wait-minutes: 45 # a 100-page crawl takes 15-20 minutes

Budget roughly 10 seconds per page unauthenticated and 13 seconds per page authenticated, so 100 pages takes 15–20 minutes. Raise max-wait-minutes past the default 30 for large crawls, or the job gives up before the scan finishes.

Gating the Build

The step exits non-zero when the score is below min-score. Two things worth knowing before you pick a number:

  • ·Start from your current score, not from 100. Run once with min-score: 0, then set the threshold at or just below what you actually score. A gate that has never passed gets ignored.
  • ·A scanner error is not an accessibility regression. fail-on-scan-error is false by default so infrastructure trouble does not block your merge. Set it true if you would rather know.

Inputs

InputDefaultDescription
api-keyrequiredADAGuard API key. Store it as a repository secret. Requires a Professional plan or above.
urlrequiredURL to scan. Must be publicly reachable — private and internal addresses are rejected.
min-score80Fail the build when the score falls below this (0-100).
scan-modesinglesingle (the given URL only), crawl (follow internal links), sitemap (read sitemap.xml, then crawl), or layout (crawl, then skip structurally duplicate pages).
max-pages1Maximum pages to scan. Ignored when scan-mode is "single". Your plan caps this.
include-subdomainstrueCrawl subdomains of the target host. Set false to stay on the exact hostname. Crawl modes only.
auth-targetScan pages behind a login. Pass a target_key from Settings → Authenticated Targets, or "latest" for your most recent session. Requires a Professional plan or above.
fail-on-scan-errorfalseFail the build when the scanner itself errors. Off by default, because that is an infrastructure problem rather than an accessibility regression.
comment-on-prtruePost results as a pull request comment, updated in place on each push. Needs pull-requests:write.
max-wait-minutes30Give up waiting after this many minutes, so a stuck scan cannot burn your runner budget.
idempotency-keyDedupe key. If a scan was already created with this key in the last 24h, its result is reused instead of spending another scan from your quota. Defaults to this run + attempt, so re-running a job after a network flake does not cost a second scan. Set to a unique value (e.g. the commit SHA) to force a fresh scan, or leave blank to disable.
api-basehttps://api.adaguard.ioAPI base URL. Change only when testing against a non-production instance.

Outputs

Give the step an id, then read ${{ steps.<id>.outputs.<name> }}.

OutputDescription
scoreAccessibility score out of 100. Empty when the scan errored.
passedWhether the scan met min-score. Also false when an authenticated scan fell back to public pages.
criticalCount of critical issues.
warningCount of warnings.
infoCount of informational issues.
pagesPages actually scanned.
scan-idScan ID, for fetching the report from the API later.
scan-statuscompleted or failed.
auth-statusnot_requested, authenticated, expired_fallback, or public_only.
dashboard-urlHuman-readable report. Requires signing in to the account that owns the API key.
report-urlJSON report endpoint, for fetching full results with your API key.

Deciding for yourself

[ YAML ]
- uses: adaguard-io/accessibility-action@v1 id: a11y with: api-key: ${{ secrets.ADAGUARD_API_KEY }} url: https://example.com min-score: 0 # never fail on score; decide below - name: Block only on critical issues if: steps.a11y.outputs.critical != '0' run: | echo "::error::${{ steps.a11y.outputs.critical }} critical issue(s)" echo "Report: ${{ steps.a11y.outputs.dashboard-url }}" exit 1

Action vs GitHub App

Both scan and report into GitHub. They differ in who decides when a scan runs.

GitHub ActionGitHub App
SetupA workflow file you writeInstall once, no config
Runs onAny workflow triggerPull requests
URLYou specify itAuto-detected preview, or .adaguard.yml
ResultStep outputs + build exit codePR comment + Check Run
ControlFull — branch on any outputThresholds via .adaguard.yml
PlanProfessional and aboveAll plans

They are not exclusive — the App for a zero-config signal on every PR, the Action for a gate you control. See the GitHub App docs.

Troubleshooting

Authenticated target not found (HTTP 404)

The auth-target value does not match a saved target. Copy the exact key from Settings → Authenticated Targets, or use "latest".

Scan did not finish within N minutes

The crawl outlasted max-wait-minutes. Raise it, or lower max-pages. A 100-page crawl needs about 20 minutes.

auth-status came back expired_fallback

The saved session expired, so only public pages were scanned and passed is false. Recapture the session at Settings → Authenticated Targets.

Fewer pages scanned than max-pages

The crawler only found that many linked pages, or some were skipped as non-HTML or duplicate layouts. scan-mode: single always scans exactly one.

The PR comment never appears

The job needs permissions: pull-requests: write, and comment-on-pr must not be false. Forked-PR runs cannot comment — that is a GitHub restriction.

401 or 403 from the API

The key is missing, revoked, or on a plan below Professional. Check the secret name matches what the workflow references.