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

Scheduled Scans

POST/v1/schedules

Create 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

urlstringrequired

The URL to scan on the recurring schedule.

labelstring

An optional label to identify this schedule (max 255 characters).

standardstring

WCAG standard to check against. Defaults to wcag22aa.

frequencystring

How often to run: daily (default), weekly, or hourly. Available frequencies depend on your plan.

Scheduled scans are available on Pro plans and above. Maximum schedules and allowed frequencies vary by tier.
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"
  }'
Response — 201 Created
{
  "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"
}
GET/v1/schedules

List scheduled scans

List all scheduled scans for your account, ordered by creation date (newest first).

Requires authentication

Response attributes

schedulesarray

Array of schedule objects.

schedules[].idstring

Unique schedule identifier.

schedules[].urlstring

The URL being scanned.

schedules[].frequencystring

Scan frequency (daily, weekly, or hourly).

schedules[].isActiveboolean

Whether the schedule is active.

schedules[].nextRunAtstring | null

When the next scan will run.

schedules[].lastRunAtstring | null

When the last scan ran.

schedules[].consecutiveFailuresinteger

Number of consecutive scan failures.

curl https://api.a11yflow.dev/v1/schedules \
  -H "Authorization: Bearer sk_live_your_key"
Response — 200 OK
{
  "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"
    }
  ]
}
GET/v1/schedules/:id

Get 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

idstringrequired

The schedule ID.

Response attributes

scheduleobject

The full schedule object.

recentScansarray

Up 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"
Response — 200 OK
{
  "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
    }
  ]
}
PATCH/v1/schedules/:id

Update 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

idstringrequired

The schedule ID.

Request body

urlstring

New URL to scan.

labelstring

New label.

standardstring

New WCAG standard.

isActiveboolean

Set 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 }'
Response — 200 OK
{
  "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"
}
DELETE/v1/schedules/:id

Delete a schedule

Permanently delete a scheduled scan. Existing scan results from previous runs are not affected.

Requires authentication

Path parameters

idstringrequired

The schedule ID to delete.

curl -X DELETE https://api.a11yflow.dev/v1/schedules/a1b2c3d4-... \
  -H "Authorization: Bearer sk_live_your_key"
Response — 200 OK
{
  "message": "Schedule deleted"
}

Webhooks

POST/v1/webhooks

Create 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

urlstringrequired

The HTTPS URL to receive webhook POST requests.

labelstring

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

Webhooks are available on Pro plans and above. Free and Starter plans will receive a 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"]
  }'
Response — 201 Created
{
  "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"
}
GET/v1/webhooks

List webhooks

List all webhooks registered for your account, ordered by creation date (newest first).

Requires authentication

Response attributes

webhooksarray

Array of webhook objects.

webhooks[].idstring

Unique webhook identifier.

webhooks[].urlstring

The destination URL.

webhooks[].labelstring | null

Optional label.

webhooks[].eventsstring[]

Subscribed events.

webhooks[].isActiveboolean

Whether the webhook is active.

curl https://api.a11yflow.dev/v1/webhooks \
  -H "Authorization: Bearer sk_live_your_key"
Response — 200 OK
{
  "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"
    }
  ]
}
GET/v1/webhooks/:id

Get 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

idstringrequired

The webhook ID.

Response attributes

webhookobject

The webhook object (same fields as list response).

deliveriesarray

Up 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"
Response — 200 OK
{
  "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"
    }
  ]
}
PATCH/v1/webhooks/:id

Update 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

idstringrequired

The webhook ID.

Request body

urlstring

New destination URL.

labelstring

New label.

eventsstring[]

New set of subscribed events.

isActiveboolean

Set 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 }'
Response — 200 OK
{
  "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"
}
DELETE/v1/webhooks/:id

Delete a webhook

Permanently delete a webhook. All associated delivery history will also be removed.

Requires authentication

Path parameters

idstringrequired

The webhook ID to delete.

curl -X DELETE https://api.a11yflow.dev/v1/webhooks/a1b2c3d4-... \
  -H "Authorization: Bearer sk_live_your_key"
Response — 200 OK
{
  "message": "Webhook deleted"
}
POST/v1/webhooks/:id/test

Test 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

idstringrequired

The webhook ID to test.

Delivery headers

The POST request sent to your URL includes these headers:

X-A11yFlow-Signaturestring

HMAC-SHA256 signature of timestamp.body.

X-A11yFlow-Eventstring

The event type ("ping").

X-A11yFlow-Deliverystring

Unique delivery ID.

X-A11yFlow-Timestampstring

Unix timestamp of the delivery.

curl -X POST https://api.a11yflow.dev/v1/webhooks/a1b2c3d4-.../test \
  -H "Authorization: Bearer sk_live_your_key"
Response — 200 OK
{
  "deliveryId": "d1e2f3g4-...",
  "status": "success",
  "responseStatus": 200
}
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."
}

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 needed

Run directly with npx — no global install required.

Authentication

Set A11YFLOW_API_KEY as an environment variable, pass --key inline, or run a11yflow auth login to store your key in ~/.a11yflowrc.

Output formats--format

table (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 login

Scan Command

Run a single-page accessibility scan and get results in the terminal.

--standardstring

WCAG standard: wcag2a, wcag2aa, wcag2aaa, wcag22aa (default).

--formatstring

Output format: table (default), json, plain.

--fail-onstring

Exit code 1 if violations at this impact or above: minor, moderate, serious, critical.

--waitnumber

Milliseconds to wait for JS rendering (0–30000). Default: 3000.

--quietboolean

Suppress progress output — print only results. Ideal for CI.

--include-htmlboolean

Include raw HTML snippets of violating elements.

Plain language mode: Use --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 sitemap

CI/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 0

Scan completed, no violations above the --fail-on threshold.

Exit code 1

Violations found at or above the --fail-on threshold.

Exit code 2

Scan failed — network error, authentication failure, or timeout.

Tip: Use --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 table

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

How it works: The AI agent calls tools like a11yflow_scan which handle job submission and polling internally. The agent gets back complete results in a single call — no async polling needed.
Plain language output: All tool responses use plain language by default — the agent can relay findings to non-technical users without translation.
# 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_scantool

Run a single-page WCAG scan. Returns score, violations with plain-language explanations, affected elements, and WCAG/EAA references.

a11yflow_crawltool

Crawl and scan multiple pages. Returns aggregate scores, per-page results, and best/worst page identification.

a11yflow_get_scantool

Retrieve results of a previous scan by ID. Supports full, summary, and violations-only formats.

a11yflow_lookup_ruletool

Look up documentation for an accessibility rule — description, WCAG guidelines, EAA obligations, and fix guidance.

a11yflow_list_rulestool

List all supported accessibility rules, optionally filtered by impact level.

a11yflow_usagetool

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

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"
    }
  ]
}

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.

eaaObligations in scan response
{
  "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:

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