The Beacon Public API Is Live
Beacon's public REST API shipped today with ten endpoints, interactive docs, API keys, and scan-to-scan regression detection baked in. Included on every plan, free tier included.
Beacon Team
Since we launched Beacon, the single most common question has been some variant of "can I run this from my own code?" People want accessibility scans in their CI pipelines. They want violation data feeding into their own dashboards. They want to run a scheduled compliance sweep across fifteen sites without opening a browser tab.
That ships today. Beacon now has a public REST API at https://api.beaconaccessibility.com/api/v1/, authenticated with API keys, documented with an interactive OpenAPI explorer, and included on every plan including the free tier.
Here is what is in it, what isn't, and how to get your first scan running in about five minutes of curl.
What's in the release
Ten endpoints. That is the whole public surface today.
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/projects | Create a project |
GET | /api/v1/projects | List your projects (cursor-paginated) |
GET | /api/v1/projects/:id | Get project details |
POST | /api/v1/projects/:id/scans | Start a scan |
GET | /api/v1/projects/:id/scans | List scans for a project, filterable by status and date |
GET | /api/v1/scans/:id | Scan status and summary, with inline regression counts |
GET | /api/v1/scans/:id/diff | Full element-level regression diff against the previous scan |
GET | /api/v1/scans/:id/violations | Violations, filterable by severity and rule ID |
GET | /api/v1/scans/:id/pages | Pages the scan visited |
GET | /api/v1/scans/:id/export | Download the report as JSON, CSV, or PDF |
That covers what a CI job or a reporting script actually needs: create a project, start a scan, poll it, pull violations filtered by severity, see which URLs were crawled, diff against the last run, and export the whole report.
Everything else we've built internally — documents, team management, billing, notifications, admin routes — stays internal. Public APIs are a contract, and we would rather keep a small contract we can stand behind than a larger one we end up apologizing for six months from now.
Regression detection is in the box
This is the part we're most excited about, and it's the reason most people want accessibility in CI in the first place.
Every time a scan completes, Beacon compares it to the previous completed scan for the same project and produces a diff: which violations are new, which are resolved, which are still there. The comparison is element-level — two violations are considered the same when they share the same rule ID on the same DOM target, so a new <img> missing alt text is correctly flagged as new even if image-alt violations already existed elsewhere on the page.
The diff is already computed by the time you poll GET /scans/:id, which returns a compact summary inline:
{"data": {"id": "scan_xyz789","status": "COMPLETED","totalIssues": 27,"criticalCount": 2,"seriousCount": 5,"moderateCount": 8,"minorCount": 12,"regression": {"baselineScanId": "scan_def456","newViolationCount": 3,"resolvedViolationCount": 8}}}
That alone is enough to fail a CI build. Check regression.newViolationCount > 0 at whichever severity threshold your team cares about, exit non-zero, and the PR goes red.
For the full element-level diff — every new violation, every resolved one, with targets and snippets — there's GET /api/v1/scans/:id/diff. It returns newViolations, resolvedViolations, and a summary object you can format into a PR comment, a Slack message, or a Grafana annotation.
A couple of edge cases worth knowing:
- First scan on a project:
baselineScanIdisnull, all current violations show up innewViolations, andresolvedViolationsis empty. - Different WCAG levels between scans: the diff still computes but also sets
wcagLevelMismatch: true, and you should interpret it with care — violations that only exist at one conformance level will look new or resolved even though nothing really changed. - The diff endpoint returns
409 SCAN_NOT_COMPLETEDif you call it before the scan finishes. PollGET /scans/:iduntilstatusisCOMPLETED.
Rate limits and quotas
Per-key rate limits are tied to your plan:
| Plan | Request rate | Scan creation rate | API keys per account |
|---|---|---|---|
| Free | 30/hour | 10/hour | 1 |
| Starter | 120/hour | 50/hour | 2 |
| Professional | 600/hour | 200/hour | 5 |
| Enterprise | 6000/hour | 1000/hour | 20 |
Two separate counters per key. The general one governs all API calls. The scan-creation one is a tighter budget that only applies to POST /projects/:id/scans, because a single runaway script with only a request-rate limit could happily burn through an entire month of scan quota in a minute or two. The burst limit means it cannot.
Your monthly scan allowance is the same pool whether you trigger scans from the dashboard or the API. One counter, same accounting. No bypass.
Five-minute quickstart
Create a key in Settings → API Keys, then:
export BEACON_KEY="bk_live_YOUR_KEY_HERE"export BEACON_API="https://api.beaconaccessibility.com/api/v1"# 1. Create a project. WCAG level and crawl depth are set on the# project and inherited by every scan that runs against it.curl -X POST "$BEACON_API/projects" \-H "Authorization: Bearer $BEACON_KEY" \-H "Content-Type: application/json" \-d '{"name":"My Site","url":"https://example.com","wcagLevel":"AA","depth":1}'
{"data": {"id": "proj_abc123","name": "My Site","url": "https://example.com","wcagLevel": "AA","depth": 1,"createdAt": "2026-04-11T14:23:45.123Z"}}
Kick off a scan. POST /scans takes no body — all scan settings are inherited from the project:
curl -X POST "$BEACON_API/projects/proj_abc123/scans" \-H "Authorization: Bearer $BEACON_KEY"
Scans are async. They go through Redis and a Playwright worker pool, and a multi-page crawl can take a minute or two to finish. So you poll:
curl "$BEACON_API/scans/scan_xyz789" \-H "Authorization: Bearer $BEACON_KEY"
You will see "status": "QUEUED", then "PROCESSING", then eventually "COMPLETED" with the regression block shown earlier. Pull the critical violations:
curl "$BEACON_API/scans/scan_xyz789/violations?severity=critical" \-H "Authorization: Bearer $BEACON_KEY"
{"data": [{"id": "viol_001","ruleId": "color-contrast","severity": "critical","target": "button.cta","element": "<button class=\"cta\">Sign Up</button>","description": "Element has insufficient color contrast","remediation": "Increase contrast ratio to at least 4.5:1"}],"pagination": {"cursor": "20","hasMore": true,"limit": 20}}
That is the whole loop. Add retries and a sensible poll interval and you have a working integration.
Response envelope and pagination
Every response is wrapped in {"data": ...}. Lists add a pagination object with a cursor and a hasMore flag. Most endpoints use an opaque cursor; the violations endpoint uses a numeric offset (violations live in an immutable JSON array, so offset paging is safe and avoids indexing a synthetic cursor). Either way you just pass whatever cursor value you got back to fetch the next page.
Errors follow the same envelope, with enough structure to branch on programmatically:
{"error": {"code": "VALIDATION_ERROR","message": "Invalid WCAG level","fields": {"wcagLevel": ["Must be one of: A, AA, AAA"]}}}
Interactive documentation
The full reference lives at api.beaconaccessibility.com/api/v1/docs, rendered with Scalar. It is generated from our Zod schemas at build time, so the docs and the running API can't drift. You can try requests inline without leaving the page.
Pricing: no separate API plan
API access is included on every Beacon plan, free tier included. The things that actually change between plans are:
- Number of keys you can have active at once (1 on Free, 2 on Starter, 5 on Professional, 20 on Enterprise)
- How fast you can call the API
- Which export formats are available (JSON everywhere, CSV from Starter up, PDF from Professional up)
- Your monthly scan allowance, which is the same number you already get on your plan
We talked about gating the API behind a paid tier. We decided against it. The person who wires a free-tier API into a weekend project tends to be the same person pushing for Beacon at their day job a few months later, and putting a paywall between that person and their first curl call would have cost us more than it saved.
A few things worth knowing about security
- Email verification is required before you can create a key — same middleware we use on other sensitive account actions.
- Revocation is immediate. The next request with a revoked key gets a 401.
- All traffic is HTTPS. The API does not speak plain HTTP.
- The burst limit on scan creation is intentionally tight. If you need a higher ceiling for a legitimate reason, open a ticket. We would much rather raise a limit after a quick conversation than have a leaked key drain somebody's monthly quota overnight.
Go build something
- Create an API key
- Browse the interactive reference
- Email us with bugs, feedback, or the one endpoint you can't live without