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 | £349+/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"
}
]
}/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."
}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"
}
]
}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