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

FieldTypeRequiredDescription
site_idstring (UUID)YesThe site to scan
pagesstring[]NoSpecific paths to scan. If omitted, scans all discovered pages.
thresholdnumber (0-100)NoMinimum score to pass. Default: 0 (no threshold).
modestringNo"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

ModeBehaviorUse case
advisoryScan runs, results reported, pipeline never failsBrownfield projects with existing violations
blockingPipeline fails if score is below thresholdGreenfield 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 1

7. 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 advisory mode and a low threshold while remediating existing issues
  • Scan only changed pages when possible using the pages parameter 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_url in PR comments so developers can jump straight to the full report