Web app docs
The Curbcut web app scans whole sites against WCAG 2.2 on our servers. These docs cover every part of it; the CI gate has its own page. Plans and limits are on the pricing page.
Getting started
- Buy a plan on the pricing page. Polar emails a license key right after checkout; it is also on the order page and in the Polar customer portal.
- Open https://app.saffo.dev and paste the key. The server checks it with Polar and signs you in with a session cookie that lasts 30 days. There are no accounts, emails or passwords.
- Add your site by its home URL, for example
https://www.example.com/. - Verify the site (below). Unverified sites cannot be scanned.
- Set the page limit, the schedule and, if you want, path patterns and the viewport. Then choose Run now or wait for the first scheduled scan.
Site verification
Each site gets its own verification token. Prove that you control the site in one of two ways:
- DNS: add a TXT record
curbcut-verification=<token>on the site's host name. For awww.host, the record may also sit on the bare domain. - File: serve the token at
/.well-known/curbcut-verification.txton the site's origin, with HTTP status 200. The file may hold other tokens too, separated by spaces or new lines.
Then choose Verify. You can remove the record or file afterwards, but keep it if you want a CI preview host to qualify (see CI docs). Scan only sites you own or are allowed to test.
Site settings
- Page limit: the most pages one scan checks, up to your plan's pages per scan.
- Include and exclude patterns: path patterns that limit a scan to part of the site or leave parts out.
- Viewport: desktop (1280 px wide) or mobile (390 px wide).
- Schedule: off, weekly or daily. Daily needs Team or Agency.
The crawler
Scans are done by CurbcutBot. Its rules are fixed in code:
- User agent:
CurbcutBot/1.0 (+https://saffo.dev/docs/web-app/#crawler). - robots.txt: read before each scan and obeyed for the
CurbcutBotuser agent (or*when there is no group for it). Disallowed pages are not fetched. - Which pages: URLs from the site's sitemap first (the
Sitemaplines of robots.txt, else/sitemap.xml), then links found on checked pages. Only pages on the site's own origin (same scheme, host and port) are fetched, and only verified sites are scanned. - No forms, no logins: the crawler submits no forms, never signs in and keeps no cookies from one page to the next, so it sees what an anonymous visitor sees. Pages behind a login are not checked.
- State-changing links skipped: URLs that look like they log out, add to a cart, delete something or change state in another way are skipped by pattern.
- Pace: one page at a time per site, with at least one second between requests.
- Page limits: a scan stops at the site's page limit, which is never more than the plan's pages per scan.
Each page is rendered in headless Chromium and checked with the open-source axe-core engine and our rule texts. No
screenshots are taken. To block the crawler, add a User-agent: CurbcutBot group with Disallow: / to robots.txt.
Scans
A scan is queued, then running, then done or failed. While it runs, the scan page shows progress and each page's status. Starter allows 4 manual scans a month per workspace; Team and Agency allow one manual scan per site per hour. A new manual or scheduled scan waits until the site's previous one has finished.
Issues
The issues view lists every issue of the latest scan, grouped by rule or by WCAG success criterion, with the number of affected pages, the explanation and the fix steps. Each issue is marked new or unchanged compared with the previous scan of the site, and issues that disappeared are counted as fixed. A page view lists one URL's issues with their CSS selectors, markup snippets and the page's HTTP status, and links to the live page, where the free browser extension can highlight the element.
Ignore
Choose Ignore on an issue and add a note, for one page or for the same element on every page. Later scans keep it ignored; the issue is still stored, and you can stop ignoring it at any time.
Trends
For each site, a chart and a table of issue counts by impact per scan over the last 13 months, with pages scanned, new issues and fixed issues per scan.
Reports
- PDF: summary, trend, issues by criterion and affected URLs, printed on our server. On Agency, set your company name and logo in settings and they replace ours. Reports are kept for 30 days.
- CSV: every issue of a scan, one row per issue and page.
Team access keys
Under Settings, create an access key for each colleague, with a label such as their name or role. They sign in with it the same way you sign in with the license key. Starter allows 2 keys, Team 10 and Agency any number. Revoke a key in settings and it stops working. Keys are shown once when created and stored only as hashes.
Webhooks
Each site can send up to 5 HTTPS webhooks. After every finished manual or scheduled scan, each webhook gets a
POST with content-type: application/json and the CurbcutBot user agent. The event is scan.finished; when the scan
found new critical or serious issues, a second message with the event issues.new follows. Delivery has a 10-second
timeout and is not retried; settings show the last status. Webhook URLs must be on public hosts.
JSON format (newIssues only on issues.new, at most 50, critical and serious only):
{
"event": "issues.new",
"site": { "id": "6c0f4a1e-2b7d-4a55-9a7e-0d6f3e1b2c44", "url": "https://www.example.com/" },
"scan": {
"id": "9b2d7f3a-51c4-4e8b-b0a6-7c1d2e3f4a5b",
"kind": "scheduled",
"status": "done",
"url": "https://app.saffo.dev/scans/9b2d7f3a-51c4-4e8b-b0a6-7c1d2e3f4a5b",
"startedAt": "2026-11-06T02:00:03.000Z",
"finishedAt": "2026-11-06T02:09:41.000Z",
"pages": 214,
"totals": { "critical": 1, "serious": 6, "moderate": 12, "minor": 3, "new": 2, "fixed": 4, "unchanged": 18 }
},
"newIssues": [
{
"ruleId": "color-contrast",
"impact": "serious",
"page": "https://www.example.com/pricing/",
"selector": ".plan > p.note",
"criteria": ["1.4.3"]
}
]
}
Slack format: a single text field, with a summary line and a link to the scan in Slack's <url|text> syntax:
{ "text": "Curbcut: <summary line> <https://app.saffo.dev/scans/9b2d7f3a-51c4-4e8b-b0a6-7c1d2e3f4a5b|Open the scan>" }
What is stored
- License key as a SHA-256 hash, with the Polar customer id, subscription id, plan and status that Polar returns
- Access keys and API tokens as SHA-256 hashes, each with the label the owner gives it
- Site home URLs, verification tokens and crawl settings (page limit, path patterns, viewport, schedule)
- Per scan: page URLs, page titles and HTTP status, and per issue the rule id, WCAG criterion, impact, CSS selector and a markup snippet of at most 300 characters; deleted after 13 months
- Ignore decisions with the note the customer types
- Webhook URLs, encrypted at rest
- Generated PDF and CSV reports, deleted after 30 days
- Agency plan only: the logo image and company name the customer uploads for PDF reports
- Request logs without IP addresses or user agents, deleted after 14 days
No names, emails, passwords, payment details or screenshots are stored; payment data stays with Polar.
Retention and deletion
- Scan results: 13 months. Reports: 30 days. Request logs: 14 days. Backups: 7 days.
- Deleting a site removes its scans at once. Delete all data in settings removes the workspace's sites, scans, keys, tokens and webhooks.
- After you cancel, the data is kept for 30 days after the paid period ends, then deleted.
The privacy policy has the full details.
Limits
Automated checks find only part of all WCAG failures. Test with a keyboard and a screen reader as well. Curbcut does not make a site compliant and does not change your pages.
Help
Write to support@saffo.dev.