REST API
The Welcomingweb REST API lets you trigger accessibility scans, poll their status, and pull summary numbers from CI/CD pipelines, monitoring scripts, or any other server-side tooling.
This page documents the v1 API, available at /api/public/v1/web/....
Quick start
# 1. Get your API key from Settings → API in the dashboard.
export WW_KEY="ww_..." # the full key you copied at creation time
# 2. List your modules to find the one you want to scan.
curl https://api.welcomingweb.com/api/public/v1/web/modules \
-H "Authorization: Bearer $WW_KEY"
# 3. Trigger a scan. Save the sessionId.
SESSION_ID=$(curl -s -X POST \
https://api.welcomingweb.com/api/public/v1/web/modules/123/scan \
-H "Authorization: Bearer $WW_KEY" \
| jq -r .sessionId)
# 4. Poll until done.
while true; do
STATUS=$(curl -s \
https://api.welcomingweb.com/api/public/v1/web/modules/123/sessions/$SESSION_ID \
-H "Authorization: Bearer $WW_KEY" \
| jq -r .status)
echo "Scan status: $STATUS"
case "$STATUS" in
SUCCESS) break ;;
FAILED) echo "Scan failed"; exit 1 ;;
*) sleep 15 ;;
esac
doneAuthentication
All endpoints require an API key, sent in one of these headers:
| Header | Example |
|---|---|
Authorization | Authorization: Bearer ww_1a2b3c4d.long-secret-portion |
X-API-Key | X-API-Key: ww_1a2b3c4d.long-secret-portion |
Create keys at Settings → API → Generate New Key. The full key is shown once at creation; only the prefix is recoverable afterwards. Revoke a key from the same screen.
Keys are account-scoped. They can access any module on your account; there is no per-module key. Treat them like passwords.
The same key works for both the web REST API documented here and the mobile SDK API.
Endpoints
GET /api/public/v1/web/modules
List all accessibility modules visible to the API key’s account.
Response (200):
[
{
"id": 123,
"domain": "example.com",
"plan": "PRO",
"status": "ACTIVE"
},
{
"id": 124,
"domain": "other-site.com",
"plan": "FREE",
"status": "INACTIVE"
}
]| Field | Description |
|---|---|
id | Numeric module id. Use this in subsequent endpoints. |
domain | The site’s primary domain. |
plan | FREE / PRO / ENTERPRISE. Scan triggering requires a paid plan. |
status | ACTIVE / INACTIVE / EXECUTING / URL_ANALYSE / DELETING. A module showing EXECUTING already has a scan in flight. |
POST /api/public/v1/web/modules/{moduleId}/scan
Trigger an on-demand accessibility scan. The scan runs asynchronously; this endpoint returns as soon as the job is dispatched.
Path parameters:
| Name | Description |
|---|---|
moduleId | The numeric id returned by GET /modules. |
No request body.
Response (200):
{
"status": "queued",
"sessionId": 1573,
"moduleId": 123,
"used": 3,
"limit": 5,
"remaining": 2,
"resetsAt": "2026-06-01T00:00:00+01:00",
"message": "Scan started. 2 manual scans remaining this month."
}Quota: each module on a paid plan gets 5 manual scans per calendar month, shared between this API and the dashboard’s Force Run button. Quota resets at the start of the next calendar month in UTC+0 (the server’s timezone).
Error responses:
| Status | Error code | When |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | FORBIDDEN_PLAN | Module is on the FREE plan. Upgrade in the dashboard. |
404 | NOT_FOUND | Module doesn’t exist or isn’t visible to this key. |
409 | ALREADY_RUNNING | A scan is already running for this module. Wait for it to finish. |
429 | QUOTA_EXHAUSTED | Monthly quota used up. Response includes resetsAt. |
Every error response also includes the same quota fields (used, limit, remaining, resetsAt) so scripts can log them or warn before they hit the cap.
GET /api/public/v1/web/modules/{moduleId}/sessions/{sessionId}
Get the status and summary of a scan session. Poll this endpoint to wait for a triggered scan to complete.
Response (200) while scan is still running:
{
"sessionId": 1573,
"moduleId": 123,
"status": "EXECUTING",
"startedAt": "2026-05-20T14:32:11",
"completedAt": null,
"message": null,
"pagesDiscovered": 0,
"pagesAnalyzed": 0,
"uniqueIssues": 0,
"totalOccurrences": 0,
"p1Count": 0,
"p2Count": 0,
"p3Count": 0,
"p4Count": 0,
"wcagBand": null,
"qualityScore": 0.0
}Response (200) after scan completes:
{
"sessionId": 1573,
"moduleId": 123,
"status": "SUCCESS",
"startedAt": "2026-05-20T14:32:11",
"completedAt": "2026-05-20T14:36:42",
"message": null,
"pagesDiscovered": 47,
"pagesAnalyzed": 47,
"uniqueIssues": 521,
"totalOccurrences": 4264,
"p1Count": 0,
"p2Count": 268,
"p3Count": 583,
"p4Count": 29,
"wcagBand": "Fails A",
"qualityScore": 79.5
}Status values:
status | Meaning |
|---|---|
PENDING | Created but not yet started. |
EXECUTING | Scan is running. Summary fields are zero. |
URL_ANALYSE | Initial discovery phase (sitemap, link analysis). |
SUCCESS | Scan complete. Summary fields populated. |
FAILED | Scan failed. message carries the reason. |
Summary fields (populated only when status == SUCCESS):
| Field | Description |
|---|---|
pagesDiscovered | Total pages found via sitemap / crawling. |
pagesAnalyzed | Subset actually scanned (may be smaller if the plan caps page count). |
uniqueIssues | Deduplicated finding count. The “521 unique issues” headline. |
totalOccurrences | Raw node-level occurrences. |
p1Count … p4Count | Issues per priority tier. P1 = Critical. |
wcagBand | "Fails A" / "A" / "AA" / "AAA" / "N/A". The conformance verdict. |
qualityScore | 0–100, weighted across A (50%), AA (40%), AAA (10%). -1 if not computable. |
Errors:
| Status | When |
|---|---|
401 | Missing or invalid API key. |
404 | Module or session doesn’t exist for this account. |
CI/CD integration examples
GitHub Actions
name: Accessibility scan
on:
push:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- name: Trigger scan and wait
env:
WW_KEY: ${{ secrets.WELCOMINGWEB_API_KEY }}
MODULE_ID: 123
MIN_QUALITY: 70
run: |
set -euo pipefail
API=https://api.welcomingweb.com/api/public/v1/web
SESSION_ID=$(curl -s -X POST \
"$API/modules/$MODULE_ID/scan" \
-H "Authorization: Bearer $WW_KEY" \
| jq -r .sessionId)
echo "Started session $SESSION_ID"
for i in {1..60}; do
RESP=$(curl -s "$API/modules/$MODULE_ID/sessions/$SESSION_ID" \
-H "Authorization: Bearer $WW_KEY")
STATUS=$(echo "$RESP" | jq -r .status)
echo "Attempt $i: $STATUS"
case "$STATUS" in
SUCCESS)
SCORE=$(echo "$RESP" | jq -r .qualityScore)
P1=$(echo "$RESP" | jq -r .p1Count)
echo "Quality score: $SCORE, P1 issues: $P1"
if [ "$P1" -gt 0 ]; then
echo "::error::Scan found $P1 P1 (critical) issues"
exit 1
fi
if [ "$(echo "$SCORE < $MIN_QUALITY" | bc)" -eq 1 ]; then
echo "::error::Quality score $SCORE below threshold $MIN_QUALITY"
exit 1
fi
exit 0
;;
FAILED)
echo "::error::Scan failed"; exit 2 ;;
*) sleep 15 ;;
esac
done
echo "::error::Timed out after 15 minutes"; exit 3GitLab CI
accessibility:
stage: test
script:
- apt-get update && apt-get install -y curl jq
- |
SESSION_ID=$(curl -s -X POST \
"$WW_API/modules/$MODULE_ID/scan" \
-H "Authorization: Bearer $WW_KEY" | jq -r .sessionId)
while true; do
STATUS=$(curl -s \
"$WW_API/modules/$MODULE_ID/sessions/$SESSION_ID" \
-H "Authorization: Bearer $WW_KEY" | jq -r .status)
[ "$STATUS" = "SUCCESS" ] && exit 0
[ "$STATUS" = "FAILED" ] && exit 1
sleep 15
done
variables:
WW_API: https://api.welcomingweb.com/api/public/v1/web
MODULE_ID: "123"Rate limits and quotas
| Resource | Limit |
|---|---|
| Manual scans (per module, per calendar month) | 5 on PRO plans |
Read endpoints (GET /modules, GET /sessions) | No hard rate limit, but cache responses where you can — polling every 15–30 seconds is plenty. |
Status codes summary
| Code | Meaning |
|---|---|
200 | Success. |
401 | Missing or invalid API key. |
402 | Payment Required — module is on the FREE plan. |
404 | Resource not found, or not visible to this key. |
409 | Conflict — a scan is already running. |
429 | Too Many Requests — monthly quota exhausted. |
500 | Internal server error. Retry after a short backoff. |
Versioning policy
- The current API is
v1. Breaking changes will go tov2at a new path. - Within
v1, we may add fields to responses without notice. Your client should ignore unknown fields. - We will not change field types, rename fields, or remove fields within
v1.
Need help?
Email support@welcomingweb.com with your API key prefix (the first 8 characters, never the full key) and the response body of the failing request.