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.

BASE http://localhost:8787

Basics

  • All requests and responses are JSON. Send Content-Type: application/json on POST.
  • A scan runs for tens of seconds to minutes, so it's a job: POST returns 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 /report if 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.
GET /api/health

Liveness and capability probe. Used by container healthchecks and load balancers, so it's always open.

Response
{
  "ok": true,
  "ai": false,
  "auth": false,
  "allowlist": null,
  "ui": true,
  "limits": { "scansPerMinute": 6, "scansPerDay": 5, "apiPerMinute": 120 },
  "scans": { "running": 0, "queued": 0, "maxConcurrent": 2 }
}
POST /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.

Request
curl -s -X POST http://localhost:8787/api/scans \
  -H 'content-type: application/json' \
  -d '{"domain":"example.com","options":{"deadlineSeconds":90}}'
Response: 202
{
  "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.

FieldTypeDefaultNotes
domainstringrequiredBare domain or a URL, the path is discarded.
options.deadlineSecondsnumber300Wall-clock ceiling, seconds. Clamped to 1–1800; 0 is raised to 1, it doesn't disable the limit.
options.harvestHostsnumber5Live subdomains to harvest JS from. Clamped to 1–50.
options.fullHistorybooleanfalseQuery all Wayback history, not just 12 months.
options.typosquatbooleanfalseAlso hunt registered lookalike domains.
GET /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).

List your own scans
curl -s http://localhost:8787/api/scans \
  -H 'x-scan-owner: 1a2b3c4d-...'
GET /api/scans/{id}

The full job record, including the report once status is done.

GET /api/scans/{id}/report

Just the graded report document, with a download filename attached. 409 if the scan hasn't finished yet.

GET /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.

Watch a scan
curl -N http://localhost:8787/api/scans/<id>/events
DELETE /api/scans/{id}

Forget a scan, including its persisted file. Scans are also removed automatically 72 hours after they finish.

Errors

StatusMeaning
400Invalid input, or a target that isn't a public domain (IPs, reserved names, and hosts that resolve to private addresses are refused).
403This instance doesn't permit scans of that domain.
409Report requested before the scan finished.
429Rate 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.