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.
1. 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
2. The Scan Endpoint
Trigger a scan by sending a POST request to the scan API:
POST /api/v1/scan
Authorization: Bearer <API_KEY>
Content-Type: application/json
{
"site_id": "your-site-uuid",
"pages": ["/", "/about", "/contact"],
"threshold": 85,
"mode": "blocking"
}Request parameters
| Field | Type | Required | Description |
|---|---|---|---|
site_id | string (UUID) | Yes | The site to scan |
pages | string[] | No | Specific paths to scan. If omitted, scans all discovered pages. |
threshold | number (0-100) | No | Minimum score to pass. Default: 0 (no threshold). |
mode | string | No | "blocking" or "advisory". Default: "advisory". |
3. Wait vs Async Modes
The scan endpoint supports two response strategies:
Synchronous (wait)
Add "wait": true to your request body. The API holds the connection open until the scan completes (up to 5 minutes) and returns results directly. Best for small sites or CI jobs that need immediate results.
{
"site_id": "...",
"wait": true,
"threshold": 85
}
// Response (after scan completes):
{
"data": {
"scan_id": "scan-uuid",
"score": 92,
"violations_count": 3,
"passed": true,
"details_url": "https://app.allyproof.com/scans/scan-uuid"
},
"error": null
}Asynchronous (default)
Without wait: true, the API returns immediately with a scan ID. Poll the scan status endpoint or use a webhook to get notified when results are ready.
// Immediate response:
{
"data": {
"scan_id": "scan-uuid",
"status": "queued"
},
"error": null
}
// Poll for results:
GET /api/v1/scans/{scan_id}
Authorization: Bearer <API_KEY>4. 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
"passed": true - If the score is below the threshold, the response includes
"passed": 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.
5. Advisory vs Blocking Mode
| Mode | Behavior | Use case |
|---|---|---|
advisory | Scan runs, results reported, pipeline never fails | Brownfield projects with existing violations |
blocking | Pipeline fails if score is below threshold | Greenfield projects or post-remediation enforcement |
Start with advisory mode to understand your baseline, then switch to blocking once your score is consistently above the threshold.
6. GitHub Actions Example
# .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://app.allyproof.com/api/v1/scan \
-H "Authorization: Bearer ${{ secrets.ALLYPROOF_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{
"site_id": "${{ vars.ALLYPROOF_SITE_ID }}",
"wait": true,
"threshold": 85,
"mode": "blocking"
}')
SCORE=$(echo "$RESPONSE" | jq -r '.data.score')
PASSED=$(echo "$RESPONSE" | jq -r '.data.passed')
VIOLATIONS=$(echo "$RESPONSE" | jq -r '.data.violations_count')
URL=$(echo "$RESPONSE" | jq -r '.data.details_url')
echo "score=$SCORE" >> "$GITHUB_OUTPUT"
echo "passed=$PASSED" >> "$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.passed }}' === '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.passed == 'false'
run: |
echo "Accessibility score ${{ steps.scan.outputs.score }} is below threshold"
exit 17. GitLab CI Example
# .gitlab-ci.yml
accessibility:
stage: test
image: alpine:latest
before_script:
- apk add --no-cache curl jq
script:
- |
RESPONSE=$(curl -s -X POST \
https://app.allyproof.com/api/v1/scan \
-H "Authorization: Bearer $ALLYPROOF_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"site_id\": \"$ALLYPROOF_SITE_ID\",
\"wait\": true,
\"threshold\": 85,
\"mode\": \"blocking\"
}")
SCORE=$(echo "$RESPONSE" | jq -r '.data.score')
PASSED=$(echo "$RESPONSE" | jq -r '.data.passed')
echo "Accessibility Score: $SCORE/100"
if [ "$PASSED" = "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
advisorymode and a low threshold while remediating existing issues - Scan only changed pages when possible using the
pagesparameter to keep CI fast - Set up email notifications as a backup so stakeholders see results even if they don't check CI
- Use the
details_urlin PR comments so developers can jump straight to the full report