CI/CD Integration
Add accessibility checks to your deployment pipeline. AllyProof can block or warn on pull requests that introduce WCAG violations, catching regressions before they reach production.
Generate an API Key
Go to Settings > API Keys and click Create Key. Give it a descriptive name (e.g. github-actions-ci) and copy the key immediately — it is shown only once.
Store the key as a secret in your CI environment:
- GitHub Actions:
Settings > Secrets > ALLYPROOF_API_KEY - GitLab CI:
Settings > CI/CD > Variables > ALLYPROOF_API_KEY
The Scan Endpoint
Trigger a scan by sending a POST request to the scan API. The site must already be added and verified in your AllyProof dashboard.
POST https://allyproof.com/api/v1/scan
x-api-key: <API_KEY>
Content-Type: application/json
{
"url": "https://example.com",
"threshold": 85,
"wait": true,
"max_pages": 20
}Request parameters
| Field | Type | Required | Description |
|---|---|---|---|
url | string (URL) | Yes | The verified site URL to scan. |
threshold | number (0-100) | No | Minimum score to pass. Default: 0 (always passes). |
wait | boolean | No | If true, holds the connection until the scan completes (up to 5 min). Default: false. |
max_pages | number (1-100) | No | Maximum pages to scan. If omitted, scans all discovered pages up to the site's configured limit. |
Note: The API key is passed via the x-api-key header, not the Authorization header.
Response Formats
Synchronous (wait: true)
When wait is true, the API holds the connection open until the scan completes and returns results directly:
{
"pass": true,
"score": 92,
"threshold": 85,
"scan_id": "scan-uuid",
"site": { "name": "My Site", "url": "https://example.com" },
"summary": {
"pages_scanned": 12,
"pages_failed": 0,
"total_violations": 3,
"critical": 0,
"serious": 1,
"moderate": 2,
"minor": 0
},
"duration_ms": 14200,
"top_violations": [
{
"rule_id": "color-contrast",
"impact": "serious",
"description": "Elements must meet minimum color contrast ratio thresholds",
"pages_affected": 3
}
],
"pages": [
{ "url": "example.com/", "violations": 1, "critical": 0 },
{ "url": "example.com/about", "violations": 2, "critical": 0 }
],
"dashboard_url": "https://allyproof.com/sites/site-uuid"
}Asynchronous (default)
Without wait: true, the API returns immediately with a scan ID and a poll URL. The response has HTTP status 202 Accepted.
{
"scan_id": "scan-uuid",
"status": "pending",
"poll_url": "/api/v1/scan?id=scan-uuid",
"message": "Scan started. Poll the poll_url for results, or use wait=true to block."
}To poll for results, hit the URL the response gave you with the same API key:
GET https://allyproof.com/api/v1/scan?id=scan-uuid
x-api-key: <API_KEY>Threshold Configuration
The threshold parameter sets the minimum acceptable accessibility score. When the scan completes:
- If the score is at or above the threshold, the response includes
"pass": true - If the score is below the threshold, the response includes
"pass": false
Use this in your CI script to decide whether to pass or fail the pipeline step. A threshold of 85 is a reasonable starting point for most sites. Raise it gradually as you fix existing violations.
GitHub Actions Example
Save this as .github/workflows/accessibility.yml:
name: Accessibility Check
on:
pull_request:
branches: [main]
jobs:
a11y-scan:
runs-on: ubuntu-latest
steps:
- name: Run AllyProof scan
id: scan
run: |
RESPONSE=$(curl -s -X POST \
https://allyproof.com/api/v1/scan \
-H "x-api-key: ${{ secrets.ALLYPROOF_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{
"url": "${{ vars.SITE_URL }}",
"wait": true,
"threshold": 85
}')
SCORE=$(echo "$RESPONSE" | jq -r '.score')
PASS=$(echo "$RESPONSE" | jq -r '.pass')
VIOLATIONS=$(echo "$RESPONSE" | jq -r '.summary.total_violations')
URL=$(echo "$RESPONSE" | jq -r '.dashboard_url')
echo "score=$SCORE" >> "$GITHUB_OUTPUT"
echo "pass=$PASS" >> "$GITHUB_OUTPUT"
echo "violations=$VIOLATIONS" >> "$GITHUB_OUTPUT"
echo "url=$URL" >> "$GITHUB_OUTPUT"
- name: Comment on PR
if: always()
uses: actions/github-script@v7
with:
script: |
const score = '${{ steps.scan.outputs.score }}';
const violations = '${{ steps.scan.outputs.violations }}';
const url = '${{ steps.scan.outputs.url }}';
const passed = '${{ steps.scan.outputs.pass }}' === 'true';
const emoji = passed ? '✅' : '❌';
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `${emoji} **AllyProof Scan**\n\nScore: **${score}/100** | Violations: **${violations}**\n\n[View full report](${url})`
});
- name: Fail if below threshold
if: steps.scan.outputs.pass == 'false'
run: |
echo "Accessibility score ${{ steps.scan.outputs.score }} is below threshold"
exit 1GitLab CI Example
Save this as .gitlab-ci.yml:
accessibility:
stage: test
image: alpine:latest
before_script:
- apk add --no-cache curl jq
script:
- |
RESPONSE=$(curl -s -X POST \
https://allyproof.com/api/v1/scan \
-H "x-api-key: $ALLYPROOF_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"url\": \"$SITE_URL\",
\"wait\": true,
\"threshold\": 85
}")
SCORE=$(echo "$RESPONSE" | jq -r '.score')
PASS=$(echo "$RESPONSE" | jq -r '.pass')
echo "Accessibility Score: $SCORE/100"
if [ "$PASS" = "false" ]; then
echo "Score below threshold — failing pipeline"
exit 1
fi
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"Best Practices
- Run scans on pull requests, not just on merge to main — catch regressions early.
- Start with a low threshold (e.g.
70) and raise it gradually as you fix existing violations. - Use
max_pagesto limit scan scope for faster CI runs. - Set up email notifications as a backup so stakeholders see results even if they don't check CI.
- Use the
dashboard_urlin PR comments so developers can jump straight to the full report.
Limitations
- Automated scanning covers approximately 57–70% of WCAG 2.2 AA criteria. Criteria that require human judgment (e.g., meaningful alt text, logical reading order) are not tested.
- Scans with
wait: truetime out after 5 minutes. For large sites, use async mode and poll. - The site must be verified in your AllyProof dashboard before the API can scan it.