REST API
API reference
The scanner is a REST API, and this page is one client of it. Start a scan, watch it resolve over Server-Sent Events, and pull the graded report. It's public and free to use, with no key required.
http://localhost:8787
Basics
- All requests and responses are JSON. Send
Content-Type: application/jsononPOST. - A scan runs for tens of seconds to minutes, so it's a job:
POSTreturns an id immediately, then you poll or stream. - The report document is the same JSON the scanner UI renders, see any completed scan's
/report. - Reports are kept for 72 hours after a scan finishes, then deleted automatically. Grab
/reportif you need to keep one. - The scan id is a UUID. Anyone with it can read that scan; the recent-scans list, though, is scoped per browser (see below), so a scan isn't discoverable without its id.
- No key or account is required. Every endpoint is open to anyone who can reach it.
/api/health
Liveness and capability probe. Used by container healthchecks and load balancers, so it's always open.
{
"ok": true,
"ai": false,
"auth": false,
"allowlist": null,
"ui": true,
"limits": { "scansPerMinute": 6, "scansPerDay": 5, "apiPerMinute": 120 },
"scans": { "running": 0, "queued": 0, "maxConcurrent": 2 }
}
/api/scans
Start a scan. Returns 202 Accepted immediately with the job id and links. A scan takes tens of seconds to minutes, so it runs as a background job: stream /events to watch it live, or poll GET /api/scans/{id} until status is done, then read /report. Don't hold a single request open waiting for the result.
Send an X-Scan-Owner header with a stable opaque token to have the scan show up in your scoped list later. Optional; the scan runs either way.
curl -s -X POST http://localhost:8787/api/scans \
-H 'content-type: application/json' \
-d '{"domain":"example.com","options":{"deadlineSeconds":90}}'
{
"id": "0f8b1f4e-…",
"domain": "example.com",
"status": "queued",
"links": {
"self": "/api/scans/0f8b1f4e-…",
"events": "/api/scans/0f8b1f4e-…/events",
"report": "/api/scans/0f8b1f4e-…/report"
}
}
Scan options
All optional, nested under options in the POST body.
| Field | Type | Default | Notes |
|---|---|---|---|
domain | string | required | Bare domain or a URL, the path is discarded. |
options.deadlineSeconds | number | 300 | Wall-clock ceiling, seconds. Clamped to 1–1800; 0 is raised to 1, it doesn't disable the limit. |
options.harvestHosts | number | 5 | Live subdomains to harvest JS from. Clamped to 1–50. |
options.fullHistory | boolean | false | Query all Wayback history, not just 12 months. |
options.typosquat | boolean | false | Also hunt registered lookalike domains. |
/api/scans?limit=50
Recent scans, newest first, without report bodies. Each carries status, and once done, grade / score / durationMs.
The list is scoped to the X-Scan-Owner header: it only returns scans started with that same token. Send an opaque token you generate and reuse (the UI keeps one in localStorage) to get your own scans back; a request with no token, or an unrecognized one, gets an empty list. This is a privacy filter, not access control, so anyone holding a scan's UUID can still open it via the endpoints below.
status is one of queued · running · done · failed · interrupted (the last meaning the server restarted mid-scan, not a finished scan).
curl -s http://localhost:8787/api/scans \
-H 'x-scan-owner: 1a2b3c4d-...'
/api/scans/{id}
The full job record, including the report once status is done.
/api/scans/{id}/report
Just the graded report document, with a download filename attached. 409 if the scan hasn't finished yet.
/api/scans/{id}/events
Server-Sent Events. Two event types:
progress: a{ phase, message, data? }tick, or{ section }carrying a fully-built report section as it resolves.end:{ status, error?, report }. The stream closes right after.
Every frame has an id:; ?from=<seq> replays everything after that sequence number, so a late or reconnecting client doesn't miss earlier sections.
curl -N http://localhost:8787/api/scans/<id>/events
/api/scans/{id}
Forget a scan, including its persisted file. Scans are also removed automatically 72 hours after they finish.
Errors
| Status | Meaning |
|---|---|
| 400 | Invalid input, or a target that isn't a public domain (IPs, reserved names, and hosts that resolve to private addresses are refused). |
| 403 | This instance doesn't permit scans of that domain. |
| 409 | Report requested before the scan finished. |
| 429 | Rate limited or daily quota reached, see the Retry-After header and the error field. |
Rate limits
Every limit below is per client IP. Going over any of them returns 429 with a Retry-After header telling you when to try again.
- Loading this instance's limits…
Only scans that actually start count toward the daily quota, an invalid or blocked domain never uses it up.