Skip to main content

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.dev
curl 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.

Key prefixes: Production keys use 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.
Authenticated request
curl https://api.a11yflow.dev/v1/scans \
  -H "Authorization: Bearer sk_live_587d70b2..."
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'
Header format
Authorization: Bearer sk_live_your_api_key

Rate 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.

TierPriceReq/minReq/month
Free£0525
Starter£19/mo201,000
Pro£49/mo605,000
Team£149/mo12025,000
Enterprise£349+/mo300Unlimited

Response Headers

X-RateLimit-Limitinteger

Maximum requests per minute for your tier.

X-RateLimit-Remaininginteger

Requests remaining in the current minute window.

X-RateLimit-Resetunix timestamp

When the current minute window resets.

X-RateLimit-Monthly-Limitinteger

Maximum requests per month for your tier.

X-RateLimit-Monthly-Remaininginteger

Requests remaining this calendar month.

Rate limit exceeded — 429
{
  "error": "Too Many Requests",
  "message": "Rate limit exceeded. Try again in 12 seconds."
}
Response headers
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1707321720
X-RateLimit-Monthly-Limit: 5000
X-RateLimit-Monthly-Remaining: 4832

Errors

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

errorstring

The error type, e.g. "Bad Request", "Unauthorized".

messagestring

A 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 response
{
  "error": "Bad Request",
  "message": "url is required and must be a valid URL"
}
Authentication error
{
  "error": "Unauthorized",
  "message": "Missing or invalid API key. Include your key in the Authorization header: Bearer sk_live_..."
}
Not found
{
  "error": "Not Found",
  "message": "Scan not found"
}

Endpoints

POST/v1/keys

Create 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

emailstringrequired

A valid email address. Used to create or look up the user account.

namestring

A label for this API key. Defaults to "Default".

Response attributes

idstring

Unique identifier for the API key.

namestring

The label you provided.

keystring

The raw API key. Shown only once. Store it securely.

keyPrefixstring

First 12 characters of the key, for identification.

createdAtdatetime

When the key was created.

tierstring

The user's subscription tier ("free", "starter", "pro", "team", "enterprise").

Important: The raw 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"
  }'
Response — 201 Created
{
  "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."
}
POST/v1/scans

Submit 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

urlstringrequired

The URL to scan. Must be a publicly accessible, valid URL.

standardstring

WCAG standard to test against. One of wcag2a, wcag2aa, wcag2aaa, or wcag22aa. Defaults to wcag22aa.

waitForinteger

Milliseconds to wait after page load before scanning (0–30000). Defaults to 3000. Increase for pages with heavy JavaScript.

includeHtmlboolean

Include raw HTML of violating elements in results. Defaults to false.

includeBestPracticesboolean

Include industry best-practice rules alongside WCAG rules. Defaults to false.

Response attributes

idstring

Unique scan ID. Use this to poll for results.

statusstring

Always "queued" on creation.

urlstring

The URL being scanned.

standardstring

The WCAG standard being tested.

createdAtdatetime

When the scan was queued.

messagestring

Confirmation 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
  }'
Response — 202 Accepted
{
  "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."
}
GET/v1/scans/:id

Get 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

idstringrequired

The scan ID returned from POST /v1/scans.

Response attributes

idstring

The scan ID.

urlstring

The scanned URL.

statusstring

One of "queued", "running", "completed", or "failed".

scoreinteger | null

Weighted 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.

scanDurationMsinteger

How long the scan took in milliseconds.

summaryobject

Aggregated counts: violations, passes, incomplete, inapplicable, and a categories breakdown.

summary.categoriesobject

Violation counts by category: aria, color, forms, keyboard, language, naming, structure, tables, other.

violationsarray

Array of violation objects. Each includes id, impact, description, help, helpUrl, tags, guidelines, remediation, and nodes.

violations[].guidelinesarray

Human-readable WCAG guideline references parsed from the violation's tags. Each has id, name, and url.

violations[].remediationobject | null

Structured fix guidance with summary (what's wrong) and fix (how to fix it). Available for common rule IDs, null otherwise.

violations[].nodesarray

Affected elements. Each has html, target (CSS selectors), xpath, failureSummary, and optionally contrastData.

incompletearray

Items 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.

contrastIssuesarray

Flat 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"
Response — 200 OK (completed)
{
  "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"
    }
  ]
}
Response — 200 OK (in progress)
{
  "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
}
GET/v1/scans/:id/report

Get 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

idstringrequired

The scan ID.

How it differs from GET /v1/scans/:id

  • Summary includes impact-level counts (critical, serious, moderate, minor)
  • Violations show affectedElements count instead of full nodes array
  • Violations include guidelines and remediation but omit tags
  • Includes incomplete items 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"
Response — 200 OK
{
  "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"
    }
  ]
}
GET/v1/rules

List 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

countinteger

Total number of rules.

rulesarray

Array of rule summary objects.

rules[].idstring

Unique rule identifier, e.g. "color-contrast".

rules[].titlestring

Human-readable rule name.

rules[].impactstring

Default severity: "critical", "serious", "moderate", or "minor".

rules[].categorystring

Rule category (color, forms, keyboard, etc.).

rules[].wcagGuidelinesarray

WCAG guideline references with id, name, and url.

curl https://api.a11yflow.dev/v1/rules
Response — 200 OK
{
  "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"
        }
      ]
    }
  ]
}
GET/v1/rules/:id

Get 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

idstringrequired

The rule ID, e.g. "color-contrast", "image-alt", "html-has-lang".

Response attributes

idstring

The rule identifier.

titlestring

Human-readable rule name.

descriptionstring

Full description of what the rule checks.

impactstring

Default severity level.

categorystring

Rule category.

wcagGuidelinesarray

WCAG guideline references.

helpUrlstring

Link to the full axe-core rule documentation.

actionsstring

Plain-English guidance on how to fix violations of this rule.

curl https://api.a11yflow.dev/v1/rules/color-contrast
Response — 200 OK
{
  "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.

wcag2aenum

WCAG 2.0 Level A — the minimum conformance level.

wcag2aaenum

WCAG 2.0 Level AA — the most commonly required level.

wcag2aaaenum

WCAG 2.0 Level AAA — the highest conformance level.

wcag22aaenum, default

WCAG 2.2 Level AA — the latest standard. Recommended.

Using a specific standard
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.

Violation with impact level
{
  "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:

1

queued

Scan has been accepted and is waiting to be picked up by a worker.

2

running

A worker has picked up the scan. Chromium is loading the page and running axe-core.

3

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.

Polling tip: Most scans complete in 5–15 seconds. We recommend polling every 2–3 seconds with a timeout of 60 seconds.
# 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