API Reference
The A11yFlow API is organised around REST. It accepts JSON-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs.
Use the API to scan any publicly accessible URL for WCAG 2.2 accessibility violations. Each scan launches a real Chromium browser, executes JavaScript, and runs the full axe-core rule set against the rendered page.
Base URL
https://api.a11yflow.devcurl https://api.a11yflow.dev/v1/scans/SCAN_ID \
-H "Authorization: Bearer sk_live_your_key"Authentication
The A11yFlow API uses API keys to authenticate requests. You can create a key using the POST /v1/keys endpoint.
Include your API key in the Authorization header using the Bearer scheme. All scan endpoints require authentication. The /v1/keys, /v1/rules, and /health endpoints do not require authentication.
Your API keys carry privileges, so keep them secure. Do not share keys in publicly accessible areas such as GitHub, client-side code, or browser requests.
sk_live_ and test keys use sk_test_. We store only a SHA-256 hash — the raw key is shown once at creation and cannot be retrieved later.curl https://api.a11yflow.dev/v1/scans \
-H "Authorization: Bearer sk_live_587d70b2..." \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'Authorization: Bearer sk_live_your_api_keyRate Limits
Rate limits are applied per API key based on your subscription tier. The API uses a sliding window for per-minute limits and a fixed window for monthly limits. Current usage is returned in every response via headers.
| Tier | Price | Req/min | Req/month |
|---|---|---|---|
| Free | £0 | 5 | 25 |
| Starter | £19/mo | 20 | 1,000 |
| Pro | £49/mo | 60 | 5,000 |
| Team | £149/mo | 120 | 25,000 |
| Enterprise | £499+/mo | 300 | Unlimited |
Response Headers
X-RateLimit-LimitintegerMaximum requests per minute for your tier.
X-RateLimit-RemainingintegerRequests remaining in the current minute window.
X-RateLimit-Resetunix timestampWhen the current minute window resets.
X-RateLimit-Monthly-LimitintegerMaximum requests per month for your tier.
X-RateLimit-Monthly-RemainingintegerRequests remaining this calendar month.
{
"error": "Too Many Requests",
"message": "Rate limit exceeded. Try again in 12 seconds."
}X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1707321720
X-RateLimit-Monthly-Limit: 5000
X-RateLimit-Monthly-Remaining: 4832Errors
A11yFlow uses conventional HTTP response codes to indicate the success or failure of an API request. Codes in the 2xx range indicate success. Codes in the 4xx range indicate an error with the information provided. Codes in the 5xx range indicate a server error.
Attributes
errorstringThe error type, e.g. "Bad Request", "Unauthorized".
messagestringA human-readable message providing more details about the error.
HTTP Status Codes
200OK — Request succeeded.201Created — Resource created successfully.202Accepted — Scan queued for processing.400Bad Request — Invalid parameters or request body.401Unauthorized — Missing or invalid API key.404Not Found — Resource or endpoint does not exist.429Too Many Requests — Rate limit exceeded.500Server Error — Something went wrong on our end.{
"error": "Bad Request",
"message": "url is required and must be a valid URL"
}{
"error": "Unauthorized",
"message": "Missing or invalid API key. Include your key in the Authorization header: Bearer sk_live_..."
}{
"error": "Not Found",
"message": "Scan not found"
}Endpoints
/v1/keysCreate an API key
Create a new API key. This is how users get their first key. If no account exists for the given email, one is created automatically.
No authentication required
Request body
emailstringrequiredA valid email address. Used to create or look up the user account.
namestringA label for this API key. Defaults to "Default".
Response attributes
idstringUnique identifier for the API key.
namestringThe label you provided.
keystringThe raw API key. Shown only once. Store it securely.
keyPrefixstringFirst 12 characters of the key, for identification.
createdAtdatetimeWhen the key was created.
tierstringThe user's subscription tier ("free", "starter", "pro", "team", "enterprise").
key value is only returned in this response. We store a SHA-256 hash — the key cannot be retrieved later. If you lose it, create a new one.curl -X POST https://api.a11yflow.dev/v1/keys \
-H "Content-Type: application/json" \
-d '{
"email": "dev@example.com",
"name": "My App"
}'{
"id": "6f6ae594-7431-4693-86ed-f5f1b82d8959",
"name": "My App",
"key": "sk_live_587d70b2a12a90c02981d55...",
"keyPrefix": "sk_live_587d",
"createdAt": "2026-02-07T19:36:09.148Z",
"tier": "free",
"message": "Store this key securely — it will not be shown again."
}/v1/scansSubmit a scan
Queue a WCAG accessibility scan for a given URL. The scan runs asynchronously — this endpoint returns immediately with a scan ID you can poll for results.
Requires authentication
Request body
urlstringrequiredThe URL to scan. Must be a publicly accessible, valid URL.
standardstringWCAG standard to test against. One of wcag2a, wcag2aa, wcag2aaa, or wcag22aa. Defaults to wcag22aa.
waitForintegerMilliseconds to wait after page load before scanning (0–30000). Defaults to 3000. Increase for pages with heavy JavaScript.
includeHtmlbooleanInclude raw HTML of violating elements in results. Defaults to false.
includeBestPracticesbooleanInclude industry best-practice rules alongside WCAG rules. Defaults to false.
Response attributes
idstringUnique scan ID. Use this to poll for results.
statusstringAlways "queued" on creation.
urlstringThe URL being scanned.
standardstringThe WCAG standard being tested.
createdAtdatetimeWhen the scan was queued.
messagestringConfirmation message with polling instructions.
curl -X POST https://api.a11yflow.dev/v1/scans \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"standard": "wcag22aa",
"waitFor": 3000,
"includeHtml": false
}'{
"id": "1c440a46-d019-41b6-b072-3749883852ce",
"status": "queued",
"url": "https://example.com",
"standard": "wcag22aa",
"createdAt": "2026-02-07T16:21:55.283Z",
"message": "Scan queued successfully. Poll GET /v1/scans/:id for results."
}/v1/scans/:idGet scan results
Retrieve the full results of a previously submitted scan, including the accessibility score, violation details with WCAG guideline mappings, category breakdown, and contrast issue data.
Requires authentication
Path parameters
idstringrequiredThe scan ID returned from POST /v1/scans.
Response attributes
idstringThe scan ID.
urlstringThe scanned URL.
statusstringOne of "queued", "running", "completed", or "failed".
scoreinteger | nullWeighted accessibility score from 0–100. Violations are weighted by impact severity (critical=10, serious=5, moderate=3, minor=1). Null if scan is not yet complete.
scanDurationMsintegerHow long the scan took in milliseconds.
summaryobjectAggregated counts: violations, passes, incomplete, inapplicable, and a categories breakdown.
summary.categoriesobjectViolation counts by category: aria, color, forms, keyboard, language, naming, structure, tables, other.
violationsarrayArray of violation objects. Each includes id, impact, description, help, helpUrl, tags, guidelines, remediation, and nodes.
violations[].guidelinesarrayHuman-readable WCAG guideline references parsed from the violation's tags. Each has id, name, and url.
violations[].remediationobject | nullStructured fix guidance with summary (what's wrong) and fix (how to fix it). Available for common rule IDs, null otherwise.
violations[].nodesarrayAffected elements. Each has html, target (CSS selectors), xpath, failureSummary, and optionally contrastData.
incompletearrayItems that could not be automatically determined and need manual review. Same structure as violations (with guidelines and remediation). These represent the ~43% of WCAG criteria that require human judgement.
contrastIssuesarrayFlat list of all contrast failures with fgColor, bgColor, contrastRatio, expectedRatio, fontSize, and fontWeight.
curl https://api.a11yflow.dev/v1/scans/SCAN_ID \
-H "Authorization: Bearer sk_live_your_key"{
"id": "2f95573f-b2a7-4094-bfdb-1f3f1093a210",
"url": "https://www.craigslist.org",
"status": "completed",
"standard": "wcag22aa",
"createdAt": "2026-02-08T19:54:38.517Z",
"startedAt": "2026-02-08T19:54:38.520Z",
"completedAt": "2026-02-08T19:54:48.480Z",
"scanDurationMs": 9963,
"score": 74,
"summary": {
"violations": 4,
"passes": 17,
"incomplete": 2,
"inapplicable": 41,
"categories": {
"aria": 0, "color": 1, "forms": 1,
"keyboard": 0, "language": 1, "naming": 0,
"structure": 0, "tables": 0, "other": 234
}
},
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"description": "Ensures the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
"help": "Elements must meet minimum color contrast ratio thresholds",
"helpUrl": "https://dequeuniversity.com/rules/axe/4.11/color-contrast",
"tags": ["cat.color", "wcag2aa", "wcag143"],
"guidelines": [
{
"id": "1.4.3",
"name": "Contrast (Minimum)",
"url": "https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum"
}
],
"remediation": {
"summary": "Text does not have sufficient colour contrast against its background.",
"fix": "Increase the contrast ratio to at least 4.5:1 for normal text or 3:1 for large text."
},
"nodes": [
{
"html": "",
"target": ["#post"],
"xpath": [],
"failureSummary": "Fix any of the following: ...",
"contrastData": {
"fgColor": "#009900",
"bgColor": "#ffffff",
"contrastRatio": 3.77,
"expectedRatio": 4.5,
"fontSize": "12.0pt (16px)",
"fontWeight": "normal"
}
}
]
}
],
"incomplete": [
{
"id": "aria-allowed-role",
"impact": "minor",
"description": "Ensure role attribute has an appropriate value for the element",
"help": "ARIA role should be appropriate for the element",
"helpUrl": "https://dequeuniversity.com/rules/axe/4.11/aria-allowed-role",
"tags": ["cat.aria", "best-practice"],
"guidelines": [],
"remediation": null,
"nodes": [
{
"html": "",
"target": [".nav-item"],
"xpath": [],
"failureSummary": "Check that the role is appropriate for this element"
}
]
}
],
"contrastIssues": [
{
"element": "",
"selector": ["#post"],
"fgColor": "#009900",
"bgColor": "#ffffff",
"contrastRatio": 3.77,
"expectedRatio": 4.5,
"fontSize": "12.0pt (16px)",
"fontWeight": "normal"
}
]
}{
"id": "2f95573f-b2a7-4094-bfdb-1f3f1093a210",
"url": "https://example.com",
"status": "queued",
"standard": "wcag22aa",
"createdAt": "2026-02-07T19:36:14.589Z",
"startedAt": null,
"completedAt": null,
"errorMessage": null
}/v1/scans/:id/reportGet scan report
Get a formatted, summary-focused report for a completed scan. Includes impact-level breakdown and affected element counts instead of full node arrays — ideal for dashboards and stakeholder reports.
Requires authentication
Path parameters
idstringrequiredThe scan ID.
How it differs from GET /v1/scans/:id
- Summary includes impact-level counts (
critical,serious,moderate,minor) - Violations show
affectedElementscount instead of fullnodesarray - Violations include
guidelinesandremediationbut omittags - Includes
incompleteitems with same summary format - Lighter payload — better for overview displays
curl https://api.a11yflow.dev/v1/scans/SCAN_ID/report \
-H "Authorization: Bearer sk_live_your_key"{
"id": "2f95573f-b2a7-4094-bfdb-1f3f1093a210",
"url": "https://www.craigslist.org",
"standard": "wcag22aa",
"scannedAt": "2026-02-08T19:54:48.480Z",
"scanDurationMs": 9963,
"score": 74,
"summary": {
"totalViolations": 4,
"totalPasses": 17,
"totalIncomplete": 2,
"totalInapplicable": 41,
"critical": 1,
"serious": 2,
"moderate": 0,
"minor": 1,
"categories": {
"aria": 0, "color": 1, "forms": 1,
"keyboard": 0, "language": 1, "naming": 0,
"structure": 0, "tables": 0, "other": 234
}
},
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"description": "Ensures the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
"help": "Elements must meet minimum color contrast ratio thresholds",
"helpUrl": "https://dequeuniversity.com/rules/axe/4.11/color-contrast",
"guidelines": [
{
"id": "1.4.3",
"name": "Contrast (Minimum)",
"url": "https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum"
}
],
"remediation": {
"summary": "Text does not have sufficient colour contrast against its background.",
"fix": "Increase the contrast ratio to at least 4.5:1 for normal text or 3:1 for large text."
},
"affectedElements": 1
}
],
"incomplete": [
{
"id": "aria-allowed-role",
"impact": "minor",
"description": "Ensure role attribute has an appropriate value for the element",
"help": "ARIA role should be appropriate for the element",
"helpUrl": "https://dequeuniversity.com/rules/axe/4.11/aria-allowed-role",
"guidelines": [],
"remediation": null,
"affectedElements": 1
}
],
"contrastIssues": [
{
"element": "",
"selector": ["#post"],
"fgColor": "#009900",
"bgColor": "#ffffff",
"contrastRatio": 3.77,
"expectedRatio": 4.5,
"fontSize": "12.0pt (16px)",
"fontWeight": "normal"
}
]
}Scheduled Scans
/v1/schedulesCreate a scheduled scan
Set up automatic recurring scans for a URL. Scans run at the configured frequency and results are accessible via the standard scan endpoints.
Requires authentication
Request body
urlstringrequiredThe URL to scan on the recurring schedule.
labelstringAn optional label to identify this schedule (max 255 characters).
standardstringWCAG standard to check against. Defaults to wcag22aa.
frequencystringHow often to run: daily (default), weekly, or hourly. Available frequencies depend on your plan.
curl -X POST https://api.a11yflow.dev/v1/schedules \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"label": "Homepage daily",
"frequency": "daily"
}'{
"id": "a1b2c3d4-...",
"url": "https://example.com",
"label": "Homepage daily",
"standard": "wcag22aa",
"frequency": "daily",
"isActive": true,
"nextRunAt": "2025-01-16T03:00:00.000Z",
"createdAt": "2025-01-15T10:30:00.000Z"
}/v1/schedulesList scheduled scans
List all scheduled scans for your account, ordered by creation date (newest first).
Requires authentication
Response attributes
schedulesarrayArray of schedule objects.
schedules[].idstringUnique schedule identifier.
schedules[].urlstringThe URL being scanned.
schedules[].frequencystringScan frequency (daily, weekly, or hourly).
schedules[].isActivebooleanWhether the schedule is active.
schedules[].nextRunAtstring | nullWhen the next scan will run.
schedules[].lastRunAtstring | nullWhen the last scan ran.
schedules[].consecutiveFailuresintegerNumber of consecutive scan failures.
curl https://api.a11yflow.dev/v1/schedules \
-H "Authorization: Bearer sk_live_your_key"{
"schedules": [
{
"id": "a1b2c3d4-...",
"url": "https://example.com",
"label": "Homepage daily",
"standard": "wcag22aa",
"frequency": "daily",
"isActive": true,
"lastRunAt": "2025-01-15T03:00:00.000Z",
"nextRunAt": "2025-01-16T03:00:00.000Z",
"consecutiveFailures": 0,
"createdAt": "2025-01-10T10:30:00.000Z",
"updatedAt": "2025-01-15T03:00:00.000Z"
}
]
}/v1/schedules/:idGet schedule details
Get a single schedule along with its 30 most recent scan results. Use this to monitor score trends and detect regressions.
Requires authentication
Path parameters
idstringrequiredThe schedule ID.
Response attributes
scheduleobjectThe full schedule object.
recentScansarrayUp to 30 recent scans with id, status, score, createdAt, completedAt, and errorMessage.
curl https://api.a11yflow.dev/v1/schedules/a1b2c3d4-... \
-H "Authorization: Bearer sk_live_your_key"{
"schedule": {
"id": "a1b2c3d4-...",
"url": "https://example.com",
"label": "Homepage daily",
"standard": "wcag22aa",
"frequency": "daily",
"isActive": true,
"lastRunAt": "2025-01-15T03:00:00.000Z",
"nextRunAt": "2025-01-16T03:00:00.000Z",
"consecutiveFailures": 0,
"createdAt": "2025-01-10T10:30:00.000Z",
"updatedAt": "2025-01-15T03:00:00.000Z"
},
"recentScans": [
{
"id": "s1t2u3v4-...",
"status": "completed",
"createdAt": "2025-01-15T03:00:00.000Z",
"completedAt": "2025-01-15T03:00:12.000Z",
"errorMessage": null,
"score": 87
}
]
}/v1/schedules/:idUpdate a schedule
Update a schedule's URL, label, standard, or active status. The frequency cannot be changed after creation — delete and recreate if needed.
Requires authentication
Path parameters
idstringrequiredThe schedule ID.
Request body
urlstringNew URL to scan.
labelstringNew label.
standardstringNew WCAG standard.
isActivebooleanSet to false to pause the schedule.
curl -X PATCH https://api.a11yflow.dev/v1/schedules/a1b2c3d4-... \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{ "isActive": false }'{
"id": "a1b2c3d4-...",
"url": "https://example.com",
"label": "Homepage daily",
"standard": "wcag22aa",
"frequency": "daily",
"isActive": false,
"lastRunAt": "2025-01-15T03:00:00.000Z",
"nextRunAt": null,
"createdAt": "2025-01-10T10:30:00.000Z",
"updatedAt": "2025-01-16T09:00:00.000Z"
}/v1/schedules/:idDelete a schedule
Permanently delete a scheduled scan. Existing scan results from previous runs are not affected.
Requires authentication
Path parameters
idstringrequiredThe schedule ID to delete.
curl -X DELETE https://api.a11yflow.dev/v1/schedules/a1b2c3d4-... \
-H "Authorization: Bearer sk_live_your_key"{
"message": "Schedule deleted"
}Webhooks
/v1/webhooksCreate a webhook
Register a URL to receive HTTP POST notifications when scan events occur. The response includes a signing secret (shown once) which you can use to verify webhook payloads.
Requires authentication
Request body
urlstringrequiredThe HTTPS URL to receive webhook POST requests.
labelstringAn optional label to identify this webhook (max 255 characters).
eventsstring[]Events to subscribe to. Defaults to ["scan.completed", "scan.failed"]. Supported: scan.completed, scan.failed, ping.
Webhook signing
Every webhook delivery includes an X-A11yFlow-Signature header containing an HMAC-SHA256 signature computed from the timestamp and payload body. Verify this against your whsec_ secret to ensure the request is authentic.
403 Forbidden response.curl -X POST https://api.a11yflow.dev/v1/webhooks \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/a11yflow",
"label": "Production",
"events": ["scan.completed", "scan.failed"]
}'{
"id": "a1b2c3d4-...",
"url": "https://example.com/webhooks/a11yflow",
"label": "Production",
"events": ["scan.completed", "scan.failed"],
"isActive": true,
"secret": "whsec_a1b2c3d4e5f6...",
"createdAt": "2025-01-15T10:30:00.000Z"
}/v1/webhooksList webhooks
List all webhooks registered for your account, ordered by creation date (newest first).
Requires authentication
Response attributes
webhooksarrayArray of webhook objects.
webhooks[].idstringUnique webhook identifier.
webhooks[].urlstringThe destination URL.
webhooks[].labelstring | nullOptional label.
webhooks[].eventsstring[]Subscribed events.
webhooks[].isActivebooleanWhether the webhook is active.
curl https://api.a11yflow.dev/v1/webhooks \
-H "Authorization: Bearer sk_live_your_key"{
"webhooks": [
{
"id": "a1b2c3d4-...",
"url": "https://example.com/webhooks/a11yflow",
"label": "Production",
"events": ["scan.completed", "scan.failed"],
"isActive": true,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}
]
}/v1/webhooks/:idGet webhook details
Get a single webhook along with its 20 most recent delivery records. Use this to inspect delivery history and debug failures.
Requires authentication
Path parameters
idstringrequiredThe webhook ID.
Response attributes
webhookobjectThe webhook object (same fields as list response).
deliveriesarrayUp to 20 most recent delivery records with id, event, status, attempts, responseStatus, and createdAt.
curl https://api.a11yflow.dev/v1/webhooks/a1b2c3d4-... \
-H "Authorization: Bearer sk_live_your_key"{
"webhook": {
"id": "a1b2c3d4-...",
"url": "https://example.com/webhooks/a11yflow",
"label": "Production",
"events": ["scan.completed", "scan.failed"],
"isActive": true,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
},
"deliveries": [
{
"id": "d1e2f3g4-...",
"event": "scan.completed",
"status": "success",
"attempts": 1,
"responseStatus": 200,
"createdAt": "2025-01-15T11:00:00.000Z"
}
]
}/v1/webhooks/:idUpdate a webhook
Update a webhook's URL, label, subscribed events, or active status. Only include the fields you want to change.
Requires authentication
Path parameters
idstringrequiredThe webhook ID.
Request body
urlstringNew destination URL.
labelstringNew label.
eventsstring[]New set of subscribed events.
isActivebooleanSet to false to pause deliveries without deleting the webhook.
curl -X PATCH https://api.a11yflow.dev/v1/webhooks/a1b2c3d4-... \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{ "isActive": false }'{
"id": "a1b2c3d4-...",
"url": "https://example.com/webhooks/a11yflow",
"label": "Production",
"events": ["scan.completed", "scan.failed"],
"isActive": false,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-16T09:00:00.000Z"
}/v1/webhooks/:idDelete a webhook
Permanently delete a webhook. All associated delivery history will also be removed.
Requires authentication
Path parameters
idstringrequiredThe webhook ID to delete.
curl -X DELETE https://api.a11yflow.dev/v1/webhooks/a1b2c3d4-... \
-H "Authorization: Bearer sk_live_your_key"{
"message": "Webhook deleted"
}/v1/webhooks/:id/testTest a webhook
Send a ping event to the webhook URL to verify it is reachable and correctly processing requests. The delivery is logged and can be inspected via the get webhook endpoint.
Requires authentication
Path parameters
idstringrequiredThe webhook ID to test.
Delivery headers
The POST request sent to your URL includes these headers:
X-A11yFlow-SignaturestringHMAC-SHA256 signature of timestamp.body.
X-A11yFlow-EventstringThe event type ("ping").
X-A11yFlow-DeliverystringUnique delivery ID.
X-A11yFlow-TimestampstringUnix timestamp of the delivery.
curl -X POST https://api.a11yflow.dev/v1/webhooks/a1b2c3d4-.../test \
-H "Authorization: Bearer sk_live_your_key"{
"deliveryId": "d1e2f3g4-...",
"status": "success",
"responseStatus": 200
}/v1/rulesList all rules
List all accessibility rules the scanner checks, with WCAG guideline mappings. Use this to understand what the scanner tests for and to build rule documentation into your own tools.
No authentication required
Response attributes
countintegerTotal number of rules.
rulesarrayArray of rule summary objects.
rules[].idstringUnique rule identifier, e.g. "color-contrast".
rules[].titlestringHuman-readable rule name.
rules[].impactstringDefault severity: "critical", "serious", "moderate", or "minor".
rules[].categorystringRule category (color, forms, keyboard, etc.).
rules[].wcagGuidelinesarrayWCAG guideline references with id, name, and url.
curl https://api.a11yflow.dev/v1/rules{
"count": 16,
"rules": [
{
"id": "color-contrast",
"title": "Color Contrast",
"impact": "serious",
"category": "color",
"wcagGuidelines": [
{
"id": "1.4.3",
"name": "Contrast (Minimum)",
"url": "https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum"
}
]
},
{
"id": "image-alt",
"title": "Image Alternative Text",
"impact": "critical",
"category": "naming",
"wcagGuidelines": [
{
"id": "1.1.1",
"name": "Non-text Content",
"url": "https://www.w3.org/WAI/WCAG22/Understanding/non-text-content"
}
]
}
]
}/v1/rules/:idGet rule details
Get detailed documentation for a specific accessibility rule, including a full description, fix guidance, and WCAG guideline mappings.
No authentication required
Path parameters
idstringrequiredThe rule ID, e.g. "color-contrast", "image-alt", "html-has-lang".
Response attributes
idstringThe rule identifier.
titlestringHuman-readable rule name.
descriptionstringFull description of what the rule checks.
impactstringDefault severity level.
categorystringRule category.
wcagGuidelinesarrayWCAG guideline references.
helpUrlstringLink to the full axe-core rule documentation.
actionsstringPlain-English guidance on how to fix violations of this rule.
curl https://api.a11yflow.dev/v1/rules/color-contrast{
"id": "color-contrast",
"title": "Color Contrast",
"description": "Ensures the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds.",
"impact": "serious",
"category": "color",
"wcagGuidelines": [
{
"id": "1.4.3",
"name": "Contrast (Minimum)",
"url": "https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum"
}
],
"helpUrl": "https://dequeuniversity.com/rules/axe/4.11/color-contrast",
"actions": "Increase the contrast ratio between the text colour and its background. For normal text, the minimum ratio is 4.5:1. For large text (18pt or 14pt bold), the minimum is 3:1."
}CLI
CLI Installation
The A11yFlow CLI lets you run accessibility scans from the terminal. Use it in local development, CI/CD pipelines, or hand it to an AI coding agent.
No install neededRun directly with npx — no global install required.
AuthenticationSet A11YFLOW_API_KEY as an environment variable, pass --key inline, or run a11yflow auth login to store your key in ~/.a11yflowrc.
Output formats--formattable (default), json (for piping), or plain (human-readable explanations).
# Run a scan (no install needed)
npx @a11yflowdev/cli scan https://example.com
# Or install globally
npm install -g @a11yflowdev/cli
# Authenticate
export A11YFLOW_API_KEY=sk_live_your_key
a11yflow scan https://example.com
# Or store the key
a11yflow auth loginScan Command
Run a single-page accessibility scan and get results in the terminal.
--standardstringWCAG standard: wcag2a, wcag2aa, wcag2aaa, wcag22aa (default).
--formatstringOutput format: table (default), json, plain.
--fail-onstringExit code 1 if violations at this impact or above: minor, moderate, serious, critical.
--waitnumberMilliseconds to wait for JS rendering (0–30000). Default: 3000.
--quietbooleanSuppress progress output — print only results. Ideal for CI.
--include-htmlbooleanInclude raw HTML snippets of violating elements.
--format plain to get easy-to-follow explanations of each violation — what's wrong, who's affected, and how to fix it.# Basic scan (table output)
a11yflow scan https://example.com
# Plain language explanations
a11yflow scan https://example.com --format plain
# JSON for piping to other tools
a11yflow scan https://example.com --format json --quiet
# Crawl multiple pages
a11yflow crawl https://example.com --max-pages 20
a11yflow crawl https://example.com --mode sitemapCI/CD Integration
Add accessibility scanning to your deployment pipeline. The CLI returns deterministic exit codes so your CI can gate merges on accessibility.
Exit code 0Scan completed, no violations above the --fail-on threshold.
Exit code 1Violations found at or above the --fail-on threshold.
Exit code 2Scan failed — network error, authentication failure, or timeout.
--quiet --format json in CI to suppress spinners and get machine-readable output.# GitHub Actions
- name: Accessibility scan
env:
A11YFLOW_API_KEY: ${{ secrets.A11YFLOW_API_KEY }}
run: |
npx @a11yflowdev/cli scan $DEPLOY_URL \
--fail-on serious \
--format tableMCP Server
MCP Server Setup
The A11yFlow MCP server exposes accessibility scanning tools to AI agents. Any MCP-compatible client — Claude Code, Claude Desktop, Cursor, Windsurf, VS Code Copilot — can scan pages, read results, and look up rules through natural tool calls.
Add the server to your MCP client configuration. The server uses stdio transport and requires an API key via environment variable.
a11yflow_scan which handle job submission and polling internally. The agent gets back complete results in a single call — no async polling needed.# Claude Code (.mcp.json in project root)
{
"mcpServers": {
"a11yflow": {
"command": "npx",
"args": ["@a11yflowdev/mcp"],
"env": {
"A11YFLOW_API_KEY": "sk_live_your_key"
}
}
}
}Available Tools
The MCP server exposes these tools. Each tool handles polling internally and returns formatted, LLM-optimised text.
a11yflow_scantoolRun a single-page WCAG scan. Returns score, violations with plain-language explanations, affected elements, and WCAG/EAA references.
a11yflow_crawltoolCrawl and scan multiple pages. Returns aggregate scores, per-page results, and best/worst page identification.
a11yflow_get_scantoolRetrieve results of a previous scan by ID. Supports full, summary, and violations-only formats.
a11yflow_lookup_ruletoolLook up documentation for an accessibility rule — description, WCAG guidelines, EAA obligations, and fix guidance.
a11yflow_list_rulestoolList all supported accessibility rules, optionally filtered by impact level.
a11yflow_usagetoolCheck current API usage — scheduled scans and webhook counts.
# Example: AI agent fixing accessibility issues
User: "scan my staging site and fix the issues"
Agent workflow:
1. Calls a11yflow_scan({
url: "https://staging.example.com"
})
2. Reads results: 4 violations found
3. For each violation:
- Reads CSS selector from result
- Greps codebase for matching component
- Applies fix (alt text, contrast, label)
4. Calls a11yflow_scan() again to verify
5. Reports: "Fixed 4 violations. Score: 74 -> 96"Reference
WCAG Standards
Supported values for the standard parameter when submitting a scan. We recommend wcag22aa — it's the latest standard and the one referenced by the European Accessibility Act.
wcag2aenumWCAG 2.0 Level A — the minimum conformance level.
wcag2aaenumWCAG 2.0 Level AA — the most commonly required level.
wcag2aaaenumWCAG 2.0 Level AAA — the highest conformance level.
wcag22aaenum, defaultWCAG 2.2 Level AA — the latest standard. Recommended.
curl -X POST https://api.a11yflow.dev/v1/scans \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"standard": "wcag2aaa"
}'Impact Levels
Every violation includes an impact field indicating how severely the issue affects users. These levels come from axe-core and are consistent across all accessibility testing tools that use it.
critical
Completely blocks access for some users. For example, an image with no alt text that conveys essential information, or a form that cannot be submitted with a keyboard.
serious
Causes significant difficulty. For example, text with insufficient colour contrast that's hard to read, or a dropdown menu that traps keyboard focus.
moderate
Causes some difficulty. For example, a table without proper header associations, making it harder to understand data relationships.
minor
A nuisance or annoyance. For example, a redundant ARIA role that doesn't cause functional issues but adds noise for screen reader users.
{
"id": "color-contrast",
"impact": "serious",
"description": "Ensures the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
"help": "Elements must meet minimum color contrast ratio thresholds",
"nodes": [
{
"target": ["#post"],
"failureSummary": "Fix any of the following: Element has insufficient color contrast of 3.77:1 (foreground color: #009900, background color: #ffffff, font size: 12.0pt, font weight: normal). Expected contrast ratio of 4.5:1"
}
]
}EAA Obligation Mapping
The European Accessibility Act (Directive 2019/882) requires digital products and services in the EU to meet accessibility standards, with enforcement from June 28, 2025. It references the harmonised standard EN 301 549, whose Section 9 maps 1:1 to WCAG 2.1/2.2 success criteria.
When violations match an EN 301 549 clause, we display a purple EAA: 9.x.x.x badge. Hovering shows the obligation category and the relevant EAA Annex I Section III grouping. The API also returns this data in the eaaObligations field on each violation.
EAA Annex I Section III Groupings
EN 301 549 Section 9 requirements are organised into the following groups, matching WCAG principles:
(a) Perceivable
Content must be presentable in ways users can perceive — text alternatives, captions, contrast, resizable text.
(b) Operable
Interface components and navigation must be operable — keyboard access, enough time, no seizure-inducing content.
(c) Robust
Content must be robust enough to be interpreted by assistive technologies — valid markup, ARIA, name/role/value.
{
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"description": "Ensures the contrast between ...",
"eaaObligations": [
{
"en301549": "9.1.4.3",
"obligation": "Perceivable — Contrast (Minimum)",
"eaaAnnex": "Annex I, Section III (a)"
}
],
"nodes": [...]
},
{
"id": "html-has-lang",
"impact": "serious",
"description": "Ensures the html element has ...",
"eaaObligations": [
{
"en301549": "9.3.1.1",
"obligation": "Understandable — Language of Page",
"eaaAnnex": "Annex I, Section III (a)"
}
],
"nodes": [...]
}
]
}Scan Lifecycle
Scans are processed asynchronously. When you submit a scan, it enters a queue and progresses through the following states:
queued
Scan has been accepted and is waiting to be picked up by a worker.
running
A worker has picked up the scan. Chromium is loading the page and running axe-core.
completed
Scan finished successfully. Results are available via GET /v1/scans/:id.
failed
Something went wrong — the page timed out, returned an error, or the scanner encountered an issue. The errorMessage field will contain details.
# Submit scan
SCAN_ID=$(curl -s -X POST https://api.a11yflow.dev/v1/scans \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}' | jq -r '.id')
# Poll until complete
while true; do
STATUS=$(curl -s https://api.a11yflow.dev/v1/scans/$SCAN_ID \
-H "Authorization: Bearer sk_live_your_key" | jq -r '.status')
echo "Status: $STATUS"
[ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] && break
sleep 3
done