Scan in your CI workflow
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.
Quick Start
Add this as .github/workflows/accessibility.yml. The only required inputs are api-key and url.
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: 80The 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
- Create a key at Settings → API Keys. It is shown once.
- In your repository, go to Settings → Secrets and variables → Actions and add it as a secret named
ADAGUARD_API_KEY. - 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.
- 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: 25auth-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.
- 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 minutesBudget 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-errorisfalseby default so infrastructure trouble does not block your merge. Set ittrueif you would rather know.
Inputs
| Input | Default | Description |
|---|---|---|
| api-keyrequired | — | ADAGuard API key. Store it as a repository secret. Requires a Professional plan or above. |
| urlrequired | — | URL to scan. Must be publicly reachable — private and internal addresses are rejected. |
| min-score | 80 | Fail the build when the score falls below this (0-100). |
| scan-mode | single | single (the given URL only), crawl (follow internal links), sitemap (read sitemap.xml, then crawl), or layout (crawl, then skip structurally duplicate pages). |
| max-pages | 1 | Maximum pages to scan. Ignored when scan-mode is "single". Your plan caps this. |
| include-subdomains | true | Crawl subdomains of the target host. Set false to stay on the exact hostname. Crawl modes only. |
| auth-target | — | Scan 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-error | false | Fail the build when the scanner itself errors. Off by default, because that is an infrastructure problem rather than an accessibility regression. |
| comment-on-pr | true | Post results as a pull request comment, updated in place on each push. Needs pull-requests:write. |
| max-wait-minutes | 30 | Give up waiting after this many minutes, so a stuck scan cannot burn your runner budget. |
| idempotency-key | — | Dedupe 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-base | https://api.adaguard.io | API base URL. Change only when testing against a non-production instance. |
Outputs
Give the step an id, then read ${{ steps.<id>.outputs.<name> }}.
| Output | Description |
|---|---|
| score | Accessibility score out of 100. Empty when the scan errored. |
| passed | Whether the scan met min-score. Also false when an authenticated scan fell back to public pages. |
| critical | Count of critical issues. |
| warning | Count of warnings. |
| info | Count of informational issues. |
| pages | Pages actually scanned. |
| scan-id | Scan ID, for fetching the report from the API later. |
| scan-status | completed or failed. |
| auth-status | not_requested, authenticated, expired_fallback, or public_only. |
| dashboard-url | Human-readable report. Requires signing in to the account that owns the API key. |
| report-url | JSON report endpoint, for fetching full results with your API key. |
Deciding for yourself
- 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 1Action vs GitHub App
Both scan and report into GitHub. They differ in who decides when a scan runs.
| Aspect | GitHub Action | GitHub App |
|---|---|---|
| Setup | A workflow file you write | Install once, no config |
| Runs on | Any workflow trigger | Pull requests |
| URL | You specify it | Auto-detected preview, or .adaguard.yml |
| Result | Step outputs + build exit code | PR comment + Check Run |
| Control | Full — branch on any output | Thresholds via .adaguard.yml |
| Plan | Professional and above | All 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 15–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.