Accessalyze API

Free public REST API for WCAG 2.1 accessibility scanning — no API key required.

Get your API key

The public API is free and requires no key. Leave your email to get notified when dedicated API keys launch — higher rate limits, usage dashboards, and priority support.

Contents

Endpoints

POST /api/v1/scan Recommended No auth

Starts an async accessibility scan for a URL. Returns a scanId immediately — poll GET /api/v1/scan/:scanId until status is "done" to retrieve the full report.

Request body

FieldTypeRequiredDescription
urlstringYesURL to scan. Bare domains (e.g. example.com) are accepted and prefixed with https://.

Example request

curl -X POST "https://accessalyze.com/api/v1/scan" \
  -H "Content-Type: application/json" \
  -d '{"url": "example.com"}'

Response (202 — scan queued)

{
  "scanId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "scanning",
  "pollUrl": "https://accessalyze.com/api/v1/scan/550e8400-e29b-41d4-a716-446655440000"
}
GET /api/v1/scan/:scanId Poll No auth

Poll for scan results. Returns { "status": "scanning" } while in progress. When status is "done", returns the full accessibility report. Recommended polling interval: 3–5 seconds.

Response when done

{
  "url": "https://example.com",
  "score": 72,
  "violationCount": 8,
  "summary": {
    "critical": 1,
    "serious": 3,
    "moderate": 3,
    "minor": 1,
    "total": 8
  },
  "violations": [
    {
      "id": "color-contrast",
      "impact": "serious",
      "description": "Elements must have sufficient color contrast",
      "count": 3,
      "wcagTags": ["wcag2aa", "wcag143"]
    }
  ],
  "wcagLevel": "AA",
  "scannedAt": "2026-04-25T00:00:00.000Z",
  "poweredBy": "Accessalyze",
  "docsUrl": "https://accessalyze.com/docs"
}
GET /api/public-scan Legacy No auth

Query-string variant — same scan engine and response shape as POST /api/v1/scan. Use this when you cannot send a request body (e.g. browser fetch with no CORS preflight).

Query parameters

ParameterRequiredDescription
urlYesThe URL to scan. Bare domains accepted.

Example request

curl "https://accessalyze.com/api/public-scan?url=example.com"

Rate limits

Both endpoints share a limit of 100 requests per day per IP address. Rate limit headers are returned on every response: RateLimit-Remaining and RateLimit-Reset.

Need higher limits or bulk scanning? Upgrade to Pro for unlimited scans via the UI, or contact us for enterprise API access.

Error codes

HTTP statusMeaning
400Missing or invalid url field / parameter
404Scan not found — scanId is invalid or expired
429Rate limit exceeded — 100 req/day per IP
500Scan failed — the URL may be unreachable or block automated scanners
503Scanner at capacity — retry after 30 seconds

Score calculation

Score starts at 100 and deductions are applied per violation found:

SeverityPoints deducted per violation
Critical−25
Serious−10
Moderate−5
Minor−2

Score is floored at 0. A score of 90+ with zero critical/serious issues indicates WCAG AA compliance.

Use cases & examples

# CI/CD: fail build if score < 80 (async polling)
BASE="https://accessalyze.com"
SCAN=$(curl -s -X POST "$BASE/api/v1/scan" \
  -H "Content-Type: application/json" \
  -d '{"url":"'"$SITE_URL"'"}')
SCAN_ID=$(echo "$SCAN" | python3 -c "import sys,json; print(json.load(sys.stdin)['scanId'])")
echo "Scan started: $SCAN_ID"

# Poll until done (max 60s)
for i in $(seq 1 20); do
  sleep 3
  RESULT=$(curl -s "$BASE/api/v1/scan/$SCAN_ID")
  STATUS=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','done'))" 2>/dev/null)
  [ "$STATUS" != "scanning" ] && break
  echo "Still scanning… ($i)"
done

SCORE=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['score'])")
echo "Accessibility score: $SCORE"
[ "$SCORE" -ge 80 ] || (echo "Score below 80 — failing build" && exit 1)
# Quick one-liner with Python (async)
import time, urllib.request, json

BASE = "https://accessalyze.com"
body = json.dumps({"url": "mysite.com"}).encode()
req = urllib.request.Request(BASE + "/api/v1/scan",
      data=body, headers={"Content-Type": "application/json"})
scan_id = json.loads(urllib.request.urlopen(req).read())["scanId"]

for _ in range(20):
    time.sleep(3)
    r = json.loads(urllib.request.urlopen(BASE + f"/api/v1/scan/{scan_id}").read())
    if r.get("status") != "scanning":
        break

print(f'Score: {r["score"]}/100 — {r["violationCount"]} violations')
for v in r["violations"][:3]:
    print(f'  [{v["impact"]}] {v["id"]}: {v["count"]} instance(s)')
Attribution: When embedding results in your product, please include: Powered by Accessalyze

Accessalyze · GitHub · WCAG Guides · Privacy